Skip to content

Commit 710cd28

Browse files
committed
Add a report breakdown for the buckets view
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 0574d4e commit 710cd28

2 files changed

Lines changed: 192 additions & 12 deletions

File tree

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

Lines changed: 131 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,88 @@
7373
</td>
7474
</tr>
7575
<tr>
76-
<td>Reports in this bucket</td>
76+
<td>Total Reports</td>
7777
<td>
7878
{{ bucket.size }}
79-
<span
80-
v-if="bucket.reassign_in_progress"
81-
class="bi bi-hourglass-split"
82-
data-toggle="tooltip"
83-
data-placement="top"
84-
title="Reports are currently being reassigned in this bucket"
85-
></span>
86-
<activitygraph
87-
:data="bucket.report_history"
88-
:range="activityRange"
89-
/>
79+
<span
80+
v-if="bucket.reassign_in_progress"
81+
class="bi bi-hourglass-split"
82+
data-toggle="tooltip"
83+
data-placement="top"
84+
title="Reports are currently being reassigned in this bucket"
85+
></span>
86+
<activitygraph
87+
:data="bucket.report_history"
88+
:range="activityRange"
89+
/>
90+
</td>
91+
</tr>
92+
<tr>
93+
<td>Report Breakdown</td>
94+
<td>
95+
<div>
96+
</div>
97+
<table class="table">
98+
<thead>
99+
<tr>
100+
<th>Platform</th>
101+
<th>%</th>
102+
<th>OS</th>
103+
<th>Browser</th>
104+
</tr>
105+
</thead>
106+
<tbody>
107+
<template
108+
v-for="platform in ['desktop', 'mobile']"
109+
:key="platform">
110+
<tr v-if="parsedSummary[platform].total > 0">
111+
<td>{{ platform == "desktop" ? "Desktop" : "Mobile" }}</td>
112+
<td>{{ parsedSummary[platform].pct }}%</td>
113+
<td>
114+
<ul class="summary-list">
115+
<li
116+
v-for="item in parsedSummary[platform].os"
117+
:key="item.name"
118+
>
119+
<img
120+
v-if="osLogoKey(item.name)"
121+
width="16px"
122+
height="16px"
123+
:alt="item.name"
124+
:src="staticLogo(osLogoKey(item.name))"
125+
/>
126+
<span v-else>{{ item.name }}</span>
127+
{{ item.pct }}%
128+
</li>
129+
</ul>
130+
</td>
131+
<td>
132+
<ul class="summary-list">
133+
<li
134+
v-for="item in parsedSummary[platform].browserVersions"
135+
:key="item.name"
136+
>
137+
{{ item.name }}: {{ item.pct }}%
138+
</li>
139+
</ul>
140+
</td>
141+
</tr>
142+
</template>
143+
</tbody>
144+
</table>
145+
<table class="table">
146+
<tbody>
147+
<tr>
148+
<th>PBM enabled</th>
149+
<td>{{ parsedSummary.pbmEnabled }}%</td>
150+
</tr>
151+
<tr>
152+
<th>Content blocked</th>
153+
<td>{{ parsedSummary.contentBlocked }}%</td>
154+
</tr>
155+
</tbody>
156+
</table>
157+
90158
</td>
91159
</tr>
92160
<tr>
@@ -239,6 +307,44 @@ export default {
239307
prettySignature() {
240308
return jsonPretty(this.bucket.signature);
241309
},
310+
parsedSummary() {
311+
const s = this.bucket.summary;
312+
if (!s || s.total === 0) {
313+
return null;
314+
}
315+
const platformPct = (n, total) =>
316+
total > 0 ? ((n / total) * 100).toFixed(1) : "0.0";
317+
const knownOS = ["Windows", "Mac", "Linux", "Android"];
318+
319+
const parsePlatform = (platform) => {
320+
const { total, os, browser_versions } = platform;
321+
const osList = [
322+
...knownOS
323+
.filter((name) => os[name] !== undefined)
324+
.map((name) => ({ name, pct: platformPct(os[name], total) })),
325+
...Object.entries(os)
326+
.filter(([name]) => !knownOS.includes(name))
327+
.map(([name, count]) => ({ name, pct: platformPct(count, total) })),
328+
];
329+
const browserVersions = Object.entries(browser_versions)
330+
.sort((a, b) => b[0] - a[0])
331+
.sort((a, b) => b[1] - a[1])
332+
.map(([name, count]) => ({ name, pct: platformPct(count, total) }));
333+
return {
334+
total,
335+
pct: (total / s.total * 100).toFixed(1),
336+
os: osList,
337+
browserVersions
338+
};
339+
};
340+
341+
return {
342+
desktop: parsePlatform(s.desktop),
343+
mobile: parsePlatform(s.mobile),
344+
pbmEnabled: totalPct(s.pbm_enabled),
345+
contentBlocked: totalPct(s.content_blocked),
346+
};
347+
},
242348
},
243349
created: function () {
244350
if (this.$route.hash.startsWith("#")) {
@@ -256,6 +362,14 @@ export default {
256362
mounted() {},
257363
methods: {
258364
formatDate: date,
365+
staticLogo(name) {
366+
return window.location.origin + "/static/img/os/" + name + ".png";
367+
},
368+
osLogoKey(name) {
369+
return (
370+
{ Linux: "linux", Mac: "macosx", Windows: "windows", Android: "android" }[name] ?? null
371+
);
372+
},
259373
buildQueryParams() {
260374
const result = {
261375
query: JSON.stringify({
@@ -444,4 +558,9 @@ button[aria-expanded="false"] .bi-eye-slash-fill {
444558
font-weight: bold;
445559
min-width: 60px;
446560
}
561+
.summary-list {
562+
list-style: none;
563+
padding: 0;
564+
margin: 0.25em 0 0;
565+
}
447566
</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)