Highlight outdated feature entries on roadmap (#4472)

* Add outdated logic

* Explain feature oudated icons

* Address comments

* UI tests

* Another fix

* lint
This commit is contained in:
Kyle Ju 2024-10-23 17:09:54 -07:00 коммит произвёл GitHub
Родитель fcd6de20eb
Коммит bbfa6c8d0d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 277 добавлений и 2 удалений

Просмотреть файл

@ -540,6 +540,7 @@ def feature_entry_to_json_basic(fe: FeatureEntry,
},
'created': {'by': fe.creator_email, 'when': _date_to_str(fe.created)},
'updated': {'by': fe.updater_email, 'when': _date_to_str(fe.updated)},
'accurate_as_of': _date_to_str(fe.accurate_as_of),
'standards': {
'spec': fe.spec_link,
'maturity': {

Просмотреть файл

@ -121,6 +121,7 @@ class FeatureConvertersTest(testing_config.CustomTestCase):
'by': 'updater@example.com',
'when': expected_date
},
'accurate_as_of': expected_date,
'standards': {
'spec': 'https://example.com/spec',
'maturity': {
@ -206,6 +207,7 @@ class FeatureConvertersTest(testing_config.CustomTestCase):
'by': 'updater@example.com',
'when': expected_date
},
'accurate_as_of': expected_date,
'standards': {
'spec': 'https://example.com/spec',
'maturity': {

Просмотреть файл

@ -1,6 +1,6 @@
import {SlPopup} from '@shoelace-style/shoelace';
import {LitElement, html, nothing} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {customElement, property, state} from 'lit/decorators.js';
import {createRef, ref} from 'lit/directives/ref.js';
import {ROADMAP_MILESTONE_CARD_CSS} from '../css/elements/chromedash-roadmap-milestone-card-css.js';
import {Channels, ReleaseInfo} from '../js-src/cs-client.js';
@ -20,7 +20,7 @@ export interface TemplateContent {
}
@customElement('chromedash-roadmap-milestone-card')
class ChromedashRoadmapMilestoneCard extends LitElement {
export class ChromedashRoadmapMilestoneCard extends LitElement {
infoPopupRef = createRef<SlPopup>();
static styles = ROADMAP_MILESTONE_CARD_CSS;
@ -37,6 +37,10 @@ class ChromedashRoadmapMilestoneCard extends LitElement {
showDates = false;
@property({type: Boolean})
signedIn = false;
@property({attribute: false})
stableMilestone!: number;
@state()
currentDate: number = Date.now();
/**
* Returns the number of days between a and b.
@ -234,6 +238,42 @@ class ChromedashRoadmapMilestoneCard extends LitElement {
`;
}
/**
* A feature is outdated if it is scheduled to ship in the next 2 milestones,
* and its accurate_as_of date is at least 4 weeks ago.
*
* @param accurateAsOf The accurate_as_of date as an ISO string.
* @param liveChromeVersion The Chrome milestone when a feature is live.
*/
_isFeatureOutdated(
accurateAsOf: string | undefined,
liveChromeVersion: number | undefined
): boolean {
if (this.stableMilestone === 0 || !liveChromeVersion) {
return false;
}
// If this feature is not shipping within two upcoming milestones, return false.
if (
!(
this.stableMilestone + 1 === liveChromeVersion ||
this.stableMilestone + 2 === liveChromeVersion
)
) {
return false;
}
if (!accurateAsOf) {
return true;
}
const accurateDate = Date.parse(accurateAsOf);
// 4-week period.
const gracePeriod = 4 * 7 * 24 * 60 * 60 * 1000;
if (accurateDate + gracePeriod < this.currentDate) {
return true;
}
return false;
}
_cardFeatureItemTemplate(f, shippingType) {
return html`
<li
@ -249,6 +289,17 @@ class ChromedashRoadmapMilestoneCard extends LitElement {
${f.name}
</a>
<span class="icon_row">
${this._isFeatureOutdated(f.accurate_as_of, this.channel?.version)
? html`
<span
class="tooltip"
id="outdated-icon"
title="Feature outdated - last checked for overall accuracy more than four weeks ago"
>
<iron-icon icon="chromestatus:error" data-tooltip></iron-icon>
</span>
`
: nothing}
${ORIGIN_TRIAL.includes(shippingType)
? html`
<span class="tooltip" title="Origin Trial">

Просмотреть файл

@ -0,0 +1,216 @@
import {assert, fixture} from '@open-wc/testing';
import {html} from 'lit';
import sinon from 'sinon';
import {ReleaseInfo, Feature} from '../js-src/cs-client';
import {
ChromedashRoadmapMilestoneCard,
TemplateContent,
} from './chromedash-roadmap-milestone-card';
describe('chromedash-roadmap-milestone-card', () => {
const mockFeature = {
id: 134,
name: 'vmvvv',
summary: 'd',
unlisted: false,
enterprise_impact: 1,
breaking_change: false,
first_enterprise_notification_milestone: null,
blink_components: ['Blink>CaptureFromElement'],
resources: {
samples: [],
docs: [],
},
created: {
by: 'example@chromium.org',
when: '2024-08-28 21:51:34.223994',
},
updated: {
by: 'example@chromium.org',
when: '2024-10-21 23:17:53.647165',
},
accurate_as_of: '2024-08-28 21:51:34.223867',
standards: {
spec: null,
maturity: {
text: null,
short_text: 'Unknown status',
val: 0,
},
},
browsers: {
chrome: {
bug: null,
blink_components: ['Blink>CaptureFromElement'],
devrel: ['devrel-chromestatus-all@google.com'],
owners: ['example@chromium.org'],
origintrial: false,
intervention: false,
prefixed: null,
flag: false,
status: {
text: 'No active development',
val: 1,
},
},
ff: {
view: {
url: null,
notes: null,
text: 'No signal',
val: 5,
},
},
safari: {
view: {
url: null,
notes: null,
text: 'No signal',
val: 5,
},
},
webdev: {
view: {
text: 'No signals',
val: 4,
url: null,
notes: null,
},
},
other: {
view: {
notes: null,
},
},
},
is_released: false,
milestone: null,
};
const stableMilestone: number = 107;
const templateContent: TemplateContent = {
channelLabel: 'Stable',
h1Class: '',
dateText: 'was',
featureHeader: 'Features in this release',
};
const channel: ReleaseInfo = {
version: 108,
final_beta: '2020-02 - 13T00:00:00',
features: {} as Feature,
};
channel.features['Origin trial'] = [mockFeature];
const testDate: number = new Date('2024-10-23').getTime();
it('can be added to the page', async () => {
const el: ChromedashRoadmapMilestoneCard =
await fixture<ChromedashRoadmapMilestoneCard>(
html`<chromedash-roadmap-milestone-card
.templateContent=${templateContent}
.channel=${channel}
.stableMilestone=${stableMilestone}
></chromedash-roadmap-milestone-card>`
);
await el.updateComplete;
assert.exists(el);
assert.instanceOf(el, ChromedashRoadmapMilestoneCard);
});
it('_isFeatureOutdated tests', async () => {
const el: ChromedashRoadmapMilestoneCard =
await fixture<ChromedashRoadmapMilestoneCard>(
html`<chromedash-roadmap-milestone-card
.templateContent=${templateContent}
.channel=${channel}
.stableMilestone=${stableMilestone}
></chromedash-roadmap-milestone-card>`
);
el.currentDate = testDate;
el.stableMilestone = 10;
assert.isFalse(el._isFeatureOutdated(undefined, 20));
// False when a feature is not shipped within two milestones.
assert.isFalse(el._isFeatureOutdated('2023-10-23 07:07:05.264715', 20));
// False when a feature is accurate within four weeks.
assert.isFalse(el._isFeatureOutdated('2024-10-23 07:07:05.264715', 11));
assert.isFalse(el._isFeatureOutdated('2024-09-28 07:07:05.264715', 11));
assert.isTrue(el._isFeatureOutdated('2024-08-23 07:07:05.264715', 11));
assert.isTrue(el._isFeatureOutdated('2023-10-23 07:07:05.264715', 11));
assert.isTrue(el._isFeatureOutdated('2023-10-23 07:07:05.264715', 12));
});
it('renders the feature oudated warning icon', async () => {
channel.features['Origin trial'][0]['accurate_as_of'] =
'2024-08-28 21:51:34.223867';
channel.version = stableMilestone + 1;
const el: ChromedashRoadmapMilestoneCard =
await fixture<ChromedashRoadmapMilestoneCard>(
html`<chromedash-roadmap-milestone-card
.templateContent=${templateContent}
.channel=${channel}
.stableMilestone=${stableMilestone}
></chromedash-roadmap-milestone-card>`
);
el.currentDate = testDate;
const oudated = el.shadowRoot!.querySelector('#outdated-icon');
assert.exists(oudated);
});
it('not renders the oudated icon when shipped in 3 milestones', async () => {
channel.features['Origin trial'][0]['accurate_as_of'] =
'2024-08-28 21:51:34.223867';
channel.version = stableMilestone + 3;
const el: ChromedashRoadmapMilestoneCard =
await fixture<ChromedashRoadmapMilestoneCard>(
html`<chromedash-roadmap-milestone-card
.templateContent=${templateContent}
.channel=${channel}
.stableMilestone=${stableMilestone}
></chromedash-roadmap-milestone-card>`
);
el.currentDate = testDate;
const oudated = el.shadowRoot!.querySelector('#outdated-icon');
assert.isNull(oudated);
});
it('not renders the oudated icon when accurate_as_of is null', async () => {
channel.features['Origin trial'][0]['accurate_as_of'] = null;
channel.version = stableMilestone + 3;
const el: ChromedashRoadmapMilestoneCard =
await fixture<ChromedashRoadmapMilestoneCard>(
html`<chromedash-roadmap-milestone-card
.templateContent=${templateContent}
.channel=${channel}
.stableMilestone=${stableMilestone}
></chromedash-roadmap-milestone-card>`
);
el.currentDate = testDate;
const oudated = el.shadowRoot!.querySelector('#outdated-icon');
assert.isNull(oudated);
});
it('not renders the oudated icon when accurate_as_of is recent', async () => {
channel.features['Origin trial'][0]['accurate_as_of'] =
'2024-09-28 21:51:34.223867';
channel.version = stableMilestone + 3;
const el: ChromedashRoadmapMilestoneCard =
await fixture<ChromedashRoadmapMilestoneCard>(
html`<chromedash-roadmap-milestone-card
.templateContent=${templateContent}
.channel=${channel}
.stableMilestone=${stableMilestone}
></chromedash-roadmap-milestone-card>`
);
el.currentDate = testDate;
const oudated = el.shadowRoot!.querySelector('#outdated-icon');
assert.isNull(oudated);
});
});

Просмотреть файл

@ -335,6 +335,7 @@ export class ChromedashRoadmap extends LitElement {
.starredFeatures=${this.starredFeatures}
.highlightFeature=${this.highlightFeature}
?signedin=${this.signedIn}
.stableMilestone=${this.channels?.['stable']?.version}
@star-toggle-event=${this.handleStarToggle}
@highlight-feature-event=${this.handleHighlightEvent}
>

Просмотреть файл

@ -280,6 +280,10 @@ const template = html`
d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
></path>
</g>
<g id="error">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></path>
</g>
</defs>
</svg>
</iron-iconset-svg>