Skip to content

Commit 14d9f3c

Browse files
KyleJujcscottiii
andauthored
Show missing features specifically for each data point (#1242)
* Implement Missing One Implementation feature list UI * Update frontend/src/static/js/components/test/webstatus-stats-missing-one-impl-chart-panel.test.ts Co-authored-by: James C Scott III <[email protected]> --------- Co-authored-by: James C Scott III <[email protected]>
1 parent e18ab7c commit 14d9f3c

File tree

4 files changed

+180
-2
lines changed

4 files changed

+180
-2
lines changed

frontend/src/static/js/api/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ export type BaselineStatusMetricsPage =
148148
components['schemas']['BaselineStatusMetricsPage'];
149149
export type BaselineStatusMetric =
150150
components['schemas']['BaselineStatusMetric'];
151+
export type MissingOneImplFeaturesPage =
152+
components['schemas']['MissingOneImplFeaturesPage'];
153+
export type MissingOneImplFeaturesList =
154+
components['schemas']['MissingOneImplFeature'][];
151155

152156
// TODO. Remove once not behind UbP
153157
const temporaryFetchOptions: FetchOptions<unknown> = {

frontend/src/static/js/components/test/webstatus-stats-missing-one-impl-chart-panel.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {SinonStub, SinonStubbedInstance, stub} from 'sinon';
1919
import {WebstatusStatsMissingOneImplChartPanel} from '../webstatus-stats-missing-one-impl-chart-panel.js'; // Path to your component
2020
import {APIClient, BrowserReleaseFeatureMetric} from '../../api/client.js';
2121
import {WebstatusLineChartPanel} from '../webstatus-line-chart-panel.js';
22+
import {ChartSelectPointEvent} from '../webstatus-gchart.js';
2223

2324
import '../webstatus-stats-missing-one-impl-chart-panel.js';
2425

@@ -133,4 +134,49 @@ describe('WebstatusStatsMissingOneImplChartPanel', () => {
133134
);
134135
expect(options.hAxis?.viewWindow?.max).to.deep.equal(expectedEndDate);
135136
});
137+
138+
it('renders missing one implementation features footer', async () => {
139+
const chart = el.shadowRoot!.querySelector(
140+
'#missing-one-implementation-chart',
141+
)!;
142+
143+
const chartClickEvent: ChartSelectPointEvent = new CustomEvent(
144+
'point-selected',
145+
{
146+
detail: {label: 'Test Label', timestamp: new Date(), value: 123},
147+
bubbles: true,
148+
},
149+
);
150+
// Simulate point-selected event on the chart component
151+
chart.dispatchEvent(chartClickEvent);
152+
await el.updateComplete;
153+
154+
// Assert that the task and renderer are set (no need to wait for the event)
155+
expect(el._pointSelectedTask).to.exist;
156+
expect(el._renderCustomPointSelectedSuccess).to.exist;
157+
await el.updateComplete;
158+
159+
const header = el.shadowRoot!.querySelector(
160+
'#missing-one-implementation-list-header',
161+
);
162+
expect(header).to.exist;
163+
// Note: \n before chrome due to a complaint from lint in the html.
164+
expect(header!.textContent?.trim()).to.contain(
165+
'The missing feature IDs on 2024-08-20 for\n chrome',
166+
);
167+
168+
const table = el.shadowRoot!.querySelector('.missing-features-table');
169+
expect(table).to.exist;
170+
171+
const rows = table!
172+
.getElementsByTagName('tbody')[0]
173+
.getElementsByTagName('tr');
174+
expect(rows.length).to.equal(10, 'should have 10 rows');
175+
176+
const firstRowCells = rows[0].querySelectorAll('td');
177+
expect(firstRowCells[0].textContent?.trim()).to.equal(
178+
'css',
179+
'first row ID',
180+
);
181+
});
136182
});

frontend/src/static/js/components/webstatus-line-chart-panel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,7 @@ export abstract class WebstatusLineChartPanel extends LitElement {
759759
error: error => this._renderPointSelectFailure(error),
760760
initial: () => this._renderPointSelectInitial(),
761761
pending: () => this._renderPointSelectPending(),
762-
})};
762+
})}
763763
</div>
764764
`;
765765
}

frontend/src/static/js/components/webstatus-stats-missing-one-impl-chart-panel.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import {Task} from '@lit/task';
18-
import {TemplateResult, html, nothing} from 'lit';
18+
import {TemplateResult, html, nothing, css} from 'lit';
1919
import {
2020
FetchFunctionConfig,
2121
WebstatusLineChartPanel,
@@ -25,14 +25,36 @@ import {
2525
BrowsersParameter,
2626
BROWSER_ID_TO_COLOR,
2727
BROWSER_ID_TO_LABEL,
28+
MissingOneImplFeaturesList,
2829
} from '../api/client.js';
30+
import {ChartSelectPointEvent} from './webstatus-gchart.js';
2931
import {customElement, state} from 'lit/decorators.js';
3032

3133
@customElement('webstatus-stats-missing-one-impl-chart-panel')
3234
export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPanel {
3335
@state()
3436
supportedBrowsers: BrowsersParameter[] = ['chrome', 'firefox', 'safari'];
3537

38+
missingFeaturesList: MissingOneImplFeaturesList = [];
39+
selectedBrowser: string = '';
40+
selectedDate: string = '';
41+
42+
static get styles() {
43+
return [
44+
super.styles,
45+
css`
46+
#missing-one-implementation-datapoint-details-complete {
47+
display: block;
48+
}
49+
.missing-features-table {
50+
width: 100%;
51+
overflow-x: auto;
52+
white-space: nowrap;
53+
}
54+
`,
55+
];
56+
}
57+
3658
private _createFetchFunctionConfigs(
3759
browsers: BrowsersParameter[],
3860
startDate: Date,
@@ -89,6 +111,74 @@ export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPa
89111
};
90112
}
91113

114+
/**
115+
* Creates a task and a renderer for handling point-selected events.
116+
* Overrides createPointSelectedTask() in the parent class when an point is
117+
* selected on the chart.
118+
*
119+
* @param {ChartSelectPointEvent} _ The point-selected event.
120+
* @returns {{ task: Task | undefined; renderSuccess?: () => TemplateResult; }}
121+
*/
122+
createPointSelectedTask(_: ChartSelectPointEvent): {
123+
task: Task | undefined;
124+
renderSuccess?: () => TemplateResult;
125+
} {
126+
const task = new Task(this, {
127+
task: async () => {
128+
// TODO(https://github.com/GoogleChrome/webstatus.dev/issues/1181):
129+
// implement the adapter logic to retrieve feature IDs.
130+
const pageData = {
131+
data: [
132+
{
133+
feature_id: 'css',
134+
},
135+
{
136+
feature_id: 'html',
137+
},
138+
{
139+
feature_id: 'javascript',
140+
},
141+
{
142+
feature_id: 'bluetooth',
143+
},
144+
],
145+
metadata: {
146+
total: 4,
147+
},
148+
};
149+
for (let i = 0; i < 80; i++) {
150+
pageData.data.push({
151+
feature_id: 'item' + i,
152+
});
153+
}
154+
this.missingFeaturesList = pageData.data;
155+
// TODO:(kyleju) return these data from the API.
156+
this.selectedDate = '2024-08-20';
157+
this.selectedBrowser = 'chrome';
158+
return this.missingFeaturesList;
159+
},
160+
args: () => [],
161+
});
162+
return {task: task, renderSuccess: this.pointSelectedTaskRenderOnSuccess};
163+
}
164+
165+
/**
166+
* Renders the success state of the createPointSelectedTask above.
167+
* This method implements the _renderCustomPointSelectedSuccess
168+
* in the parent class.
169+
*
170+
* @returns {TemplateResult} The rendered content for the success state.
171+
*/
172+
pointSelectedTaskRenderOnSuccess(): TemplateResult {
173+
return html`
174+
<div slot="header" id="${this.getPanelID()}-list-header">
175+
The missing feature IDs on ${this.selectedDate} for
176+
${this.selectedBrowser}:
177+
</div>
178+
${this.renderMissingFeaturesTable()}
179+
`;
180+
}
181+
92182
getPanelID(): string {
93183
return 'missing-one-implementation';
94184
}
@@ -98,4 +188,42 @@ export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPa
98188
renderControls(): TemplateResult {
99189
return html`${nothing}`;
100190
}
191+
192+
renderMissingFeaturesTable(): TemplateResult {
193+
const numCols = Math.ceil(this.missingFeaturesList.length / 10);
194+
195+
// Create table body with `numCols` columns and 10 rows each.
196+
const bodyRows = [];
197+
for (let i = 0; i < 10; i++) {
198+
const cells = [];
199+
for (let j = 0; j < numCols; j++) {
200+
const featureIndex = j * 10 + i;
201+
if (featureIndex < this.missingFeaturesList.length) {
202+
const feature_id = this.missingFeaturesList[featureIndex].feature_id;
203+
cells.push(
204+
html` <td>
205+
<a href="/features/${feature_id}">${feature_id}</a>
206+
</td>`,
207+
);
208+
} else {
209+
// Empty cell.
210+
cells.push(html`<td></td>`);
211+
}
212+
}
213+
214+
bodyRows.push(
215+
html`<tr>
216+
${cells}
217+
</tr>`,
218+
);
219+
}
220+
221+
return html`
222+
<table class="missing-features-table">
223+
<tbody>
224+
${bodyRows}
225+
</tbody>
226+
</table>
227+
`;
228+
}
101229
}

0 commit comments

Comments
 (0)