Skip to content

Commit 34ce217

Browse files
authored
Add a report breakdown for the buckets view (#164)
This provides a summary of how many reports come from each platform/OS as well as how many have PBM or ETP blocking. The goal is to make it easier to see things like buckets being specific to specific platforms.
1 parent 311e447 commit 34ce217

2 files changed

Lines changed: 193 additions & 1 deletion

File tree

server/frontend/src/components/Buckets/View.vue

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
</td>
7474
</tr>
7575
<tr>
76-
<td>Reports in this bucket</td>
76+
<td>Total Reports</td>
7777
<td>
7878
{{ bucket.size }}
7979
<span
@@ -89,6 +89,76 @@
8989
/>
9090
</td>
9191
</tr>
92+
<tr v-if="parsedSummary">
93+
<td>Report Breakdown</td>
94+
<td>
95+
<div></div>
96+
<table class="table">
97+
<thead>
98+
<tr>
99+
<th>Platform</th>
100+
<th>%</th>
101+
<th>OS</th>
102+
<th>Browser</th>
103+
</tr>
104+
</thead>
105+
<tbody>
106+
<template
107+
v-for="platform in ['desktop', 'mobile']"
108+
:key="platform"
109+
>
110+
<tr v-if="parsedSummary[platform].total > 0">
111+
<td>
112+
{{ platform == "desktop" ? "Desktop" : "Mobile" }}
113+
</td>
114+
<td>{{ parsedSummary[platform].pct }}%</td>
115+
<td>
116+
<ul class="summary-list">
117+
<li
118+
v-for="item in parsedSummary[platform].os"
119+
:key="item.name"
120+
>
121+
<img
122+
v-if="osLogoKey(item.name)"
123+
width="16px"
124+
height="16px"
125+
:alt="item.name"
126+
:src="staticLogo(osLogoKey(item.name))"
127+
/>
128+
<span v-else>{{ item.name }}</span>
129+
{{ item.pct }}%
130+
</li>
131+
</ul>
132+
</td>
133+
<td>
134+
<ul class="summary-list">
135+
<li
136+
v-for="item in parsedSummary[platform]
137+
.browserVersions"
138+
:key="item.name"
139+
>
140+
{{ item.name }}: {{ item.pct }}%
141+
</li>
142+
</ul>
143+
</td>
144+
</tr>
145+
</template>
146+
</tbody>
147+
</table>
148+
<table class="table">
149+
<tbody>
150+
<tr>
151+
<th>PBM enabled</th>
152+
<td>{{ parsedSummary.pbmEnabled }}%</td>
153+
</tr>
154+
<tr>
155+
<th>Content blocked</th>
156+
<td>{{ parsedSummary.contentBlocked }}%</td>
157+
</tr>
158+
</tbody>
159+
</table>
160+
</td>
161+
</tr>
92162
<tr>
93163
<td>ML Valid Probability Filter</td>
94164
<td>
@@ -239,6 +309,49 @@ export default {
239309
prettySignature() {
240310
return jsonPretty(this.bucket.signature);
241311
},
312+
parsedSummary() {
313+
const s = this.bucket.summary;
314+
if (!s || s.total === 0) {
315+
return null;
316+
}
317+
318+
const formatPercent = (n, total) =>
319+
total > 0 ? ((n / total) * 100).toFixed(1) : "0.0";
320+
321+
const knownOS = ["Windows", "Mac", "Linux", "Android"];
322+
323+
const parsePlatform = (platform) => {
324+
const { total, os, browser_versions } = platform;
325+
const osList = [
326+
...knownOS
327+
.filter((name) => os[name] !== undefined)
328+
.map((name) => ({ name, pct: formatPercent(os[name], total) })),
329+
...Object.entries(os)
330+
.filter(([name]) => !knownOS.includes(name))
331+
.map(([name, count]) => ({
332+
name,
333+
pct: formatPercent(count, total),
334+
})),
335+
];
336+
const browserVersions = Object.entries(browser_versions)
337+
.sort((a, b) => b[0] - a[0])
338+
.sort((a, b) => b[1] - a[1])
339+
.map(([name, count]) => ({ name, pct: formatPercent(count, total) }));
340+
return {
341+
total,
342+
pct: formatPercent(total, s.total),
343+
os: osList,
344+
browserVersions,
345+
};
346+
};
347+
348+
return {
349+
desktop: parsePlatform(s.desktop),
350+
mobile: parsePlatform(s.mobile),
351+
pbmEnabled: formatPercent(s.pbm_enabled, s.total),
352+
contentBlocked: formatPercent(s.content_blocked, s.total),
353+
};
354+
},
242355
},
243356
created: function () {
244357
if (this.$route.hash.startsWith("#")) {
@@ -256,6 +369,19 @@ export default {
256369
mounted() {},
257370
methods: {
258371
formatDate: date,
372+
staticLogo(name) {
373+
return window.location.origin + "/static/img/os/" + name + ".png";
374+
},
375+
osLogoKey(name) {
376+
return (
377+
{
378+
Linux: "linux",
379+
Mac: "macosx",
380+
Windows: "windows",
381+
Android: "android",
382+
}[name] ?? null
383+
);
384+
},
259385
buildQueryParams() {
260386
const result = {
261387
query: JSON.stringify({
@@ -444,4 +570,9 @@ button[aria-expanded="false"] .bi-eye-slash-fill {
444570
font-weight: bold;
445571
min-width: 60px;
446572
}
573+
.summary-list {
574+
list-style: none;
575+
padding: 0;
576+
margin: 0.25em 0 0;
577+
}
447578
</style>

server/reportmanager/views.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,67 @@ def retrieve(self, request, *args, **kwargs):
996996

997997
response.data["report_history"] = list(hits.values("begin", "count"))
998998

999+
reports = ReportEntry.objects.filter(bucket_id=response.data["id"])
1000+
1001+
os_counts = {
1002+
(row["os__name"] or "Unknown"): row["count"]
1003+
for row in reports.values("os__name").annotate(count=Count("id"))
1004+
}
1005+
1006+
desktop_browser_versions: dict[str, int] = {}
1007+
mobile_browser_versions: dict[str, int] = {}
1008+
for row in reports.values("app__name", "app__version", "os__name").annotate(
1009+
count=Count("id")
1010+
):
1011+
app_name = row["app__name"] or "Unknown"
1012+
app_version = row["app__version"] or "Unknown"
1013+
os_name = (row["os__name"] or "Unknown").lower()
1014+
major = app_version.split(".")[0]
1015+
key = f"{app_name} {major}"
1016+
if os_name == "android":
1017+
mobile_browser_versions[key] = (
1018+
mobile_browser_versions.get(key, 0) + row["count"]
1019+
)
1020+
else:
1021+
desktop_browser_versions[key] = (
1022+
desktop_browser_versions.get(key, 0) + row["count"]
1023+
)
1024+
1025+
mobile_os = {k: v for k, v in os_counts.items() if k.lower() == "android"}
1026+
desktop_os = {k: v for k, v in os_counts.items() if k.lower() != "android"}
1027+
1028+
agg = reports.aggregate(
1029+
total=Count("id"),
1030+
pbm_count=Count(
1031+
"id",
1032+
filter=Q(
1033+
details__boolean__broken_site_report_tab_info_antitracking_is_private_browsing=True
1034+
),
1035+
),
1036+
blocked_count=Count(
1037+
"id",
1038+
filter=Q(
1039+
details__boolean__broken_site_report_tab_info_antitracking_has_tracking_content_blocked=True
1040+
),
1041+
),
1042+
)
1043+
1044+
response.data["summary"] = {
1045+
"total": agg["total"],
1046+
"desktop": {
1047+
"total": sum(desktop_os.values()),
1048+
"os": desktop_os,
1049+
"browser_versions": desktop_browser_versions,
1050+
},
1051+
"mobile": {
1052+
"total": sum(mobile_os.values()),
1053+
"os": mobile_os,
1054+
"browser_versions": mobile_browser_versions,
1055+
},
1056+
"pbm_enabled": agg["pbm_count"],
1057+
"content_blocked": agg["blocked_count"],
1058+
}
1059+
9991060
return response
10001061

10011062
def __validate(self, bucket, submit_save, reassign, limit, offset, created):

0 commit comments

Comments
 (0)