Xây dựng một hệ thống tracking hành vi người dùng (phần 2)
Các bạn có thể xem lại phần 1 tại đây.
Sau khi đã có được các tag tiến hành xây dựng các Helper để hỗ trợ việc render ra file tracking.js
Để render ra file javascript sử dụng file .erb.File .erb trong ruby cũng tương tự như các .jsp trong java .php nó cho phép nhúng code ruby vào html(trong trường hợp này là nhúng code ruby vào javascript.Để tránh làm file này bị phình quá to nên split nhỏ file này ra thành các file khác và file này chỉ viết những logic quan trọng và include các file js khác vào.
RadaTracking
Sau khi đã có được các tag tiến hành xây dựng các Helper để hỗ trợ việc render ra file tracking.js
Helper này có nhiệm vụ xử lý các logic của file tracking.js nhưng nếu xử lý trực tiếp trên file js thì nó quá dài dòng và dễ bị lộ logic của file.
JS helper
module JsHelper
def renderBindClicks(tags)
click_tags = tags.inject([]) do |click_tags, tag|
if tag&.trigger[:value] == 'click'
event_type = tag&.selector[:dom]
selector = if event_type == "classes element"
".#{tag&.selector[:value]}"
elsif event_type == "id element"
"\##{tag&.selector[:value]}"
end
if selector.present?
click_tags << "'#{selector}'"
end
end
click_tags
end
if click_tags.present?
return "[#{click_tags.uniq.join(', ')}].forEach(bindClick);".html_safe
end
end
def renderBindClickExtend(tags)
selector = nil
attribute = nil
click_tags = []
tags.each do |tag|
obj_temp = {}
if tag&.trigger[:value] == 'click'
if tag&.handle[:value] == 'other' && !tag&.handle[:other_handle].blank?
attribute = tag&.handle[:other_handle].chomp
event_type = tag&.selector[:dom]
if event_type == "classes element"
selector = ".#{tag&.selector[:value]}"
elsif event_type == "id element"
selector = "\##{tag&.selector[:value]}"
end
obj_temp["attr"] = attribute
obj_temp["selector"] = selector
click_tags.push(obj_temp)
else
event_type = tag&.selector[:dom]
if event_type == "classes element"
selector = ".#{tag&.selector[:value]}"
elsif event_type == "id element"
selector = "\##{tag&.selector[:value]}"
end
obj_temp["selector"] = selector
click_tags.push(obj_temp)
end
end
end
# binding.pry
return "#{click_tags.to_json}.forEach(bindClickExtend);".html_safe
end
def renderImport(tags)
import_tag = tags.inject([]) do |import_tags, tag|
trigger_other_value = if tag&.trigger[:value] == 'other'
"#{tag&.trigger[:other_value]}"
end
if trigger_other_value.present?
import_tags.push(trigger_other_value)
end
import_tags
end
if import_tag.present?
return "#{import_tag.uniq.join("\n")}".html_safe
end
end
def renderBindFocus(tags)
focus_tags = tags.inject([]) do |focus_tags, tag|
if tag&.trigger[:value] == 'focus'
event_type = tag&.selector[:dom]
selector = if event_type == "classes element"
".#{tag&.selector[:value]}"
elsif event_type == "id element"
"\##{tag&.selector[:value]}"
end
if selector.present?
focus_tags << "'#{selector}'"
end
end
focus_tags
end
if focus_tags.present?
return "bindFocus([#{focus_tags.uniq.join(', ')}]);".html_safe
end
end
# Generate debug command for javascript
def debug(msg, param = nil)
if Rails.env.development?
if param && msg
return "debug(\"#{msg}\" + #{param});"
elsif msg
return "debug(\"#{msg}\");"
elsif param
return "debug(#{param});"
end
end
return nil
end
# Generate debug function for javascript
def debug_js_function
Rails.env.development? ?
"var debug = function(obj){\n\
if (typeof(obj) == 'string')\n\
console.debug('Spymaster: ' + obj);\n\
else\n\
console.debug(obj);\n\
};" : nil
end
# Render a javascript file
def render_part(name, assigns = {})
# fpath = Dir.glob(Rails.root.join("client-api/js/components/_#{name}.*")).first
# return nil if not fpath
# template = File.new(fpath).read
# result = ERB.new(template).result(OpenStruct.new(assigns).instance_eval { binding })
# return result
render partial: name, locals: assigns
end
def domain
Rails.env.development? ? 'http://localhost:3000' : 'https://tracking.pedia.vn'
end
end
Để render ra file javascript sử dụng file .erb.File .erb trong ruby cũng tương tự như các .jsp trong java .php nó cho phép nhúng code ruby vào html(trong trường hợp này là nhúng code ruby vào javascript.Để tránh làm file này bị phình quá to nên split nhỏ file này ra thành các file khác và file này chỉ viết những logic quan trọng và include các file js khác vào.
RadaTracking
<%= render partial: 'tagmanager/components/rada_tracking' %>
<%= render partial: 'tagmanager/components/scroll_tracking' %>
<%= render partial: 'tagmanager/components/action_cookie' %>
var RadaTracking = (function() {
var rada_info = {
<% if Rails.env.development? %>
url_api:"//localhost:3000/api/tracking/rada_track",
request_cid:"//localhost:3000/api/tracking/generate_client_id",
<% elsif Rails.env.staging? %>
url_api: "//tcs-rada-reporter-staging.ingress.v2.cloud.edumall.io/api/tracking/rada_track",
request_cid: "//tcs-rada-reporter-staging.ingress.v2.cloud.edumall.io/api/tracking/generate_client_id",
<% elsif Rails.env.production? %>
url_api:"//rada-reporter.edumall.io/api/tracking/rada_track",
request_cid:"//rada-reporter.edumall.io/api/tracking/generate_client_id",
<% end %>
method: "post"
}
var app_info = {
name: <%= "'#{@app_name}'".html_safe %>,
version: "beta",
// user: $('.data_user').val(),
}
//set expires for cookie is 10h
var cookie_info = {
name: "rada_tracking",
expires: 10
}
//init data for track
function build_data_for_track(){
var time_start = new Date().getTime();
var storage_key = "rada" + time_start;
var dataTemp = {
storage_key: storage_key,
time_start: time_start,
app_name: app_info.name,
app_version: app_info.version,
referer:document.referrer,
url: window.location.href,
client_os: RadaReporter.get_device_info().os_name,
client_browser: RadaReporter.get_browser_info().name,
type_track:"",
user: app_info.user,
extras: {},
}
return dataTemp;
}
//push data to localStorage
function push_data_to_lc(name,data){
data_json = JSON.stringify(data);
localStorage.setItem(name,data_json);
}
//transformation data before send data to server
function tranform_data(params){
var data_tranform = [];
var substring = "rada";
for (i = 0; i < params.length; i++) {
var obj_temp = {};
if(params.key(i).indexOf(substring) !== -1){
obj_temp.key = params.key(i);
obj_temp.data = JSON.parse(params.getItem(params.key(i)));
data_tranform.push(obj_temp)
}
}
return data_tranform;
}
//after send data to server then delete old data
function delete_old_localStorage(params){
for (i = 0; i < params.length; i++) {
localStorage.removeItem(params[i]["key"]);
}
}
function send_data_to_server (){
var data_send_server = {};
var data_local_storage = tranform_data(localStorage);
if(getValueCookie("rada_tracking")){
data_send_server.client_id = getValueCookie("rada_tracking");
}else{
generate_client_id();
data_send_server.client_id = "Anonymous";
}
data_send_server.source = JSON.stringify(data_local_storage);
// data_send_server.time_send = new Date().getTime();
$.ajax({
url: rada_info.url_api,
type: rada_info.method,
data: data_send_server,
success: function(response) {
delete_old_localStorage(data_local_storage);
push_data_to_lc("time_send",new Date().getTime());
console.log('save data to server success');
//time_send_succes = Time.now
//save_time_now_to_local
}
});
}
function getValueCookie(name){
if(name === undefined){
return undefined;
}else{
var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) return match[2];
}
}
function generate_client_id(){
var check_cookie = getValueCookie("rada_tracking");
if(!check_cookie){
$.ajax({
url: rada_info.request_cid,
type: "POST",
data: {action:"request_cid"},
success: function(response) {
var client_id = response.client_id;
Action_cookie.set_cookie(cookie_info.name,client_id,cookie_info.expires);
}
});
}
}
return {
generate_cid: generate_client_id,
build_data: build_data_for_track,
push_data: push_data_to_lc,
tranform_data: tranform_data,
delete_data: delete_old_localStorage,
send_data: send_data_to_server
}
})();
var Focus_tracker = (function() {
var checks ={};
var check = {};
return function(params,callback){
$(window).scroll(function() {
clearTimeout($.data(this, 'scrollTimer'));
$.data(this, 'scrollTimer', setTimeout(function() {
for(var i=0;i<params.length;i++){
var selectors = $(params[i]);
if(selectors.length > 1){
for(var x=0;x<selectors.length;x++){
var a = selectors[x].getBoundingClientRect().top;
if(a >= 0 && a <= window.innerHeight && !checks[x]){
callback(params[i]);
checks[x] = true;
}
}
}if(selectors.length == 1){
var b = selectors[0].getBoundingClientRect().top;
if(b >= 0 && b <= window.innerHeight && !check[i]){
callback(params[i]);
check[i] = true;
}
}
}
}, 5000));
});
}
})();
var TagConfig = {
"SendIntervalInSecs": <%= Rails.env.development? ? 40 : 120 %>,
"BeatIntervalInSecs": 5
};
var RadarTag = (function() {
function debug_log(msg) {
<% if Rails.env.development? %>
console.log(msg);
<% end %>
}
function handleViewPage() {
var data = RadaTracking.build_data();
data["type_track"] = "page_view";
RadaTracking.push_data(data["storage_key"],data);
// debug_log(data);
}
function handleTimeSpent(){
var data = RadaTracking.build_data();
data["type_track"] = "time_in_page";
data["value_track"] = 0;
const beatInterval = TagConfig.BeatIntervalInSecs;
setInterval(function(){
data["value_track"] += beatInterval;
RadaTracking.push_data(data["storage_key"],data);
// debug_log(data["value_track"]);
}, beatInterval * 1000);
}
//this function is not working
function handleClick(params, event) {
var data = RadaTracking.build_data();
data["type_track"] = "click_in_page";
data["value_track"] = params.selector;
data["extras"]["btn_name"] = event.target.textContent;
RadaTracking.push_data(data["storage_key"],data);
}
function handleClickExtend(params,event){
var data = RadaTracking.build_data();
var obj_temp = params.selector;
data["type_track"] = "click_in_page";
data["value_track"] = obj_temp.selector;
data["extras"]["bnt_name"] = event.target.textContent;
if(Object.prototype.hasOwnProperty.call(obj_temp,'attr')){
data["extras"]["other_value"] = event.target.getAttribute(obj_temp.attr);
}
RadaTracking.push_data(data["storage_key"],data);
}
function handleFocus(params){
var data = RadaTracking.build_data();
data["type_track"] = "focus_in_page";
var callback = function(params){
data["time_start"] = new Date().getTime();
data["value_track"] = params;
// debug_log(data);
RadaTracking.push_data(data["storage_key"],data);
}
Focus_tracker(params.selector,callback);
}
function triggerViewPage(){
handleViewPage();
}
function triggerTimeSpent(){
handleTimeSpent();
}
//this function is not working
function triggerClick(params) {
try {
$(document).on("click", params.selector, function(event) {
params.handle(params, event);
})
} catch (e) {
debug_log("some thing when wrong,in triggerClick");
}
}
function triggerClickExtend(params) {
try {
$(document).on("click", params.selector.selector, function(event) {
params.handle(params, event);
})
} catch (e) {
debug_log("some thing when wrong,in triggerClickExtend")
}
}
function triggerFocus(params) {
try {
params.handle(params);
}catch (e) {
debug_log("some thing when wrong,in triggerFocus")
}
}
//this function is not working
function bindClick(selector) {
var params = {};
params.selector = selector;
params.trigger = triggerClick;
params.handle = handleClick;
params.trigger(params);
}
function bindClickExtend(selector){
var params = {};
params.selector = selector;
params.trigger = triggerClickExtend;
params.handle = handleClickExtend;
params.trigger(params);
}
function bindFocus(selector) {
var params = {};
params.selector = selector;
params.trigger = triggerFocus;
params.handle = handleFocus;
params.trigger(params);
}
function handleScroll(){
window.tracker_scroll = window.ScrollTracker();
window.tracker_scroll.on({
percentages: {
every: [50]
}
}, function(evt) {
var data = RadaTracking.build_data();
data["type_track"] = "scroll_track";
data["value_track"] = evt.data.label;
data["extras"] = window.tracker_scroll._show_info();
data["extras"]["value_depth"] = evt.data.depth;
RadaTracking.push_data(data["storage_key"],data);
// debug_log(data);
});
}
function triggerScroll(){
handleScroll();
}
function set_cookie_after_load(){
RadaTracking.generate_cid();
}
function set_current_time(){
return new Date().getTime();
}
return {
onReady: function() {
set_cookie_after_load();
triggerViewPage();
triggerTimeSpent();
triggerScroll();
loopsend = setInterval(function(){
var time_send = localStorage.getItem("time_send");
var current_time = set_current_time();
if(time_send){
time_send = JSON.parse(time_send);
// console.log((current_time - time_send));
if ((current_time - time_send) >= (TagConfig.SendIntervalInSecs * 1000)){
RadaTracking.send_data();
}
}else{
RadaTracking.push_data("time_send",current_time);
}
}, TagConfig.SendIntervalInSecs * 250);
// <%= renderBindClicks(@tags) %>
<%= renderBindClickExtend(@tags) %>
<%= renderImport(@tags) %>
<%= renderBindFocus(@tags) %>
}
}
})();
$(document).ready(function() {
RadarTag.onReady();
});
Trên đây là bài viết tổng quan về xây dựng hệ thống rada tracking,một số vấn đề chuyên sâu về hệ thống này sẽ được viết ở một bài viết khác.
Nhận xét
Đăng nhận xét