@miso.ai/client-sdk-ui 1.11.0-beta.0 → 1.11.0-beta.10

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 (118) hide show
  1. package/dist/cjs/actor/data.js +6 -7
  2. package/dist/cjs/actor/feedback.js +6 -39
  3. package/dist/cjs/actor/fields.js +14 -6
  4. package/dist/cjs/actor/hub.js +1 -2
  5. package/dist/cjs/actor/index.js +0 -7
  6. package/dist/cjs/actor/interactions.js +4 -15
  7. package/dist/cjs/actor/sessions.js +2 -33
  8. package/dist/cjs/actor/tracker.js +25 -26
  9. package/dist/cjs/actor/utils.js +0 -49
  10. package/dist/cjs/actor/view.js +40 -40
  11. package/dist/cjs/actor/views.js +32 -8
  12. package/dist/cjs/asset/svgs.js +18 -8
  13. package/dist/cjs/constants.js +18 -19
  14. package/dist/cjs/defaults/hybrid-search/answer-box.js +19 -3
  15. package/dist/cjs/defaults/hybrid-search/phrases.js +5 -5
  16. package/dist/cjs/defaults/hybrid-search/templates.js +37 -9
  17. package/dist/cjs/element/role/index.js +12 -5
  18. package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
  19. package/dist/cjs/element/role/miso-total.js +20 -0
  20. package/dist/cjs/layout/affiliation/affiliation.js +0 -3
  21. package/dist/cjs/layout/banner/banner.js +0 -4
  22. package/dist/cjs/layout/button/more-button.js +73 -0
  23. package/dist/cjs/layout/container/container.js +0 -3
  24. package/dist/cjs/layout/error/error.js +0 -4
  25. package/dist/cjs/layout/index.js +7 -0
  26. package/dist/cjs/layout/input/facets.js +5 -5
  27. package/dist/cjs/layout/input/option-list.js +0 -4
  28. package/dist/cjs/layout/input/search-box.js +33 -12
  29. package/dist/cjs/layout/list/cards.js +0 -3
  30. package/dist/cjs/layout/list/carousel.js +0 -3
  31. package/dist/cjs/layout/list/collection.js +25 -7
  32. package/dist/cjs/layout/list/list.js +0 -3
  33. package/dist/cjs/layout/radio/feedback.js +0 -3
  34. package/dist/cjs/layout/radio/radio.js +0 -4
  35. package/dist/cjs/layout/raf.js +4 -2
  36. package/dist/cjs/layout/text/text.js +0 -4
  37. package/dist/cjs/layout/text/typewriter/index.js +3 -3
  38. package/dist/cjs/layout/triggerable.js +79 -0
  39. package/dist/cjs/util/states.js +0 -15
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/workflow/answer-based.js +109 -111
  42. package/dist/cjs/workflow/ask.js +35 -27
  43. package/dist/cjs/workflow/asks.js +1 -1
  44. package/dist/cjs/workflow/autocompletable.js +33 -0
  45. package/dist/cjs/workflow/autocomplete.js +122 -0
  46. package/dist/cjs/workflow/base.js +169 -102
  47. package/dist/cjs/workflow/explore.js +26 -19
  48. package/dist/cjs/workflow/hybrid-search-answer.js +99 -0
  49. package/dist/cjs/workflow/hybrid-search-results.js +70 -0
  50. package/dist/cjs/workflow/hybrid-search-views.js +122 -0
  51. package/dist/cjs/workflow/hybrid-search.js +98 -103
  52. package/dist/cjs/workflow/options.js +32 -17
  53. package/dist/cjs/workflow/processors.js +185 -17
  54. package/dist/cjs/workflow/recommendation.js +36 -49
  55. package/dist/cjs/workflow/search-based.js +186 -0
  56. package/dist/cjs/workflow/search.js +21 -68
  57. package/package.json +2 -2
  58. package/src/actor/data.js +2 -4
  59. package/src/actor/feedback.js +6 -23
  60. package/src/actor/fields.js +14 -6
  61. package/src/actor/hub.js +1 -2
  62. package/src/actor/index.js +0 -1
  63. package/src/actor/interactions.js +4 -10
  64. package/src/actor/sessions.js +3 -30
  65. package/src/actor/tracker.js +25 -19
  66. package/src/actor/utils.js +0 -47
  67. package/src/actor/view.js +27 -15
  68. package/src/actor/views.js +36 -8
  69. package/src/asset/svgs.js +19 -7
  70. package/src/constants.js +25 -16
  71. package/src/defaults/hybrid-search/answer-box.js +17 -5
  72. package/src/defaults/hybrid-search/phrases.js +4 -4
  73. package/src/defaults/hybrid-search/templates.js +25 -9
  74. package/src/element/role/index.js +2 -1
  75. package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
  76. package/src/element/role/miso-total.js +16 -0
  77. package/src/layout/affiliation/affiliation.js +1 -5
  78. package/src/layout/banner/banner.js +0 -5
  79. package/src/layout/button/more-button.js +69 -0
  80. package/src/layout/container/container.js +1 -5
  81. package/src/layout/error/error.js +0 -5
  82. package/src/layout/index.js +2 -1
  83. package/src/layout/input/facets.js +5 -6
  84. package/src/layout/input/option-list.js +0 -5
  85. package/src/layout/input/search-box.js +27 -14
  86. package/src/layout/list/cards.js +0 -4
  87. package/src/layout/list/carousel.js +0 -4
  88. package/src/layout/list/collection.js +22 -9
  89. package/src/layout/list/list.js +0 -4
  90. package/src/layout/radio/feedback.js +0 -4
  91. package/src/layout/radio/radio.js +0 -5
  92. package/src/layout/raf.js +2 -2
  93. package/src/layout/text/text.js +0 -5
  94. package/src/layout/text/typewriter/index.js +4 -5
  95. package/src/layout/trackable.js +1 -1
  96. package/src/layout/triggerable.js +76 -0
  97. package/src/util/states.js +0 -14
  98. package/src/version.js +1 -1
  99. package/src/workflow/answer-based.js +101 -77
  100. package/src/workflow/ask.js +33 -25
  101. package/src/workflow/asks.js +1 -1
  102. package/src/workflow/autocompletable.js +29 -0
  103. package/src/workflow/autocomplete.js +113 -0
  104. package/src/workflow/base.js +147 -93
  105. package/src/workflow/explore.js +23 -8
  106. package/src/workflow/hybrid-search-answer.js +92 -0
  107. package/src/workflow/hybrid-search-results.js +63 -0
  108. package/src/workflow/hybrid-search-views.js +124 -0
  109. package/src/workflow/hybrid-search.js +102 -73
  110. package/src/workflow/options.js +26 -11
  111. package/src/workflow/processors.js +165 -14
  112. package/src/workflow/recommendation.js +32 -33
  113. package/src/workflow/search-based.js +183 -0
  114. package/src/workflow/search.js +21 -65
  115. package/dist/cjs/actor/autocomplete.js +0 -160
  116. package/dist/cjs/workflow/utils.js +0 -42
  117. package/src/actor/autocomplete.js +0 -133
  118. package/src/workflow/utils.js +0 -29
@@ -1,10 +1,15 @@
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;
7
- return trimObj({ ...data, status, ongoing });
11
+ const ongoing = status === STATUS.READY ? data.ongoing : undefined;
12
+ return trimObj({ status, ongoing, ...data }); // let orinigal data override status and ongoing
8
13
  }
9
14
 
10
15
  function getStatus(data) {
@@ -35,7 +40,8 @@ export function writeMisoIdToMeta(data) {
35
40
 
36
41
  export function writeKeywordsToData(data) {
37
42
  const { request, value } = data;
38
- if (!request || !value) {
43
+ // we still want the value in loading status
44
+ if (!request) {
39
45
  return data;
40
46
  }
41
47
  const { payload } = request || {};
@@ -73,23 +79,168 @@ export function writeAnswerStageToMeta(data) {
73
79
  };
74
80
  }
75
81
 
76
- export function addDataInstructions(data, instructions) {
77
- if (!instructions) {
82
+ export function writeFiltersToPayload(payload = {}, filters) {
83
+ return trimObj({
84
+ ...payload,
85
+ ...composeFiltersPayload(filters),
86
+ });
87
+ }
88
+
89
+ function composeFiltersPayload(filters) {
90
+ if (!filters) {
91
+ return undefined;
92
+ }
93
+ const { facets } = filters;
94
+ return trimObj({
95
+ facet_filters: composeFacetFilters(facets),
96
+ });
97
+ }
98
+
99
+ function composeFacetFilters(facets) {
100
+ if (!facets) {
101
+ return undefined;
102
+ }
103
+ const filters = {};
104
+ for (const field in facets) {
105
+ const values = facets[field];
106
+ if (!values || !values.length) {
107
+ continue; // just in case
108
+ }
109
+ filters[field] = {
110
+ terms: values,
111
+ };
112
+ }
113
+ return Object.keys(filters).length ? filters : undefined;
114
+ }
115
+
116
+ export function retainFacetCounts(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) {
78
129
  return data;
79
130
  }
80
131
  return {
81
- _inst: {
82
- ...data._inst,
83
- ...instructions,
84
- },
85
132
  ...data,
133
+ value: {
134
+ ...value,
135
+ facet_counts: currentFacetCounts,
136
+ },
86
137
  };
87
138
  }
88
139
 
89
- export function removeDataInstructions(data) {
90
- if (typeof data !== 'object') {
140
+ export function concatResults(oldData, newData, { role = ROLE.PRODUCTS } = {}) {
141
+ if (!newData.value) {
142
+ return oldData;
143
+ }
144
+ const { [role]: oldProducts = [] } = oldData.value;
145
+ const { [role]: newProducts = [] } = newData.value;
146
+ // keep exhaustion flag
147
+ // TODO
148
+ return {
149
+ ...newData,
150
+ value: {
151
+ ...newData.value,
152
+ [role]: [...oldProducts, ...newProducts],
153
+ },
154
+ }
155
+ }
156
+
157
+ export function markExhaustion(data, { role = ROLE.PRODUCTS } = {}) {
158
+ const { status, request, value } = data;
159
+ // keep track of the exhaustion state
160
+ if (status !== STATUS.READY || !request || !value) {
91
161
  return data;
92
162
  }
93
- const { _inst, ...rest } = data;
94
- return rest;
163
+ const { [role]: products } = value;
164
+ if (!products) {
165
+ return data;
166
+ }
167
+ const { rows } = request.payload;
168
+ const { length } = products;
169
+ const exhausted = length < rows;
170
+ if (!exhausted) {
171
+ return data;
172
+ }
173
+ return {
174
+ ...data,
175
+ meta: {
176
+ ...data.meta,
177
+ exhausted: true,
178
+ },
179
+ };
180
+ }
181
+
182
+ // interactions //
183
+ export function buildBaseInteraction(args) {
184
+ const { role, type, items } = args;
185
+ const payload = {
186
+ type: type === 'viewable' ? 'viewable_impression' : type,
187
+ context: {
188
+ custom_context: trimObj({
189
+ property: role,
190
+ }),
191
+ },
192
+ };
193
+
194
+ if (isPerformanceEventType(type)) {
195
+ if (isProductRole(role)) {
196
+ payload.product_ids = (items || []).map(v => typeof v === 'string' ? v : v.product_id).filter(v => v);
197
+ } else {
198
+ payload.product_ids = [];
199
+ payload.context.custom_context.items = (items || []).map(v => v.text || v.id || v);
200
+ }
201
+ }
202
+
203
+ switch (type) {
204
+ case EVENT_TYPE.SUBMIT:
205
+ payload.context.custom_context.value = args.value;
206
+ break;
207
+ case EVENT_TYPE.FEEDBACK:
208
+ payload.context.custom_context.value = payload.value = args.value;
209
+ payload.context.custom_context.result_type = payload.result_type = args.result_type;
210
+ break;
211
+ }
212
+
213
+ return trimObj(payload);
214
+ }
215
+
216
+ export function writeAffiliationInfoToInteraction(payload, args) {
217
+ if (args.role !== ROLE.AFFILIATION) {
218
+ return payload;
219
+ }
220
+ const { items } = args;
221
+ const channel = (items[0] && items[0].channel) || undefined;
222
+ const positions = items.map(v => v.position);
223
+ return mergeInteraction(payload, {
224
+ context: {
225
+ custom_context: {
226
+ channel,
227
+ positions,
228
+ },
229
+ },
230
+ });
231
+ }
232
+
233
+ export function mergeInteraction(base = {}, patch = {}) {
234
+ return trimObj({
235
+ ...base,
236
+ ...patch,
237
+ context:trimObj({
238
+ ...base.context,
239
+ ...patch.context,
240
+ custom_context: trimObj({
241
+ ...(base.context && base.context.custom_context),
242
+ ...(patch.context && patch.context.custom_context),
243
+ }),
244
+ }),
245
+ });
95
246
  }
@@ -1,39 +1,49 @@
1
- import { defineValues, trimObj, API } from '@miso.ai/commons';
1
+ import { defineValues, API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- import { fields } from '../actor/index.js';
5
3
  import { ListLayout } from '../layout/index.js';
6
4
  import { ROLE } from '../constants.js';
5
+ import { mergeRolesOptions } from './options.js';
6
+ import { mergeInteraction } from './processors.js';
7
7
 
8
8
  const DEFAULT_API_OPTIONS = Object.freeze({
9
+ ...Workflow.DEFAULT_API_OPTIONS,
9
10
  group: API.GROUP.RECOMMENDATION,
10
11
  name: API.NAME.USER_TO_PRODUCTS,
11
12
  payload: {
13
+ ...Workflow.DEFAULT_API_OPTIONS.payload,
12
14
  fl: ['*'],
13
15
  },
14
16
  });
15
17
 
16
18
  const DEFAULT_LAYOUTS = Object.freeze({
19
+ ...Workflow.DEFAULT_LAYOUTS,
17
20
  [ROLE.PRODUCTS]: ListLayout.type,
18
21
  });
19
22
 
20
23
  const DEFAULT_TRACKERS = Object.freeze({
24
+ ...Workflow.DEFAULT_TRACKERS,
21
25
  [ROLE.PRODUCTS]: {},
22
26
  });
23
27
 
24
28
  const DEFAULT_OPTIONS = Object.freeze({
29
+ ...Workflow.DEFAULT_OPTIONS,
25
30
  api: DEFAULT_API_OPTIONS,
26
31
  layouts: DEFAULT_LAYOUTS,
27
32
  trackers: DEFAULT_TRACKERS,
28
33
  });
29
34
 
35
+ const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
36
+ main: ROLE.PRODUCTS,
37
+ members: Object.keys(DEFAULT_LAYOUTS),
38
+ });
39
+
30
40
  export default class Recommendation extends Workflow {
31
41
 
32
42
  constructor(context, id) {
33
43
  super({
34
44
  name: 'recommendation',
35
45
  context,
36
- roles: Object.keys(DEFAULT_LAYOUTS),
46
+ roles: ROLES_OPTIONS,
37
47
  defaults: DEFAULT_OPTIONS,
38
48
  id,
39
49
  });
@@ -45,28 +55,15 @@ export default class Recommendation extends Workflow {
45
55
  defineValues(this, { id });
46
56
  }
47
57
 
48
- _initReset(args) {
58
+ _initSession(args) {
49
59
  this._context._members.set(args.id, this);
50
- super._initReset(args);
60
+ super._initSession(args);
51
61
  }
52
62
 
53
63
  // lifecycle //
54
64
  start() {
55
- this._sessions.start();
56
65
  // in recommendation workflow, start() triggers query
57
- // TODO: we should still make the query lifecycle
58
- const { session } = this;
59
- this._hub.update(fields.request(), mergeApiOptions(this._options.resolved.api, { session }));
60
- return this;
61
- }
62
-
63
- // TODO: deprecate this
64
- startTracker() {
65
- this.useApi(false);
66
- this.useLayouts(false);
67
- this._sessions.start();
68
- this.notifyViewUpdate(ROLE.CONTAINER);
69
- this.notifyViewUpdate(ROLE.PRODUCTS);
66
+ this._request();
70
67
  return this;
71
68
  }
72
69
 
@@ -76,21 +73,23 @@ export default class Recommendation extends Workflow {
76
73
  }
77
74
 
78
75
  // interactions //
79
- _preprocessInteraction(payload) {
80
- payload = super._preprocessInteraction(payload) || {};
81
- const { context = {} } = payload;
82
- const { custom_context = {} } = context;
83
- return {
84
- ...payload,
76
+ _defaultProcessInteraction(payload, args) {
77
+ payload = super._defaultProcessInteraction(payload, args);
78
+ payload = this._writeUnitIdToInteraction(payload, args);
79
+ return payload;
80
+ }
81
+
82
+ _writeUnitIdToInteraction(payload) {
83
+ const unit_id = this.id;
84
+ const unit_instance_uuid = this.uuid;
85
+ return mergeInteraction(payload, {
85
86
  context: {
86
- ...context,
87
- custom_context: trimObj({
88
- unit_id: this.id,
89
- unit_instance_uuid: this.uuid,
90
- ...custom_context,
91
- }),
87
+ custom_context: {
88
+ unit_id,
89
+ unit_instance_uuid,
90
+ },
92
91
  },
93
- };
92
+ });
94
93
  }
95
94
 
96
95
  // destroy //
@@ -0,0 +1,183 @@
1
+ import { API } from '@miso.ai/commons';
2
+ import Workflow from './base.js';
3
+ import { fields } from '../actor/index.js';
4
+ import { ROLE } from '../constants.js';
5
+ import { SearchBoxLayout, ListLayout, TextLayout, FacetsLayout, MoreButtonLayout } from '../layout/index.js';
6
+ import { writeKeywordsToData, writeFiltersToPayload, retainFacetCounts, markExhaustion, concatResults } from './processors.js';
7
+ import { mergeRolesOptions, makeConfigurable } from './options.js';
8
+
9
+ const DEFAULT_ROWS = 10;
10
+ const DEFAULT_PAGE_LIMIT = 10;
11
+
12
+ const DEFAULT_API_OPTIONS = Object.freeze({
13
+ ...Workflow.DEFAULT_API_OPTIONS,
14
+ group: API.GROUP.SEARCH,
15
+ payload: {
16
+ ...Workflow.DEFAULT_API_OPTIONS.payload,
17
+ fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'title'],
18
+ rows: DEFAULT_ROWS,
19
+ },
20
+ });
21
+
22
+ const DEFAULT_LAYOUTS = Object.freeze({
23
+ ...Workflow.DEFAULT_LAYOUTS,
24
+ [ROLE.QUERY]: [SearchBoxLayout.type],
25
+ [ROLE.PRODUCTS]: [ListLayout.type, { incremental: true, infiniteScroll: true }],
26
+ [ROLE.KEYWORDS]: [TextLayout.type, { raw: true }],
27
+ [ROLE.TOTAL]: [TextLayout.type, { raw: true, format: 'number' }],
28
+ [ROLE.FACETS]: [FacetsLayout.type],
29
+ [ROLE.MORE]: [MoreButtonLayout.type],
30
+ });
31
+
32
+ const DEFAULT_TRACKERS = Object.freeze({
33
+ ...Workflow.DEFAULT_TRACKERS,
34
+ [ROLE.PRODUCTS]: {},
35
+ });
36
+
37
+ const DEFAULT_PAGINATION = Object.freeze({
38
+ active: false,
39
+ mode: 'infiniteScroll',
40
+ pageLimit: 10,
41
+ });
42
+
43
+ const DEFAULT_OPTIONS = Object.freeze({
44
+ ...Workflow.DEFAULT_OPTIONS,
45
+ api: DEFAULT_API_OPTIONS,
46
+ layouts: DEFAULT_LAYOUTS,
47
+ trackers: DEFAULT_TRACKERS,
48
+ pagination: DEFAULT_PAGINATION,
49
+ });
50
+
51
+ const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
52
+ main: ROLE.PRODUCTS,
53
+ members: Object.keys(DEFAULT_LAYOUTS),
54
+ mappings: {
55
+ [ROLE.FACETS]: 'facet_counts',
56
+ },
57
+ });
58
+
59
+ export default class SearchBasedWorkflow extends Workflow {
60
+
61
+ _initSubscriptions(args) {
62
+ super._initSubscriptions(args);
63
+ this._unsubscribes = [
64
+ ...this._unsubscribes,
65
+ this._hub.on(fields.query(), args => this._query(args)),
66
+ this._hub.on(fields.filters(), filters => this._refine(filters)),
67
+ this._hub.on(fields.more(), () => this._more()),
68
+ ];
69
+ }
70
+
71
+ restart() {
72
+ super.restart();
73
+ this._page = 0;
74
+ }
75
+
76
+ // properties //
77
+ get exhausted() {
78
+ const data = this._hub.states[fields.data()];
79
+ return !!(data && data.meta && data.meta.exhausted);
80
+ }
81
+
82
+ // query //
83
+ query(args) {
84
+ if (!args.q) {
85
+ throw new Error(`q is required in query() call`);
86
+ }
87
+ this._hub.update(fields.query(), args);
88
+ }
89
+
90
+ _query(args) {
91
+ // keep track of the query args for refine and more calls
92
+ this._queryArgs = args;
93
+
94
+ // start a new session
95
+ this.restart();
96
+
97
+ // payload
98
+ const payload = this._buildPayload(args);
99
+
100
+ this._request({ payload });
101
+ }
102
+
103
+ _refine(filters) {
104
+ // remember current facet_counts
105
+ const { facet_counts } = this._hub.states[fields.data()].value || {};
106
+ this._currentFacetCounts = facet_counts;
107
+
108
+ // start a new session
109
+ this.restart();
110
+
111
+ // payload
112
+ const query = this._getQuery();
113
+ const payload = this._buildPayload({ ...query, filters });
114
+
115
+ this._request({ payload });
116
+ }
117
+
118
+ _more() {
119
+ if (this.exhausted || this._page >= DEFAULT_PAGE_LIMIT - 1) {
120
+ return; // ignore
121
+ }
122
+ // don't create a new session!
123
+
124
+ // payload
125
+ const query = this._getQuery();
126
+ const filters = this._hub.states[fields.filters()]; // get stored filters
127
+ const start = (this._page + 1) * this._getPageSize();
128
+ const payload = this._buildPayload({ ...query, filters, start });
129
+
130
+ this._request({ payload });
131
+
132
+ this._page++;
133
+ }
134
+
135
+ _getQuery() {
136
+ return this._queryArgs;
137
+ }
138
+
139
+ _getPageSize() {
140
+ return this._options.resolved.api.payload.rows;
141
+ }
142
+
143
+ _buildPayload({ filters, ...payload } = {}) {
144
+ payload = writeFiltersToPayload(payload, filters);
145
+ return payload;
146
+ }
147
+
148
+ // data //
149
+ _defaultProcessData(data) {
150
+ data = super._defaultProcessData(data);
151
+ data = writeKeywordsToData(data);
152
+ data = retainFacetCounts(data, this._currentFacetCounts);
153
+ data = markExhaustion(data);
154
+ return data;
155
+ }
156
+
157
+ _updateDataInHub(data) {
158
+ data = this._appendResultsFromMoreRequest(data);
159
+ super._updateDataInHub(data);
160
+ }
161
+
162
+ _appendResultsFromMoreRequest(data) {
163
+ const { request } = data;
164
+ if (!request || !request.payload.start) {
165
+ return data; // the initial state, or not from "more" request
166
+ }
167
+ // concat records if it's from "more" request
168
+ const currentData = this._hub.states[fields.data()];
169
+ return concatResults(currentData, data);
170
+ }
171
+
172
+ }
173
+
174
+ makeConfigurable(SearchBasedWorkflow.prototype, ['pagination']);
175
+
176
+ Object.assign(SearchBasedWorkflow, {
177
+ DEFAULT_API_OPTIONS,
178
+ DEFAULT_LAYOUTS,
179
+ DEFAULT_TRACKERS,
180
+ DEFAULT_PAGINATION,
181
+ DEFAULT_OPTIONS,
182
+ ROLES_OPTIONS,
183
+ });
@@ -1,11 +1,7 @@
1
1
  import { API } from '@miso.ai/commons';
2
- import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- import * as sources from '../source.js';
5
- import { fields, AutocompleteActor } from '../actor/index.js';
6
- import { ROLE, DATA_ASPECT } from '../constants.js';
7
- import { ListLayout, SearchBoxLayout } from '../layout/index.js';
8
- import { makeConfigurable } from './options.js';
2
+ import { ROLE } from '../constants.js';
3
+ import SearchBasedWorkflow from './search-based.js';
4
+ import { makeAutocompletable } from './autocompletable.js';
9
5
 
10
6
  const DEFAULT_API_OPTIONS = Object.freeze({
11
7
  group: API.GROUP.SEARCH,
@@ -16,7 +12,6 @@ const DEFAULT_API_OPTIONS = Object.freeze({
16
12
  });
17
13
 
18
14
  const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
19
- actor: false,
20
15
  api: {
21
16
  group: API.GROUP.SEARCH,
22
17
  name: API.NAME.AUTOCOMPLETE,
@@ -25,54 +20,43 @@ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
25
20
  fl: ['title', 'url', 'cover_image'],
26
21
  },
27
22
  },
28
- throttle: 300,
29
23
  });
30
24
 
31
25
  const DEFAULT_LAYOUTS = Object.freeze({
32
- [ROLE.QUERY]: SearchBoxLayout.type,
33
- [ROLE.PRODUCTS]: ListLayout.type,
26
+ ...SearchBasedWorkflow.DEFAULT_LAYOUTS,
34
27
  });
35
28
 
36
29
  const DEFAULT_TRACKERS = Object.freeze({
37
- [ROLE.PRODUCTS]: {},
30
+ ...SearchBasedWorkflow.DEFAULT_TRACKERS,
38
31
  });
39
32
 
40
33
  const DEFAULT_OPTIONS = Object.freeze({
34
+ ...SearchBasedWorkflow.DEFAULT_OPTIONS,
41
35
  api: DEFAULT_API_OPTIONS,
42
36
  autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
43
37
  layouts: DEFAULT_LAYOUTS,
44
38
  trackers: DEFAULT_TRACKERS,
45
39
  });
46
40
 
47
- export default class Search extends Workflow {
41
+ const ROLES_OPTIONS = SearchBasedWorkflow.ROLES_OPTIONS;
42
+
43
+ export default class Search extends SearchBasedWorkflow {
48
44
 
49
45
  constructor(plugin, client) {
50
46
  super({
51
47
  name: 'search',
52
48
  plugin,
53
49
  client,
54
- roles: Object.keys(DEFAULT_LAYOUTS),
50
+ roles: ROLES_OPTIONS,
55
51
  defaults: DEFAULT_OPTIONS,
56
52
  });
57
53
  }
58
54
 
59
- _initActors(args) {
60
- super._initActors(args);
61
- this._autocomplete = new AutocompleteActor(this._hub, {
62
- source: sources.api(this._client),
63
- options: this._options,
64
- });
55
+ _initProperties(args) {
56
+ super._initProperties(args);
57
+ this._initAutocomplete(args);
65
58
  }
66
59
 
67
- _initSubscriptions(args) {
68
- super._initSubscriptions(args);
69
- this._unsubscribes = [
70
- ...this._unsubscribes,
71
- this._hub.on(fields.query(), args => this._query(args)),
72
- ];
73
- }
74
-
75
- // lifecycle //
76
60
  autoQuery({ setValue = true, focus = true } = {}) {
77
61
  const q = new URLSearchParams(window.location.search).get('q');
78
62
  const { layout } = this._views.get(ROLE.QUERY);
@@ -89,46 +73,18 @@ export default class Search extends Workflow {
89
73
  }
90
74
  }
91
75
 
92
- query(args) {
93
- if (!args.q) {
94
- throw new Error(`q is required in query() call`);
95
- }
96
- this._hub.update(fields.query(), args);
97
- }
98
-
99
- _query(args) {
100
- // start a new session
101
- this.restart();
102
- const { session } = this;
103
-
104
- // build payload and trigger request
105
- const payload = this._buildPayload(args);
106
- const event = mergeApiOptions(this._options.resolved.api, { payload, session });
107
- this._request(event);
108
- }
109
-
110
- _buildPayload(args) {
111
- return args;
112
- }
113
-
114
- _request(event) {
115
- this._hub.update(fields.request(), event);
116
- }
117
-
118
- updateCompletions(event) {
119
- // TODO: verify
120
- this._hub.update(fields.data(DATA_ASPECT.AUTOCOMPLETE), {
121
- ...event,
122
- source: 'manual',
123
- });
124
- return this;
125
- }
126
-
127
76
  notifyViewUpdate(role = ROLE.PRODUCTS, ...args) {
128
77
  super.notifyViewUpdate(role, ...args);
129
78
  return this;
130
79
  }
131
80
 
81
+ // destroy //
82
+ _destroy(options) {
83
+ this._feedback._destroy();
84
+ this._destroyAutocomplete();
85
+ super._destroy(options);
86
+ }
87
+
132
88
  }
133
89
 
134
- makeConfigurable(Search.prototype, ['autocomplete']);
90
+ makeAutocompletable(Search.prototype);