Skip to content

Commit 96f9821

Browse files
authored
feat(frontend): Add developer upvotes column to overview page (#1791)
* feat(frontend): Add developer upvotes column to overview page Adds a new 'Developer Upvotes' column to the feature overview table. This provides a signal of developer interest for each feature at a glance. The change includes: - A new cell renderer to display the upvote count with an icon and tooltip. - A utility function to format large numbers (e.g., 12345 to 12.3K). - Unit tests for the new column and formatting logic. - E2E tests and snapshot updates to verify the new column's appearance. - Updated CSV export to include the developer upvotes data. Fixes #1665 * revert utils.ts changes * fix test
1 parent 614853d commit 96f9821

10 files changed

+359
-246
lines changed

e2e/tests/overview-page.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ test('screenshot for availability sort', async ({page}) => {
4848
await expect(pageContainer).toHaveScreenshot();
4949
});
5050

51+
test('screenshot for developer upvotes column', async ({page}) => {
52+
await gotoOverviewPageUrl(
53+
page,
54+
'http://localhost:5555/?columns=name,baseline_status,availability_chrome,availability_firefox,availability_edge,' +
55+
'availability_safari,chrome_usage,developer_signal_upvotes',
56+
);
57+
const pageContainer = page.locator('.page-container');
58+
await expect(pageContainer).toHaveScreenshot();
59+
});
60+
5161
test('shows an error that their query is invalid', async ({page}) => {
5262
await page.goto('http://localhost:5555/?q=available_on%3Achrom');
5363

245 KB
Loading
332 KB
Loading
272 KB
Loading

e2e/tests/overview-page.spec.ts-snapshots/webstatus-feature-overview-all-columns-chromium-linux.csv

Lines changed: 82 additions & 82 deletions
Large diffs are not rendered by default.

e2e/tests/overview-page.spec.ts-snapshots/webstatus-feature-overview-all-columns-firefox-linux.csv

Lines changed: 82 additions & 82 deletions
Large diffs are not rendered by default.

e2e/tests/overview-page.spec.ts-snapshots/webstatus-feature-overview-all-columns-webkit-linux.csv

Lines changed: 82 additions & 82 deletions
Large diffs are not rendered by default.

frontend/src/static/js/components/test/webstatus-overview-cells.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,66 @@ describe('renderChromeUsage', () => {
117117
});
118118
});
119119

120+
describe('renderDeveloperSignalUpvotes', () => {
121+
let container: HTMLElement;
122+
let feature: components['schemas']['Feature'];
123+
beforeEach(() => {
124+
container = document.createElement('div');
125+
126+
feature = {
127+
feature_id: 'id',
128+
name: 'name',
129+
};
130+
});
131+
132+
it('renders upvotes when available', async () => {
133+
feature.developer_signals = {upvotes: 12345, link: 'http://example.com'};
134+
const result = CELL_DEFS[ColumnKey.DeveloperSignalUpvotes].cellRenderer(
135+
feature,
136+
{search: ''},
137+
{},
138+
);
139+
render(result, container);
140+
const el = await fixture(container);
141+
const tooltip = el.querySelector('sl-tooltip');
142+
expect(tooltip).to.exist;
143+
expect(tooltip!.getAttribute('content')).to.equal(
144+
'12,345 developer upvotes. Need this feature across browsers? Click this and upvote it on GitHub.',
145+
);
146+
const span = el.querySelector('span');
147+
expect(span).to.exist;
148+
expect(el.textContent.trim()).to.equal('12.3K');
149+
});
150+
151+
it('renders nothing when upvotes are not available', async () => {
152+
const result = CELL_DEFS[ColumnKey.DeveloperSignalUpvotes].cellRenderer(
153+
feature,
154+
{search: ''},
155+
{},
156+
);
157+
render(result, container);
158+
const el = await fixture(container);
159+
expect(el.textContent.trim()).to.equal('');
160+
});
161+
162+
it('renders upvotes with correct formatting for smaller numbers', async () => {
163+
feature.developer_signals = {
164+
upvotes: 500,
165+
link: 'http://example.com',
166+
};
167+
const result = CELL_DEFS[ColumnKey.DeveloperSignalUpvotes].cellRenderer(
168+
feature,
169+
{search: ''},
170+
{},
171+
);
172+
render(result, container);
173+
const el = await fixture(container);
174+
const span = el.querySelector('span');
175+
expect(span).to.exist;
176+
expect(el.textContent.trim()).to.equal('500');
177+
});
178+
});
179+
120180
describe('renderBaselineStatus', () => {
121181
let container: HTMLElement;
122182
beforeEach(() => {

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
TOP_HTML_INTEROP_ISSUES,
2929
} from '../utils/constants.js';
3030
import './webstatus-feature-badge.js';
31+
import {formatDeveloperUpvotesMessages} from '../utils/format.js';
3132

3233
const MISSING_VALUE = html``;
3334

@@ -89,6 +90,7 @@ export enum ColumnKey {
8990
ExpFirefoxAndroid = 'experimental_firefox_android',
9091
ExpSafariIos = 'experimental_safari_ios',
9192
ChromeUsage = 'chrome_usage',
93+
DeveloperSignalUpvotes = 'developer_signal_upvotes',
9294
}
9395

9496
const columnKeyMapping = Object.entries(ColumnKey).reduce(
@@ -244,6 +246,32 @@ export const renderChromeUsage: CellRenderer = (
244246
return html`<span id="chrome-usage">${usage}</span>`;
245247
};
246248

249+
const renderDeveloperSignalUpvotes: CellRenderer = (
250+
feature,
251+
_routerLocation,
252+
_options,
253+
) => {
254+
const upvotes = feature.developer_signals?.upvotes;
255+
const link = feature.developer_signals?.link;
256+
if (upvotes === undefined || link === undefined) {
257+
// If upvotes is undefined, link will be undefined as well. But we currently mark both as optional.
258+
// This check is just in case something goes wrong.
259+
return html`${nothing}`;
260+
}
261+
262+
const messages = formatDeveloperUpvotesMessages(upvotes);
263+
264+
return html`
265+
<sl-tooltip content="${messages.message}">
266+
<div class="dev-signal-cell">
267+
<span
268+
><a href=${link} target="_blank">${messages.shorthandNumber}</a></span
269+
>
270+
</div>
271+
</sl-tooltip>
272+
`;
273+
};
274+
247275
function formatDateString(dateString: string): string {
248276
return formatDate(new Date(dateString));
249277
}
@@ -678,6 +706,13 @@ export const CELL_DEFS: Record<ColumnKey, ColumnDefinition> = {
678706
cellRenderer: renderChromeUsage,
679707
options: {},
680708
},
709+
[ColumnKey.DeveloperSignalUpvotes]: {
710+
nameInDialog: 'Developer Upvotes',
711+
headerHtml: html`Upvotes <sl-icon name="hand-thumbs-up"></sl-icon>`,
712+
cellClass: 'centered',
713+
cellRenderer: renderDeveloperSignalUpvotes,
714+
options: {},
715+
},
681716
};
682717

683718
export function calcColGroupSpans(

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ export class WebstatusOverviewFilters extends LitElement {
239239
case ColumnKey.ChromeUsage:
240240
columns.push(name);
241241
break;
242+
case ColumnKey.DeveloperSignalUpvotes:
243+
columns.push(name);
244+
break;
242245
case ColumnKey.StableChrome:
243246
case ColumnKey.StableEdge:
244247
case ColumnKey.StableFirefox:
@@ -265,6 +268,8 @@ export class WebstatusOverviewFilters extends LitElement {
265268
const browserImpl = feature.browser_implementations!;
266269
const wptData = feature.wpt;
267270
const chromeUsage = feature.usage?.chrome?.daily?.toString() || '';
271+
const developerSignalUpvotes =
272+
feature.developer_signals?.upvotes?.toString() || '';
268273
const row: string[] = [];
269274

270275
const pushBrowserChannelValue = (
@@ -292,6 +297,9 @@ export class WebstatusOverviewFilters extends LitElement {
292297
case ColumnKey.ChromeUsage:
293298
row.push(chromeUsage);
294299
break;
300+
case ColumnKey.DeveloperSignalUpvotes:
301+
row.push(developerSignalUpvotes);
302+
break;
295303
case ColumnKey.StableChrome:
296304
case ColumnKey.StableEdge:
297305
case ColumnKey.StableFirefox:

0 commit comments

Comments
 (0)