diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index c05f5bab866..1efe26d28ef 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -45,6 +45,13 @@ export interface LogRow { uniqueLabels?: LogsStreamLabels; } +export interface LogsLabelStat { + active?: boolean; + count: number; + proportion: number; + value: string; +} + export enum LogsMetaKind { Number, String, @@ -88,6 +95,22 @@ export enum LogsDedupStrategy { 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; function isDuplicateRow(row: LogRow, other: LogRow, strategy: LogsDedupStrategy): boolean { switch (strategy) { diff --git a/public/app/core/specs/logs_model.test.ts b/public/app/core/specs/logs_model.test.ts index 5e427468339..22673278b13 100644 --- a/public/app/core/specs/logs_model.test.ts +++ b/public/app/core/specs/logs_model.test.ts @@ -1,4 +1,4 @@ -import { dedupLogRows, LogsDedupStrategy, LogsModel } from '../logs_model'; +import { calculateLogsLabelStats, dedupLogRows, LogsDedupStrategy, LogsModel } from '../logs_model'; describe('dedupLogRows()', () => { 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, + }, + ]); + }); +}); diff --git a/public/app/features/explore/LogLabels.tsx b/public/app/features/explore/LogLabels.tsx index 3f8393e4059..91e2d44e517 100644 --- a/public/app/features/explore/LogLabels.tsx +++ b/public/app/features/explore/LogLabels.tsx @@ -2,32 +2,9 @@ import _ from 'lodash'; import React, { PureComponent } from 'react'; import classnames from 'classnames'; -import { LogsStreamLabels, LogRow } from 'app/core/logs_model'; +import { calculateLogsLabelStats, LogsLabelStat, LogsStreamLabels, LogRow } from 'app/core/logs_model'; -interface FieldStat { - 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) { +function StatsRow({ active, count, proportion, value }: LogsLabelStat) { const percent = `${Math.round(proportion * 100)}%`; const barStyle = { width: percent }; 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; class Stats extends PureComponent<{ - stats: FieldStat[]; + stats: LogsLabelStat[]; label: string; value: string; rowCount: number; @@ -92,7 +69,7 @@ class Label extends PureComponent< value: string; onClickLabel?: (label: string, value: string) => void; }, - { showStats: boolean; stats: FieldStat[] } + { showStats: boolean; stats: LogsLabelStat[] } > { state = { stats: null, @@ -115,7 +92,7 @@ class Label extends PureComponent< if (state.showStats) { 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 }; }); };