3
3
import os
4
4
5
5
import cartopy
6
+ import google
6
7
import matplotlib
7
8
import matplotlib .cm as cm
8
9
import matplotlib .colors as colors
11
12
from google .analytics .data_v1beta import BetaAnalyticsDataClient
12
13
from google .analytics .data_v1beta .types import DateRange , Dimension , Metric , RunReportRequest
13
14
15
+ # Project ID Numbers
14
16
PORTAL_ID = '266784902'
15
17
FOUNDATIONS_ID = '281776420'
16
18
COOKBOOKS_ID = '324070631'
17
19
20
+
21
+ # Access Secrets
18
22
PRIVATE_KEY_ID = os .environ .get ('PRIVATE_KEY_ID' )
19
23
PRIVATE_KEY = os .environ .get ('PRIVATE_KEY' )
20
24
32
36
'universe_domain' : 'googleapis.com' ,
33
37
}
34
38
35
- client = BetaAnalyticsDataClient .from_service_account_info (credentials_dict )
39
+ try :
40
+ client = BetaAnalyticsDataClient .from_service_account_info (credentials_dict )
41
+ except google .auth .exceptions .MalformedError as e :
42
+ print ('Malformed Error:' , repr (e ))
43
+ # Insight into reason for failure without exposing secret key
44
+ print ('Length of PRIVATE_KEY:' , len (PRIVATE_KEY )) # 0: Secret not found, ~734: Secret malformed
36
45
37
- pre_project_date = '2020-03-31' # random date before project start
46
+ pre_project_date = '2020-03-31' # Random date before project start
38
47
39
48
40
49
def _format_rounding (value ):
50
+ """
51
+ Helper function for rounding string displays. 1,232 -> 1.2K
52
+ """
41
53
return f'{ round (value / 1000 , 1 ):.1f} K'
42
54
43
55
44
56
def _run_total_users_report (property_id ):
57
+ """
58
+ Function for requesting cumulative active users from a project since project start.
59
+ """
45
60
request = RunReportRequest (
46
61
property = f'properties/{ property_id } ' ,
47
62
dimensions = [],
@@ -58,16 +73,24 @@ def _run_total_users_report(property_id):
58
73
59
74
60
75
def get_total_users (PORTAL_ID , FOUNDATIONS_ID , COOKBOOKS_ID ):
76
+ """
77
+ Function for taking cumulative active users from each project and dumping it into a JSON with the current datetime.
78
+ """
61
79
metrics_dict = {}
62
80
metrics_dict ['Now' ] = str (datetime .datetime .now ())
63
81
metrics_dict ['Portal' ] = _run_total_users_report (PORTAL_ID )
64
82
metrics_dict ['Foundations' ] = _run_total_users_report (FOUNDATIONS_ID )
65
83
metrics_dict ['Cookbooks' ] = _run_total_users_report (COOKBOOKS_ID )
84
+
85
+ # Save to JSON, Remember Action is called from root directory
66
86
with open ('portal/metrics/user_metrics.json' , 'w' ) as outfile :
67
87
json .dump (metrics_dict , outfile )
68
88
69
89
70
90
def _run_active_users_this_year (property_id ):
91
+ """
92
+ Function for requesting active users by day from a project since year start.
93
+ """
71
94
current_year = datetime .datetime .now ().year
72
95
start_date = f'{ current_year } -01-01'
73
96
@@ -87,14 +110,19 @@ def _run_active_users_this_year(property_id):
87
110
dates .append (date )
88
111
user_counts .append (int (row .metric_values [0 ].value ))
89
112
113
+ # Days need to be sorted chronologically
90
114
return zip (* sorted (zip (dates , user_counts ), key = lambda x : x [0 ]))
91
115
92
116
93
117
def plot_projects_this_year (PORTAL_ID , FOUNDATIONS_ID , COOKBOOKS_ID ):
118
+ """
119
+ Function for taking year-to-date active users by day and plotting it for each project.
120
+ """
94
121
portal_dates , portal_users = _run_active_users_this_year (PORTAL_ID )
95
122
foundations_dates , foundations_users = _run_active_users_this_year (FOUNDATIONS_ID )
96
123
cookbooks_dates , cookbooks_users = _run_active_users_this_year (COOKBOOKS_ID )
97
124
125
+ # Plotting code
98
126
plt .figure (figsize = (10 , 5.5 ))
99
127
plt .title ('Year-to-Date Pythia Active Users' , fontsize = 15 )
100
128
@@ -109,6 +137,9 @@ def plot_projects_this_year(PORTAL_ID, FOUNDATIONS_ID, COOKBOOKS_ID):
109
137
110
138
111
139
def _run_top_pages_report (property_id ):
140
+ """
141
+ Function for requesting top 5 pages from a project.
142
+ """
112
143
request = RunReportRequest (
113
144
property = f'properties/{ property_id } ' ,
114
145
dimensions = [Dimension (name = 'pageTitle' )],
@@ -123,48 +154,58 @@ def _run_top_pages_report(property_id):
123
154
views = int (row .metric_values [0 ].value )
124
155
views_dict [page ] = views
125
156
157
+ # Sort by views and grab the top 5
126
158
top_pages = sorted (views_dict .items (), key = lambda item : item [1 ], reverse = True )[:5 ]
159
+ # String manipulation on page titles "Cartopy - Pythia Foundations" -> "Cartopy"
127
160
pages = [page .split ('—' )[0 ] for page , _ in top_pages ]
128
161
views = [views for _ , views in top_pages ]
129
162
163
+ # Reverse order of lists, so they'll plot with most visited page on top (i.e. last)
130
164
return pages [::- 1 ], views [::- 1 ]
131
165
132
166
133
167
def plot_top_pages (PORTAL_ID , FOUNDATIONS_ID , COOKBOOKS_ID ):
168
+ """
169
+ Function that takes the top 5 viewed pages for all 3 projects and plot them on a histogram.
170
+ """
134
171
portal_pages , portal_views = _run_top_pages_report (PORTAL_ID )
135
172
foundations_pages , foundations_views = _run_top_pages_report (FOUNDATIONS_ID )
136
173
cookbooks_pages , cookbooks_views = _run_top_pages_report (COOKBOOKS_ID )
137
174
138
- pages = cookbooks_pages + foundations_pages + portal_pages
139
-
175
+ # Plotting code
140
176
fig , ax = plt .subplots (figsize = (10 , 5.5 ))
141
177
plt .title ('All-Time Top Pages' , fontsize = 15 )
142
178
143
- y = np .arange (5 )
144
- y2 = np .arange (6 , 11 )
145
- y3 = np .arange (12 , 17 )
146
- y4 = np .append (y , y2 )
147
- y4 = np .append (y4 , y3 )
179
+ y = np .arange (5 ) # 0-4 for Cookbooks
180
+ y2 = np .arange (6 , 11 ) # 6-10 for Foundations
181
+ y3 = np .arange (12 , 17 ) # 12-16 for Portal
148
182
149
183
bar1 = ax .barh (y3 , portal_views , align = 'center' , label = 'Portal' , color = 'purple' )
150
184
bar2 = ax .barh (y2 , foundations_views , align = 'center' , label = 'Foundations' , color = 'royalblue' )
151
185
bar3 = ax .barh (y , cookbooks_views , align = 'center' , label = 'Cookbooks' , color = 'indianred' )
152
186
187
+ y4 = np .append (y , y2 )
188
+ y4 = np .append (y4 , y3 ) # 0-4,6-19,12-6 for page labels to have a gap between projects
189
+ pages = cookbooks_pages + foundations_pages + portal_pages # List of all pages
153
190
ax .set_yticks (y4 , labels = pages , fontsize = 12 )
154
191
192
+ # Adds round-formatted views label to end of each bar
155
193
ax .bar_label (bar1 , fmt = _format_rounding , padding = 5 , fontsize = 10 )
156
194
ax .bar_label (bar2 , fmt = _format_rounding , padding = 5 , fontsize = 10 )
157
195
ax .bar_label (bar3 , fmt = _format_rounding , padding = 5 , fontsize = 10 )
158
196
159
197
ax .set_xscale ('log' )
160
- ax .set_xlim ([10 , 10 ** 5 ])
198
+ ax .set_xlim ([10 , 10 ** 5 ]) # set_xlim must be after setting xscale to log
161
199
ax .set_xlabel ('Page Views' , fontsize = 12 )
162
200
163
201
plt .legend (fontsize = 12 , loc = 'lower right' )
164
202
plt .savefig ('portal/metrics/toppages.png' , bbox_inches = 'tight' )
165
203
166
204
167
205
def _run_usersXcountry_report (property_id ):
206
+ """
207
+ Function for requesting users by country for a project.
208
+ """
168
209
request = RunReportRequest (
169
210
property = f'properties/{ property_id } ' ,
170
211
dimensions = [Dimension (name = 'country' )],
@@ -183,6 +224,9 @@ def _run_usersXcountry_report(property_id):
183
224
184
225
185
226
def plot_usersXcountry (FOUNDATIONS_ID ):
227
+ """
228
+ Function for taking users by country for Pythia Foundations and plotting them on a map.
229
+ """
186
230
users_by_country = _run_usersXcountry_report (FOUNDATIONS_ID )
187
231
188
232
# Google API Country names do not match Cartopy Country Shapefile names
@@ -201,11 +245,13 @@ def plot_usersXcountry(FOUNDATIONS_ID):
201
245
for key in dict_api2cartopy :
202
246
users_by_country [dict_api2cartopy [key ]] = users_by_country .pop (key )
203
247
248
+ # Sort by views and grab the top 10 countries for a text box
204
249
top_10_countries = sorted (users_by_country .items (), key = lambda item : item [1 ], reverse = True )[:10 ]
205
250
top_10_text = '\n ' .join (
206
251
f'{ country } : { _format_rounding (value )} ' for i , (country , value ) in enumerate (top_10_countries )
207
252
)
208
253
254
+ # Plotting code
209
255
fig = plt .figure (figsize = (10 , 4 ))
210
256
ax = plt .axes (projection = cartopy .crs .PlateCarree (), frameon = False )
211
257
ax .set_title ('Pythia Foundations Users by Country' , fontsize = 15 )
@@ -215,12 +261,13 @@ def plot_usersXcountry(FOUNDATIONS_ID):
215
261
countries = reader .records ()
216
262
217
263
colormap = plt .get_cmap ('Blues' )
218
- newcmp = colors .ListedColormap (colormap (np .linspace (0.2 , 1 , 128 )))
264
+ newcmp = colors .ListedColormap (colormap (np .linspace (0.2 , 1 , 128 ))) # Truncate colormap to remove white hues
219
265
newcmp .set_extremes (under = 'grey' )
220
266
221
- norm = colors .LogNorm (vmin = 1 , vmax = max (users_by_country .values ()))
267
+ norm = colors .LogNorm (vmin = 1 , vmax = max (users_by_country .values ())) # Plot on log scale
222
268
mappable = cm .ScalarMappable (norm = norm , cmap = newcmp )
223
269
270
+ # Loop through countries and plot their color
224
271
for country in countries :
225
272
country_name = country .attributes ['SOVEREIGNT' ]
226
273
if country_name in users_by_country .keys ():
@@ -238,10 +285,12 @@ def plot_usersXcountry(FOUNDATIONS_ID):
238
285
[country .geometry ], cartopy .crs .PlateCarree (), facecolor = 'grey' , edgecolor = 'white' , linewidth = 0.7
239
286
)
240
287
288
+ # Add colorbar
241
289
cax = fig .add_axes ([0.1 , - 0.015 , 0.67 , 0.03 ])
242
290
cbar = fig .colorbar (mappable = mappable , cax = cax , spacing = 'uniform' , orientation = 'horizontal' , extend = 'min' )
243
291
cbar .set_label ('Unique Users' )
244
292
293
+ # Add top 10 countries text
245
294
props = dict (boxstyle = 'round' , facecolor = 'white' , edgecolor = 'white' )
246
295
ax .text (1.01 , 0.5 , top_10_text , transform = ax .transAxes , fontsize = 12 , verticalalignment = 'center' , bbox = props )
247
296
0 commit comments