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:
Родитель
fcd6de20eb
Коммит
bbfa6c8d0d
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче