Skip to content

Commit 6919a6c

Browse files
authored
Merge pull request #152 from BuildFire/feat/new-analytics-integration
Feat/new analytics integration
2 parents f219be9 + ffb96ae commit 6919a6c

26 files changed

+983
-212
lines changed

control/content/app.services.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
* A REST-ful factory used to validate RSS feed url.
6464
*/
6565
.factory("FeedParseService", ['$q', '$http', function ($q, $http) {
66+
const validFeedsData = [];
6667
var validateFeedUrl = function (_feedUrl) {
6768
var deferred = $q.defer();
6869
if (!_feedUrl) {
@@ -82,8 +83,31 @@
8283
});
8384
return deferred.promise;
8485
};
86+
var getFeedData = function (_feedURL) {
87+
var deferred = $q.defer();
88+
if (!_feedURL) {
89+
deferred.reject(new Error('Undefined feed url'));
90+
}
91+
const feedData = validFeedsData.find(feed => feed.feedURL === _feedURL);
92+
if (feedData && feedData.response) {
93+
deferred.resolve(feedData.response);
94+
return deferred.promise;
95+
}
96+
$http.post('https://proxy.buildfire.com/parsefeedurl', {
97+
feedUrl: _feedURL
98+
})
99+
.success(function (response) {
100+
validFeedsData.push({feedURL: _feedURL, response});
101+
deferred.resolve(response);
102+
})
103+
.error(function (error) {
104+
deferred.reject(error);
105+
});
106+
return deferred.promise;
107+
};
85108
return {
86-
validateFeedUrl: validateFeedUrl
109+
validateFeedUrl: validateFeedUrl,
110+
getFeedData: getFeedData
87111
};
88112
}])
89113
.factory("Utils", ['$q', '$http', function ($q, $http) {

control/content/assets/css/content.app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
.main_view {
2+
padding-right: 8px;
3+
}
4+
15
/*style to fix bottom margin for alert messages*/
26
.margin-fix{
37
margin-bottom: 30px;

control/content/controllers/content.home.controller.js

Lines changed: 268 additions & 31 deletions
Large diffs are not rendered by default.

control/content/enums.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@
3131
UNDEFINED_ID: 'Undefined id provided',
3232
ITEM_ARRAY_FOUND: 'Array of Items provided'
3333
})
34+
35+
/**
36+
* MEDIUM_TYPES will be used to filter item whether it have video content, audio content, image content or other.
37+
*/
38+
.constant('MEDIUM_TYPES', {
39+
VIDEO: 'VIDEO',
40+
AUDIO: 'AUDIO',
41+
IMAGE: 'IMAGE',
42+
OTHER: 'OTHER'
43+
})
3444

3545
/**
3646
* LAYOUTS will be used to set item list layout and item details layout.

control/content/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<!-- endbuild -->
4141

4242
<!-- build:bundleSharedJSFilesCP -->
43+
<script src="../../widget/global/AnalyticsManager.js"></script>
4344
<script src="../../widget/global/utils.js"></script>
4445
<script src="../../widget/global/models/Feed.js"></script>
4546
<!-- endbuild -->

control/content/searchEngine.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,57 @@ const searchEngine = {
7373

7474
return options;
7575
},
76+
getIndexedFeedData(feedTag, callback) {
77+
this.getIndexedFeedPage({page: 0, pageSize: 2500, feedTag}, [], callback);
78+
},
79+
80+
getIndexedFeedPage(options, hits, callback) {
81+
const {page, pageSize, feedTag} = options;
82+
buildfire.services.searchEngine.search({ tag: feedTag, pageSize, pageIndex: page},
83+
(err, res) => {
84+
if (err) return callback(err);
85+
86+
if (res && res.hits && res.hits.hits && res.hits.hits.length) {
87+
hits = hits.concat(res.hits.hits);
88+
}
89+
90+
if (res && res.hits && res.hits.total > ((page+1)*pageSize)) {
91+
options.page += 1;
92+
return this.getIndexedFeedPage(options, hits, callback);
93+
}
94+
return callback(null, hits);
95+
});
96+
},
97+
98+
updateFeedRecords(records, callback) {
99+
if (!records || !records.length) return callback();
100+
101+
const batchSize = 20;
102+
const batch = records.splice(0, batchSize);
103+
104+
const promises = batch.map(_record =>
105+
new Promise((resolve, reject) =>
106+
buildfire.services.searchEngine.update(
107+
{
108+
id: _record._id,
109+
title: _record._source.data.title,
110+
tag: _record._source.tag,
111+
description: _record._source.searchable.description,
112+
keywords: _record._source.searchable.keywords,
113+
imageUrl: _record._source.image_url,
114+
data: {
115+
..._record._source.data,
116+
registeredToAnalytics: _record.registeredToAnalytics ? _record.registeredToAnalytics : false,
117+
type: _record.type,
118+
src: _record.src
119+
}
120+
},
121+
(err, result) => {
122+
if (err) return reject(err);
123+
else resolve();
124+
}))
125+
);
126+
127+
Promise.allSettled(promises).then(() => this.updateFeedRecords(records, callback));
128+
},
76129
};

control/content/templates/home.html

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,36 @@ <h4 class="spHeaderText">Entity Title</h4>
4646

4747
<div class="form-group margin-bottom-twenty">
4848
<div class="row">
49-
<div class="col-sm-6">
50-
<label class="tooltip-container">Advanced Settings
49+
<div class="col-sm-7">
50+
<label class="tooltip-container">Advanced Analytics
51+
<span class="btn-info-icon btn-primary">
52+
<span class="cp-tooltip">
53+
Enable additional analytics for every item in this feed.
54+
</span>
55+
</span>
56+
</label>
57+
</div>
58+
<div class="col-sm-5">
59+
<div class="button-switch" style="float: right;">
60+
<input type="checkbox" id="enableFeedAnalytics">
61+
<label for="enableFeedAnalytics" class="label-success"></label>
62+
</div>
63+
</div>
64+
</div>
65+
</div>
66+
67+
<div class="form-group margin-bottom-twenty">
68+
<div class="row">
69+
<div class="col-sm-7">
70+
<label class="tooltip-container">Search Indexing Settings
5171
<span class="btn-info-icon btn-primary">
5272
<span class="cp-tooltip bottom-cp-tooltip">
5373
Advanced settings is used only to configure Search Engine Indexing. Adjust the feed's key parameters to enable searchability of feed items in the global search located in the app's side menu.
5474
</span>
5575
</span>
5676
</label>
5777
</div>
58-
<div class="col-sm-6">
78+
<div class="col-sm-5">
5979
<div class="button-switch" style="float: right;">
6080
<input type="checkbox" id="enableSearchEngineConfig">
6181
<label for="enableSearchEngineConfig" class="label-success"></label>

control/design/assets/css/design.app.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.main_view {
2+
padding-right: 8px;
3+
}
14
/*style for alert messages*/
25
.margin-fix{
36
margin-bottom: 30px;

gulpfile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ const widgetJSFiles = [
7777
"widget/assets/js/videogular.js",
7878
"widget/assets/js/videogular-controls.js",
7979
"widget/assets/js/videogular-overlay-play.js",
80+
"widget/assets/js/vimeoPlayer.min.js",
81+
"widget/assets/js/youtubePlayer.min.js",
8082
"widget/app.js",
8183
"widget/modals.js",
8284
"widget/app.services.js",

widget/app.services.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,145 @@
112112
return _;
113113
}])
114114

115+
/*
116+
* A factory which is used to track analytics actions
117+
*/
118+
.factory('trackAnalyticsActions', ['MEDIUM_TYPES', function (MEDIUM_TYPES) {
119+
let analyticsTrackingInterval = null;
120+
let lastAnalyticsTime = null;
121+
let openedItems = [];
122+
let playedItems = [];
123+
124+
function trackPlayedItem(options) {
125+
const { item, itemType } = options;
126+
127+
const metaData = {
128+
itemId: item.guid,
129+
itemTitle: item.title,
130+
imageUrl: item.imageSrcUrl,
131+
};
132+
133+
if (!playedItems.includes(item.guid)) {
134+
playedItems.push(item.guid);
135+
AnalyticsManager.trackEvent(`${item.guid}_playsCount`, metaData);
136+
if (itemType === 'video') {
137+
AnalyticsManager.trackEvent(`videoPlaysCount`, metaData);
138+
} else {
139+
AnalyticsManager.trackEvent(`audioPlaysCount`, metaData);
140+
}
141+
}
142+
}
143+
144+
function trackOpenedItem(item) {
145+
if (!openedItems.includes(item.guid)) {
146+
openedItems.push(item.guid);
147+
const metaData = {
148+
itemId: item.guid,
149+
itemTitle: item.title,
150+
imageUrl: item.imageSrcUrl,
151+
};
152+
AnalyticsManager.trackEvent(`${item.guid}_opensCount`, metaData);
153+
switch (item.type) {
154+
case MEDIUM_TYPES.VIDEO:
155+
AnalyticsManager.trackEvent('videoOpensCount', metaData);
156+
break;
157+
case MEDIUM_TYPES.AUDIO:
158+
AnalyticsManager.trackEvent('audioOpensCount', metaData);
159+
break;
160+
default:
161+
AnalyticsManager.trackEvent('articleOpensCount', metaData);
162+
break;
163+
}
164+
}
165+
}
166+
167+
function trackItemWatchState(options) {
168+
const { state, currentTime, item, itemType } = options;
169+
170+
const metaData = {
171+
itemId: item.guid,
172+
itemTitle: item.title,
173+
imageUrl: item.imageSrcUrl,
174+
};
175+
const eventKey = `${item.guid}_secondsWatch`;
176+
if (state === 'play') {
177+
trackPlayedItem({ item, itemType });
178+
if (currentTime) {
179+
lastAnalyticsTime = currentTime;
180+
}
181+
182+
if (!analyticsTrackingInterval) {
183+
analyticsTrackingInterval = setInterval(() => {
184+
lastAnalyticsTime += 5;
185+
metaData._buildfire = { aggregationValue: 5 }; // 5 seconds
186+
AnalyticsManager.trackEvent(eventKey, metaData);
187+
}, 5 * 1000);
188+
}
189+
} else if (state === 'pause') {
190+
if (analyticsTrackingInterval) {
191+
clearInterval(analyticsTrackingInterval);
192+
analyticsTrackingInterval = null;
193+
194+
const extraTime = parseInt(currentTime - lastAnalyticsTime);
195+
lastAnalyticsTime = currentTime;
196+
if (currentTime > 0 && extraTime > 0 && extraTime <= 5) {
197+
metaData._buildfire = { aggregationValue: extraTime };
198+
AnalyticsManager.trackEvent(eventKey, metaData);
199+
}
200+
}
201+
} else if (state === 'buffer' && currentTime) {
202+
lastAnalyticsTime = currentTime;
203+
}
204+
}
205+
206+
return {
207+
trackPlayedItem,
208+
trackOpenedItem,
209+
trackItemWatchState
210+
}
211+
}])
212+
213+
.factory('utils', ['$filter' , function ($filter) {
214+
/**
215+
* @name getImageUrl()
216+
* Used to extract image url
217+
* @param item
218+
* @returns {*}
219+
*/
220+
function getImageUrl(item) {
221+
var i = 0,
222+
length = 0,
223+
imageUrl = '';
224+
if (item.image && item.image.url) {
225+
imageUrl = item.image.url;
226+
} else if (item.enclosures && item.enclosures.length > 0) {
227+
length = item.enclosures.length;
228+
for (i = 0; i < length; i++) {
229+
if (item.enclosures[i].type.indexOf('image') === 0 || item.enclosures[i].type.indexOf('img') != -1) {
230+
imageUrl = item.enclosures[i].url;
231+
break;
232+
}
233+
}
234+
}
235+
if (imageUrl) {
236+
return imageUrl;
237+
}
238+
else {
239+
if (item['media:thumbnail'] && item['media:thumbnail']['@'] && item['media:thumbnail']['@'].url) {
240+
return item['media:thumbnail']['@'].url;
241+
} else if (item['media:group'] && item['media:group']['media:content'] && item['media:group']['media:content']['media:thumbnail'] && item['media:group']['media:content']['media:thumbnail']['@'] && item['media:group']['media:content']['media:thumbnail']['@'].url) {
242+
return item['media:group']['media:content']['media:thumbnail']['@'].url;
243+
} else if (item.description) {
244+
return $filter('extractImgSrc')(item.description);
245+
} else {
246+
return '';
247+
}
248+
}
249+
}
250+
251+
return { getImageUrl }
252+
}])
253+
115254
/**
116255
* A factory which is used to hold selected item before going on item details page.
117256
*/

0 commit comments

Comments
 (0)