Skip to content

Commit e734f86

Browse files
Refactor stale feature banner to be reusable (#5574)
* Refactor stale feature banner logic to be reusable * Fix tests * Type clarity and changes
1 parent 110957f commit e734f86

File tree

7 files changed

+1271
-490
lines changed

7 files changed

+1271
-490
lines changed

client-src/elements/chromedash-feature-page.ts

Lines changed: 27 additions & 253 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import {DETAILS_STYLES} from './chromedash-feature-detail';
1414
import './chromedash-feature-highlights.js';
1515
import {GateDict} from './chromedash-gate-chip.js';
1616
import {Process, ProgressItem} from './chromedash-gate-column.js';
17-
import {showToastMessage, isVerifiedWithinGracePeriod} from './utils.js';
17+
import {
18+
showToastMessage,
19+
getFeatureOutdatedBanner,
20+
findClosestShippingDate,
21+
closestShippingDateInfo,
22+
} from './utils.js';
1823
import {
1924
STAGE_TYPES_SHIPPING,
2025
STAGE_TYPES_ORIGIN_TRIAL,
@@ -116,14 +121,14 @@ export class ChromedashFeaturePage extends LitElement {
116121
@state()
117122
loading = true;
118123
@state()
119-
isUpcoming = false;
120-
@state()
121-
hasShipped = false;
122-
@state()
123124
currentDate: number = Date.now();
124125
@state()
125-
// The closest milestone shipping date as an ISO string.
126-
closestShippingDate: string = '';
126+
shippingInfo: closestShippingDateInfo = {
127+
// The closest milestone shipping date as an ISO string.
128+
closestShippingDate: '',
129+
hasShipped: false,
130+
isUpcoming: false,
131+
};
127132

128133
connectedCallback() {
129134
super.connectedCallback();
@@ -134,155 +139,6 @@ export class ChromedashFeaturePage extends LitElement {
134139
return this.feature && Object.keys(this.feature).length !== 0;
135140
}
136141

137-
async fetchClosestShippingDate(milestone: number): Promise<string> {
138-
if (milestone === 0) {
139-
return '';
140-
}
141-
try {
142-
const newMilestonesInfo = await window.csClient.getSpecifiedChannels(
143-
milestone,
144-
milestone
145-
);
146-
return newMilestonesInfo[milestone]?.final_beta;
147-
} catch {
148-
showToastMessage(
149-
'Some errors occurred. Please refresh the page or try again later.'
150-
);
151-
return '';
152-
}
153-
}
154-
155-
/**
156-
* Determine if this feature is upcoming - scheduled to ship
157-
* within two milestones, then find the closest shipping date
158-
* for that upcoming milestone or an already shipped milestone.*/
159-
async findClosestShippingDate(channels, stages: Array<StageDict>) {
160-
const latestStableVersion = channels['stable']?.version;
161-
if (!latestStableVersion || !stages) {
162-
return;
163-
}
164-
165-
const shippingTypeMilestones = new Set<number | undefined>();
166-
const otTypeMilestones = new Set<number | undefined>();
167-
for (const stage of stages) {
168-
if (STAGE_TYPES_SHIPPING.has(stage.stage_type)) {
169-
shippingTypeMilestones.add(stage.desktop_first);
170-
shippingTypeMilestones.add(stage.android_first);
171-
shippingTypeMilestones.add(stage.ios_first);
172-
shippingTypeMilestones.add(stage.webview_first);
173-
}
174-
}
175-
for (const stage of stages) {
176-
if (STAGE_TYPES_ORIGIN_TRIAL.has(stage.stage_type)) {
177-
otTypeMilestones.add(stage.desktop_first);
178-
otTypeMilestones.add(stage.android_first);
179-
otTypeMilestones.add(stage.ios_first);
180-
otTypeMilestones.add(stage.webview_first);
181-
}
182-
}
183-
184-
const upcomingMilestonesTarget = new Set<number | undefined>([
185-
...shippingTypeMilestones,
186-
...otTypeMilestones,
187-
]);
188-
// Check if this feature is shipped within two milestones.
189-
let foundMilestone = 0;
190-
if (upcomingMilestonesTarget.has(latestStableVersion + 1)) {
191-
foundMilestone = latestStableVersion + 1;
192-
this.isUpcoming = true;
193-
} else if (upcomingMilestonesTarget.has(latestStableVersion + 2)) {
194-
foundMilestone = latestStableVersion + 2;
195-
this.isUpcoming = true;
196-
}
197-
198-
if (this.isUpcoming) {
199-
Object.keys(channels).forEach(key => {
200-
if (channels[key].version === foundMilestone) {
201-
this.closestShippingDate = channels[key].final_beta;
202-
}
203-
});
204-
} else {
205-
const shippedMilestonesTarget = shippingTypeMilestones;
206-
// If not upcoming, find the closest milestone that has shipped.
207-
let latestMilestone = 0;
208-
for (const ms of shippedMilestonesTarget) {
209-
if (ms && ms <= latestStableVersion) {
210-
latestMilestone = Math.max(latestMilestone, ms);
211-
}
212-
}
213-
214-
if (latestMilestone === latestStableVersion) {
215-
this.closestShippingDate = channels['stable']?.final_beta;
216-
this.hasShipped = true;
217-
} else {
218-
this.closestShippingDate =
219-
await this.fetchClosestShippingDate(latestMilestone);
220-
this.hasShipped = true;
221-
}
222-
}
223-
}
224-
225-
/**
226-
* Determine if it should show warnings to a feature author, if
227-
* a shipped feature is outdated, and it has edit access.*/
228-
isShippedFeatureOutdatedForAuthor() {
229-
return this.userCanEdit() && this.isShippedFeatureOutdated();
230-
}
231-
232-
/**
233-
* Determine if it should show warnings to all readers, if
234-
* a shipped feature is outdated, and last update was > 2 months.*/
235-
isShippedFeatureOutdatedForAll() {
236-
if (!this.isShippedFeatureOutdated()) {
237-
return false;
238-
}
239-
240-
// Represent two months grace period.
241-
const nineWeekPeriod = 9 * 7 * 24 * 60 * 60 * 1000;
242-
const isVerified = isVerifiedWithinGracePeriod(
243-
this.feature.accurate_as_of,
244-
this.currentDate,
245-
nineWeekPeriod
246-
);
247-
return !isVerified;
248-
}
249-
250-
/**
251-
* A feature is outdated if it has shipped, and its
252-
* accurate_as_of is before its latest shipping date before today.*/
253-
isShippedFeatureOutdated(): boolean {
254-
// Check if a feature has shipped.
255-
if (!this.hasShipped) {
256-
return false;
257-
}
258-
259-
// If accurate_as_of is missing from a shipped feature, it is likely
260-
// an old feature. Treat it as not oudated.
261-
if (!this.feature.accurate_as_of) {
262-
return false;
263-
}
264-
265-
return (
266-
Date.parse(this.feature.accurate_as_of) <
267-
Date.parse(this.closestShippingDate)
268-
);
269-
}
270-
271-
/**
272-
* A feature is outdated if it is scheduled to ship in the next 2 milestones,
273-
* and its accurate_as_of date is at least 4 weeks ago.*/
274-
isUpcomingFeatureOutdated(): boolean {
275-
if (!this.isUpcoming) {
276-
return false;
277-
}
278-
279-
const isVerified = isVerifiedWithinGracePeriod(
280-
this.feature.accurate_as_of,
281-
this.currentDate
282-
);
283-
return !isVerified;
284-
}
285-
286142
fetchData() {
287143
this.loading = true;
288144
Promise.all([
@@ -295,7 +151,7 @@ export class ChromedashFeaturePage extends LitElement {
295151
window.csClient.getChannels(),
296152
])
297153
.then(
298-
([
154+
async ([
299155
feature,
300156
gatesRes,
301157
commentRes,
@@ -315,7 +171,11 @@ export class ChromedashFeaturePage extends LitElement {
315171
if (this.feature.name) {
316172
document.title = `${this.feature.name} - ${this.appTitle}`;
317173
}
318-
this.findClosestShippingDate(channels, feature.stages);
174+
this.shippingInfo = await findClosestShippingDate(
175+
channels,
176+
feature.stages
177+
);
178+
319179
this.loading = false;
320180
}
321181
)
@@ -632,103 +492,17 @@ export class ChromedashFeaturePage extends LitElement {
632492
</div>
633493
`);
634494
}
635-
if (this.isUpcomingFeatureOutdated()) {
636-
if (this.userCanEdit()) {
637-
warnings.push(html`
638-
<div class="warning layout horizontal center">
639-
<span class="tooltip" id="outdated-icon" title="Feature outdated ">
640-
<sl-icon name="exclamation-circle-fill" data-tooltip></sl-icon>
641-
</span>
642-
<span>
643-
Your feature hasn't been verified as accurate since&nbsp;
644-
<sl-relative-time
645-
date=${this.feature.accurate_as_of ?? ''}
646-
></sl-relative-time
647-
>, but it is scheduled to ship&nbsp;
648-
<sl-relative-time
649-
date=${this.closestShippingDate}
650-
></sl-relative-time
651-
>. Please
652-
<a href="/guide/verify_accuracy/${this.featureId}"
653-
>verify that your feature is accurate</a
654-
>.
655-
</span>
656-
</div>
657-
`);
658-
} else {
659-
warnings.push(html`
660-
<div class="warning layout horizontal center">
661-
<span class="tooltip" id="outdated-icon" title="Feature outdated ">
662-
<sl-icon name="exclamation-circle-fill" data-tooltip></sl-icon>
663-
</span>
664-
<span>
665-
This feature hasn't been verified as accurate since&nbsp;
666-
<sl-relative-time
667-
date=${this.feature.accurate_as_of ?? ''}
668-
></sl-relative-time
669-
>, but it is scheduled to ship&nbsp;
670-
<sl-relative-time
671-
date=${this.closestShippingDate}
672-
></sl-relative-time
673-
>.
674-
</span>
675-
</div>
676-
`);
677-
}
495+
const userCanEdit = this.userCanEdit();
496+
const featureOutdatedBanner = getFeatureOutdatedBanner(
497+
this.feature,
498+
this.shippingInfo,
499+
this.currentDate,
500+
userCanEdit
501+
);
502+
if (featureOutdatedBanner) {
503+
warnings.push(featureOutdatedBanner);
678504
}
679505

680-
if (this.isShippedFeatureOutdated()) {
681-
if (this.isShippedFeatureOutdatedForAuthor()) {
682-
warnings.push(html`
683-
<div class="warning layout horizontal center">
684-
<span
685-
class="tooltip"
686-
id="shipped-outdated-author"
687-
title="Feature outdated "
688-
>
689-
<sl-icon name="exclamation-circle-fill" data-tooltip></sl-icon>
690-
</span>
691-
<span>
692-
Your feature hasn't been verified as accurate since&nbsp;
693-
<sl-relative-time
694-
date=${this.feature.accurate_as_of ?? ''}
695-
></sl-relative-time
696-
>, but it claims to have shipped&nbsp;
697-
<sl-relative-time
698-
date=${this.closestShippingDate}
699-
></sl-relative-time
700-
>. Please
701-
<a href="/guide/verify_accuracy/${this.featureId}"
702-
>verify that your feature is accurate</a
703-
>.
704-
</span>
705-
</div>
706-
`);
707-
} else if (this.isShippedFeatureOutdatedForAll()) {
708-
warnings.push(html`
709-
<div class="warning layout horizontal center">
710-
<span
711-
class="tooltip"
712-
id="shipped-outdated-all"
713-
title="Feature outdated "
714-
>
715-
<sl-icon name="exclamation-circle-fill" data-tooltip></sl-icon>
716-
</span>
717-
<span>
718-
This feature hasn't been verified as accurate since&nbsp;
719-
<sl-relative-time
720-
date=${this.feature.accurate_as_of ?? ''}
721-
></sl-relative-time
722-
>, but it claims to have shipped&nbsp;
723-
<sl-relative-time
724-
date=${this.closestShippingDate}
725-
></sl-relative-time
726-
>.
727-
</span>
728-
</div>
729-
`);
730-
}
731-
}
732506
return warnings;
733507
}
734508

0 commit comments

Comments
 (0)