Skip to content

Commit e0ac403

Browse files
committed
feat: Add download size display to channel table
- Add "Download Size" column to channel map table showing per-channel package sizes - Implement human-readable size formatting (B, kB, MB, GB, TB) in JavaScript - Update ChannelData TypeScript interface to include size field - Add responsive design (hidden on mobile screens) - Include comprehensive unit tests for size display functionality Addresses user need for bandwidth awareness on metered connections by showing accurate download sizes for each channel, since different channels can have different package sizes. Fixes #1864
1 parent 042ea5b commit e0ac403

3 files changed

Lines changed: 90 additions & 2 deletions

File tree

static/js/public/snap-details/channelMap.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ interface ChannelData {
3535
"released-at": string;
3636
risk: string;
3737
version: string;
38+
size: number;
3839
channel?: string;
3940
revision: string;
4041
}
@@ -557,6 +558,19 @@ class ChannelMap {
557558
el.innerHTML = tbody.join("");
558559
}
559560

561+
/**
562+
* Format file size in human readable format
563+
* @param {number} bytes - Size in bytes
564+
* @returns {string} - Formatted size string
565+
*/
566+
formatSize(bytes: number): string {
567+
if (bytes === 0) return "0 B";
568+
const k = 1000;
569+
const sizes = ["B", "kB", "MB", "GB", "TB"];
570+
const i = Math.floor(Math.log(bytes) / Math.log(k));
571+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
572+
}
573+
560574
/**
561575
* Prepare the channel map tables
562576
*
@@ -623,6 +637,7 @@ class ChannelMap {
623637
trackName,
624638
trackInfo["risk"],
625639
trackInfo["version"],
640+
this.formatSize(trackInfo["size"]),
626641
trackInfo["released-at"],
627642
trackInfo["confinement"],
628643
]);

templates/store/snap-details/_channel_map.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@
130130
<tr>
131131
<th>Channel</th>
132132
<th>Version</th>
133-
<th width="25%" class="u-hide--medium u-hide--small">Published</th>
133+
<th width="18%" class="u-hide--medium u-hide--small">Download Size</th>
134+
<th width="20%" class="u-hide--medium u-hide--small">Published</th>
134135
<th width="12%"></th>
135136
</tr>
136137
</thead>
@@ -190,10 +191,11 @@
190191
</script>
191192

192193
<script type="text/template" id="channel-map-row-template" data-js="channel-map-row">
193-
<tr class="${rowClass}" data-js="slide-install-instructions" data-channel="${row[0]}/${row[1]}" data-confinement="${row[4]}">
194+
<tr class="${rowClass}" data-js="slide-install-instructions" data-channel="${row[0]}/${row[1]}" data-confinement="${row[5]}">
194195
<td>${row[0]}/${row[1]}</td>
195196
<td>${row[2]}</td>
196197
<td class="u-hide--medium u-hide--small">${row[3]}</td>
198+
<td class="u-hide--medium u-hide--small">${row[4]}</td>
197199
<td class="u-align--center"><a href="#" class="p-channel-map__version-table-install" data-analytics-click data-analytics-target="snap_details_channel_version_install" data-analytics-channel="${row[0]}/${row[1]}" data-analytics-version="${row[2]}">Install &rsaquo;</a></td>
198200
</tr>
199201
</script>

tests/store/tests_details.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,77 @@ def test_user_not_connected(self):
389389
assert response.status_code == 200
390390
self.assert_context("is_users_snap", False)
391391

392+
@responses.activate
393+
def test_package_size_in_channel_map(self):
394+
"""Test that package size is correctly included in channel map data"""
395+
payload = {
396+
"snap-id": "id",
397+
"name": "test-snap",
398+
"default-track": None,
399+
"snap": {
400+
"title": "Test Snap",
401+
"summary": "This is a test snap",
402+
"description": "this is a description",
403+
"media": [],
404+
"license": "MIT",
405+
"publisher": {
406+
"display-name": "Test Publisher",
407+
"username": "testpub",
408+
"validation": True,
409+
},
410+
"categories": [{"name": "test"}],
411+
"trending": False,
412+
"unlisted": False,
413+
"links": {},
414+
},
415+
"channel-map": [
416+
{
417+
"channel": {
418+
"architecture": "amd64",
419+
"name": "stable",
420+
"risk": "stable",
421+
"track": "latest",
422+
"released-at": "2018-09-18T14:45:28.064633+00:00",
423+
},
424+
"created-at": "2018-09-18T14:45:28.064633+00:00",
425+
"version": "1.0",
426+
"confinement": "strict",
427+
"download": {"size": 52428800}, # 50 MB
428+
}
429+
],
430+
}
431+
432+
responses.add(
433+
responses.Response(
434+
method="GET", url=self.api_url, json=payload, status=200
435+
)
436+
)
437+
438+
metrics_url = "https://api.snapcraft.io/api/v1/snaps/metrics"
439+
responses.add(
440+
responses.Response(
441+
method="POST", url=metrics_url, json={}, status=200
442+
)
443+
)
444+
445+
response = self.client.get(self.endpoint_url)
446+
447+
assert response.status_code == 200
448+
# Check that channel_map contains size information
449+
context = self.get_context_variable("channel_map")
450+
self.assertIsNotNone(context)
451+
# Check that size is included in the channel map data structure
452+
self.assertIn("amd64", context)
453+
self.assertIn("latest", context["amd64"])
454+
channel_data = context["amd64"]["latest"][0]
455+
self.assertEqual(channel_data["size"], 52428800)
456+
# Check that the Download Size column header is in the HTML
457+
expected_header = (
458+
b'<th width="18%" class="u-hide--medium u-hide--small">'
459+
b"Download Size</th>"
460+
)
461+
self.assertIn(expected_header, response.data)
462+
392463
@responses.activate
393464
def test_user_connected_on_not_own_snap(self):
394465
payload = SNAP_PAYLOAD

0 commit comments

Comments
 (0)