Coverage for webapp/publisher/snaps/metrics_views.py: 81%
129 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 22:43 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 22:43 +0000
1# Standard library
2from json import loads
3from dateutil import relativedelta
4import math
6# Packages
7import flask
8import webapp.metrics.helper as metrics_helper
9import webapp.metrics.metrics as metrics
10from canonicalwebteam.store_api.dashboard import Dashboard
11from canonicalwebteam.store_api.devicegw import DeviceGW
13# Local
14from webapp.helpers import api_session
15from webapp.decorators import login_required
16from webapp.publisher.snaps import logic
18dashboard = Dashboard(api_session)
19device_gateway = DeviceGW("snap", api_session)
21downsample_data_limit = 500
22downsample_target_size = 10
25@login_required
26def get_account_snaps_metrics():
27 if not flask.request.data:
28 error = {"error": "Please provide a list of snaps"}
29 return flask.jsonify(error), 500
31 try:
32 metrics = {"buckets": [], "snaps": []}
34 snaps = loads(flask.request.data)
35 metrics_query = metrics_helper.build_snap_installs_metrics_query(snaps)
37 if metrics_query:
38 snap_metrics = dashboard.get_publisher_metrics(
39 flask.session, json=metrics_query
40 )
41 metrics = metrics_helper.transform_metrics(
42 metrics, snap_metrics, snaps
43 )
44 return flask.jsonify(metrics), 200
45 except Exception:
46 error = {"error": "An error occured while fetching metrics"}
47 return flask.jsonify(error), 500
50@login_required
51def get_measure_snap(snap_name):
52 return flask.redirect(
53 flask.url_for(".publisher_snap_metrics", snap_name=snap_name)
54 )
57@login_required
58def publisher_snap_metrics(snap_name):
59 """
60 A view to display the snap metrics page for specific snaps.
61 """
62 # If this fails, the page will 404
63 dashboard.get_snap_info(flask.session, snap_name)
65 context = {
66 # Data direct from details API
67 "snap_name": snap_name,
68 # pass snap id from here?
69 "is_linux": "Linux" in flask.request.headers["User-Agent"],
70 }
72 return flask.render_template("store/publisher.html", **context)
75@login_required
76def get_active_devices(snap_name):
77 snap_details = dashboard.get_snap_info(flask.session, snap_name)
79 snap_id = snap_details["snap_id"]
81 installed_base_metric = logic.verify_base_metrics(
82 flask.request.args.get("active-devices", default="version", type=str)
83 )
85 period = flask.request.args.get("period", default="30d", type=str)
86 active_device_period = logic.extract_metrics_period(period)
88 page = flask.request.args.get("page", default=1, type=int)
90 metric_requested_length = active_device_period["int"]
91 metric_requested_bucket = active_device_period["bucket"]
93 page_time_length = flask.request.args.get(
94 "page-length", default=3, type=int
95 )
96 total_page_num = 1
97 if metric_requested_bucket == "d" or (
98 metric_requested_bucket == "m"
99 and page_time_length >= metric_requested_length
100 ):
101 dates = metrics_helper.get_dates_for_metric(
102 metric_requested_length, metric_requested_bucket
103 )
104 start = dates["start"]
105 end = dates["end"]
106 else:
107 page_period_length = (
108 (metric_requested_length * 12)
109 if metric_requested_bucket == "y"
110 else metric_requested_length
111 )
112 total_page_num = math.floor(page_period_length / page_time_length)
114 end = metrics_helper.get_last_metrics_processed_date() + (
115 relativedelta.relativedelta(
116 months=-(page_time_length * (page - 1))
117 )
118 )
119 start = end + (relativedelta.relativedelta(months=-(page_time_length)))
121 # Decrease the date by a day to make sure
122 # there is no overlapping dates across the pages.
123 if page != 1:
124 end = end + relativedelta.relativedelta(days=-1)
126 installed_base = logic.get_installed_based_metric(installed_base_metric)
128 new_metrics_query = metrics_helper.build_active_device_metric_query(
129 snap_id=snap_id, installed_base=installed_base, end=end, start=start
130 )
132 metrics_response = dashboard.get_publisher_metrics(
133 flask.session, json=new_metrics_query
134 )
135 days_without_data = metrics_helper.get_days_without_data(metrics_response)
136 active_metrics = metrics_helper.find_metric(
137 metrics_response["metrics"], installed_base
138 )
140 metrics_data = active_metrics
141 buckets = metrics_data["buckets"]
142 series = metrics_data["series"]
143 metric_name = metrics_data["metric_name"]
144 # Add constants to a variable
145 if len(series) > downsample_data_limit:
146 (
147 downsampled_buckets,
148 downsampled_series,
149 ) = metrics_helper.downsample_series(
150 buckets, series, downsample_target_size
151 )
152 else:
153 downsampled_buckets = buckets
154 downsampled_series = series
156 series = downsampled_series
157 if metric_name == "weekly_installed_base_by_channel":
158 for s in series:
159 if "/" not in s["name"]:
160 s["name"] = f"latest/{s['name']}"
162 if installed_base_metric == "os":
163 for item in series:
164 item["name"] = metrics._capitalize_os_name(item["name"])
166 active_devices = metrics.ActiveDevices(
167 name=metric_name,
168 series=series,
169 buckets=downsampled_buckets,
170 status=metrics_data["status"],
171 )
173 latest_active = 0
174 if active_devices:
175 latest_active = active_devices.get_number_latest_active_devices()
177 return flask.jsonify(
178 {
179 "active_devices": dict(active_devices),
180 "latest_active_devices": latest_active,
181 "total_page_num": total_page_num,
182 "days_without_data": days_without_data,
183 }
184 )
187@login_required
188def get_latest_active_devices(snap_name):
189 snap_details = dashboard.get_snap_info(flask.session, snap_name)
190 snap_id = snap_details["snap_id"]
191 # get latest active devices
192 latest_day_period = logic.extract_metrics_period("1d")
193 latest_installed_base = logic.get_installed_based_metric("version")
194 latest_day_query_json = metrics_helper.build_metric_query_installed_base(
195 snap_id=snap_id,
196 installed_base=latest_installed_base,
197 metric_period=latest_day_period["int"],
198 metric_bucket=latest_day_period["bucket"],
199 )
201 latest_day_response = dashboard.get_publisher_metrics(
202 flask.session, json=latest_day_query_json
203 )
205 latest_active = 0
207 if latest_day_response:
208 latest_active_metrics = metrics_helper.find_metric(
209 latest_day_response["metrics"], latest_installed_base
210 )
211 if latest_active_metrics:
212 latest_active_devices = metrics.ActiveDevices(
213 name=latest_active_metrics["metric_name"],
214 series=latest_active_metrics["series"],
215 buckets=latest_active_metrics["buckets"],
216 status=latest_active_metrics["status"],
217 )
218 latest_active = (
219 latest_active_devices.get_number_latest_active_devices()
220 )
221 return flask.jsonify(
222 {
223 "latest_active_devices": latest_active,
224 }
225 )
228@login_required
229def get_metric_annotaion(snap_name):
230 details = dashboard.get_snap_info(flask.session, snap_name)
231 annotations = {"name": "annotations", "series": [], "buckets": []}
233 for category in details["categories"]["items"]:
234 date = category["since"].split("T")[0]
235 new_date = logic.convert_date(category["since"])
237 if date not in annotations["buckets"]:
238 annotations["buckets"].append(date)
240 index_of_date = annotations["buckets"].index(date)
242 single_series = {
243 "values": [0] * (len(annotations)),
244 "name": category["name"],
245 "display_name": category["name"].capitalize().replace("-", " "),
246 "display_date": new_date,
247 "date": date,
248 }
250 single_series["values"][index_of_date] = 1
252 annotations["series"].append(single_series)
254 annotations["series"] = sorted(
255 annotations["series"], key=lambda k: k["date"]
256 )
257 return flask.jsonify(annotations)
260@login_required
261def get_country_metric(snap_name):
262 snap_details = dashboard.get_snap_info(flask.session, snap_name)
263 snap_id = snap_details["snap_id"]
264 metrics_query_json = metrics_helper.build_metric_query_country(
265 snap_id=snap_id,
266 )
268 metrics_response = dashboard.get_publisher_metrics(
269 flask.session, json=metrics_query_json
270 )
272 country_metric = metrics_helper.find_metric(
273 metrics_response["metrics"], "weekly_installed_base_by_country"
274 )
275 country_devices = metrics.CountryDevices(
276 name=country_metric["metric_name"],
277 series=country_metric["series"],
278 buckets=country_metric["buckets"],
279 status=country_metric["status"],
280 private=True,
281 )
283 territories_total = 0
284 if country_devices:
285 territories_total = country_devices.get_number_territories()
287 return flask.jsonify(
288 {
289 "active_devices": country_devices.country_data,
290 "territories_total": territories_total,
291 }
292 )