1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-10-18 20:09:08 +02:00

feat: aggregate metrics for the same timestamp (#5876)

This commit is contained in:
Mateusz Kwasniewski 2024-01-12 12:19:30 +01:00 committed by GitHub
parent a88763283a
commit c816ffd49d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 9 deletions

View File

@ -20,6 +20,7 @@ import {
useQueryParams,
withDefault,
} from 'use-query-params';
import { aggregateFeatureMetrics } from './aggregateFeatureMetrics';
export const FeatureMetrics = () => {
const projectId = useRequiredPathParam('projectId');
@ -53,9 +54,13 @@ export const FeatureMetrics = () => {
}, [featureMetrics]);
const filteredMetrics = useMemo(() => {
return cachedMetrics
?.filter((metric) => selectedEnvironment === metric.environment)
.filter((metric) => selectedApplications.includes(metric.appName));
return aggregateFeatureMetrics(
cachedMetrics
?.filter((metric) => selectedEnvironment === metric.environment)
.filter((metric) =>
selectedApplications.includes(metric.appName),
) || [],
);
}, [
cachedMetrics,
selectedEnvironment,

View File

@ -71,6 +71,6 @@ const createChartPoints = (
return metrics.map((metric) => ({
x: metric.timestamp,
y: y(metric),
variants: metric.variants,
variants: metric.variants || {},
}));
};

View File

@ -9,7 +9,7 @@ test('render hourly metrics stats', async () => {
expect(screen.getByText('50%')).toBeInTheDocument();
expect(
screen.getByText(
'Total requests for the feature in the environment in the last 48 hours.',
'Total requests for the feature in the environment in the last 48 hours (local time).',
),
).toBeInTheDocument();
});
@ -21,7 +21,7 @@ test('render daily metrics stats', async () => {
expect(
screen.getByText(
'Total requests for the feature in the environment in the last 7 days.',
'Total requests for the feature in the environment in the last 7 days (UTC).',
),
).toBeInTheDocument();
});

View File

@ -0,0 +1,76 @@
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
import { aggregateFeatureMetrics } from './aggregateFeatureMetrics';
describe('aggregateFeatureMetrics', () => {
it('should aggregate yes and no values for identical timestamps', () => {
const data: IFeatureMetricsRaw[] = [
{
featureName: 'Feature1',
appName: 'App1',
environment: 'dev',
timestamp: '2024-01-12T08:00:00.000Z',
yes: 10,
no: 5,
variants: { a: 1, b: 2 },
},
{
featureName: 'Feature1',
appName: 'App1',
environment: 'dev',
timestamp: '2024-01-12T08:00:00.000Z',
yes: 15,
no: 10,
variants: { a: 2, b: 1 },
},
];
const result = aggregateFeatureMetrics(data);
expect(result).toEqual([
{
featureName: 'Feature1',
appName: 'App1',
environment: 'dev',
timestamp: '2024-01-12T08:00:00.000Z',
yes: 25,
no: 15,
variants: { a: 3, b: 3 },
},
]);
});
it('should handle undefined variants correctly', () => {
const data: IFeatureMetricsRaw[] = [
{
featureName: 'Feature2',
appName: 'App2',
environment: 'test',
timestamp: '2024-01-13T09:00:00.000Z',
yes: 20,
no: 10,
variants: undefined,
},
{
featureName: 'Feature2',
appName: 'App2',
environment: 'test',
timestamp: '2024-01-13T09:00:00.000Z',
yes: 30,
no: 15,
variants: undefined,
},
];
const result = aggregateFeatureMetrics(data);
expect(result).toEqual([
{
featureName: 'Feature2',
appName: 'App2',
environment: 'test',
timestamp: '2024-01-13T09:00:00.000Z',
yes: 50,
no: 25,
variants: undefined,
},
]);
});
});

View File

@ -0,0 +1,35 @@
import { IFeatureMetricsRaw } from 'interfaces/featureToggle';
// multiple applications may have metrics for the same timestamp
export const aggregateFeatureMetrics = (
metrics: IFeatureMetricsRaw[],
): IFeatureMetricsRaw[] => {
const resultMap = new Map<string, IFeatureMetricsRaw>();
metrics.forEach((obj) => {
let aggregated = resultMap.get(obj.timestamp);
if (!aggregated) {
aggregated = { ...obj, yes: 0, no: 0, variants: {} };
resultMap.set(obj.timestamp, aggregated);
}
aggregated.yes += obj.yes;
aggregated.no += obj.no;
if (obj.variants) {
aggregated.variants = aggregated.variants || {};
for (const [key, value] of Object.entries(obj.variants)) {
aggregated.variants[key] =
(aggregated.variants[key] || 0) + value;
}
}
});
return Array.from(resultMap.values()).map((item) => ({
...item,
variants:
item.variants && Object.keys(item.variants).length === 0
? undefined
: item.variants,
}));
};

View File

@ -1,6 +1,6 @@
export const daysOrHours = (hoursBack: number): string => {
if (hoursBack > 48) {
return `${Math.floor(hoursBack / 24)} days`;
return `${Math.floor(hoursBack / 24)} days (UTC)`;
}
return `${hoursBack} hours`;
return `${hoursBack} hours (local time)`;
};

View File

@ -107,5 +107,5 @@ export interface IFeatureMetricsRaw {
timestamp: string;
yes: number;
no: number;
variants: Record<string, number>;
variants?: Record<string, number>;
}