grafana/public/app/features/explore/Logs.tsx
Hugo Häggmark 4b3440325e
Explore: Replaces TimeSeries with GraphSeriesXY (#18475)
* Wip: Compiles and runs

* WIP: Logs Graph partially working

* Refactor: Adds GraphSeriesToggler

* Refactor: Adds tickDecimals to YAxis

* Refactor: Adds TimeZone and PlotSelection to Graph

* Refactor: Makes the graphResult work in Explore

* Refactor: Adds ExploreGraphPanel that is used by Logs and Explore

* Fix: Fixes strange behaviour with ExploreMode not beeing changed

* Fix: Adds onSelectionChanged to GraphWithLegend

* Refactor: Cleans up unused comments

* ExploreGraph: Disable colorpicker
2019-08-13 07:32:43 +02:00

279 lines
8.4 KiB
TypeScript

import _ from 'lodash';
import React, { PureComponent } from 'react';
import { rangeUtil } from '@grafana/data';
import { Switch } from '@grafana/ui';
import {
RawTimeRange,
LogLevel,
TimeZone,
AbsoluteTimeRange,
LogsMetaKind,
LogsModel,
LogsDedupStrategy,
LogRowModel,
} from '@grafana/data';
import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
import { LogLabels } from './LogLabels';
import { LogRow } from './LogRow';
import { LogsDedupDescription } from 'app/core/logs_model';
import ExploreGraphPanel from './ExploreGraphPanel';
import { ExploreId } from 'app/types';
const PREVIEW_LIMIT = 100;
function renderMetaItem(value: any, kind: LogsMetaKind) {
if (kind === LogsMetaKind.LabelsMap) {
return (
<span className="logs-meta-item__labels">
<LogLabels labels={value} plain />
</span>
);
}
return value;
}
interface Props {
data?: LogsModel;
dedupedData?: LogsModel;
width: number;
exploreId: ExploreId;
highlighterExpressions: string[];
loading: boolean;
absoluteRange: AbsoluteTimeRange;
timeZone: TimeZone;
scanning?: boolean;
scanRange?: RawTimeRange;
dedupStrategy: LogsDedupStrategy;
hiddenLogLevels: Set<LogLevel>;
onChangeTime?: (range: AbsoluteTimeRange) => void;
onClickLabel?: (label: string, value: string) => void;
onStartScanning?: () => void;
onStopScanning?: () => void;
onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
onToggleLogLevel: (hiddenLogLevels: LogLevel[]) => void;
getRowContext?: (row: LogRowModel, options?: any) => Promise<any>;
}
interface State {
deferLogs: boolean;
renderAll: boolean;
showLabels: boolean;
showTime: boolean;
}
export default class Logs extends PureComponent<Props, State> {
deferLogsTimer: NodeJS.Timer;
renderAllTimer: NodeJS.Timer;
state = {
deferLogs: true,
renderAll: false,
showLabels: false,
showTime: true,
};
componentDidMount() {
// Staged rendering
if (this.state.deferLogs) {
const { data } = this.props;
const rowCount = data && data.rows ? data.rows.length : 0;
// Render all right away if not too far over the limit
const renderAll = rowCount <= PREVIEW_LIMIT * 2;
this.deferLogsTimer = setTimeout(() => this.setState({ deferLogs: false, renderAll }), rowCount);
}
}
componentDidUpdate(prevProps: Props, prevState: State) {
// Staged rendering
if (prevState.deferLogs && !this.state.deferLogs && !this.state.renderAll) {
this.renderAllTimer = setTimeout(() => this.setState({ renderAll: true }), 2000);
}
}
componentWillUnmount() {
clearTimeout(this.deferLogsTimer);
clearTimeout(this.renderAllTimer);
}
onChangeDedup = (dedup: LogsDedupStrategy) => {
const { onDedupStrategyChange } = this.props;
if (this.props.dedupStrategy === dedup) {
return onDedupStrategyChange(LogsDedupStrategy.none);
}
return onDedupStrategyChange(dedup);
};
onChangeLabels = (event: React.SyntheticEvent) => {
const target = event.target as HTMLInputElement;
this.setState({
showLabels: target.checked,
});
};
onChangeTime = (event: React.SyntheticEvent) => {
const target = event.target as HTMLInputElement;
this.setState({
showTime: target.checked,
});
};
onToggleLogLevel = (hiddenRawLevels: string[]) => {
const hiddenLogLevels: LogLevel[] = hiddenRawLevels.map((level: LogLevel) => LogLevel[level]);
this.props.onToggleLogLevel(hiddenLogLevels);
};
onClickScan = (event: React.SyntheticEvent) => {
event.preventDefault();
this.props.onStartScanning();
};
onClickStopScan = (event: React.SyntheticEvent) => {
event.preventDefault();
this.props.onStopScanning();
};
render() {
const {
data,
exploreId,
highlighterExpressions,
loading = false,
onClickLabel,
timeZone,
scanning,
scanRange,
width,
dedupedData,
} = this.props;
if (!data) {
return null;
}
const { deferLogs, renderAll, showLabels, showTime } = this.state;
const { dedupStrategy } = this.props;
const hasData = data && data.rows && data.rows.length > 0;
const hasLabel = hasData && dedupedData.hasUniqueLabels;
const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
const showDuplicates = dedupStrategy !== LogsDedupStrategy.none && dedupCount > 0;
const meta = data.meta ? [...data.meta] : [];
if (dedupStrategy !== LogsDedupStrategy.none) {
meta.push({
label: 'Dedup count',
value: dedupCount,
kind: LogsMetaKind.Number,
});
}
// Staged rendering
const processedRows = dedupedData.rows;
const firstRows = processedRows.slice(0, PREVIEW_LIMIT);
const lastRows = processedRows.slice(PREVIEW_LIMIT);
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
// React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
const getRows = () => processedRows;
return (
<div className="logs-panel">
<div className="logs-panel-graph">
<ExploreGraphPanel
exploreId={exploreId}
series={data.series}
width={width}
onHiddenSeriesChanged={this.onToggleLogLevel}
/>
</div>
<div className="logs-panel-options">
<div className="logs-panel-controls">
<Switch label="Time" checked={showTime} onChange={this.onChangeTime} transparent />
<Switch label="Labels" checked={showLabels} onChange={this.onChangeLabels} transparent />
<ToggleButtonGroup label="Dedup" transparent={true}>
{Object.keys(LogsDedupStrategy).map((dedupType: string, i) => (
<ToggleButton
key={i}
value={dedupType}
onChange={this.onChangeDedup}
selected={dedupStrategy === dedupType}
// @ts-ignore
tooltip={LogsDedupDescription[dedupType]}
>
{dedupType}
</ToggleButton>
))}
</ToggleButtonGroup>
</div>
</div>
{hasData && meta && (
<div className="logs-panel-meta">
{meta.map(item => (
<div className="logs-panel-meta__item" key={item.label}>
<span className="logs-panel-meta__label">{item.label}:</span>
<span className="logs-panel-meta__value">{renderMetaItem(item.value, item.kind)}</span>
</div>
))}
</div>
)}
<div className="logs-rows">
{hasData &&
!deferLogs && // Only inject highlighterExpression in the first set for performance reasons
firstRows.map((row, index) => (
<LogRow
key={index}
getRows={getRows}
getRowContext={this.props.getRowContext}
highlighterExpressions={highlighterExpressions}
row={row}
showDuplicates={showDuplicates}
showLabels={showLabels && hasLabel}
showTime={showTime}
timeZone={timeZone}
onClickLabel={onClickLabel}
/>
))}
{hasData &&
!deferLogs &&
renderAll &&
lastRows.map((row, index) => (
<LogRow
key={PREVIEW_LIMIT + index}
getRows={getRows}
getRowContext={this.props.getRowContext}
row={row}
showDuplicates={showDuplicates}
showLabels={showLabels && hasLabel}
showTime={showTime}
timeZone={timeZone}
onClickLabel={onClickLabel}
/>
))}
{hasData && deferLogs && <span>Rendering {dedupedData.rows.length} rows...</span>}
</div>
{!loading && !hasData && !scanning && (
<div className="logs-panel-nodata">
No logs found.
<a className="link" onClick={this.onClickScan}>
Scan for older logs
</a>
</div>
)}
{scanning && (
<div className="logs-panel-nodata">
<span>{scanText}</span>
<a className="link" onClick={this.onClickStopScan}>
Stop scan
</a>
</div>
)}
</div>
);
}
}