@miso.ai/client-sdk-ui 1.10.2-beta.1 → 1.10.2-beta.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.
@@ -14,6 +14,7 @@ exports.request = request;
14
14
  exports.response = response;
15
15
  exports.session = session;
16
16
  exports.suggestions = suggestions;
17
+ exports.tracker = tracker;
17
18
  exports.view = view;
18
19
  function session() {
19
20
  return 'session';
@@ -39,6 +40,9 @@ function view(role) {
39
40
  function event() {
40
41
  return 'event';
41
42
  }
43
+ function tracker() {
44
+ return 'tracker';
45
+ }
42
46
  function interaction() {
43
47
  return 'interaction';
44
48
  }
@@ -12,41 +12,57 @@ var _utils = require("./utils.js");
12
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
13
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
14
14
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
15
+ const VALUELESS_ITEMS = [Symbol('valueless')];
15
16
  class Tracker {
16
- constructor(view) {
17
- this._view = view;
18
- this._role = view.role;
17
+ constructor(_ref) {
18
+ let {
19
+ hub,
20
+ role,
21
+ valueless = false,
22
+ options
23
+ } = _ref;
24
+ this._hub = hub;
25
+ this._role = role;
26
+ this._valueless = !!valueless;
27
+ this._options = options;
19
28
  // TODO: know item type
20
29
 
21
30
  this._states = new _states.default(this._getSessionId());
22
31
  }
23
32
  get options() {
24
- return this._view._getTrackerOptions();
33
+ return typeof this._options === 'function' ? this._options() : this._options;
25
34
  }
26
35
  getState(item) {
27
36
  return this._states.getFullState(item);
28
37
  }
29
38
  _trigger(type, items) {
30
39
  let meta = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
40
+ if (this._valueless) {
41
+ meta = items;
42
+ items = VALUELESS_ITEMS;
43
+ }
31
44
  (0, _constants.validateEventType)(type);
32
45
  this._syncSession();
33
- items = this._states.untriggered((0, _commons.asArray)(items), type);
34
- if (items.length === 0) {
35
- return;
46
+ if (type !== _constants.EVENT_TYPE.SUBMIT) {
47
+ items = this._states.untriggered((0, _commons.asArray)(items), type);
48
+ if (items.length === 0) {
49
+ return;
50
+ }
51
+ this._states.set(items, type, _constants.TRACKING_STATUS.TRIGGERED);
36
52
  }
37
- this._states.set(items, type, _constants.TRACKING_STATUS.TRIGGERED);
38
53
  const property = this._role === 'results' ? 'products' : this._role; // TODO: ad-hoc, see #83
39
54
  const misoId = this._getMisoId();
40
55
  const request = this._getRequestPayload();
56
+ const values = this._valueless ? undefined : items;
41
57
 
42
58
  // TODO: should trigger 'tracker' and let workflow translate to interactions
43
- this._view.hub.trigger(fields.interaction(), (0, _utils.toInteraction)({
59
+ this._hub.trigger(fields.interaction(), (0, _utils.toInteraction)({
44
60
  property,
45
61
  misoId,
46
62
  request
47
63
  }, {
48
64
  event: type,
49
- values: items,
65
+ values,
50
66
  meta
51
67
  }));
52
68
  }
@@ -76,7 +92,7 @@ class Tracker {
76
92
  return request && request.payload;
77
93
  }
78
94
  _getViewState() {
79
- return this._view.hub.states[fields.view(this._role)];
95
+ return this._hub.states[fields.view(this._role)];
80
96
  }
81
97
  }
82
98
  exports.default = Tracker;
@@ -34,10 +34,13 @@ function toInteraction(_ref, _ref2) {
34
34
  // TODO: ad-hoc
35
35
  const question_source = request && request._meta && request._meta.question_source;
36
36
  const isProductType = isProduct(property);
37
- const product_ids = isProductType ? values.map(v => typeof v === 'string' ? v : v.product_id).filter(v => v) : [];
38
- const items = isProductType ? undefined : values.map(v => v.text || v.id || v);
39
- const channel = property === _constants.ROLE.AFFILIATION ? values[0] && values[0].channel : undefined;
40
- const positions = property === _constants.ROLE.AFFILIATION ? values.map(v => v.position) : undefined;
37
+ const isSubmit = event === _constants.EVENT_TYPE.SUBMIT;
38
+ const isAffiliation = property === _constants.ROLE.AFFILIATION;
39
+ const product_ids = isProductType && values ? values.map(v => typeof v === 'string' ? v : v.product_id).filter(v => v) : !isSubmit ? [] : undefined;
40
+ const items = !isProductType && !isSubmit && values ? values.map(v => v.text || v.id || v) : undefined;
41
+ const value = isSubmit ? values : undefined;
42
+ const channel = isAffiliation ? values[0] && values[0].channel : undefined;
43
+ const positions = isAffiliation ? values.map(v => v.position) : undefined;
41
44
  const payload = (0, _commons.trimObj)({
42
45
  type: event === 'viewable' ? 'viewable_impression' : event,
43
46
  product_ids,
@@ -48,6 +51,7 @@ function toInteraction(_ref, _ref2) {
48
51
  property,
49
52
  channel,
50
53
  items,
54
+ value,
51
55
  positions,
52
56
  trigger: manual ? 'manual' : 'auto',
53
57
  question_source
@@ -79,9 +79,12 @@ class ViewActor {
79
79
  this._layout = layout;
80
80
 
81
81
  // initialize new layout
82
- // don't use _safeApplyOnLayout, for any error should be thrown
83
- if (layout && typeof layout.initialize === 'function') {
84
- layout.initialize(this);
82
+ if (layout) {
83
+ layout._view = this;
84
+ // don't use _safeApplyOnLayout, for any error should be thrown
85
+ if (typeof layout.initialize === 'function') {
86
+ layout.initialize(this);
87
+ }
85
88
  }
86
89
 
87
90
  // in case the data is already there
@@ -90,8 +93,21 @@ class ViewActor {
90
93
  });
91
94
  }
92
95
  get tracker() {
96
+ const {
97
+ role
98
+ } = this;
99
+ if (role === _constants.ROLE.CONTAINER) {
100
+ return this._views._getContainerTracker();
101
+ }
93
102
  if (!this._tracker) {
94
- this._tracker = new _tracker.default(this);
103
+ const {
104
+ hub
105
+ } = this;
106
+ this._tracker = new _tracker.default({
107
+ hub,
108
+ role,
109
+ options: () => this._getTrackerOptions()
110
+ });
95
111
  }
96
112
  return this._tracker;
97
113
  }
@@ -8,6 +8,7 @@ var _commons = require("@miso.ai/commons");
8
8
  var _constants = require("../constants.js");
9
9
  var fields = _interopRequireWildcard(require("./fields.js"));
10
10
  var _view = _interopRequireDefault(require("./view.js"));
11
+ var _tracker = _interopRequireDefault(require("./tracker.js"));
11
12
  var _trackers = require("../util/trackers.js");
12
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
14
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
@@ -27,6 +28,7 @@ class ViewsActor {
27
28
  this._layoutFactory = layouts;
28
29
  this._options = options;
29
30
  this._containers = new Map();
31
+ this._containerTracker = undefined;
30
32
  this._views = {};
31
33
  this._trackers = new Trackers(this, roles);
32
34
  for (const role of roles) {
@@ -202,6 +204,19 @@ class ViewsActor {
202
204
  _getTrackerOptions(role) {
203
205
  return (0, _trackers.normalizeTrackerOptions)(this._trackerOptions && this._trackerOptions[role] || false);
204
206
  }
207
+ _getContainerTracker() {
208
+ if (!this._containerTracker) {
209
+ const hub = this._hub;
210
+ const role = _constants.ROLE.CONTAINER;
211
+ this._containerTracker = new _tracker.default({
212
+ hub,
213
+ role,
214
+ valueless: true,
215
+ options: () => this._getTrackerOptions(role)
216
+ });
217
+ }
218
+ return this._containerTracker;
219
+ }
205
220
  _error(e) {
206
221
  // TODO: hub trigger error event
207
222
  console.error(e);
@@ -242,5 +257,8 @@ class Trackers {
242
257
  get: () => views.get(role).tracker
243
258
  });
244
259
  }
260
+ Object.defineProperty(this, _constants.ROLE.CONTAINER, {
261
+ get: () => views._getContainerTracker()
262
+ });
245
263
  }
246
264
  }
@@ -47,9 +47,10 @@ const LAYOUT_CATEGORY = exports.LAYOUT_CATEGORY = Object.freeze({
47
47
  const EVENT_TYPE = exports.EVENT_TYPE = Object.freeze({
48
48
  IMPRESSION: 'impression',
49
49
  VIEWABLE: 'viewable',
50
- CLICK: 'click'
50
+ CLICK: 'click',
51
+ SUBMIT: 'submit'
51
52
  });
52
- const TRACKING_EVENT_TYPES = exports.TRACKING_EVENT_TYPES = [EVENT_TYPE.IMPRESSION, EVENT_TYPE.VIEWABLE, EVENT_TYPE.CLICK];
53
+ const TRACKING_EVENT_TYPES = exports.TRACKING_EVENT_TYPES = [EVENT_TYPE.IMPRESSION, EVENT_TYPE.VIEWABLE, EVENT_TYPE.CLICK, EVENT_TYPE.SUBMIT];
53
54
  const TRACKING_STATUS = exports.TRACKING_STATUS = Object.freeze({
54
55
  UNTRACKED: 'untracked',
55
56
  TRACKING: 'tracking',
@@ -7,6 +7,7 @@ exports.default = void 0;
7
7
  var _constants = require("../../constants.js");
8
8
  var _raf = _interopRequireDefault(require("../raf.js"));
9
9
  var _misoBanner = _interopRequireDefault(require("../../element/miso-banner.js"));
10
+ var _viewables = _interopRequireDefault(require("../../util/viewables.js"));
10
11
  const _excluded = ["logo"];
11
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
13
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; }
@@ -27,15 +28,19 @@ class ContainerLayout extends _raf.default {
27
28
  super(Object.assign({
28
29
  logo
29
30
  }, options));
31
+ this._impression = false;
32
+ this._viewables = new _viewables.default();
30
33
  }
31
- _render(element, _ref2) {
32
- let {
33
- state
34
- } = _ref2;
34
+ _syncElement(element) {
35
+ if (this._element !== element) {
36
+ this._viewables.untrack(this._element);
37
+ }
38
+ super._syncElement(element);
39
+ }
40
+ _render(element, states) {
35
41
  this._syncBanner(element);
36
- this._syncStatus(element, {
37
- state
38
- });
42
+ this._syncStatus(element, states);
43
+ this._trackInteractions(element, states);
39
44
  }
40
45
  _syncBanner(element) {
41
46
  const bannerTagName = _misoBanner.default.tagName;
@@ -56,17 +61,17 @@ class ContainerLayout extends _raf.default {
56
61
  } = element;
57
62
  const logo = localLogo !== undefined && localLogo !== 'auto' ? localLogo : globalLogo;
58
63
  // logo === 'auto': only add banner when a component of main role (like producs, answer) is present
59
- return logo === 'auto' ? element.components.some(_ref3 => {
64
+ return logo === 'auto' ? element.components.some(_ref2 => {
60
65
  let {
61
66
  role
62
- } = _ref3;
67
+ } = _ref2;
63
68
  return isMainRole(role);
64
69
  }) : !!logo;
65
70
  }
66
- _syncStatus(element, _ref4) {
71
+ _syncStatus(element, _ref3) {
67
72
  let {
68
73
  state
69
- } = _ref4;
74
+ } = _ref3;
70
75
  if (!element) {
71
76
  return;
72
77
  }
@@ -89,6 +94,42 @@ class ContainerLayout extends _raf.default {
89
94
  element.removeAttribute('miso-id');
90
95
  }
91
96
  }
97
+ _trackInteractions(element, _ref4) {
98
+ let {
99
+ state
100
+ } = _ref4;
101
+ const {
102
+ status
103
+ } = state;
104
+ if (status === _constants.STATUS.READY) {
105
+ this._trackImpression();
106
+ this._trackViewable(element); // don't await
107
+ }
108
+ }
109
+ _trackImpression() {
110
+ const {
111
+ impression: options
112
+ } = this._view.tracker.options || {};
113
+ if (!options) {
114
+ return;
115
+ }
116
+ if (this._impression) {
117
+ return;
118
+ }
119
+ this._impression = true;
120
+ this._view.tracker.impression();
121
+ }
122
+ async _trackViewable(element) {
123
+ const {
124
+ viewable: options
125
+ } = this._view.tracker.options || {};
126
+ if (!options) {
127
+ return;
128
+ }
129
+ if (await this._viewables.track(element, options)) {
130
+ this._view.tracker.viewable();
131
+ }
132
+ }
92
133
  }
93
134
  exports.default = ContainerLayout;
94
135
  function isMainRole(role) {
@@ -80,7 +80,6 @@ class OptionListLayout extends _template.default {
80
80
  this._initTrackable();
81
81
  }
82
82
  initialize(view) {
83
- this._view = view;
84
83
  const {
85
84
  proxyElement,
86
85
  hub
@@ -158,7 +158,6 @@ class SearchBoxLayout extends _template.default {
158
158
  this._debounce = (0, _commons.debounce)(this.autocomplete.debounce);
159
159
  }
160
160
  initialize(view) {
161
- this._view = view;
162
161
  const {
163
162
  proxyElement,
164
163
  hub
@@ -378,6 +377,16 @@ class SearchBoxLayout extends _template.default {
378
377
  this._view.hub.trigger(_index.fields.query(), {
379
378
  q: value
380
379
  });
380
+ this._trackSubmit(value);
381
+ }
382
+ _trackSubmit(value) {
383
+ const {
384
+ [_constants.EVENT_TYPE.SUBMIT]: options
385
+ } = this._view.tracker.options || {};
386
+ if (!options) {
387
+ return;
388
+ }
389
+ this._view.tracker.submit(value);
381
390
  }
382
391
  }
383
392
  exports.default = SearchBoxLayout;
@@ -115,11 +115,9 @@ class CollectionLayout extends _template.default {
115
115
  templates: Object.assign({}, DEFAULT_TEMPLATES, templates),
116
116
  itemType
117
117
  }, options));
118
- this._view = undefined;
119
118
  this._initTrackable();
120
119
  }
121
120
  initialize(view) {
122
- this._view = view;
123
121
  this._unsubscribes.push(view.proxyElement.on('click', this._onClick.bind(this)));
124
122
  }
125
123
  _preprocess(_ref2) {
@@ -65,7 +65,7 @@ class RadioLayout extends _template.default {
65
65
  initialize(view) {
66
66
  const {
67
67
  proxyElement
68
- } = this._view = view;
68
+ } = view;
69
69
  this._unsubscribes = [...this._unsubscribes, view.hub.on(this._field, () => this._refresh()), proxyElement.on('click', e => this._handleClick(e))];
70
70
  this._defaultValue ? this._select(this._defaultValue) : this._clear();
71
71
  }
@@ -56,8 +56,7 @@ class TypewriterLayout extends _progressive.default {
56
56
  TypewriterLayout.MisoClient.plugins.install('std:ui-markdown');
57
57
  }
58
58
  }
59
- initialize(view) {
60
- this._view = view;
59
+ initialize() {
61
60
  this._setup();
62
61
  }
63
62
 
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.TrackableMixin = void 0;
7
7
  exports.makeTrackable = makeTrackable;
8
- var _constants = require("../constants.js");
9
8
  var _bindings = _interopRequireDefault(require("../util/bindings.js"));
10
9
  var _viewables = _interopRequireDefault(require("../util/viewables.js"));
11
10
  var _trackers = require("../util/trackers.js");
@@ -4,4 +4,4 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _default = exports.default = '1.10.2-beta.1';
7
+ var _default = exports.default = '1.10.2-beta.3';
@@ -36,6 +36,9 @@ function mergeDefaults(workflow) {
36
36
  interactions: (0, _options.mergeInteractionsOptions)(DEFAULT_INTERACTIONS, interactions)
37
37
  });
38
38
  }
39
+ function getRevision(data) {
40
+ return data && data.value && data.value.revision;
41
+ }
39
42
  class Workflow extends _commons.Component {
40
43
  constructor(_ref2) {
41
44
  var _this;
@@ -129,6 +132,15 @@ class Workflow extends _commons.Component {
129
132
  if (session.uuid !== this.session.uuid) {
130
133
  return; // ignore data for old session or inactive session
131
134
  }
135
+
136
+ // compare revision to omit outdated data, if any
137
+ const revision = getRevision(data);
138
+ if (!(0, _commons.isNullLike)(revision)) {
139
+ const currentRevision = getRevision(this._hub.states[_index.fields.data()]);
140
+ if (!(0, _commons.isNullLike)(currentRevision) && revision <= currentRevision) {
141
+ return;
142
+ }
143
+ }
132
144
  this._sessions.start(); // in case session not started yet
133
145
 
134
146
  data = this._defaultProcessData(data);
@@ -29,7 +29,11 @@ const DEFAULT_LAYOUTS = Object.freeze({
29
29
  }]
30
30
  });
31
31
  const DEFAULT_TRACKERS = Object.freeze({
32
- [_constants.ROLE.RELATED_QUESTIONS]: {}
32
+ [_constants.ROLE.RELATED_QUESTIONS]: {},
33
+ [_constants.ROLE.CONTAINER]: {},
34
+ [_constants.ROLE.QUERY]: {
35
+ [_constants.EVENT_TYPE.SUBMIT]: {}
36
+ }
33
37
  });
34
38
  const DEFAULT_OPTIONS = Object.freeze({
35
39
  api: DEFAULT_API_OPTIONS,
package/package.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
27
27
  },
28
28
  "dependencies": {
29
- "@miso.ai/commons": "1.10.2-beta.1"
29
+ "@miso.ai/commons": "1.10.2-beta.3"
30
30
  },
31
- "version": "1.10.2-beta.1"
31
+ "version": "1.10.2-beta.3"
32
32
  }
@@ -30,6 +30,10 @@ export function event() {
30
30
  return 'event';
31
31
  }
32
32
 
33
+ export function tracker() {
34
+ return 'tracker';
35
+ }
36
+
33
37
  export function interaction() {
34
38
  return 'interaction';
35
39
  }
@@ -1,21 +1,25 @@
1
1
  import { asArray } from '@miso.ai/commons';
2
- import { TRACKING_EVENT_TYPES, TRACKING_STATUS, validateEventType } from '../constants.js';
2
+ import { TRACKING_EVENT_TYPES, TRACKING_STATUS, EVENT_TYPE, validateEventType } from '../constants.js';
3
3
  import * as fields from './fields.js';
4
4
  import States from '../util/states.js';
5
5
  import { toInteraction } from './utils.js';
6
6
 
7
+ const VALUELESS_ITEMS = [Symbol('valueless')];
8
+
7
9
  export default class Tracker {
8
10
 
9
- constructor(view) {
10
- this._view = view;
11
- this._role = view.role;
11
+ constructor({ hub, role, valueless = false, options }) {
12
+ this._hub = hub;
13
+ this._role = role;
14
+ this._valueless = !!valueless;
15
+ this._options = options;
12
16
  // TODO: know item type
13
17
 
14
18
  this._states = new States(this._getSessionId());
15
19
  }
16
20
 
17
21
  get options() {
18
- return this._view._getTrackerOptions();
22
+ return typeof this._options === 'function' ? this._options() : this._options;
19
23
  }
20
24
 
21
25
  getState(item) {
@@ -23,21 +27,28 @@ export default class Tracker {
23
27
  }
24
28
 
25
29
  _trigger(type, items, meta = {}) {
30
+ if (this._valueless) {
31
+ meta = items;
32
+ items = VALUELESS_ITEMS;
33
+ }
26
34
  validateEventType(type);
27
35
  this._syncSession();
28
36
 
29
- items = this._states.untriggered(asArray(items), type);
30
- if (items.length === 0) {
31
- return;
37
+ if (type !== EVENT_TYPE.SUBMIT) {
38
+ items = this._states.untriggered(asArray(items), type);
39
+ if (items.length === 0) {
40
+ return;
41
+ }
42
+ this._states.set(items, type, TRACKING_STATUS.TRIGGERED);
32
43
  }
33
- this._states.set(items, type, TRACKING_STATUS.TRIGGERED);
34
44
 
35
45
  const property = this._role === 'results' ? 'products' : this._role; // TODO: ad-hoc, see #83
36
46
  const misoId = this._getMisoId();
37
47
  const request = this._getRequestPayload();
48
+ const values = this._valueless ? undefined : items;
38
49
 
39
50
  // TODO: should trigger 'tracker' and let workflow translate to interactions
40
- this._view.hub.trigger(fields.interaction(), toInteraction({ property, misoId, request }, { event: type, values: items, meta }));
51
+ this._hub.trigger(fields.interaction(), toInteraction({ property, misoId, request }, { event: type, values, meta }));
41
52
  }
42
53
 
43
54
  _syncSession() {
@@ -66,7 +77,7 @@ export default class Tracker {
66
77
  }
67
78
 
68
79
  _getViewState() {
69
- return this._view.hub.states[fields.view(this._role)];
80
+ return this._hub.states[fields.view(this._role)];
70
81
  }
71
82
 
72
83
  }
@@ -1,5 +1,5 @@
1
1
  import { trimObj, uuidToTimestamp } from '@miso.ai/commons';
2
- import { ROLE } from '../constants.js';
2
+ import { ROLE, EVENT_TYPE } from '../constants.js';
3
3
 
4
4
  const PRODUCT_TYPED_PROPERTIES = new Set([
5
5
  ROLE.PRODUCTS,
@@ -22,10 +22,16 @@ export function toInteraction({ property, misoId, request }, { event, values, ma
22
22
  // TODO: ad-hoc
23
23
  const question_source = request && request._meta && request._meta.question_source;
24
24
  const isProductType = isProduct(property);
25
- const product_ids = isProductType ? values.map(v => typeof v === 'string' ? v : v.product_id).filter(v => v) : [];
26
- const items = isProductType ? undefined : values.map(v => v.text || v.id || v);
27
- const channel = property === ROLE.AFFILIATION ? values[0] && values[0].channel : undefined;
28
- const positions = property === ROLE.AFFILIATION ? values.map(v => v.position) : undefined;
25
+ const isSubmit = event === EVENT_TYPE.SUBMIT;
26
+ const isAffiliation = property === ROLE.AFFILIATION;
27
+
28
+ const product_ids = isProductType && values ? values.map(v => typeof v === 'string' ? v : v.product_id).filter(v => v) : !isSubmit ? [] : undefined;
29
+ const items = !isProductType && !isSubmit && values ? values.map(v => v.text || v.id || v) : undefined;
30
+ const value = isSubmit ? values : undefined;
31
+
32
+ const channel = isAffiliation ? values[0] && values[0].channel : undefined;
33
+ const positions = isAffiliation ? values.map(v => v.position) : undefined;
34
+
29
35
  const payload = trimObj({
30
36
  type: event === 'viewable' ? 'viewable_impression' : event,
31
37
  product_ids,
@@ -36,6 +42,7 @@ export function toInteraction({ property, misoId, request }, { event, values, ma
36
42
  property,
37
43
  channel,
38
44
  items,
45
+ value,
39
46
  positions,
40
47
  trigger: manual ? 'manual' : 'auto',
41
48
  question_source,
package/src/actor/view.js CHANGED
@@ -77,9 +77,12 @@ export default class ViewActor {
77
77
  this._layout = layout;
78
78
 
79
79
  // initialize new layout
80
- // don't use _safeApplyOnLayout, for any error should be thrown
81
- if (layout && typeof layout.initialize === 'function') {
82
- layout.initialize(this);
80
+ if (layout) {
81
+ layout._view = this;
82
+ // don't use _safeApplyOnLayout, for any error should be thrown
83
+ if (typeof layout.initialize === 'function') {
84
+ layout.initialize(this);
85
+ }
83
86
  }
84
87
 
85
88
  // in case the data is already there
@@ -87,8 +90,13 @@ export default class ViewActor {
87
90
  }
88
91
 
89
92
  get tracker() {
93
+ const { role } = this;
94
+ if (role === ROLE.CONTAINER) {
95
+ return this._views._getContainerTracker();
96
+ }
90
97
  if (!this._tracker) {
91
- this._tracker = new Tracker(this);
98
+ const { hub } = this;
99
+ this._tracker = new Tracker({ hub, role, options: () => this._getTrackerOptions() });
92
100
  }
93
101
  return this._tracker;
94
102
  }
@@ -2,6 +2,7 @@ import { defineValues, delegateGetters } from '@miso.ai/commons';
2
2
  import { STATUS, ROLE } from '../constants.js';
3
3
  import * as fields from './fields.js';
4
4
  import ViewActor from './view.js';
5
+ import Tracker from './tracker.js';
5
6
  import { normalizeTrackerOptions } from '../util/trackers.js';
6
7
 
7
8
  export default class ViewsActor {
@@ -19,6 +20,7 @@ export default class ViewsActor {
19
20
  this._layoutFactory = layouts;
20
21
  this._options = options;
21
22
  this._containers = new Map();
23
+ this._containerTracker = undefined;
22
24
  this._views = {};
23
25
  this._trackers = new Trackers(this, roles);
24
26
 
@@ -201,6 +203,15 @@ export default class ViewsActor {
201
203
  return normalizeTrackerOptions((this._trackerOptions && this._trackerOptions[role]) || false);
202
204
  }
203
205
 
206
+ _getContainerTracker() {
207
+ if (!this._containerTracker) {
208
+ const hub = this._hub;
209
+ const role = ROLE.CONTAINER;
210
+ this._containerTracker = new Tracker({ hub, role, valueless: true, options: () => this._getTrackerOptions(role) });
211
+ }
212
+ return this._containerTracker;
213
+ }
214
+
204
215
  _error(e) {
205
216
  // TODO: hub trigger error event
206
217
  console.error(e);
@@ -246,6 +257,9 @@ class Trackers {
246
257
  get: () => views.get(role).tracker,
247
258
  });
248
259
  }
260
+ Object.defineProperty(this, ROLE.CONTAINER, {
261
+ get: () => views._getContainerTracker(),
262
+ });
249
263
  }
250
264
 
251
265
  }
package/src/constants.js CHANGED
@@ -57,12 +57,14 @@ export const EVENT_TYPE = Object.freeze({
57
57
  IMPRESSION: 'impression',
58
58
  VIEWABLE: 'viewable',
59
59
  CLICK: 'click',
60
+ SUBMIT: 'submit',
60
61
  });
61
62
 
62
63
  export const TRACKING_EVENT_TYPES = [
63
64
  EVENT_TYPE.IMPRESSION,
64
65
  EVENT_TYPE.VIEWABLE,
65
66
  EVENT_TYPE.CLICK,
67
+ EVENT_TYPE.SUBMIT,
66
68
  ];
67
69
 
68
70
  export const TRACKING_STATUS = Object.freeze({
@@ -1,6 +1,7 @@
1
1
  import { ROLE, STATUS, LAYOUT_CATEGORY } from '../../constants.js';
2
2
  import RafLayout from '../raf.js';
3
3
  import MisoBannerElement from '../../element/miso-banner.js';
4
+ import Viewables from '../../util/viewables.js';
4
5
 
5
6
  const TYPE = 'container';
6
7
 
@@ -19,11 +20,21 @@ export default class ContainerLayout extends RafLayout {
19
20
  logo,
20
21
  ...options,
21
22
  });
23
+ this._impression = false;
24
+ this._viewables = new Viewables();
22
25
  }
23
26
 
24
- _render(element, { state }) {
27
+ _syncElement(element) {
28
+ if (this._element !== element) {
29
+ this._viewables.untrack(this._element);
30
+ }
31
+ super._syncElement(element);
32
+ }
33
+
34
+ _render(element, states) {
25
35
  this._syncBanner(element);
26
- this._syncStatus(element, { state });
36
+ this._syncStatus(element, states);
37
+ this._trackInteractions(element, states);
27
38
  }
28
39
 
29
40
  _syncBanner(element) {
@@ -62,6 +73,36 @@ export default class ContainerLayout extends RafLayout {
62
73
  }
63
74
  }
64
75
 
76
+ _trackInteractions(element, { state }) {
77
+ const { status } = state;
78
+ if (status === STATUS.READY) {
79
+ this._trackImpression();
80
+ this._trackViewable(element); // don't await
81
+ }
82
+ }
83
+
84
+ _trackImpression() {
85
+ const { impression: options } = this._view.tracker.options || {};
86
+ if (!options) {
87
+ return;
88
+ }
89
+ if (this._impression) {
90
+ return;
91
+ }
92
+ this._impression = true;
93
+ this._view.tracker.impression();
94
+ }
95
+
96
+ async _trackViewable(element) {
97
+ const { viewable: options } = this._view.tracker.options || {};
98
+ if (!options) {
99
+ return;
100
+ }
101
+ if (await this._viewables.track(element, options)) {
102
+ this._view.tracker.viewable();
103
+ }
104
+ }
105
+
65
106
  }
66
107
 
67
108
  function isMainRole(role) {
@@ -67,7 +67,6 @@ export default class OptionListLayout extends TemplateBasedLayout {
67
67
  }
68
68
 
69
69
  initialize(view) {
70
- this._view = view;
71
70
  const { proxyElement, hub } = view;
72
71
  this._unsubscribes = [
73
72
  ...this._unsubscribes,
@@ -1,5 +1,5 @@
1
1
  import { defineValues, escapeHtml, findInAncestors, debounce } from '@miso.ai/commons';
2
- import { LAYOUT_CATEGORY, STATUS } from '../../constants.js';
2
+ import { LAYOUT_CATEGORY, STATUS, EVENT_TYPE } from '../../constants.js';
3
3
  import { fields } from '../../actor/index.js';
4
4
  import TemplateBasedLayout from '../template.js';
5
5
  import { helpers, imageBlock, productInfoBlock } from '../templates.js';
@@ -134,7 +134,6 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
134
134
  }
135
135
 
136
136
  initialize(view) {
137
- this._view = view;
138
137
  const { proxyElement, hub } = view;
139
138
  this._unsubscribes = [
140
139
  ...this._unsubscribes,
@@ -326,6 +325,15 @@ export default class SearchBoxLayout extends TemplateBasedLayout {
326
325
  this.close();
327
326
  // TODO: q -> value
328
327
  this._view.hub.trigger(fields.query(), { q: value });
328
+ this._trackSubmit(value);
329
+ }
330
+
331
+ _trackSubmit(value) {
332
+ const { [EVENT_TYPE.SUBMIT]: options } = this._view.tracker.options || {};
333
+ if (!options) {
334
+ return;
335
+ }
336
+ this._view.tracker.submit(value);
329
337
  }
330
338
 
331
339
  }
@@ -86,12 +86,10 @@ export default class CollectionLayout extends TemplateBasedLayout {
86
86
  itemType,
87
87
  ...options,
88
88
  });
89
- this._view = undefined;
90
89
  this._initTrackable();
91
90
  }
92
91
 
93
92
  initialize(view) {
94
- this._view = view;
95
93
  this._unsubscribes.push(view.proxyElement.on('click', this._onClick.bind(this)));
96
94
  }
97
95
 
@@ -49,7 +49,7 @@ export default class RadioLayout extends TemplateBasedLayout {
49
49
  }
50
50
 
51
51
  initialize(view) {
52
- const { proxyElement } = this._view = view;
52
+ const { proxyElement } = view;
53
53
  this._unsubscribes = [
54
54
  ...this._unsubscribes,
55
55
  view.hub.on(this._field, () => this._refresh()),
@@ -50,8 +50,7 @@ export default class TypewriterLayout extends ProgressiveLayout {
50
50
  }
51
51
  }
52
52
 
53
- initialize(view) {
54
- this._view = view;
53
+ initialize() {
55
54
  this._setup();
56
55
  }
57
56
 
@@ -1,4 +1,3 @@
1
- import { EVENT_TYPE } from '../constants.js';
2
1
  import Bindings from '../util/bindings.js';
3
2
  import Viewables from '../util/viewables.js';
4
3
  import { validateClick } from '../util/trackers.js';
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.10.2-beta.1';
1
+ export default '1.10.2-beta.3';
@@ -1,4 +1,4 @@
1
- import { Component } from '@miso.ai/commons';
1
+ import { Component, isNullLike } from '@miso.ai/commons';
2
2
  import { Hub, SessionMaker, DataActor, ViewsActor, InteractionsActor, fields } from '../actor/index.js';
3
3
  import * as sources from '../source/index.js';
4
4
  import { STATUS, ROLE } from '../constants.js';
@@ -22,6 +22,10 @@ function mergeDefaults(workflow, { layouts, interactions, ...defaults } = {}) {
22
22
  };
23
23
  }
24
24
 
25
+ function getRevision(data) {
26
+ return data && data.value && data.value.revision;
27
+ }
28
+
25
29
  export default class Workflow extends Component {
26
30
 
27
31
  constructor({
@@ -108,6 +112,15 @@ export default class Workflow extends Component {
108
112
  return; // ignore data for old session or inactive session
109
113
  }
110
114
 
115
+ // compare revision to omit outdated data, if any
116
+ const revision = getRevision(data);
117
+ if (!isNullLike(revision)) {
118
+ const currentRevision = getRevision(this._hub.states[fields.data()]);
119
+ if (!isNullLike(currentRevision) && revision <= currentRevision) {
120
+ return;
121
+ }
122
+ }
123
+
111
124
  this._sessions.start(); // in case session not started yet
112
125
 
113
126
  data = this._defaultProcessData(data);
@@ -2,7 +2,7 @@ import { API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
3
  import { mergeApiOptions } from './options.js';
4
4
  import { fields } from '../actor/index.js';
5
- import { ROLE } from '../constants.js';
5
+ import { ROLE, EVENT_TYPE } from '../constants.js';
6
6
  import { ListLayout, SearchBoxLayout } from '../layout/index.js';
7
7
 
8
8
  const DEFAULT_API_OPTIONS = Object.freeze({
@@ -17,6 +17,10 @@ const DEFAULT_LAYOUTS = Object.freeze({
17
17
 
18
18
  const DEFAULT_TRACKERS = Object.freeze({
19
19
  [ROLE.RELATED_QUESTIONS]: {},
20
+ [ROLE.CONTAINER]: {},
21
+ [ROLE.QUERY]: {
22
+ [EVENT_TYPE.SUBMIT]: {},
23
+ },
20
24
  });
21
25
 
22
26
  const DEFAULT_OPTIONS = Object.freeze({