@miso.ai/client-sdk-ui 1.10.3 → 1.11.0-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 (101) hide show
  1. package/dist/cjs/actor/autocomplete.js +4 -1
  2. package/dist/cjs/actor/data.js +15 -6
  3. package/dist/cjs/actor/fields.js +10 -10
  4. package/dist/cjs/actor/filters.js +114 -0
  5. package/dist/cjs/actor/hub.js +2 -1
  6. package/dist/cjs/actor/sessions.js +3 -5
  7. package/dist/cjs/actor/view.js +19 -6
  8. package/dist/cjs/actor/views.js +32 -19
  9. package/dist/cjs/constants.js +9 -2
  10. package/dist/cjs/defaults/ask/constants.js +3 -2
  11. package/dist/cjs/defaults/ask/templates.js +112 -219
  12. package/dist/cjs/defaults/common/templates.js +234 -0
  13. package/dist/cjs/defaults/hybrid-search/answer-box.js +115 -0
  14. package/dist/cjs/defaults/hybrid-search/constants.js +8 -0
  15. package/dist/cjs/defaults/hybrid-search/controls.js +17 -0
  16. package/dist/cjs/defaults/hybrid-search/index.js +31 -0
  17. package/dist/cjs/defaults/hybrid-search/phrases.js +27 -0
  18. package/dist/cjs/defaults/hybrid-search/templates.js +127 -0
  19. package/dist/cjs/defaults/index.js +4 -2
  20. package/dist/cjs/element/container/index.js +7 -0
  21. package/dist/cjs/element/container/miso-hybrid-search.js +21 -0
  22. package/dist/cjs/element/role/index.js +21 -0
  23. package/dist/cjs/element/role/miso-facets.js +20 -0
  24. package/dist/cjs/element/role/miso-hits.js +20 -0
  25. package/dist/cjs/element/role/miso-keywords.js +20 -0
  26. package/dist/cjs/layout/index.js +7 -0
  27. package/dist/cjs/layout/input/facets.js +294 -0
  28. package/dist/cjs/layout/input/option-list.js +4 -4
  29. package/dist/cjs/layout/input/search-box.js +6 -8
  30. package/dist/cjs/layout/list/collection.js +1 -2
  31. package/dist/cjs/layout/radio/radio.js +1 -1
  32. package/dist/cjs/layout/raf.js +1 -0
  33. package/dist/cjs/layout/template.js +3 -1
  34. package/dist/cjs/layout/templates.js +15 -0
  35. package/dist/cjs/layout/text/text.js +38 -5
  36. package/dist/cjs/layout/trackable.js +2 -16
  37. package/dist/cjs/plugin.js +4 -1
  38. package/dist/cjs/{source/index.js → source.js} +7 -4
  39. package/dist/cjs/util/bindings.js +3 -0
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/workflow/answer-based.js +335 -0
  42. package/dist/cjs/workflow/ask.js +98 -271
  43. package/dist/cjs/workflow/base.js +69 -35
  44. package/dist/cjs/workflow/explore.js +10 -0
  45. package/dist/cjs/workflow/hybrid-search.js +140 -0
  46. package/dist/cjs/workflow/index.js +7 -0
  47. package/dist/cjs/workflow/processors.js +113 -0
  48. package/dist/cjs/workflow/recommendation.js +12 -3
  49. package/dist/cjs/workflow/search.js +34 -12
  50. package/package.json +2 -2
  51. package/src/actor/autocomplete.js +4 -2
  52. package/src/actor/data.js +8 -2
  53. package/src/actor/fields.js +10 -10
  54. package/src/actor/filters.js +103 -0
  55. package/src/actor/hub.js +2 -1
  56. package/src/actor/sessions.js +3 -4
  57. package/src/actor/view.js +19 -2
  58. package/src/actor/views.js +30 -16
  59. package/src/constants.js +10 -0
  60. package/src/defaults/ask/constants.js +1 -0
  61. package/src/defaults/ask/templates.js +70 -179
  62. package/src/defaults/common/templates.js +173 -0
  63. package/src/defaults/hybrid-search/answer-box.js +118 -0
  64. package/src/defaults/hybrid-search/constants.js +2 -0
  65. package/src/defaults/hybrid-search/controls.js +11 -0
  66. package/src/defaults/hybrid-search/index.js +9 -0
  67. package/src/defaults/hybrid-search/phrases.js +22 -0
  68. package/src/defaults/hybrid-search/templates.js +83 -0
  69. package/src/defaults/index.js +2 -0
  70. package/src/element/container/index.js +1 -0
  71. package/src/element/container/miso-hybrid-search.js +19 -0
  72. package/src/element/role/index.js +3 -0
  73. package/src/element/role/miso-facets.js +16 -0
  74. package/src/element/role/miso-hits.js +16 -0
  75. package/src/element/role/miso-keywords.js +16 -0
  76. package/src/layout/index.js +1 -0
  77. package/src/layout/input/facets.js +242 -0
  78. package/src/layout/input/option-list.js +3 -4
  79. package/src/layout/input/search-box.js +6 -7
  80. package/src/layout/list/collection.js +1 -2
  81. package/src/layout/radio/radio.js +1 -1
  82. package/src/layout/raf.js +1 -0
  83. package/src/layout/template.js +2 -1
  84. package/src/layout/templates.js +15 -0
  85. package/src/layout/text/text.js +28 -3
  86. package/src/layout/trackable.js +2 -15
  87. package/src/plugin.js +6 -2
  88. package/src/{source/index.js → source.js} +7 -4
  89. package/src/util/bindings.js +4 -0
  90. package/src/version.js +1 -1
  91. package/src/workflow/answer-based.js +280 -0
  92. package/src/workflow/ask.js +82 -204
  93. package/src/workflow/base.js +64 -27
  94. package/src/workflow/explore.js +10 -1
  95. package/src/workflow/hybrid-search.js +125 -0
  96. package/src/workflow/index.js +1 -0
  97. package/src/workflow/processors.js +95 -0
  98. package/src/workflow/recommendation.js +9 -2
  99. package/src/workflow/search.js +35 -7
  100. package/dist/cjs/source/api.js +0 -23
  101. package/src/source/api.js +0 -12
@@ -1,48 +1,27 @@
1
1
  import { defineValues, trimObj, API } from '@miso.ai/commons';
2
- import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- import * as sources from '../source/index.js';
5
- import { fields, FeedbackActor, AutocompleteActor } from '../actor/index.js';
6
- import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
7
- import { SearchBoxLayout, OptionListLayout, ListLayout, TextLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
8
- import { makeConfigurable } from './options.js';
9
- import { processData as processAffiliationData } from '../affiliation/index.js';
2
+ import AnswerBasedWorkflow from './answer-based.js';
3
+ import { fields } from '../actor/index.js';
4
+ import { ROLE, ORGANIC_QUESTION_SOURCE } from '../constants.js';
5
+ import { OptionListLayout, ListLayout, TextLayout } from '../layout/index.js';
10
6
 
11
7
  const DEFAULT_API_OPTIONS = Object.freeze({
12
- group: API.GROUP.ASK,
8
+ ...AnswerBasedWorkflow.DEFAULT_API_OPTIONS,
13
9
  name: API.NAME.QUESTIONS,
14
10
  payload: {
15
- source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at'],
11
+ ...AnswerBasedWorkflow.DEFAULT_API_OPTIONS.payload,
16
12
  related_resource_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at'],
17
- cite_link: 1,
18
- cite_start: '[',
19
- cite_end: ']',
20
- },
21
- });
22
-
23
- const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
24
- actor: false,
25
- api: {
26
- group: API.GROUP.ASK,
27
- name: API.NAME.AUTOCOMPLETE,
28
13
  },
29
14
  });
30
15
 
31
16
  const DEFAULT_LAYOUTS = Object.freeze({
32
- [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
33
- [ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
34
- [ROLE.ANSWER]: TypewriterLayout.type,
35
- [ROLE.FEEDBACK]: FeedbackLayout.type,
36
- [ROLE.SOURCES]: [ListLayout.type, { incremental: true, itemType: 'article', templates: { ordered: true } }],
17
+ ...AnswerBasedWorkflow.DEFAULT_LAYOUTS,
37
18
  [ROLE.RELATED_RESOURCES]: [ListLayout.type, { incremental: true, itemType: 'article' }],
38
19
  [ROLE.QUERY_SUGGESTIONS]: OptionListLayout.type,
39
- [ROLE.AFFILIATION]: [AffiliationLayout.type, { incremental: true, itemType: 'affiliation', link: { rel: 'noopener nofollow' } }],
40
20
  });
41
21
 
42
22
  const DEFAULT_TRACKERS = Object.freeze({
43
- [ROLE.SOURCES]: {},
23
+ ...AnswerBasedWorkflow.DEFAULT_TRACKERS,
44
24
  [ROLE.RELATED_RESOURCES]: {},
45
- [ROLE.AFFILIATION]: {},
46
25
  [ROLE.QUERY_SUGGESTIONS]: {
47
26
  click: {
48
27
  validate: event => event.button === 0, // left click only
@@ -51,13 +30,13 @@ const DEFAULT_TRACKERS = Object.freeze({
51
30
  });
52
31
 
53
32
  const DEFAULT_OPTIONS = Object.freeze({
33
+ ...AnswerBasedWorkflow.DEFAULT_OPTIONS,
54
34
  api: DEFAULT_API_OPTIONS,
55
- autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
56
35
  layouts: DEFAULT_LAYOUTS,
57
36
  trackers: DEFAULT_TRACKERS,
58
37
  });
59
38
 
60
- export default class Ask extends Workflow {
39
+ export default class Ask extends AnswerBasedWorkflow {
61
40
 
62
41
  constructor(context, parentQuestionId) {
63
42
  super({
@@ -65,38 +44,47 @@ export default class Ask extends Workflow {
65
44
  context,
66
45
  roles: Object.keys(DEFAULT_LAYOUTS),
67
46
  defaults: DEFAULT_OPTIONS,
47
+ parentQuestionId,
68
48
  });
69
- this._context = context;
70
- this._autoQuery = false;
49
+ }
50
+
51
+ _initProperties(args) {
52
+ super._initProperties(args);
53
+ const { parentQuestionId } = args;
71
54
  defineValues(this, { parentQuestionId });
55
+ }
72
56
 
73
- this._feedback = new FeedbackActor(this._hub);
57
+ _initSubscriptions(args) {
74
58
  this._setSuggestedQuestions();
59
+ super._initSubscriptions(args);
60
+ }
75
61
 
76
- /*
77
- this._autocomplete = new AutocompleteActor(this._hub, {
78
- source: sources.api(this._client),
79
- options: this._options,
80
- });
81
- */
82
-
83
- this._unsubscribes = [
84
- ...this._unsubscribes,
85
- this._hub.on(fields.query(), payload => this.query(payload)),
86
- this._hub.on(fields.view(ROLE.ANSWER), data => this._onAnswerViewUpdate(data)),
87
- this._views.get(ROLE.ANSWER).on('citation-click', event => this._onCitationClick(event)),
88
- ];
89
-
90
- parentQuestionId && context._byPqid.set(parentQuestionId, this);
91
- context._events.emit('create', this);
62
+ _setSuggestedQuestions() {
63
+ const { previous } = this;
64
+ if (!previous) {
65
+ return;
66
+ }
67
+ const values = previous.states[fields.data()].value;
68
+ const value = values.suggested_followup_questions || values.followup_questions || [];
69
+ this._hub.update(fields.suggestions(), { value: value.map(text => ({ text })) });
70
+ }
92
71
 
93
- this.reset(); // create a session so query suggestions can be tracked
72
+ _initReset(args) {
73
+ // register at context
74
+ args.parentQuestionId && this._context._byPqid.set(args.parentQuestionId, this);
75
+ this._context._events.emit('create', this);
76
+ super._initReset(args);
94
77
  }
95
78
 
96
- get questionId() {
97
- return this._questionId;
79
+ // lifecycle //
80
+ restart() {
81
+ if (this._questionId) {
82
+ this._context._byQid.delete(this._questionId);
83
+ }
84
+ super.restart();
98
85
  }
99
86
 
87
+ // properties //
100
88
  get rootQuestionId() {
101
89
  return this._context.root.questionId;
102
90
  }
@@ -116,174 +104,65 @@ export default class Ask extends Workflow {
116
104
  return questionId ? this._context.getByParentQuestionId(questionId, { autoCreate: true }) : undefined;
117
105
  }
118
106
 
119
- // lifecycle //
120
- autoQuery(options = {}) {
121
- const { setValue = true, focus = true } = this._autoQuery = options;
122
- const searchParams = new URLSearchParams(window.location.search);
123
- const q = searchParams.get('q');
124
- const qs = searchParams.get('qs');
125
- const { layout } = this._views.get(ROLE.QUERY);
126
- if (layout) {
127
- if (q && setValue) {
128
- layout.value = q;
129
- }
130
- if (!q && focus) {
131
- layout.focus();
132
- }
133
- }
134
- if (q) {
135
- this.query({ q, qs });
136
- }
137
- }
138
-
139
- query({ q: question, qs: questionSource, ...payload } = {}) {
140
- payload = { ...payload, question };
141
- questionSource = questionSource || ORGANIC_QUESTION_SOURCE;
142
-
143
- if (this.parentQuestionId) {
144
- payload.parent_question_id = this.parentQuestionId;
145
- }
146
- (payload._meta || (payload._meta = {})).question_source = questionSource;
147
-
148
- // clear question id from previous session
149
- if (this._questionId) {
150
- this._context._byQid.delete(this._questionId);
151
- this._questionId = undefined;
152
- }
153
-
154
- this.restart();
155
-
156
- const { session } = this;
157
- session.meta.question_source = questionSource;
158
- this._hub.update(fields.request(), mergeApiOptions(this._options.resolved.api, { payload, session }));
159
-
160
- return this;
161
- }
162
-
163
- restart() {
164
- // TODO: move to workflow base
165
- const { session, status, ongoing } = this._hub.states[fields.view(ROLE.ANSWER)] || {};
166
- if (session && status !== STATUS.INITIAL && (status !== STATUS.READY || ongoing)) {
167
- // it's interrupted by a new question
168
- const state = Object.freeze({ session, status, ongoing });
169
- this._emit('interrupt', state);
170
- this._emit('finally', state);
171
- }
172
- super.restart();
173
- }
174
-
175
- /*
176
- updateCompletions(event) {
177
- // TODO: verify
178
- this._hub.update(fields.completions(), {
179
- ...event,
180
- source: 'manual',
107
+ // query //
108
+ _buildPayload({ q, qs, ...payload } = {}) {
109
+ const { parentQuestionId } = this;
110
+ return trimObj({
111
+ ...payload,
112
+ question: q, // question, not q
113
+ parent_question_id: parentQuestionId,
114
+ _meta: {
115
+ ...payload._meta,
116
+ question_source: qs || ORGANIC_QUESTION_SOURCE, // might be null, not undefined
117
+ },
181
118
  });
182
- return this;
183
119
  }
184
- */
185
120
 
186
- _defaultProcessData(data) {
187
- data = super._defaultProcessData(data);
188
- const { value, request } = data;
189
- if (!value) {
190
- return data;
191
- }
192
- const { question_id: questionId, question } = value;
193
- const organic = request && request.payload && request.payload._meta && request.payload._meta.question_source === ORGANIC_QUESTION_SOURCE;
121
+ // interactions //
122
+ _preprocessInteraction(payload) {
123
+ payload = super._preprocessInteraction(payload) || {};
194
124
 
195
- // capture question ID and register at context
196
- if (!this._questionId && questionId) {
197
- this._questionId = questionId;
198
- this._context._byQid.set(questionId, this);
199
- }
125
+ const { context = {} } = payload;
126
+ const { ...custom_context } = context.custom_context || {};
127
+ let { rootQuestionId, parentQuestionId, questionId } = this;
200
128
 
201
- // update URL if autoQuery.updateUrl is not false
202
- if (this._autoQuery && this._autoQuery.updateUrl !== false) {
203
- const url = new URL(window.location);
204
- const currentQuestion = url.searchParams.get('q');
205
- // TODO: review this, handle &s=
206
- if (question !== currentQuestion && (!organic || this._autoQuery.updateUrl === true)) {
207
- url.searchParams.set('q', question);
208
- window.history.replaceState({}, '', url);
209
- }
129
+ if (custom_context.property === ROLE.QUERY_SUGGESTIONS) {
130
+ questionId = parentQuestionId;
131
+ parentQuestionId = this.previous && this.previous.parentQuestionId;
132
+ custom_context.property = 'suggested_followup_questions';
133
+ custom_context.question_source = this.previous && this.previous.session.meta.question_source;
210
134
  }
135
+ custom_context.question_id = questionId;
136
+ custom_context.root_question_id = rootQuestionId;
137
+ custom_context.parent_question_id = parentQuestionId;
211
138
 
212
- // affiliation
213
- data = processAffiliationData(data);
214
-
215
- // 1. put answer_stage to meta
216
- // 2. mark ongoing flag
217
139
  return {
218
- ...data,
219
- ongoing: !value.finished,
220
- meta: {
221
- ...data.meta,
222
- answer_stage: value.answer_stage,
140
+ ...payload,
141
+ context: {
142
+ ...context,
143
+ custom_context: trimObj(custom_context),
223
144
  },
224
145
  };
225
146
  }
226
147
 
227
- _onAnswerViewUpdate({ session, status, ongoing }) {
228
- if (status === STATUS.INITIAL) {
229
- return;
230
- }
231
- const done = status === STATUS.READY && !ongoing;
232
- const erroneous = status === STATUS.ERRONEOUS;
233
- const eventName = done ? 'done' : erroneous ? 'error' : status;
234
- const state = Object.freeze({ session, status, ongoing });
235
- this._emit(eventName, state);
236
- if (done || erroneous) {
237
- this._emit('finally', state);
238
- }
239
- }
240
-
241
- _setSuggestedQuestions() {
242
- const { previous } = this;
243
- if (!previous) {
244
- return;
245
- }
246
- const values = previous.states[fields.data()].value;
247
- const value = values.suggested_followup_questions || values.followup_questions || [];
248
- this._hub.update(fields.suggestions(), { value: value.map(text => ({ text })) });
249
- }
250
-
251
- _onCitationClick({ index }) {
252
- const { value } = this.states[fields.data()] || {};
253
- const { sources } = value || {};
254
- // index is 1-based
255
- const source = sources && sources[index - 1];
256
- if (!source || !source.product_id) {
257
- return;
148
+ // helpers //
149
+ _writeQuestionId(questionId) {
150
+ // capture question ID and register at context
151
+ if (!this._questionId && questionId) {
152
+ this._questionId = questionId;
153
+ this._context._byQid.set(questionId, this);
258
154
  }
259
- // TODO: should we track impression as well?
260
- this._trackers.sources.click([source.product_id], { manual: false });
261
155
  }
262
156
 
263
- _preprocessInteraction(payload) {
264
- payload = super._preprocessInteraction(payload) || {};
265
- const { context = {} } = payload;
266
- const { custom_context = {} } = context;
267
- let { rootQuestionId, parentQuestionId, questionId } = this;
268
- // when we track suggested questions, we are actually tracking the suggested_followup_questions of the previous workflow
269
- if (custom_context.property === ROLE.QUERY_SUGGESTIONS) {
270
- questionId = parentQuestionId;
271
- parentQuestionId = this.previous && this.previous.parentQuestionId;
272
- custom_context.property = 'suggested_followup_questions';
273
- custom_context.question_source = this.previous && this.previous.session.meta.question_source;
157
+ _clearQuestionId() {
158
+ // clear question id from previous session
159
+ if (this._questionId) {
160
+ this._context._byQid.delete(this._questionId);
161
+ this._questionId = undefined;
274
162
  }
275
- payload.context = {
276
- ...context,
277
- custom_context: trimObj({
278
- root_question_id: rootQuestionId,
279
- parent_question_id: parentQuestionId,
280
- question_id: questionId,
281
- ...custom_context,
282
- }),
283
- };
284
- return payload;
285
163
  }
286
164
 
165
+ // destroy //
287
166
  _destroy(options) {
288
167
  const { parentQuestionId, questionId } = this;
289
168
  if (parentQuestionId) {
@@ -292,7 +171,6 @@ export default class Ask extends Workflow {
292
171
  if (questionId) {
293
172
  this._context._byQid.delete(questionId);
294
173
  }
295
- this._feedback._destroy();
296
174
  super._destroy(options);
297
175
  }
298
176
 
@@ -1,16 +1,23 @@
1
1
  import { Component, isNullLike } from '@miso.ai/commons';
2
2
  import { Hub, SessionMaker, DataActor, ViewsActor, InteractionsActor, fields } from '../actor/index.js';
3
- import * as sources from '../source/index.js';
3
+ import * as sources from '../source.js';
4
4
  import { STATUS, ROLE } from '../constants.js';
5
5
  import { ContainerLayout, ErrorLayout } from '../layout/index.js';
6
6
  import { injectLogger } from './utils.js';
7
7
  import { WorkflowOptions, mergeLayoutsOptions, mergeInteractionsOptions, makeConfigurable } from './options.js';
8
+ import { writeDataStatus, writeMisoIdToMeta, addDataInstructions } from './processors.js';
8
9
 
9
10
  const DEFAULT_LAYOUTS = Object.freeze({
10
11
  [ROLE.CONTAINER]: ContainerLayout.type,
11
12
  [ROLE.ERROR]: ErrorLayout.type,
12
13
  });
13
14
 
15
+ const ROLES_CONFIG = Object.freeze({
16
+ [ROLE.ERROR]: {
17
+ mapping: data => data.error,
18
+ },
19
+ });
20
+
14
21
  function mergeDefaults(workflow, { layouts, interactions, ...defaults } = {}) {
15
22
  const DEFAULT_INTERACTIONS = {
16
23
  preprocess: [payload => workflow._preprocessInteraction(payload)],
@@ -28,36 +35,56 @@ function getRevision(data) {
28
35
 
29
36
  export default class Workflow extends Component {
30
37
 
31
- constructor({
32
- name,
33
- context,
34
- plugin,
35
- client,
36
- roles,
37
- defaults,
38
- }) {
39
- super(name || 'workflow', plugin);
38
+ constructor(args) {
39
+ super(args.name || 'workflow', args.plugin);
40
+
41
+ let { context, plugin, client } = args;
40
42
  this._context = context;
41
43
  this._plugin = plugin = plugin || context._plugin;
42
44
  this._client = client = client || context._client;
43
- this._name = name;
44
- this._roles = roles;
45
+ args = Object.freeze({ ...args, plugin, client });
46
+
47
+ this._name = args.name;
48
+ this._roles = args.roles;
49
+ this._rolesConfig = { ...ROLES_CONFIG, ...args.rolesConfig };
50
+
51
+ this._extensions = plugin._getExtensions(client);
52
+ this._options = new WorkflowOptions(context && context._options, mergeDefaults(this, args.defaults));
53
+ this._hub = injectLogger(new Hub(), (...args) => this._log(...args));
45
54
 
46
- const extensions = this._extensions = plugin._getExtensions(client);
47
- const options = this._options = new WorkflowOptions(context && context._options, mergeDefaults(this, defaults));
48
- const hub = this._hub = injectLogger(new Hub(), (...args) => this._log(...args));
49
- const layouts = plugin.layouts;
55
+ this._initProperties(args);
56
+ this._initActors(args);
57
+ this._initSubscriptions(args);
58
+ this._initReset(args);
59
+ }
60
+
61
+ _initProperties() {}
62
+
63
+ _initActors() {
64
+ const hub = this._hub;
65
+ const client = this._client;
66
+ const roles = this._roles;
67
+ const rolesConfig = this._rolesConfig;
68
+ const options = this._options;
69
+ const extensions = this._extensions;
70
+ const layouts = this._plugin.layouts;
50
71
 
51
72
  this._sessions = new SessionMaker(hub);
52
73
  this._data = new DataActor(hub, { source: sources.api(client), options });
53
- this._views = new ViewsActor(hub, { extensions, layouts, roles, options, workflow: name });
74
+ this._views = new ViewsActor(hub, { extensions, layouts, roles, rolesConfig, options, workflow: this._name });
54
75
  this._interactions = new InteractionsActor(hub, { client, options });
76
+ }
55
77
 
78
+ _initSubscriptions() {
56
79
  this._unsubscribes = [
57
80
  this._hub.on(fields.response(), data => this.updateData(data)),
58
81
  ];
59
82
  }
60
83
 
84
+ _initReset() {
85
+ this.reset();
86
+ }
87
+
61
88
  get uuid() {
62
89
  return this.session && this.session.uuid;
63
90
  }
@@ -75,6 +102,11 @@ export default class Workflow extends Component {
75
102
  return this._hub.states;
76
103
  }
77
104
 
105
+ get status() {
106
+ const data = this._hub.states[fields.data()];
107
+ return data && data.status;
108
+ }
109
+
78
110
  get views() {
79
111
  return this._views.interface;
80
112
  }
@@ -91,13 +123,12 @@ export default class Workflow extends Component {
91
123
  }
92
124
 
93
125
  restart() {
94
- this.reset();
95
- this.start();
126
+ this._sessions.restart();
96
127
  return this;
97
128
  }
98
129
 
99
130
  // states //
100
- updateData(data) {
131
+ updateData(data, { instructions } = {}) {
101
132
  if (!data) {
102
133
  throw new Error(`Data is required.`);
103
134
  }
@@ -123,31 +154,37 @@ export default class Workflow extends Component {
123
154
 
124
155
  this._sessions.start(); // in case session not started yet
125
156
 
157
+ data = writeDataStatus(data);
126
158
  data = this._defaultProcessData(data);
127
159
  for (const process of this._options.resolved.dataProcessor) {
128
160
  data = process(data);
129
161
  }
130
162
 
163
+ // write data instructions from function args
164
+ data = addDataInstructions(data, instructions);
165
+ data = this._mergeDataIfNecessary(data);
166
+
131
167
  this._hub.update(fields.data(), data);
132
168
 
133
169
  return this;
134
170
  }
135
171
 
136
172
  _defaultProcessData(data) {
137
- const { value } = data;
138
- // put miso_id to meta
139
- if (!value || !value.miso_id) {
173
+ return writeMisoIdToMeta(data);
174
+ }
175
+
176
+ _mergeDataIfNecessary(data) {
177
+ if (!data._inst || !data._inst.merge) {
140
178
  return data;
141
179
  }
142
180
  return {
181
+ ...this._hub.states[fields.data()],
143
182
  ...data,
144
- meta: {
145
- ...data.meta,
146
- miso_id: value.miso_id,
147
- },
148
183
  };
149
184
  }
150
185
 
186
+ _handleResponseObject() {}
187
+
151
188
  // TODO: notifyViewUpdateAll()
152
189
 
153
190
  notifyViewUpdate(role, state) {
@@ -39,9 +39,16 @@ export default class Explore extends Workflow {
39
39
  roles: Object.keys(DEFAULT_LAYOUTS),
40
40
  defaults: DEFAULT_OPTIONS,
41
41
  });
42
+ }
43
+
44
+ _initProperties(args) {
45
+ super._initProperties(args);
42
46
  this._productId = undefined;
43
47
  this._linkFn = undefined;
48
+ }
44
49
 
50
+ _initSubscriptions(args) {
51
+ super._initSubscriptions(args);
45
52
  this._unsubscribes = [
46
53
  ...this._unsubscribes,
47
54
  this._views.get(ROLE.RELATED_QUESTIONS).on('click', event => this._handleRelatedQuestionClick(event)),
@@ -49,6 +56,7 @@ export default class Explore extends Workflow {
49
56
  ];
50
57
  }
51
58
 
59
+ // configuration //
52
60
  set productId(value) {
53
61
  console.warning('DEPRECATED: use useApi() instead');
54
62
  this.useApi({
@@ -84,7 +92,8 @@ export default class Explore extends Workflow {
84
92
  if (relatedQuestions) {
85
93
  this._hub.update(fields.request(), mergeApiOptions(this._options.resolved.api, { session }));
86
94
  } else {
87
- this.updateData({ session, value: { related_questions: [] } });
95
+ // TODO: ad-hoc
96
+ this.updateData({ session, request: {}, value: { related_questions: [] } });
88
97
  }
89
98
  return this;
90
99
  }