Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Highlight outdated feature entries on roadmap #4472

Merged
merged 6 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down
2 changes: 2 additions & 0 deletions api/converters_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def test_feature_entry_to_json_basic__normal(self):
'by': '[email protected]',
'when': expected_date
},
'accurate_as_of': expected_date,
'standards': {
'spec': 'https://example.com/spec',
'maturity': {
Expand Down Expand Up @@ -206,6 +207,7 @@ def test_feature_entry_to_json_basic__feature_release(self):
'by': '[email protected]',
'when': expected_date
},
'accurate_as_of': expected_date,
'standards': {
'spec': 'https://example.com/spec',
'maturity': {
Expand Down
55 changes: 53 additions & 2 deletions client-src/elements/chromedash-roadmap-milestone-card.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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">
Expand Down
216 changes: 216 additions & 0 deletions client-src/elements/chromedash-roadmap-milestone-card_test.ts
Original file line number Diff line number Diff line change
@@ -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: '[email protected]',
when: '2024-08-28 21:51:34.223994',
},
updated: {
by: '[email protected]',
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: ['[email protected]'],
owners: ['[email protected]'],
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 () => {
KyleJu marked this conversation as resolved.
Show resolved Hide resolved
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);
});
});
1 change: 1 addition & 0 deletions client-src/elements/chromedash-roadmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
>
Expand Down
4 changes: 4 additions & 0 deletions client-src/elements/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down