Tests for label stats calculation

This commit is contained in:
David Kaltschmidt 2018-12-03 17:00:50 +01:00
parent 5916cb3e7c
commit 9f0b1e533f
3 changed files with 82 additions and 29 deletions

View File

@ -45,6 +45,13 @@ export interface LogRow {
uniqueLabels?: LogsStreamLabels; uniqueLabels?: LogsStreamLabels;
} }
export interface LogsLabelStat {
active?: boolean;
count: number;
proportion: number;
value: string;
}
export enum LogsMetaKind { export enum LogsMetaKind {
Number, Number,
String, String,
@ -88,6 +95,22 @@ export enum LogsDedupStrategy {
signature = 'signature', signature = 'signature',
} }
export function calculateLogsLabelStats(rows: LogRow[], label: string): LogsLabelStat[] {
// Consider only rows that have the given label
const rowsWithLabel = rows.filter(row => row.labels[label] !== undefined);
const rowCount = rowsWithLabel.length;
// Get label value counts for eligible rows
const countsByValue = _.countBy(rowsWithLabel, row => (row as LogRow).labels[label]);
const sortedCounts = _.chain(countsByValue)
.map((count, value) => ({ count, value, proportion: count / rowCount }))
.sortBy('count')
.reverse()
.value();
return sortedCounts;
}
const isoDateRegexp = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-6]\d[,\.]\d+([+-][0-2]\d:[0-5]\d|Z)/g; const isoDateRegexp = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-6]\d[,\.]\d+([+-][0-2]\d:[0-5]\d|Z)/g;
function isDuplicateRow(row: LogRow, other: LogRow, strategy: LogsDedupStrategy): boolean { function isDuplicateRow(row: LogRow, other: LogRow, strategy: LogsDedupStrategy): boolean {
switch (strategy) { switch (strategy) {

View File

@ -1,4 +1,4 @@
import { dedupLogRows, LogsDedupStrategy, LogsModel } from '../logs_model'; import { calculateLogsLabelStats, dedupLogRows, LogsDedupStrategy, LogsModel } from '../logs_model';
describe('dedupLogRows()', () => { describe('dedupLogRows()', () => {
test('should return rows as is when dedup is set to none', () => { test('should return rows as is when dedup is set to none', () => {
@ -106,3 +106,56 @@ describe('dedupLogRows()', () => {
]); ]);
}); });
}); });
describe('calculateLogsLabelStats()', () => {
test('should return no stats for empty rows', () => {
expect(calculateLogsLabelStats([], '')).toEqual([]);
});
test('should return no stats of label is not found', () => {
const rows = [
{
entry: 'foo 1',
labels: {
foo: 'bar',
},
},
];
expect(calculateLogsLabelStats(rows as any, 'baz')).toEqual([]);
});
test('should return stats for found labels', () => {
const rows = [
{
entry: 'foo 1',
labels: {
foo: 'bar',
},
},
{
entry: 'foo 0',
labels: {
foo: 'xxx',
},
},
{
entry: 'foo 2',
labels: {
foo: 'bar',
},
},
];
expect(calculateLogsLabelStats(rows as any, 'foo')).toMatchObject([
{
value: 'bar',
count: 2,
},
{
value: 'xxx',
count: 1,
},
]);
});
});

View File

@ -2,32 +2,9 @@ import _ from 'lodash';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { LogsStreamLabels, LogRow } from 'app/core/logs_model'; import { calculateLogsLabelStats, LogsLabelStat, LogsStreamLabels, LogRow } from 'app/core/logs_model';
interface FieldStat { function StatsRow({ active, count, proportion, value }: LogsLabelStat) {
active?: boolean;
value: string;
count: number;
proportion: number;
}
function calculateStats(rows: LogRow[], label: string): FieldStat[] {
// Consider only rows that have the given label
const rowsWithLabel = rows.filter(row => row.labels[label] !== undefined);
const rowCount = rowsWithLabel.length;
// Get label value counts for eligible rows
const countsByValue = _.countBy(rowsWithLabel, row => (row as LogRow).labels[label]);
const sortedCounts = _.chain(countsByValue)
.map((count, value) => ({ count, value, proportion: count / rowCount }))
.sortBy('count')
.reverse()
.value();
return sortedCounts;
}
function StatsRow({ active, count, proportion, value }: FieldStat) {
const percent = `${Math.round(proportion * 100)}%`; const percent = `${Math.round(proportion * 100)}%`;
const barStyle = { width: percent }; const barStyle = { width: percent };
const className = classnames('logs-stats-row', { 'logs-stats-row--active': active }); const className = classnames('logs-stats-row', { 'logs-stats-row--active': active });
@ -48,7 +25,7 @@ function StatsRow({ active, count, proportion, value }: FieldStat) {
const STATS_ROW_LIMIT = 5; const STATS_ROW_LIMIT = 5;
class Stats extends PureComponent<{ class Stats extends PureComponent<{
stats: FieldStat[]; stats: LogsLabelStat[];
label: string; label: string;
value: string; value: string;
rowCount: number; rowCount: number;
@ -92,7 +69,7 @@ class Label extends PureComponent<
value: string; value: string;
onClickLabel?: (label: string, value: string) => void; onClickLabel?: (label: string, value: string) => void;
}, },
{ showStats: boolean; stats: FieldStat[] } { showStats: boolean; stats: LogsLabelStat[] }
> { > {
state = { state = {
stats: null, stats: null,
@ -115,7 +92,7 @@ class Label extends PureComponent<
if (state.showStats) { if (state.showStats) {
return { showStats: false, stats: null }; return { showStats: false, stats: null };
} }
const stats = calculateStats(this.props.allRows, this.props.label); const stats = calculateLogsLabelStats(this.props.allRows, this.props.label);
return { showStats: true, stats }; return { showStats: true, stats };
}); });
}; };