Skip to content

Commit c3acdfc

Browse files
alvaromateoM7mdisk
andauthored
WD-17150 - aliases section in snap details (#5356)
* WD-17150 - Add aliases section to snap details * WD-17150 - add tests * WD-17150 - Extra check in case snap_details endpoint fails * WIP - 404 error on details endpoint * WIP - extra log * ALL THE LOGS IN THE WORLD * Revert "ALL THE LOGS IN THE WORLD" This reverts commit d965af0. * WD-17150 - Fix linting and test * WD-17150 - Update header of section to specify clearly the info * WD-17150 - Move alias section according to design --------- Co-authored-by: Mohammad Iskandarany <m7md.k.isk@gmail.com>
1 parent e02c1e8 commit c3acdfc

3 files changed

Lines changed: 288 additions & 0 deletions

File tree

templates/store/snap-details/_details.html

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,65 @@ <h5 class="p-muted-heading">Source code</h5>
7676
<hr>
7777
{% endif %}
7878

79+
{%- if aliases -%}
80+
{%- set show_more = "Show more" -%}
81+
{%- set show_less = "Show less" -%}
82+
83+
{#-
84+
# The list starts displaying maximum 3 elements with the rest hidden.
85+
# With the script provided and the link element rendered the user can toggle
86+
# displaying and hiding all the aliases.
87+
-#}
88+
<h5 class="p-muted-heading">Command &rsaquo; Alias</h5>
89+
<ul class="p-list js-expandable-list">
90+
{%- set aliases_len = aliases|length -%}
91+
{%- set default_visible_aliases = 3 -%}
92+
{%- for alias in aliases -%}
93+
{%- set invisible_class = "" -%}
94+
{%- if loop.index > default_visible_aliases -%}
95+
{%- set invisible_class = "js-hidable u-hide" -%}
96+
{%- endif -%}
97+
<li class="p-list__item {{ invisible_class }}">
98+
{{ alias[0]|safe }} &rsaquo; {{ alias[1]|safe }}
99+
</li>
100+
{%- endfor -%}
101+
{%- if aliases_len > default_visible_aliases -%}
102+
<li class="p-list__item">
103+
<a class="js-toggle-full-list" href="#" title="Display more/less aliases">
104+
{{ show_more }}
105+
</a>
106+
</li>
107+
{%- endif -%}
108+
</ul>
109+
<hr>
110+
111+
<script>
112+
// Handle aliases "show more" button
113+
const list = document.querySelector(".js-expandable-list");
114+
const showMoreButton = document.querySelector(".js-toggle-full-list");
115+
let hasAllElementsDisplayed = false;
116+
117+
const updateAliasesListUI = (displayAllElements, elements) => {
118+
// change button
119+
showMoreButton.innerHTML = displayAllElements ? "{{ show_less }}" : "{{ show_more }}";
120+
// toggle hide classes
121+
for (const element of elements) {
122+
element.classList.toggle('u-hide', !displayAllElements);
123+
}
124+
}
125+
126+
if (list && showMoreButton) {
127+
const listElements = list.querySelectorAll(".js-hidable");
128+
showMoreButton.addEventListener("click", (e) => {
129+
e.preventDefault();
130+
// switch toggle
131+
hasAllElementsDisplayed = !hasAllElementsDisplayed;
132+
updateAliasesListUI(hasAllElementsDisplayed, listElements);
133+
});
134+
}
135+
</script>
136+
{%- endif -%}
137+
79138
{% if links["issues"] %}
80139
<h5 class="p-muted-heading">Report a bug</h5>
81140
<ul class="p-list">

tests/store/tests_details.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from webapp.app import create_app
55

66

7+
EMPTY_EXTRA_DETAILS_PAYLOAD = {"aliases": None, "package_name": "vault"}
8+
9+
710
class GetDetailsPageTest(TestCase):
811
def setUp(self):
912
self.snap_name = "toto"
@@ -40,6 +43,15 @@ def setUp(self):
4043
]
4144
)
4245
self.endpoint_url = "/" + self.snap_name
46+
self.api_url_details = "".join(
47+
[
48+
"https://api.snapcraft.io/api/v1/",
49+
"snaps/details/",
50+
self.snap_name,
51+
"?",
52+
urlencode({"fields": ",".join(["aliases"])}),
53+
]
54+
)
4355

4456
def create_app(self):
4557
app = create_app(testing=True)
@@ -48,6 +60,16 @@ def create_app(self):
4860

4961
return app
5062

63+
def assert_not_in_context(self, name):
64+
try:
65+
self.get_context_variable(name)
66+
except Exception:
67+
# flask-testing throws exception if context doesn't have "name"
68+
# that's what we expect so we just return and let the test pass
69+
return
70+
# If we reach this point it means the variable IS in context
71+
self.fail(f"Context variable exists: {name}")
72+
5173
@responses.activate
5274
def test_api_404(self):
5375
payload = {"error-list": [{"code": "resource-not-found"}]}
@@ -65,6 +87,77 @@ def test_api_404(self):
6587

6688
assert response.status_code == 404
6789

90+
@responses.activate
91+
def test_extra_details_404(self):
92+
payload = {
93+
"snap-id": "id",
94+
"name": "toto",
95+
"default-track": None,
96+
"snap": {
97+
"title": "Snap Title",
98+
"summary": "This is a summary",
99+
"description": "this is a description",
100+
"media": [],
101+
"license": "license",
102+
"publisher": {
103+
"display-name": "Toto",
104+
"username": "toto",
105+
"validation": True,
106+
},
107+
"categories": [{"name": "test"}],
108+
"trending": False,
109+
"unlisted": False,
110+
"links": {},
111+
},
112+
"channel-map": [
113+
{
114+
"channel": {
115+
"architecture": "amd64",
116+
"name": "stable",
117+
"risk": "stable",
118+
"track": "latest",
119+
"released-at": "2018-09-18T14:45:28.064633+00:00",
120+
},
121+
"created-at": "2018-09-18T14:45:28.064633+00:00",
122+
"version": "1.0",
123+
"confinement": "conf",
124+
"download": {"size": 100000},
125+
}
126+
],
127+
}
128+
extra_details_payload = {
129+
"error_list": [
130+
{
131+
"code": "resource-not-found",
132+
"message": "No snap named 'toto' found in series '16'.",
133+
}
134+
],
135+
"errors": ["No snap named 'toto' found in series '16'."],
136+
"result": "error",
137+
}
138+
139+
responses.add(
140+
responses.Response(
141+
method="GET", url=self.api_url, json=payload, status=200
142+
)
143+
)
144+
responses.add(
145+
responses.Response(
146+
method="GET",
147+
url=self.api_url_details,
148+
json=extra_details_payload,
149+
status=404,
150+
)
151+
)
152+
153+
response = self.client.get(self.endpoint_url)
154+
155+
assert len(responses.calls) == 2
156+
assert responses.calls[0].request.url == self.api_url
157+
assert responses.calls[1].request.url == self.api_url_details
158+
159+
assert response.status_code == 404
160+
68161
@responses.activate
69162
def test_api_500(self):
70163
payload = {"error-list": []}
@@ -125,6 +218,14 @@ def test_no_channel_map(self):
125218
method="GET", url=self.api_url, json=payload, status=200
126219
)
127220
)
221+
responses.add(
222+
responses.Response(
223+
method="GET",
224+
url=self.api_url_details,
225+
json=EMPTY_EXTRA_DETAILS_PAYLOAD,
226+
status=200,
227+
)
228+
)
128229

129230
response = self.client.get(self.endpoint_url)
130231

@@ -174,6 +275,14 @@ def test_user_connected(self):
174275
method="GET", url=self.api_url, json=payload, status=200
175276
)
176277
)
278+
responses.add(
279+
responses.Response(
280+
method="GET",
281+
url=self.api_url_details,
282+
json=EMPTY_EXTRA_DETAILS_PAYLOAD,
283+
status=200,
284+
)
285+
)
177286

178287
metrics_url = "https://api.snapcraft.io/api/v1/snaps/metrics"
179288
responses.add(
@@ -239,6 +348,14 @@ def test_user_not_connected(self):
239348
method="GET", url=self.api_url, json=payload, status=200
240349
)
241350
)
351+
responses.add(
352+
responses.Response(
353+
method="GET",
354+
url=self.api_url_details,
355+
json=EMPTY_EXTRA_DETAILS_PAYLOAD,
356+
status=200,
357+
)
358+
)
242359

243360
metrics_url = "https://api.snapcraft.io/api/v1/snaps/metrics"
244361
responses.add(
@@ -296,6 +413,14 @@ def test_user_connected_on_not_own_snap(self):
296413
method="GET", url=self.api_url, json=payload, status=200
297414
)
298415
)
416+
responses.add(
417+
responses.Response(
418+
method="GET",
419+
url=self.api_url_details,
420+
json=EMPTY_EXTRA_DETAILS_PAYLOAD,
421+
status=200,
422+
)
423+
)
299424

300425
metrics_url = "https://api.snapcraft.io/api/v1/snaps/metrics"
301426
responses.add(
@@ -311,3 +436,91 @@ def test_user_connected_on_not_own_snap(self):
311436

312437
assert response.status_code == 200
313438
self.assert_context("is_users_snap", False)
439+
440+
@responses.activate
441+
def test_extra_details(self):
442+
payload = {
443+
"snap-id": "toto_id",
444+
"name": "toto",
445+
"default-track": None,
446+
"snap": {
447+
"title": "Snap Title",
448+
"summary": "This is a summary",
449+
"description": "this is a description",
450+
"media": [],
451+
"license": "license",
452+
"publisher": {
453+
"display-name": "Toto",
454+
"username": "toto",
455+
"validation": True,
456+
},
457+
"categories": [{"name": "test"}],
458+
"trending": False,
459+
"unlisted": False,
460+
"links": {},
461+
},
462+
"channel-map": [
463+
{
464+
"channel": {
465+
"architecture": "amd64",
466+
"name": "stable",
467+
"risk": "stable",
468+
"track": "latest",
469+
"released-at": "2018-09-18T14:45:28.064633+00:00",
470+
},
471+
"created-at": "2018-09-18T14:45:28.064633+00:00",
472+
"version": "1.0",
473+
"confinement": "conf",
474+
"download": {"size": 100000},
475+
}
476+
],
477+
}
478+
payload_extra_details = {
479+
"aliases": [
480+
{"name": "nu", "target": "nu"},
481+
{
482+
"name": "nu_plugin_stress_internals",
483+
"target": "nu-plugin-stress-internals",
484+
},
485+
{"name": "nu_plugin_gstat", "target": "nu-plugin-gstat"},
486+
{"name": "nu_plugin_formats", "target": "nu-plugin-formats"},
487+
{"name": "nu_plugin_polars", "target": "nu-plugin-polars"},
488+
],
489+
"package_name": "toto",
490+
}
491+
492+
responses.add(
493+
responses.Response(
494+
method="GET", url=self.api_url, json=payload, status=200
495+
)
496+
)
497+
responses.add(
498+
responses.Response(
499+
method="GET",
500+
url=self.api_url_details,
501+
json=payload_extra_details,
502+
status=200,
503+
)
504+
)
505+
metrics_url = "https://api.snapcraft.io/api/v1/snaps/metrics"
506+
responses.add(
507+
responses.Response(
508+
method="POST", url=metrics_url, json={}, status=200
509+
)
510+
)
511+
512+
response = self.client.get(self.endpoint_url)
513+
assert response.status_code == 200
514+
self.assert_context(
515+
"aliases",
516+
[
517+
["toto.nu", "nu"],
518+
[
519+
"toto.nu-plugin-stress-internals",
520+
"nu_plugin_stress_internals",
521+
],
522+
["toto.nu-plugin-gstat", "nu_plugin_gstat"],
523+
["toto.nu-plugin-formats", "nu_plugin_formats"],
524+
["toto.nu-plugin-polars", "nu_plugin_polars"],
525+
],
526+
)

webapp/store/snap_details_views.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
"links",
4040
]
4141

42+
FIELDS_EXTRA_DETAILS = [
43+
"aliases",
44+
]
45+
4246

4347
def snap_details_views(store):
4448
snap_regex = "[a-z0-9-]*[a-z][a-z0-9-]*"
@@ -208,6 +212,18 @@ def snap_details(snap_name):
208212
status_code = 200
209213

210214
context = _get_context_snap_details(snap_name)
215+
extra_details = device_gateway.get_snap_details(
216+
snap_name, fields=FIELDS_EXTRA_DETAILS
217+
)
218+
219+
if extra_details and extra_details["aliases"]:
220+
context["aliases"] = [
221+
[
222+
f"{extra_details['package_name']}.{alias_obj['target']}",
223+
alias_obj["name"],
224+
]
225+
for alias_obj in extra_details["aliases"]
226+
]
211227

212228
country_metric_name = "weekly_installed_base_by_country_percent"
213229
os_metric_name = "weekly_installed_base_by_operating_system_normalized"

0 commit comments

Comments
 (0)