All files / components/result/addons ImpressionTracker.jsx

7.69% Statements 5/65
0% Branches 0/48
0% Functions 0/24
8.33% Lines 5/60

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159        1x   1x   1x               1x                                                                                                                                                                                                                                                                           1x                
import { Actions, helper } from '@appbaseio/reactivecore';
import VueTypes from '../../../utils/vueTypes';
import { connect } from '../../../utils/index';
 
const { recordImpressions } = Actions;
 
const { isEqual } = helper;
 
const debounce = (method, delay) => {
	clearTimeout(method._tId);
	// eslint-disable-next-line
	method._tId = setTimeout(() => {
		method();
	}, delay);
};
 
const ImpressionTracker = {
	name: 'ImpressionTracker',
	inject: ['$$store'],
	props: {
		hits: VueTypes.hits,
	},
	created() {
		// Represents the list of hits returned by the query
		this.currentHits= []; // An array of hits objects
		// An object to track the recorded impressions
		// It can have the values in following shape
		// { "hit_id": { "index": "test" }}
		this.trackedIds= {};
		// An object to know the the un-tracked impression i.e not recorded by BE
		// It can have the values in following shape
		// { "query_id": [{ "id": "hit_id", "index": "test"}]}
		this.waitingToBeTracked= {};
	},
	mounted() {
		this.setCurrentHits(this.hits);
		// Add scroll events to track the impressions
		if (window) {
			window.addEventListener('scroll', this.tracker);
		}
	},
	destroy() {
		// Clear the interval
		this.clearTrackerInterval();
	},
	watch: {
		hits(newVal, oldVal) {
			if (newVal && newVal !== oldVal) {
				// Only compare hit ids for performance reasons
				const prevHitIds = oldVal.map(hit => hit._id);
				const currentHitIds = newVal.map(hit => hit._id);
				if (!isEqual(currentHitIds, prevHitIds)) {
					this.setCurrentHits(newVal);
				}
			}
		},
	},
	methods: {
		inViewPort(el) {
			const rect = el.getBoundingClientRect();
			return (
				rect.top >= 0
				&& rect.left >= 0
				&& rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
				&& rect.right <= (window.innerWidth || document.documentElement.clientWidth)
			);
		},
		setCurrentHits(hits) {
			this.currentHits = hits;
			// Reset the tracked Ids for new hits
			this.trackedIds = {};
			if (hits && hits.length) {
				this.tracker();
				// Run the tracker function on an interval of 1s to track the impressions for
				// non-scroll views for e.g on tab change
				this.setTrackerInterval();
			}
		},
		recordImpression() {
			if (Object.keys(this.waitingToBeTracked).length) {
				const unTrackedHits = { ...this.waitingToBeTracked };
				Object.keys(unTrackedHits).forEach(queryId => {
					if (unTrackedHits[queryId] && unTrackedHits[queryId].length) {
						this.trackImpressions(queryId, unTrackedHits[queryId]);
						// Removed tracked impressions from waiting list
						delete this.waitingToBeTracked[queryId];
					}
				});
			}
		},
		addToWaitingList(hitObject) {
			const queryId = this.getQueryId();
			if (hitObject && queryId) {
				const impression = {
					id: hitObject._id,
					index: hitObject._index,
				};
				// Check if query id already present in waiting list
				if (this.waitingToBeTracked[queryId]) {
					this.waitingToBeTracked[queryId].push(impression);
				} else {
					this.waitingToBeTracked[queryId] = [impression];
				}
			}
		},
		tracker() {
			if (!this.getHitIds().length) {
				this.clearTrackerInterval();
				return;
			}
			// only run at client-side
			if (window && document) {
				this.getHitIds().forEach(id => {
					const element = document.getElementById(id);
					if (element) {
						if (this.inViewPort(element)) {
							// Add the hit id in the list of tracked ids
							const hitObject = this.currentHits.find(hit => hit._id === id);
							this.trackedIds[id] = true;
							// Add hit to waiting list to be recorded
							this.addToWaitingList(hitObject);
						}
					}
				});
			}
			debounce(this.recordImpression, 300);
		},
		setTrackerInterval() {
			this.intervalID = setInterval(this.tracker, 1000);
		},
		clearTrackerInterval() {
			if (this.intervalID) {
				clearInterval(this.intervalID);
				// Reset interval ID
				this.intervalID = null;
			}
		},
		getQueryId() {
			const state = this.$$store ? this.$$store.getState() : null;
			return state ? state.analytics.searchId : null;
		},
		getHitIds() {
			return this.currentHits.map(hit => hit._id).filter(id => !this.trackedIds[id]);
		}
	},
	render() {
		return this.$slots.default;
	},
};
 
const mapDispatchToProps = {
	trackImpressions: recordImpressions,
};
 
export default connect(
	() => null,
	mapDispatchToProps,
)(ImpressionTracker);