Commit c3692794 by Hugo Häggmark

Splitted up LogLabels into LogLabelStats and LogLabel

parent 23202ab1
...@@ -56,7 +56,7 @@ export interface LogRowModel { ...@@ -56,7 +56,7 @@ export interface LogRowModel {
uniqueLabels?: LogsStreamLabels; uniqueLabels?: LogsStreamLabels;
} }
export interface LogsLabelStat { export interface LogLabelStatsModel {
active?: boolean; active?: boolean;
count: number; count: number;
proportion: number; proportion: number;
...@@ -188,7 +188,7 @@ export const LogsParsers: { [name: string]: LogsParser } = { ...@@ -188,7 +188,7 @@ export const LogsParsers: { [name: string]: LogsParser } = {
}, },
}; };
export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): LogsLabelStat[] { export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): LogLabelStatsModel[] {
// Consider only rows that satisfy the matcher // Consider only rows that satisfy the matcher
const rowsWithField = rows.filter(row => extractor.test(row.entry)); const rowsWithField = rows.filter(row => extractor.test(row.entry));
const rowCount = rowsWithField.length; const rowCount = rowsWithField.length;
...@@ -204,7 +204,7 @@ export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): Log ...@@ -204,7 +204,7 @@ export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): Log
return sortedCounts; return sortedCounts;
} }
export function calculateLogsLabelStats(rows: LogRowModel[], label: string): LogsLabelStat[] { export function calculateLogsLabelStats(rows: LogRowModel[], label: string): LogLabelStatsModel[] {
// Consider only rows that have the given label // Consider only rows that have the given label
const rowsWithLabel = rows.filter(row => row.labels[label] !== undefined); const rowsWithLabel = rows.filter(row => row.labels[label] !== undefined);
const rowCount = rowsWithLabel.length; const rowCount = rowsWithLabel.length;
......
import React, { PureComponent } from 'react';
import { calculateLogsLabelStats, LogLabelStatsModel, LogRowModel } from 'app/core/logs_model';
import { LogLabelStats } from './LogLabelStats';
interface Props {
getRows?: () => LogRowModel[];
label: string;
plain?: boolean;
value: string;
onClickLabel?: (label: string, value: string) => void;
}
interface State {
showStats: boolean;
stats: LogLabelStatsModel[];
}
export class LogLabel extends PureComponent<Props, State> {
state = {
stats: null,
showStats: false,
};
onClickClose = () => {
this.setState({ showStats: false });
};
onClickLabel = () => {
const { onClickLabel, label, value } = this.props;
if (onClickLabel) {
onClickLabel(label, value);
}
};
onClickStats = () => {
this.setState(state => {
if (state.showStats) {
return { showStats: false, stats: null };
}
const allRows = this.props.getRows();
const stats = calculateLogsLabelStats(allRows, this.props.label);
return { showStats: true, stats };
});
};
render() {
const { getRows, label, plain, value } = this.props;
const { showStats, stats } = this.state;
const tooltip = `${label}: ${value}`;
return (
<span className="logs-label">
<span className="logs-label__value" title={tooltip}>
{value}
</span>
{!plain && (
<span title="Filter for label" onClick={this.onClickLabel} className="logs-label__icon fa fa-search-plus" />
)}
{!plain && getRows && <span onClick={this.onClickStats} className="logs-label__icon fa fa-signal" />}
{showStats && (
<span className="logs-label__stats">
<LogLabelStats
stats={stats}
rowCount={getRows().length}
label={label}
value={value}
onClickClose={this.onClickClose}
/>
</span>
)}
</span>
);
}
}
import React, { PureComponent } from 'react';
import classnames from 'classnames';
import { LogLabelStatsModel } from 'app/core/logs_model';
function LogLabelStatsRow(logLabelStatsModel: LogLabelStatsModel) {
const { active, count, proportion, value } = logLabelStatsModel;
const percent = `${Math.round(proportion * 100)}%`;
const barStyle = { width: percent };
const className = classnames('logs-stats-row', { 'logs-stats-row--active': active });
return (
<div className={className}>
<div className="logs-stats-row__label">
<div className="logs-stats-row__value">{value}</div>
<div className="logs-stats-row__count">{count}</div>
<div className="logs-stats-row__percent">{percent}</div>
</div>
<div className="logs-stats-row__bar">
<div className="logs-stats-row__innerbar" style={barStyle} />
</div>
</div>
);
}
const STATS_ROW_LIMIT = 5;
interface Props {
stats: LogLabelStatsModel[];
label: string;
value: string;
rowCount: number;
onClickClose: () => void;
}
export class LogLabelStats extends PureComponent<Props> {
render() {
const { label, rowCount, stats, value, onClickClose } = this.props;
const topRows = stats.slice(0, STATS_ROW_LIMIT);
let activeRow = topRows.find(row => row.value === value);
let otherRows = stats.slice(STATS_ROW_LIMIT);
const insertActiveRow = !activeRow;
// Remove active row from other to show extra
if (insertActiveRow) {
activeRow = otherRows.find(row => row.value === value);
otherRows = otherRows.filter(row => row.value !== value);
}
const otherCount = otherRows.reduce((sum, row) => sum + row.count, 0);
const topCount = topRows.reduce((sum, row) => sum + row.count, 0);
const total = topCount + otherCount;
const otherProportion = otherCount / total;
return (
<div className="logs-stats">
<div className="logs-stats__header">
<span className="logs-stats__title">
{label}: {total} of {rowCount} rows have that label
</span>
<span className="logs-stats__close fa fa-remove" onClick={onClickClose} />
</div>
<div className="logs-stats__body">
{topRows.map(stat => <LogLabelStatsRow key={stat.value} {...stat} active={stat.value === value} />)}
{insertActiveRow && activeRow && <LogLabelStatsRow key={activeRow.value} {...activeRow} active />}
{otherCount > 0 && (
<LogLabelStatsRow key="__OTHERS__" count={otherCount} value="Other" proportion={otherProportion} />
)}
</div>
</div>
);
}
}
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import classnames from 'classnames';
import { calculateLogsLabelStats, LogsLabelStat, LogsStreamLabels, LogRowModel } from 'app/core/logs_model'; import { LogsStreamLabels, LogRowModel } from 'app/core/logs_model';
import { LogLabel } from './LogLabel';
function StatsRow({ active, count, proportion, value }: LogsLabelStat) { interface Props {
const percent = `${Math.round(proportion * 100)}%`;
const barStyle = { width: percent };
const className = classnames('logs-stats-row', { 'logs-stats-row--active': active });
return (
<div className={className}>
<div className="logs-stats-row__label">
<div className="logs-stats-row__value">{value}</div>
<div className="logs-stats-row__count">{count}</div>
<div className="logs-stats-row__percent">{percent}</div>
</div>
<div className="logs-stats-row__bar">
<div className="logs-stats-row__innerbar" style={barStyle} />
</div>
</div>
);
}
const STATS_ROW_LIMIT = 5;
export class Stats extends PureComponent<{
stats: LogsLabelStat[];
label: string;
value: string;
rowCount: number;
onClickClose: () => void;
}> {
render() {
const { label, rowCount, stats, value, onClickClose } = this.props;
const topRows = stats.slice(0, STATS_ROW_LIMIT);
let activeRow = topRows.find(row => row.value === value);
let otherRows = stats.slice(STATS_ROW_LIMIT);
const insertActiveRow = !activeRow;
// Remove active row from other to show extra
if (insertActiveRow) {
activeRow = otherRows.find(row => row.value === value);
otherRows = otherRows.filter(row => row.value !== value);
}
const otherCount = otherRows.reduce((sum, row) => sum + row.count, 0);
const topCount = topRows.reduce((sum, row) => sum + row.count, 0);
const total = topCount + otherCount;
const otherProportion = otherCount / total;
return (
<div className="logs-stats">
<div className="logs-stats__header">
<span className="logs-stats__title">
{label}: {total} of {rowCount} rows have that label
</span>
<span className="logs-stats__close fa fa-remove" onClick={onClickClose} />
</div>
<div className="logs-stats__body">
{topRows.map(stat => <StatsRow key={stat.value} {...stat} active={stat.value === value} />)}
{insertActiveRow && activeRow && <StatsRow key={activeRow.value} {...activeRow} active />}
{otherCount > 0 && (
<StatsRow key="__OTHERS__" count={otherCount} value="Other" proportion={otherProportion} />
)}
</div>
</div>
);
}
}
class Label extends PureComponent<
{
getRows?: () => LogRowModel[]; getRows?: () => LogRowModel[];
label: string; labels: LogsStreamLabels;
plain?: boolean; plain?: boolean;
value: string;
onClickLabel?: (label: string, value: string) => void; onClickLabel?: (label: string, value: string) => void;
},
{ showStats: boolean; stats: LogsLabelStat[] }
> {
state = {
stats: null,
showStats: false,
};
onClickClose = () => {
this.setState({ showStats: false });
};
onClickLabel = () => {
const { onClickLabel, label, value } = this.props;
if (onClickLabel) {
onClickLabel(label, value);
}
};
onClickStats = () => {
this.setState(state => {
if (state.showStats) {
return { showStats: false, stats: null };
}
const allRows = this.props.getRows();
const stats = calculateLogsLabelStats(allRows, this.props.label);
return { showStats: true, stats };
});
};
render() {
const { getRows, label, plain, value } = this.props;
const { showStats, stats } = this.state;
const tooltip = `${label}: ${value}`;
return (
<span className="logs-label">
<span className="logs-label__value" title={tooltip}>
{value}
</span>
{!plain && (
<span title="Filter for label" onClick={this.onClickLabel} className="logs-label__icon fa fa-search-plus" />
)}
{!plain && getRows && <span onClick={this.onClickStats} className="logs-label__icon fa fa-signal" />}
{showStats && (
<span className="logs-label__stats">
<Stats
stats={stats}
rowCount={getRows().length}
label={label}
value={value}
onClickClose={this.onClickClose}
/>
</span>
)}
</span>
);
}
} }
export default class LogLabels extends PureComponent<{ export class LogLabels extends PureComponent<Props> {
getRows?: () => LogRowModel[];
labels: LogsStreamLabels;
plain?: boolean;
onClickLabel?: (label: string, value: string) => void;
}> {
render() { render() {
const { getRows, labels, onClickLabel, plain } = this.props; const { getRows, labels, onClickLabel, plain } = this.props;
return Object.keys(labels).map(key => ( return Object.keys(labels).map(key => (
<Label key={key} getRows={getRows} label={key} value={labels[key]} plain={plain} onClickLabel={onClickLabel} /> <LogLabel key={key} getRows={getRows} label={key} value={labels[key]} plain={plain} onClickLabel={onClickLabel} />
)); ));
} }
} }
...@@ -3,11 +3,12 @@ import _ from 'lodash'; ...@@ -3,11 +3,12 @@ import _ from 'lodash';
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
import classnames from 'classnames'; import classnames from 'classnames';
import { LogRowModel, LogsLabelStat, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model'; import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
import LogLabels, { Stats } from './LogLabels'; import { LogLabels } from './LogLabels';
import { findHighlightChunksInText } from 'app/core/utils/text'; import { findHighlightChunksInText } from 'app/core/utils/text';
import { LogLabelStats } from './LogLabelStats';
interface RowProps { interface Props {
highlighterExpressions?: string[]; highlighterExpressions?: string[];
row: LogRowModel; row: LogRowModel;
showDuplicates: boolean; showDuplicates: boolean;
...@@ -18,10 +19,10 @@ interface RowProps { ...@@ -18,10 +19,10 @@ interface RowProps {
onClickLabel?: (label: string, value: string) => void; onClickLabel?: (label: string, value: string) => void;
} }
interface RowState { interface State {
fieldCount: number; fieldCount: number;
fieldLabel: string; fieldLabel: string;
fieldStats: LogsLabelStat[]; fieldStats: LogLabelStatsModel[];
fieldValue: string; fieldValue: string;
parsed: boolean; parsed: boolean;
parser?: LogsParser; parser?: LogsParser;
...@@ -49,7 +50,7 @@ const FieldHighlight = onClick => props => { ...@@ -49,7 +50,7 @@ const FieldHighlight = onClick => props => {
* Once a parser is found, it will determine fields, that will be highlighted. * Once a parser is found, it will determine fields, that will be highlighted.
* When the user requests stats for a field, they will be calculated and rendered below the row. * When the user requests stats for a field, they will be calculated and rendered below the row.
*/ */
export class LogRow extends PureComponent<RowProps, RowState> { export class LogRow extends PureComponent<Props, State> {
mouseMessageTimer: NodeJS.Timer; mouseMessageTimer: NodeJS.Timer;
state = { state = {
...@@ -177,7 +178,7 @@ export class LogRow extends PureComponent<RowProps, RowState> { ...@@ -177,7 +178,7 @@ export class LogRow extends PureComponent<RowProps, RowState> {
{!parsed && !needsHighlighter && row.entry} {!parsed && !needsHighlighter && row.entry}
{showFieldStats && ( {showFieldStats && (
<div className="logs-row__stats"> <div className="logs-row__stats">
<Stats <LogLabelStats
stats={fieldStats} stats={fieldStats}
label={fieldLabel} label={fieldLabel}
value={fieldValue} value={fieldValue}
......
...@@ -17,7 +17,7 @@ import { Switch } from 'app/core/components/Switch/Switch'; ...@@ -17,7 +17,7 @@ import { Switch } from 'app/core/components/Switch/Switch';
import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
import Graph from './Graph'; import Graph from './Graph';
import LogLabels from './LogLabels'; import { LogLabels } from './LogLabels';
import { LogRow } from './LogRow'; import { LogRow } from './LogRow';
const PREVIEW_LIMIT = 100; const PREVIEW_LIMIT = 100;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment