Skip to content

Commit 0ae1eff

Browse files
DanielRyanSmithDanielRyanSmith
andauthored
Utilize overview table in "Missing One" panel (#1458)
* Separate data loading from table component * fix global saved search sorting * use TaskTracker status * remove unused variable * use taskTracker in overview-table * Use overview table for missing one table * remove unused variable * overview table uses TaskTracker * fix optional chaining --------- Co-authored-by: DanielRyanSmith <danielrsmith@google.com>
1 parent a938858 commit 0ae1eff

5 files changed

Lines changed: 89 additions & 77 deletions

File tree

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

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,8 @@ describe('WebstatusStatsMissingOneImplChartPanel', () => {
215215
`;
216216
expect(header).dom.to.equal(expectedHeader);
217217

218-
const table = el.shadowRoot!.querySelector('.missing-features-table');
218+
const table = el.shadowRoot!.querySelector('webstatus-overview-table');
219219
expect(table).to.exist;
220-
221-
const rows = table!
222-
.getElementsByTagName('tbody')[0]
223-
.getElementsByTagName('tr');
224-
expect(rows.length).to.equal(10, 'should have 10 rows');
225-
226-
const firstRowCells = rows[0].querySelectorAll('td');
227-
const textContent = firstRowCells[0].textContent
228-
?.split('\n')
229-
.map(word => word.trim())
230-
.filter(word => word.length > 0)
231-
.join(' ');
232-
expect(textContent).to.equal('grid TOP CSS', 'first row ID');
233220
});
234221

235222
it('renders empty features footer', async () => {

frontend/src/static/js/components/webstatus-overview-data-loader.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,13 @@ export class WebstatusOverviewDataLoader extends LitElement {
5353
searchKey: string,
5454
searchValue: string,
5555
): components['schemas']['Feature'][] {
56-
if (!this.taskTracker.data?.data) {
56+
if (!this.taskTracker.data) {
5757
return [];
5858
}
5959

6060
const features: components['schemas']['Feature'][] = [];
61-
for (const feature of this.taskTracker.data.data) {
61+
const data = this.taskTracker.data?.data || [];
62+
for (const feature of data) {
6263
if (searchKey === 'id' && feature?.feature_id === searchValue) {
6364
features.push(feature);
6465
break;
@@ -126,18 +127,27 @@ export class WebstatusOverviewDataLoader extends LitElement {
126127
}
127128

128129
if (
129-
this.taskTracker.status === TaskStatus.COMPLETE &&
130-
this.taskTracker.data
130+
this.taskTracker.data?.data &&
131+
this.taskTracker.status === TaskStatus.COMPLETE
131132
) {
132133
this.taskTracker.data.data =
133134
this.reorderByQueryTerms() || this.taskTracker.data?.data;
134135
}
135136

137+
const featureTaskTracker: TaskTracker<
138+
components['schemas']['Feature'][],
139+
ApiError
140+
> = {
141+
status: this.taskTracker.status,
142+
error: this.taskTracker.error,
143+
data: this.taskTracker.data?.data,
144+
};
145+
136146
return html`<webstatus-overview-table
137147
.columns=${columns}
138148
.headerCells=${headerCells}
139149
.location=${this.location}
140-
.taskTracker=${this.taskTracker}
150+
.taskTracker=${featureTaskTracker}
141151
></webstatus-overview-table>`;
142152
}
143153
}

frontend/src/static/js/components/webstatus-overview-table.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {TaskTracker} from '../utils/task-tracker.js';
3838
@customElement('webstatus-overview-table')
3939
export class WebstatusOverviewTable extends LitElement {
4040
@property({type: Object})
41-
taskTracker: TaskTracker<components['schemas']['FeaturePage'], ApiError> = {
41+
taskTracker: TaskTracker<components['schemas']['Feature'][], ApiError> = {
4242
status: TaskStatus.INITIAL, // Initial state
4343
error: undefined,
4444
data: undefined,
@@ -168,7 +168,7 @@ export class WebstatusOverviewTable extends LitElement {
168168
case TaskStatus.PENDING:
169169
return this.renderBodyWhenPending(columns);
170170
case TaskStatus.COMPLETE:
171-
return this.taskTracker.data?.data?.length === 0
171+
return this.taskTracker.data?.length === 0
172172
? this.renderBodyWhenNoResults(columns)
173173
: this.renderBodyWhenComplete(columns);
174174
case TaskStatus.ERROR:
@@ -178,9 +178,7 @@ export class WebstatusOverviewTable extends LitElement {
178178

179179
renderBodyWhenComplete(columns: ColumnKey[]): TemplateResult {
180180
return html`
181-
${this.taskTracker.data?.data?.map(f =>
182-
this.renderFeatureRow(f, columns),
183-
)}
181+
${this.taskTracker.data?.map(f => this.renderFeatureRow(f, columns))}
184182
`;
185183
}
186184

@@ -234,7 +232,7 @@ export class WebstatusOverviewTable extends LitElement {
234232
renderBodyWhenPending(columns: ColumnKey[]): TemplateResult {
235233
const DEFAULT_SKELETON_ROWS = 10;
236234
const skeleton_rows =
237-
this.taskTracker.data?.data?.length || DEFAULT_SKELETON_ROWS;
235+
this.taskTracker.data?.length || DEFAULT_SKELETON_ROWS;
238236
return html`
239237
${map(
240238
range(skeleton_rows),

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

Lines changed: 66 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {Task} from '@lit/task';
17+
import {Task, TaskStatus} from '@lit/task';
1818
import {TemplateResult, html, nothing, css} from 'lit';
19+
import {type components} from 'webstatus.dev-backend';
1920
import {
2021
FetchFunctionConfig,
2122
WebstatusLineChartPanel,
@@ -29,17 +30,34 @@ import {
2930
BROWSER_ID_TO_LABEL,
3031
} from '../api/client.js';
3132
import {ChartSelectPointEvent} from './webstatus-gchart.js';
32-
import {customElement} from 'lit/decorators.js';
33+
import {customElement, property} from 'lit/decorators.js';
3334
import {formatOverviewPageUrl} from '../utils/urls.js';
3435
import {
35-
getTopCssIdentifierTemplate,
36-
getTopHtmlIdentifierTemplate,
37-
} from './utils.js';
36+
ColumnKey,
37+
parseColumnsSpec,
38+
renderSavedSearchHeaderCells,
39+
} from './webstatus-overview-cells.js';
40+
import {MISSING_ONE_TABLE_COLUMNS} from '../utils/constants.js';
41+
import {TaskTracker} from '../utils/task-tracker.js';
42+
import {ApiError, UnknownError} from '../api/errors.js';
43+
import {toast} from '../utils/toast.js';
3844

3945
@customElement('webstatus-stats-missing-one-impl-chart-panel')
4046
export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPanel<BrowsersParameter> {
4147
readonly series: BrowsersParameter[] = ['chrome', 'firefox', 'safari'];
4248

49+
@property({type: Object})
50+
taskTracker: TaskTracker<components['schemas']['Feature'][], ApiError> = {
51+
status: TaskStatus.INITIAL, // Initial state
52+
error: undefined,
53+
data: undefined,
54+
};
55+
56+
supportedBrowsers: BrowsersParameter[] = ['chrome', 'firefox', 'safari'];
57+
58+
@property({type: Boolean})
59+
isLoadingFeatures = false;
60+
4361
missingFeaturesList: MissingOneImplFeaturesList = [];
4462
selectedBrowser: string = '';
4563
selectedDate: string = '';
@@ -153,6 +171,16 @@ export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPa
153171
this.featureListHref = formatOverviewPageUrl({search: ''}, {q: query});
154172
}
155173

174+
async getAllFeatureData(
175+
features: MissingOneImplFeaturesList,
176+
): Promise<components['schemas']['Feature'][]> {
177+
if (features.length === 0) {
178+
return [];
179+
}
180+
const query = `id:${features.map(f => f.feature_id).join(' OR id:')}`;
181+
return await this.apiClient.getAllFeatures(query, 'name_asc');
182+
}
183+
156184
/**
157185
* Creates a task and a renderer for handling point-selected events.
158186
* Overrides createPointSelectedTask() in the parent class when an point is
@@ -181,9 +209,33 @@ export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPa
181209
this.selectedDate = targetDate.toISOString().substring(0, 10);
182210
this.selectedBrowser = label;
183211
this.updateFeatureListHref(features);
184-
return features;
212+
return await this.getAllFeatureData(features);
185213
},
186214
args: () => [targetDate, targetBrowser],
215+
onComplete: features => {
216+
this.taskTracker = {
217+
status: TaskStatus.COMPLETE,
218+
error: undefined,
219+
data: features,
220+
};
221+
},
222+
onError: async (error: unknown) => {
223+
if (error instanceof ApiError) {
224+
this.taskTracker = {
225+
status: TaskStatus.ERROR,
226+
error: error,
227+
data: undefined,
228+
};
229+
await toast(`${error.message}`, 'danger', 'exclamation-triangle');
230+
} else {
231+
// Should never reach here but let's handle it.
232+
this.taskTracker = {
233+
status: TaskStatus.ERROR,
234+
error: new UnknownError('unknown error fetching features'),
235+
data: undefined,
236+
};
237+
}
238+
},
187239
});
188240
return {task: task, renderSuccess: this.pointSelectedTaskRenderOnSuccess};
189241
}
@@ -236,52 +288,14 @@ export class WebstatusStatsMissingOneImplChartPanel extends WebstatusLineChartPa
236288
}
237289

238290
renderMissingFeaturesTable(): TemplateResult {
239-
const numCols = Math.ceil(this.missingFeaturesList.length / 10);
240-
241-
// Create table body with `numCols` columns and 10 rows each.
242-
const bodyRows = [];
243-
for (let i = 0; i < 10; i++) {
244-
const cells = [];
245-
for (let j = 0; j < numCols; j++) {
246-
const featureIndex = j * 10 + i;
247-
if (featureIndex < this.missingFeaturesList.length) {
248-
const featureId = this.missingFeaturesList[featureIndex].feature_id;
249-
const extraIdentifiers: TemplateResult[] = [];
250-
const cssIdentifier = getTopCssIdentifierTemplate(featureId);
251-
if (cssIdentifier) {
252-
extraIdentifiers.push(cssIdentifier);
253-
}
254-
const htmlIdentifier = getTopHtmlIdentifierTemplate(featureId);
255-
if (htmlIdentifier) {
256-
extraIdentifiers.push(htmlIdentifier);
257-
}
258-
cells.push(
259-
html` <td>
260-
<div class="missing-feature-id">
261-
<a href="/features/${featureId}">${featureId}</a>
262-
${extraIdentifiers}
263-
</div>
264-
</td>`,
265-
);
266-
} else {
267-
// Empty cell.
268-
cells.push(html`<td></td>`);
269-
}
270-
}
291+
const columns: ColumnKey[] = parseColumnsSpec(MISSING_ONE_TABLE_COLUMNS);
292+
let headerCells: TemplateResult[] = [];
293+
headerCells = renderSavedSearchHeaderCells(this.getPanelText(), columns);
271294

272-
bodyRows.push(
273-
html`<tr>
274-
${cells}
275-
</tr>`,
276-
);
277-
}
278-
279-
return html`
280-
<table class="missing-features-table">
281-
<tbody>
282-
${bodyRows}
283-
</tbody>
284-
</table>
285-
`;
295+
return html`<webstatus-overview-table
296+
.columns=${columns}
297+
.headerCells=${headerCells}
298+
.taskTracker=${this.taskTracker}
299+
></webstatus-overview-table>`;
286300
}
287301
}

frontend/src/static/js/utils/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,6 @@ export const VOCABULARY = [
214214
doc: 'Negate search term with a leading minus',
215215
},
216216
];
217+
218+
export const MISSING_ONE_TABLE_COLUMNS: string =
219+
'name,availability_chrome,availability_firefox,availability_safari,availability_chrome_android,availability_firefox_android,availability_safari_ios,chrome_usage';

0 commit comments

Comments
 (0)