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

Refactor: Enhance WebstatusLineChartPanel with data fetching, aggregation, and additional series calculations #1175

Merged
merged 1 commit into from
Feb 19, 2025
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@
* limitations under the License.
*/

import {fixture, html as testHtml, expect} from '@open-wc/testing';
import {fixture, html as testHtml, expect, oneEvent} from '@open-wc/testing';
import {
WebstatusLineChartPanel,
LineChartMetricData,
FetchFunctionConfig,
AdditionalSeriesConfig,
} from '../webstatus-line-chart-panel.js';
import {Task} from '@lit/task';
import {WebStatusDataObj} from '../webstatus-gchart.js';
import {TemplateResult, html} from 'lit';
import {customElement} from 'lit/decorators.js';
import {taskUpdateComplete} from './test-helpers.test.js';
import {createMockIterator, taskUpdateComplete} from './test-helpers.test.js';

// Interface for the data used in LineChartMetricData
interface MetricDataPoint {
Expand Down Expand Up @@ -171,14 +173,97 @@ describe('WebstatusLineChartPanel', () => {
'#test-panel-pending',
);
expect(pendingMessage).to.exist;
expect(pendingMessage!.textContent).to.include('Loading stats.');
expect(pendingMessage!.textContent).to.include('Loading chart');
});

it('renders error state', async () => {
el.rejectTask(new Error('Test Error'));
await taskUpdateComplete();
const errorMessage = el.shadowRoot!.querySelector('#test-panel-error');
expect(errorMessage).to.exist;
expect(errorMessage!.textContent).to.include('Error when loading stats.');
expect(errorMessage!.textContent).to.include('Error when loading chart');
});

describe('_fetchAndAggregateData', () => {
it('fetches data and applies additional series calculators', async () => {
const fetchFunctionConfigs: FetchFunctionConfig<MetricDataPoint>[] = [
{
label: 'Metric 1',
fetchFunction: () =>
createMockIterator<MetricDataPoint>([
{date: new Date('2024-01-01'), value: 10},
{date: new Date('2024-01-02'), value: 20},
{date: new Date('2024-01-04'), value: 35},
]),
timestampExtractor: (dataPoint: MetricDataPoint) => dataPoint.date,
valueExtractor: (dataPoint: MetricDataPoint) => dataPoint.value,
},
{
label: 'Metric 2',
fetchFunction: () =>
createMockIterator<MetricDataPoint>([
{date: new Date('2024-01-01'), value: 15},
{date: new Date('2024-01-02'), value: 25},
{date: new Date('2024-01-03'), value: 30},
]),
timestampExtractor: (dataPoint: MetricDataPoint) => dataPoint.date,
valueExtractor: (dataPoint: MetricDataPoint) => dataPoint.value,
},
];

const additionalSeriesConfigs: AdditionalSeriesConfig<MetricDataPoint>[] =
[
{
label: 'Total',
calculator: el.calculateMax,
cacheMap: new Map(),
timestampExtractor: (dataPoint: MetricDataPoint) => dataPoint.date,
valueExtractor: (dataPoint: MetricDataPoint) => dataPoint.value,
},
];

await el._fetchAndAggregateData(
fetchFunctionConfigs,
additionalSeriesConfigs,
);
await el.updateComplete;

expect(el.data).to.exist;
expect(el.data!.cols).to.deep.equal([
{type: 'date', label: 'Date', role: 'domain'},
{type: 'number', label: 'Metric 1', role: 'data'},
{type: 'number', label: 'Metric 2', role: 'data'},
{type: 'number', label: 'Total', role: 'data'}, // Check for the additional 'Total' column
]);
expect(el.data!.rows).to.deep.equal([
[new Date('2024-01-01'), 10, 15, 15], // Total should be 15 (max of 10 and 15)
[new Date('2024-01-02'), 20, 25, 25], // Total should be 25 (max of 20 and 25)
[new Date('2024-01-03'), null, 30, 30], // Max should be 30
[new Date('2024-01-04'), 35, null, 35], // Max should be 35
]);
});

it('dispatches data-fetch-starting and data-fetch-complete events', async () => {
const fetchFunctionConfigs: FetchFunctionConfig<MetricDataPoint>[] = [
{
label: 'Metric 1',
fetchFunction: () =>
createMockIterator([{date: new Date('2024-01-01'), value: 10}]),
timestampExtractor: (dataPoint: MetricDataPoint) => dataPoint.date,
valueExtractor: (dataPoint: MetricDataPoint) => dataPoint.value,
},
];

const startingListener = oneEvent(el, 'data-fetch-starting');
const completeListener = oneEvent(el, 'data-fetch-complete');

await el._fetchAndAggregateData(fetchFunctionConfigs);

await startingListener;
const {detail} = await completeListener;
expect(detail.get('Metric 1')!.data).to.deep.equal([
{date: new Date('2024-01-01'), value: 10},
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,41 @@ import {
APIClient,
BaselineStatusMetric,
BrowserReleaseFeatureMetric,
BrowsersParameter,
} from '../../api/client.js';
import {
LineChartMetricData,
WebstatusLineChartPanel,
} from '../webstatus-line-chart-panel.js';
import {WebstatusLineChartPanel} from '../webstatus-line-chart-panel.js';

import '../webstatus-stats-global-feature-count-chart-panel.js';
import {createMockIterator, taskUpdateComplete} from './test-helpers.test.js';

describe('WebstatusStatsGlobalFeatureCountChartPanel', () => {
let el: WebstatusStatsGlobalFeatureCountChartPanel;
let apiClientStub: SinonStubbedInstance<APIClient>;
let setDisplayDataFromMapStub: SinonStub;
let fetchAndAggregateDataStub: SinonStub;
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-01-31');

beforeEach(async () => {
apiClientStub = stub(new APIClient(''));
fetchAndAggregateDataStub = stub(
WebstatusLineChartPanel.prototype,
'_fetchAndAggregateData',
);
setDisplayDataFromMapStub = stub(
WebstatusLineChartPanel.prototype,
'setDisplayDataFromMap',
);
el =
await fixture<WebstatusStatsGlobalFeatureCountChartPanel>(testHtml`<webstatus-stats-global-feature-chart-panel
.startDate=${new Date('2024-01-01')}
.endDate=${new Date('2024-01-31')}
.startDate=${startDate}
.endDate=${endDate}
></webstatus-stats-global-feature-chart-panel>`);
el.apiClient = apiClientStub;
await el.updateComplete;
});

afterEach(() => {
setDisplayDataFromMapStub.restore();
fetchAndAggregateDataStub.restore();
});

it('renders the card', async () => {
Expand All @@ -66,113 +69,77 @@ describe('WebstatusStatsGlobalFeatureCountChartPanel', () => {
expect(header!.textContent).to.contain('Global feature support');
});

it('fetches data and calls setDisplayDataFromMap', async () => {
const mockMissingOneCountData = new Map<
BrowsersParameter,
BrowserReleaseFeatureMetric[]
>([
[
'chrome',
[
{timestamp: '2024-01-01', count: 10},
{timestamp: '2024-01-02', count: 12},
],
],
[
'edge',
[
{timestamp: '2024-01-01', count: 8},
{timestamp: '2024-01-02', count: 11},
],
],
[
'firefox',
[
{timestamp: '2024-01-01', count: 9},
{timestamp: '2024-01-02', count: 10},
],
],
[
'safari',
[
{timestamp: '2024-01-01', count: 7},
{timestamp: '2024-01-02', count: 13},
],
],
]);
const mockBaselineData: BaselineStatusMetric[] = [
{timestamp: '2024-01-01', count: 20},
];

apiClientStub.getFeatureCountsForBrowser.callsFake(browser => {
const data = mockMissingOneCountData.get(browser)?.slice();
return createMockIterator(data!);
});
apiClientStub.listAggregatedBaselineStatusCounts.callsFake(() => {
return createMockIterator(mockBaselineData);
});

await el._task?.run();
await el.updateComplete;
await taskUpdateComplete();

expect(setDisplayDataFromMapStub.calledOnce).to.be.true;
const args = setDisplayDataFromMapStub.firstCall.args;
expect(args.length).to.equal(1); // Ensure it has one argument

const metricDataArray = args[0] as Array<
LineChartMetricData<{
timestamp: string;
count?: number | undefined;
}>
>;

const browserToDataMap = new Map<
string,
{
timestamp: string;
count?: number | undefined;
}[]
>();
metricDataArray.forEach(item => {
browserToDataMap.set(item.label, item.data);
});
it('calls _fetchAndAggregateData with correct arguments', async () => {
expect(fetchAndAggregateDataStub).to.have.been.calledOnce;
const [fetchFunctionConfigs, additionalSeriesConfigs] =
fetchAndAggregateDataStub.getCall(0).args;
expect(fetchFunctionConfigs.length).to.equal(5); // 4 browsers + total
// Test Chrome configuration
const chromeConfig = fetchFunctionConfigs[0];
expect(chromeConfig.label).to.equal('Chrome');
expect(chromeConfig.fetchFunction).to.be.a('function');
const chromeTestDataPoint: BrowserReleaseFeatureMetric = {
timestamp: '2024-01-01',
count: 10,
};
expect(chromeConfig.timestampExtractor(chromeTestDataPoint)).to.deep.equal(
new Date('2024-01-01'),
);
expect(chromeConfig.valueExtractor(chromeTestDataPoint)).to.equal(10);

// Test Firefox configuration
const firefoxConfig = fetchFunctionConfigs[1];
expect(firefoxConfig.label).to.equal('Firefox');
expect(firefoxConfig.fetchFunction).to.be.a('function');
const firefoxTestDataPoint: BrowserReleaseFeatureMetric = {
timestamp: '2024-01-01',
count: 9,
};
expect(
firefoxConfig.timestampExtractor(firefoxTestDataPoint),
).to.deep.equal(new Date('2024-01-01'));
expect(firefoxConfig.valueExtractor(firefoxTestDataPoint)).to.equal(9);

// Test Safari configuration
const safariConfig = fetchFunctionConfigs[2];
expect(safariConfig.label).to.equal('Safari');
expect(safariConfig.fetchFunction).to.be.a('function');
const safariTestDataPoint: BrowserReleaseFeatureMetric = {
timestamp: '2024-01-01',
count: 7,
};
expect(safariConfig.timestampExtractor(safariTestDataPoint)).to.deep.equal(
new Date('2024-01-01'),
);
expect(safariConfig.valueExtractor(safariTestDataPoint)).to.equal(7);

// Test Edge configuration
const edgeConfig = fetchFunctionConfigs[3];
expect(edgeConfig.label).to.equal('Edge');
expect(edgeConfig.fetchFunction).to.be.a('function');
const edgeTestDataPoint: BrowserReleaseFeatureMetric = {
timestamp: '2024-01-01',
count: 8,
};
expect(edgeConfig.timestampExtractor(edgeTestDataPoint)).to.deep.equal(
new Date('2024-01-01'),
);
expect(edgeConfig.valueExtractor(edgeTestDataPoint)).to.equal(8);

// Test Total configuration
const totalConfig = fetchFunctionConfigs[4];
expect(totalConfig.label).to.equal('Total number of Baseline features');
expect(totalConfig.fetchFunction).to.be.a('function');
const totalTestDataPoint: BaselineStatusMetric = {
timestamp: '2024-01-01',
count: 20,
};
expect(totalConfig.timestampExtractor(totalTestDataPoint)).to.deep.equal(
new Date('2024-01-01'),
);
expect(totalConfig.valueExtractor(totalTestDataPoint)).to.equal(20);

const expectedMap = new Map([
[
'Chrome',
[
{timestamp: '2024-01-01', count: 10},
{timestamp: '2024-01-02', count: 12},
],
],
[
'Edge',
[
{timestamp: '2024-01-01', count: 8},
{timestamp: '2024-01-02', count: 11},
],
],
[
'Firefox',
[
{timestamp: '2024-01-01', count: 9},
{timestamp: '2024-01-02', count: 10},
],
],
[
'Safari',
[
{timestamp: '2024-01-01', count: 7},
{timestamp: '2024-01-02', count: 13},
],
],
[
'Total number of Baseline features',
[{timestamp: '2024-01-01', count: 20}],
],
]);
expect(browserToDataMap).to.deep.equal(expectedMap);
expect(additionalSeriesConfigs).to.be.undefined;
});

it('generates chart options correctly', () => {
Expand Down
Loading
Loading