Skip to content

Commit b541d3b

Browse files
edisileCopilot
andauthored
Old snaps warning in the channel map (#5598)
* feature: warning for 1 and 2+ years old snaps * feature: show old snap warning in channel map * fix: align warning behavior between js and py * feature: add basic tests for channel map * Update templates/store/snap-details/_channel_map.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update static/js/public/snap-details/channelMap.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: cleanup after copilot suggestions --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 5091d81 commit b541d3b

5 files changed

Lines changed: 318 additions & 11 deletions

File tree

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { describe } from "vitest";
2+
import { getByText } from "@testing-library/dom";
3+
import userEvent from "@testing-library/user-event";
4+
import "@testing-library/jest-dom";
5+
6+
import channelMap from "../channelMap";
7+
// @ts-expect-error importing the HTML template as a string
8+
import CHANNEL_MAP_HTML from "../../../../../templates/store/snap-details/_channel_map.html?raw";
9+
10+
const OPEN_CHANNEL_MAP_BTN = `
11+
<button
12+
id="open-channel-map"
13+
class="p-button"
14+
data-js="open-channel-map"
15+
data-controls="channel-map-versions"
16+
aria-controls="channel-map-versions">
17+
Open channel map
18+
</button>`;
19+
20+
function openChannelMap() {
21+
document.querySelector<HTMLButtonElement>("#open-channel-map")!.click();
22+
}
23+
24+
const now = new Date();
25+
const today = new Intl.DateTimeFormat("en-GB", {
26+
day: "numeric",
27+
month: "long",
28+
year: "numeric",
29+
}).format(now);
30+
const oneYearAgo = today.replace(
31+
now.getFullYear().toString(),
32+
(now.getFullYear() - 1).toString(),
33+
);
34+
const twoYearsAgo = today.replace(
35+
now.getFullYear().toString(),
36+
(now.getFullYear() - 2).toString(),
37+
);
38+
39+
const selector = "#js-channel-map";
40+
const packageName = "Test package";
41+
const snapId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
42+
const channelMapData = {
43+
amd64: {
44+
latest: [
45+
{
46+
channel: "stable",
47+
confinement: "strict",
48+
"released-at": oneYearAgo,
49+
revision: "1",
50+
risk: "stable",
51+
size: 100,
52+
version: "1.0-amd64",
53+
track: "latest",
54+
},
55+
{
56+
channel: "candidate",
57+
confinement: "strict",
58+
"released-at": oneYearAgo,
59+
revision: "2",
60+
risk: "candidate",
61+
size: 100,
62+
version: "1.1-amd64",
63+
track: "latest",
64+
},
65+
{
66+
channel: "beta",
67+
confinement: "strict",
68+
"released-at": oneYearAgo,
69+
revision: "2",
70+
risk: "beta",
71+
size: 100,
72+
version: "1.1-amd64",
73+
track: "latest",
74+
},
75+
{
76+
channel: "edge",
77+
confinement: "strict",
78+
"released-at": oneYearAgo,
79+
revision: "3",
80+
risk: "edge",
81+
size: 100,
82+
version: "1.2-amd64",
83+
track: "latest",
84+
},
85+
{
86+
channel: "old",
87+
confinement: "strict",
88+
"released-at": twoYearsAgo,
89+
revision: "1",
90+
risk: "stable",
91+
size: 100,
92+
version: "0.1-amd64",
93+
track: "latest",
94+
},
95+
],
96+
},
97+
arm64: {
98+
latest: [
99+
{
100+
channel: "stable",
101+
confinement: "strict",
102+
"released-at": oneYearAgo,
103+
revision: "4",
104+
risk: "stable",
105+
size: 100,
106+
version: "1.0-arm64",
107+
track: "latest",
108+
},
109+
{
110+
channel: "candidate",
111+
confinement: "strict",
112+
"released-at": oneYearAgo,
113+
revision: "2",
114+
risk: "candidate",
115+
size: 100,
116+
version: "1.1-arm64",
117+
track: "latest",
118+
},
119+
{
120+
channel: "beta",
121+
confinement: "strict",
122+
"released-at": oneYearAgo,
123+
revision: "5",
124+
risk: "beta",
125+
size: 100,
126+
version: "1.1-arm64",
127+
track: "latest",
128+
},
129+
{
130+
channel: "edge",
131+
confinement: "strict",
132+
"released-at": oneYearAgo,
133+
revision: "6",
134+
risk: "edge",
135+
size: 100,
136+
version: "1.2-arm64",
137+
track: "latest",
138+
},
139+
{
140+
channel: "old",
141+
confinement: "strict",
142+
"released-at": twoYearsAgo,
143+
revision: "1",
144+
risk: "stable",
145+
size: 100,
146+
version: "0.1-arm64",
147+
track: "latest",
148+
},
149+
],
150+
},
151+
};
152+
const defaultTrack = "latest";
153+
const hasSboms = false;
154+
155+
describe("Channel map popup", () => {
156+
beforeEach(() => {
157+
document.body.innerHTML = `
158+
${OPEN_CHANNEL_MAP_BTN}
159+
${CHANNEL_MAP_HTML}`;
160+
161+
channelMap(
162+
selector,
163+
packageName,
164+
snapId,
165+
channelMapData,
166+
defaultTrack,
167+
hasSboms,
168+
);
169+
170+
openChannelMap();
171+
});
172+
173+
afterEach(() => {
174+
document.body.innerHTML = "";
175+
});
176+
177+
test("renders correctly", () => {
178+
const tbody = document.querySelector(`tbody`)!;
179+
expect(tbody.childElementCount).toEqual(
180+
channelMapData["amd64"].latest.length,
181+
);
182+
});
183+
184+
test("architecture select updates table", async () => {
185+
const user = userEvent.setup();
186+
187+
const select = document.querySelector<HTMLSelectElement>(
188+
`[data-js="arch-select"]`,
189+
)!;
190+
await user.selectOptions(select, "arm64");
191+
192+
const tbody = document.querySelector(`tbody`)!;
193+
expect(getByText(tbody!, `1.0-arm64`)).not.toBeNull();
194+
});
195+
196+
test("clicking on install shows instructions", async () => {
197+
const user = userEvent.setup();
198+
const btn = document.querySelector(
199+
`[data-js="slide-install-instructions"]`,
200+
)!;
201+
await user.click(btn);
202+
203+
expect(document.querySelector(`.p-channel-map__slides`)).toHaveClass(
204+
"show-right",
205+
);
206+
});
207+
208+
test("clicking on install shows instructions for selected channel", async () => {
209+
const user = userEvent.setup();
210+
const btn = document.querySelector(
211+
`[data-js="slide-install-instructions"][data-channel="latest/beta"]`,
212+
)!;
213+
await user.click(btn);
214+
215+
expect(getByText(document.body, /--beta/)).not.toBeNull();
216+
});
217+
218+
test("edge channel has warning", async () => {
219+
const user = userEvent.setup();
220+
const btn = document.querySelector(
221+
`[data-js="slide-install-instructions"][data-channel="latest/edge"]`,
222+
)!;
223+
await user.click(btn);
224+
225+
expect(getByText(document.body, /edge channel can break/)).not.toBeNull();
226+
});
227+
228+
test("beta channel has warning", async () => {
229+
const user = userEvent.setup();
230+
const btn = document.querySelector(
231+
`[data-js="slide-install-instructions"][data-channel="latest/beta"]`,
232+
)!;
233+
await user.click(btn);
234+
235+
expect(
236+
getByText(document.body, /beta channel may have unfinished parts/),
237+
).not.toBeNull();
238+
});
239+
240+
test("candidate channel has warning", async () => {
241+
const user = userEvent.setup();
242+
const btn = document.querySelector(
243+
`[data-js="slide-install-instructions"][data-channel="latest/candidate"]`,
244+
)!;
245+
await user.click(btn);
246+
247+
expect(
248+
getByText(
249+
document.body,
250+
/candidate channel need additional real world experimentation/,
251+
),
252+
).not.toBeNull();
253+
});
254+
255+
test("old snaps have warning", async () => {
256+
const user = userEvent.setup();
257+
const btn = document.querySelector(
258+
`[data-js="slide-install-instructions"][data-channel="latest/old"]`,
259+
)!;
260+
await user.click(btn);
261+
262+
expect(getByText(document.body, /might be unmaintained/)).not.toBeNull();
263+
});
264+
});

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ function isSnapElement(target: HTMLElement): target is SnapElement {
1515
);
1616
}
1717

18+
function getDateNYearsAgo(n: number): Date {
19+
const date = new Date();
20+
date.setFullYear(date.getFullYear() - n);
21+
return date;
22+
}
23+
1824
interface SlideInstallInstructionsElement extends HTMLElement {
1925
dataset: {
2026
channel: string;
@@ -476,17 +482,33 @@ class ChannelMap {
476482
"Snaps on the candidate channel need additional real world experimentation before the move to stable.";
477483
}
478484

479-
const template = this.INSTALL_TEMPLATE.split("${channel}")
480-
.join(channel)
481-
.split("${paramString}")
482-
.join(paramString);
485+
const template = this.INSTALL_TEMPLATE.replaceAll(
486+
"${channel}",
487+
channel,
488+
).replaceAll("${paramString}", paramString);
489+
490+
const [track, risk] = channel.split("/");
491+
const release = this.channelMapData[this.arch!][track].find(
492+
(c) => c.risk === risk,
493+
);
494+
if (release) {
495+
const releaseDate = new Date(release["released-at"]);
496+
497+
if (releaseDate < getDateNYearsAgo(2)) {
498+
warning = `This channel hasn't been updated in a while. It might be unmaintained and have stability or security issues.`;
499+
}
500+
}
483501

484502
const newDiv = document.createElement("div");
485503
newDiv.innerHTML = template;
486504

487-
const warningEl = newDiv.querySelector("[data-js='warning']");
488-
if (warningEl) {
489-
warningEl.innerHTML = warning;
505+
if (warning) {
506+
const warningEl = newDiv.querySelector("[data-js='warning']");
507+
if (warningEl) {
508+
warningEl.innerHTML = warning;
509+
}
510+
} else {
511+
newDiv.querySelector(".p-notification--caution")?.remove();
490512
}
491513

492514
const holder = document.querySelector(

templates/store/snap-details/_channel_map.html

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
<div class="p-strip--light is-shallow">
44
<div class="u-fixed-width">
55
<p class="p-heading--4">Install {{ default_track }}/{{ lowest_risk_available }} of {{ snap_title }}</p>
6+
{% if is_last_updated_old %}
7+
<div class="p-notification--caution is-borderless is-inline" style="background-color: transparent;">
8+
<div class="p-notification__content">
9+
<p class="p-notification__message">
10+
This channel hasn't been updated in a while. It might be unmaintained and have stability or security issues.
11+
</p>
12+
</div>
13+
</div>
14+
{% endif %}
15+
616
<p>Ubuntu 16.04 or later?</p>
717
<button
818
data-snap="{{ package_name }}
@@ -144,13 +154,18 @@
144154
</div>
145155
</div>
146156
</div>
157+
147158
<script type="text/template" id="install-window-template" data-js="install-window" nonce="{{ CSP_NONCE }}">
148159
<div class="u-fixed-width">
149160
<a href="#" data-js="slide-all-versions">&lsaquo; All versions</a>
150161
</div>
151162
<div class="u-fixed-width">
152-
<p class="p-heading--4">Install ${channel} of {{ snap_title }}</p>
153-
<span data-js="warning"></span>
163+
<p class="p-heading--4">Install ${channel} of {{ snap_title }}</p>
164+
<div class="p-notification--caution is-borderless is-inline" style="background-color: transparent;">
165+
<div class="p-notification__content">
166+
<p class="p-notification__message" data-js="warning"></p>
167+
</div>
168+
</div>
154169
<p>Ubuntu 16.04 or later?</p>
155170
<button data-snap="{{ package_name }}?channel=${channel}" class="p-view-store-button" data-js="open-desktop">View in Desktop store</button>
156171
<p class="p-form-help-text">Make sure <a href="/docs/installing-snapd">snap support</a> is enabled in your Desktop store.</p>
@@ -168,6 +183,7 @@
168183
</p>
169184
</div>
170185
</script>
186+
171187
<script type="text/template" id="channel-map-row-template" data-js="channel-map-row" nonce="{{ CSP_NONCE }}">
172188
<tr class="${rowClass}" data-js="slide-install-instructions" data-channel="${row[0]}/${row[1]}" data-confinement="${row[4]}">
173189
<td>${row[0]}/${row[1]}</td>
@@ -176,6 +192,7 @@
176192
<td class="u-align--center"><a href="#" class="p-channel-map__version-table-install">Install &rsaquo;</a></td>
177193
</tr>
178194
</script>
195+
179196
<script type="text/template" id="channel-map-security-table-row-template" data-js="channel-map-security-table-row" nonce="{{ CSP_NONCE }}">
180197
<tr>
181198
<td>${row[0]}/${row[1]}</td>
@@ -184,4 +201,5 @@
184201
<td>${row[4]}</td>
185202
</tr>
186203
</script>
204+
187205
<div class="p-channel-map-overlay" data-js="close-channel-map" style="display: none"></div>

templates/store/snap-details/_details.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ <h3 class="p-muted-heading">Last updated</h3>
1616
<li>{{ updates[1]["released-at-display"] }} - <small>{{ updates[1]["track"] }}/{{ updates[1]["risk"] }}</small></li>
1717
{% endif %}
1818
</ul>
19-
{% if old_snap_info %}
19+
{% if is_snap_old %}
2020
<div class="p-notification--caution is-borderless">
2121
<div class="p-notification__content">
2222
<p class="p-notification__message">

0 commit comments

Comments
 (0)