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;
|
||||
}
|
||||
|
||||
export interface LogsLabelStat {
|
||||
export interface LogLabelStatsModel {
|
||||
active?: boolean;
|
||||
count: 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
|
||||
const rowsWithField = rows.filter(row => extractor.test(row.entry));
|
||||
const rowCount = rowsWithField.length;
|
||||
@ -204,7 +204,7 @@ export function calculateFieldStats(rows: LogRowModel[], extractor: RegExp): Log
|
||||
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
|
||||
const rowsWithLabel = rows.filter(row => row.labels[label] !== undefined);
|
||||
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 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) {
|
||||
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<{
|
||||
interface Props {
|
||||
getRows?: () => LogRowModel[];
|
||||
labels: LogsStreamLabels;
|
||||
plain?: boolean;
|
||||
onClickLabel?: (label: string, value: string) => void;
|
||||
}> {
|
||||
}
|
||||
|
||||
export class LogLabels extends PureComponent<Props> {
|
||||
render() {
|
||||
const { getRows, labels, onClickLabel, plain } = this.props;
|
||||
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 classnames from 'classnames';
|
||||
|
||||
import { LogRowModel, LogsLabelStat, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
|
||||
import LogLabels, { Stats } from './LogLabels';
|
||||
import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
|
||||
import { LogLabels } from './LogLabels';
|
||||
import { findHighlightChunksInText } from 'app/core/utils/text';
|
||||
import { LogLabelStats } from './LogLabelStats';
|
||||
|
||||
interface RowProps {
|
||||
interface Props {
|
||||
highlighterExpressions?: string[];
|
||||
row: LogRowModel;
|
||||
showDuplicates: boolean;
|
||||
@ -18,10 +19,10 @@ interface RowProps {
|
||||
onClickLabel?: (label: string, value: string) => void;
|
||||
}
|
||||
|
||||
interface RowState {
|
||||
interface State {
|
||||
fieldCount: number;
|
||||
fieldLabel: string;
|
||||
fieldStats: LogsLabelStat[];
|
||||
fieldStats: LogLabelStatsModel[];
|
||||
fieldValue: string;
|
||||
parsed: boolean;
|
||||
parser?: LogsParser;
|
||||
@ -49,7 +50,7 @@ const FieldHighlight = onClick => props => {
|
||||
* 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.
|
||||
*/
|
||||
export class LogRow extends PureComponent<RowProps, RowState> {
|
||||
export class LogRow extends PureComponent<Props, State> {
|
||||
mouseMessageTimer: NodeJS.Timer;
|
||||
|
||||
state = {
|
||||
@ -177,7 +178,7 @@ export class LogRow extends PureComponent<RowProps, RowState> {
|
||||
{!parsed && !needsHighlighter && row.entry}
|
||||
{showFieldStats && (
|
||||
<div className="logs-row__stats">
|
||||
<Stats
|
||||
<LogLabelStats
|
||||
stats={fieldStats}
|
||||
label={fieldLabel}
|
||||
value={fieldValue}
|
||||
|
@ -17,7 +17,7 @@ import { Switch } from 'app/core/components/Switch/Switch';
|
||||
import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
|
||||
|
||||
import Graph from './Graph';
|
||||
import LogLabels from './LogLabels';
|
||||
import { LogLabels } from './LogLabels';
|
||||
import { LogRow } from './LogRow';
|
||||
|
||||
const PREVIEW_LIMIT = 100;
|
||||
|
Loading…
Reference in New Issue
Block a user