mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Splitted up LogLabels into LogLabelStats and LogLabel
This commit is contained in:
parent
23202ab130
commit
c369279401
@ -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 } = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
|||||||
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;
|
||||||
|
74
public/app/features/explore/LogLabel.tsx
Normal file
74
public/app/features/explore/LogLabel.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
72
public/app/features/explore/LogLabelStats.tsx
Normal file
72
public/app/features/explore/LogLabelStats.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,147 +1,20 @@
|
|||||||
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[];
|
|
||||||
label: string;
|
|
||||||
plain?: boolean;
|
|
||||||
value: string;
|
|
||||||
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<{
|
|
||||||
getRows?: () => LogRowModel[];
|
getRows?: () => LogRowModel[];
|
||||||
labels: LogsStreamLabels;
|
labels: LogsStreamLabels;
|
||||||
plain?: boolean;
|
plain?: boolean;
|
||||||
onClickLabel?: (label: string, value: string) => void;
|
onClickLabel?: (label: string, value: string) => void;
|
||||||
}> {
|
}
|
||||||
|
|
||||||
|
export class LogLabels extends PureComponent<Props> {
|
||||||
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';
|
|||||||
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 {
|
|||||||
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 => {
|
|||||||
* 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> {
|
|||||||
{!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';
|
|||||||
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;
|
||||||
|
Loading…
Reference in New Issue
Block a user