Skip to content

Commit a1c806e

Browse files
[feature] Entity specific analytics (#1389)
* [feature] Entity specific analytics Addresses: #1292 <img width="1101" alt="Screen Shot 2023-01-04 at 8 07 53 AM" src="https://user-images.githubusercontent.com/35935196/210561884-9f2220bf-0c69-45ff-bef4-d9f9dc974e3b.png"> ### Determining the visibility threshold for section views Threshold indicates at what percentage of the target's visibility the tracking should be executed. 0.75 threshold indicates that the tracking is done when 75% of the section is visible in the screen. However for larger sections, 75% of the content can never be visible. Therefore we need to adjust the threshold. A good rule of thumb is if the section is double the screen size, adjust the threshold to slightly less than 0.5 and if the section is 4 times the screen size, adjust threshold to less than 0.25, and so on REF: https://stackoverflow.com/questions/66296057/intersectionobserver-does-not-work-on-small-screens-for-long-sections-js/ https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#thresholds Co-authored-by: Pralish Kayastha <[email protected]> Co-authored-by: Pralish Kayastha <[email protected]>
1 parent b43d050 commit a1c806e

File tree

30 files changed

+1163
-24
lines changed

30 files changed

+1163
-24
lines changed

Dockerfile.dev

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RUN apk --no-cache update \
2020
&& apk --no-cache upgrade \
2121
&& apk add --no-cache \
2222
build-base \
23+
python2 \
2324
ttf-ubuntu-font-family \
2425
git \
2526
postgresql-dev \

app/assets/config/manifest.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
//= link_tree ../images
22
//= link_directory ../stylesheets .css
3+
//= link_tree ../javascripts
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//= require moment
2+
//= require bootstrap-daterangepicker
3+
4+
$(function() {
5+
$('[data-toggle="tooltip"]').tooltip();
6+
7+
$('#reportrange').daterangepicker({
8+
opens: 'left',
9+
locale: {
10+
format: 'YYYY/MM/DD'
11+
},
12+
startDate: $("#start_date").val() || moment().startOf('month'),
13+
endDate: $("#end_date").val() || moment().endOf('month'),
14+
ranges: {
15+
[moment().format('MMMM YYYY')]: [moment().startOf('month'), moment().endOf('month')],
16+
'3 months': [moment().startOf('month').subtract(2, 'months'), moment().endOf('month')],
17+
'6 months': [moment().startOf('month').subtract(5, 'months'), moment().endOf('month')],
18+
'1 year': [moment().startOf('month').subtract(11, 'months'), moment().endOf('month')]
19+
}
20+
}, function(start, end, label) {
21+
$('#start_date').val(start.format('YYYY-MM-DD'));
22+
$('#end_date').val(end.format('YYYY-MM-DD'));
23+
$('#interval').val(label);
24+
cb();
25+
$("#analytics_filter").submit();
26+
});
27+
28+
cb();
29+
});
30+
31+
function cb() {
32+
if (!$('#start_date').val() && !$('#end_date').val()) {
33+
$('#reportrange span').html(moment().format('MMMM YYYY'));
34+
} else if ($('#interval').val() == 'Custom Range') {
35+
$('#reportrange span').html(moment($("#start_date").val()).format('MMMM D, YYYY') + ' - ' + moment($("#end_date").val()).format('MMMM D, YYYY'));
36+
} else {
37+
$('#reportrange span').html($('#interval').val());
38+
}
39+
}

app/assets/stylesheets/application.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
@import "./actiontext.scss";
2020
@import 'select2/dist/css/select2.css';
2121
@import './direct_upload.scss';
22+
@import 'bootstrap-daterangepicker/daterangepicker';
2223

2324
.alert-file_url {
2425
display: none;
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,175 @@
11
// Place all the styles related to the dashboard controller here.
22
// They will automatically be included in application.css.
33
// You can use Sass (SCSS) here: https://sass-lang.com/
4+
5+
.vr-analytics {
6+
color: #293845;
7+
8+
&-title {
9+
font-size: 32px;
10+
line-height: 44px;
11+
font-weight: bold;
12+
}
13+
14+
&-sub-title {
15+
font-size: 24px;
16+
line-height: 33px;
17+
text-transform: capitalize;
18+
}
19+
20+
&-count,
21+
&-percent-change,
22+
&-event-label {
23+
color: #535F6A;
24+
font-size: 16px;
25+
line-height: 22px;
26+
}
27+
28+
&-count {
29+
margin-right: 10px;
30+
31+
@media (min-width: 768px) {
32+
margin-left: 24px;
33+
}
34+
}
35+
36+
&-count-lg {
37+
font-size: 24px;
38+
line-height: 33px;
39+
margin-right: 10px;
40+
}
41+
42+
&-tooltips {
43+
height: 16px;
44+
width: 16px;
45+
border-radius: 50%;
46+
background-color: #D4D7DA;
47+
display: flex;
48+
align-items: center;
49+
justify-content: center;
50+
font-size: 12px;
51+
font-weight: 600;
52+
margin-left: 8px;
53+
cursor: default;
54+
}
55+
56+
&-percent-change {
57+
.positive {
58+
color: #63C32D;
59+
}
60+
61+
.negative {
62+
color: #E93E3E;
63+
}
64+
}
65+
66+
&-filter-dropdown-container {
67+
& > button {
68+
color: #BD34D1;
69+
font-size: 16px;
70+
font-weight: 600;
71+
}
72+
}
73+
74+
&-filters {
75+
&-pages {
76+
border: none;
77+
outline: none;
78+
-moz-appearance:none; /* Firefox */
79+
-webkit-appearance:none; /* Safari and Chrome */
80+
appearance:none;
81+
background: transparent;
82+
background-image: url("data:image/svg+xml;utf8,<svg fill='%23bd34d1' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/><path d='M0 0h24v24H0z' fill='none'/></svg>");
83+
background-repeat: no-repeat;
84+
background-position-x: 98.5%;
85+
background-position-y: 50%;
86+
}
87+
88+
&-pages,
89+
&-ranges > span,
90+
&-ranges > i {
91+
color: #BD34D1;
92+
font-size: 16px;
93+
font-weight: 600;
94+
cursor: pointer;
95+
}
96+
97+
@media (max-width: 767px) {
98+
&-pages,
99+
&-ranges {
100+
width: 100%;
101+
border: 1px solid #E5E7E8;
102+
border-radius: 5px;
103+
padding: 9px 16px;
104+
margin-bottom: 8px;
105+
}
106+
}
107+
108+
}
109+
110+
&-card {
111+
box-shadow: -4px 12px 24px #29384514;
112+
border: 1px solid #E9EAEC;
113+
border-radius: 5px;
114+
position: relative;
115+
116+
& > a {
117+
position: absolute;
118+
top: 0;
119+
left: 0;
120+
bottom: 0;
121+
right: 0;
122+
}
123+
124+
&-section {
125+
padding: 16px;
126+
}
127+
128+
&-resource-poster-conatiner {
129+
margin-right: 10px;
130+
131+
& > img {
132+
height: 48px;
133+
width: 85px;
134+
object-fit: cover;
135+
}
136+
}
137+
138+
&-resource-title {
139+
font-weight: bold;
140+
position: relative;
141+
pointer-events: none;
142+
z-index: 1;
143+
144+
& > a {
145+
color: #293845;
146+
pointer-events: all;
147+
}
148+
}
149+
150+
&-resource-duration {
151+
color: #535F6A;
152+
}
153+
}
154+
155+
&-section {
156+
padding: 32px 0;
157+
158+
&-header {
159+
margin-bottom: 24px;
160+
}
161+
}
162+
163+
&-page-visit-events {
164+
&-donut-chart {
165+
padding: 16px;
166+
}
167+
}
168+
169+
170+
&-blank-states {
171+
color: grey;
172+
padding: 0 25px 40px;
173+
font-size: 14px;
174+
}
175+
}

app/controllers/comfy/admin/api_namespaces_controller.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require 'will_paginate/array'
22

33
class Comfy::Admin::ApiNamespacesController < Comfy::Admin::Cms::BaseController
4-
before_action :set_api_namespace, only: %i[ show edit update destroy discard_failed_api_actions rerun_failed_api_actions export export_api_resources duplicate_with_associations duplicate_without_associations export_without_associations_as_json export_with_associations_as_json social_share_metadata api_action_workflow ]
4+
before_action :set_api_namespace, except: %i[index new create import_as_json]
55

66
before_action :ensure_authority_for_creating_api, only: %i[ new create import_as_json]
77
before_action :ensure_authority_for_viewing_all_api, only: :index
@@ -12,6 +12,7 @@ class Comfy::Admin::ApiNamespacesController < Comfy::Admin::Cms::BaseController
1212
before_action :ensure_authority_for_allow_exports_in_api, only: %i[ export export_api_resources export_without_associations_as_json export_with_associations_as_json ]
1313
before_action :ensure_authority_for_allow_duplication_in_api, only: %i[ duplicate_with_associations duplicate_without_associations ]
1414
before_action :ensure_authority_for_allow_social_share_metadata_in_api, only: %i[ social_share_metadata ]
15+
before_action :ensure_authority_to_manage_analytics, only: :analytics_metadata
1516
before_action :ensure_authority_for_full_access_for_api_actions_only_in_api, only: %i[ api_action_workflow discard_failed_api_actions rerun_failed_api_actions ]
1617

1718
# GET /api_namespaces or /api_namespaces.json
@@ -219,6 +220,25 @@ def social_share_metadata
219220
end
220221
end
221222

223+
224+
def analytics_metadata
225+
respond_to do |format|
226+
if @api_namespace.update(analytics_metadata_params)
227+
format.html do
228+
flash[:notice] = 'Analytics Metadata successfully updated.'
229+
redirect_to @api_namespace
230+
end
231+
format.json { render :show, status: :ok, location: @api_namespace }
232+
else
233+
format.html do
234+
flash[:error] = @api_namespace.errors.full_messages
235+
render :edit, status: :unprocessable_entity
236+
end
237+
format.json { render json: @api_namespace.errors, status: :unprocessable_entity }
238+
end
239+
end
240+
end
241+
222242
def api_action_workflow
223243
respond_to do |format|
224244
if @api_namespace.update(api_action_workflow_params)
@@ -269,4 +289,8 @@ def api_action_workflow_params
269289
def api_namespace_social_share_metadata_params
270290
params.require(:api_namespace).permit(social_share_metadata: [:title, :description, :image])
271291
end
292+
293+
def analytics_metadata_params
294+
params.require(:api_namespace).permit(analytics_metadata: [:title, :author, :thumbnail])
295+
end
272296
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
class Comfy::Admin::V2::DashboardController < Comfy::Admin::Cms::BaseController
2+
include AhoyEventsHelper
3+
4+
before_action :ensure_authority_to_manage_analytics
5+
6+
def dashboard
7+
@start_date = params[:start_date]&.to_date || Date.today.beginning_of_month
8+
@end_date = params[:end_date]&.to_date || Date.today.end_of_month
9+
date_range = @start_date.beginning_of_day..@end_date.end_of_day
10+
11+
@visits = Ahoy::Visit.where(started_at: @start_date.beginning_of_day..@end_date.end_of_day)
12+
13+
Ahoy::Event::EVENT_CATEGORIES.values.each do |event_category|
14+
if event_category == Ahoy::Event::EVENT_CATEGORIES[:page_visit]
15+
events = Ahoy::Event.where(name: 'comfy-cms-page-visit').joins(:visit)
16+
else
17+
events = Ahoy::Event.jsonb_search(:properties, { category: event_category }).joins(:visit)
18+
end
19+
events = events.jsonb_search(:properties, { page_id: params[:page] }) if params[:page].present?
20+
instance_variable_set("@previous_period_#{event_category}_events", events.where(time: previous_period(params[:interval], @start_date, @end_date)))
21+
instance_variable_set("@#{event_category}_events", events.where(time: date_range))
22+
end
23+
24+
# legacy and system events does not have category
25+
# separating out 'comfy-cms-page-visit' event since we have a seprate section
26+
@legacy_and_system_events = Ahoy::Event.where.not('properties::jsonb ? :key', key: 'category').where.not(name: 'comfy-cms-page-visit').joins(:visit)
27+
@previous_period_legacy_and_system_events = @legacy_and_system_events.where(time: previous_period(params[:interval], @start_date, @end_date))
28+
@legacy_and_system_events = @legacy_and_system_events.where(time: date_range)
29+
end
30+
31+
32+
private
33+
34+
def previous_period(interval, start_date, end_date)
35+
today = Date.current
36+
interval = interval || today.strftime('%B %Y')
37+
38+
case interval
39+
when "#{today.strftime('%B %Y')}"
40+
today.prev_month.beginning_of_month.beginning_of_day..today.prev_month.end_of_month.end_of_day
41+
when "3 months"
42+
(start_date - 3.months).beginning_of_month.beginning_of_day..(start_date - 1.month).end_of_month.end_of_day
43+
when "6 months"
44+
(today - 6.months).beginning_of_month.beginning_of_day..(start_date - 1.month).end_of_month.end_of_day
45+
when "1 year"
46+
(today - 12.months).beginning_of_month.beginning_of_day..(start_date - 1.month).end_of_month.end_of_day
47+
else
48+
49+
days_diff = (end_date - start_date).to_i
50+
(start_date - (days_diff + 1).days).beginning_of_day..(start_date - 1.day).end_of_day
51+
end
52+
end
53+
end

app/helpers/api_forms_helper.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
module ApiFormsHelper
2-
def render_form(id)
2+
def render_form(id, options = {})
33
# usage in cms {{ cms:helper render_form, 1 }} here 1 is the id
44
# usage in rails = render_form @api_form.id
55
@api_form = ApiForm.find_by(id: id)
66
if @api_form
77
@api_namespace = @api_form.api_namespace
8-
render partial: 'comfy/admin/api_forms/render'
8+
data = options['data'] || {}
9+
10+
data["violet-track-form-submit"] = "true" unless data["violet-track-form-submit"].present?
11+
render partial: 'comfy/admin/api_forms/render', locals: { data: data }
912
end
1013
end
1114

0 commit comments

Comments
 (0)