mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Logging render performance
- moved from grid to flexbox - calculate Explore results only when query transactions change to prevent expensive re-renders - split up rendering of graph and log data - render log results in 2 stages
This commit is contained in:
@@ -16,7 +16,7 @@ const DEFAULT_EXPLORE_STATE: ExploreState = {
|
|||||||
datasourceMissing: false,
|
datasourceMissing: false,
|
||||||
datasourceName: '',
|
datasourceName: '',
|
||||||
exploreDatasources: [],
|
exploreDatasources: [],
|
||||||
graphRange: DEFAULT_RANGE,
|
graphInterval: 1000,
|
||||||
history: [],
|
history: [],
|
||||||
initialQueries: [],
|
initialQueries: [],
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
|
|||||||
@@ -50,6 +50,35 @@ interface ExploreProps {
|
|||||||
urlState: ExploreUrlState;
|
urlState: ExploreUrlState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calulcateResultsFromQueryTransactions(
|
||||||
|
queryTransactions: QueryTransaction[],
|
||||||
|
datasource: any,
|
||||||
|
graphInterval: number
|
||||||
|
) {
|
||||||
|
const graphResult = _.flatten(
|
||||||
|
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
|
||||||
|
);
|
||||||
|
const tableResult = mergeTablesIntoModel(
|
||||||
|
new TableModel(),
|
||||||
|
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
|
||||||
|
);
|
||||||
|
const logsResult =
|
||||||
|
datasource && datasource.mergeStreams
|
||||||
|
? datasource.mergeStreams(
|
||||||
|
_.flatten(
|
||||||
|
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
|
||||||
|
),
|
||||||
|
graphInterval
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
graphResult,
|
||||||
|
tableResult,
|
||||||
|
logsResult,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Explore provides an area for quick query iteration for a given datasource.
|
* Explore provides an area for quick query iteration for a given datasource.
|
||||||
* Once a datasource is selected it populates the query section at the top.
|
* Once a datasource is selected it populates the query section at the top.
|
||||||
@@ -122,9 +151,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
datasourceMissing: false,
|
datasourceMissing: false,
|
||||||
datasourceName: datasource,
|
datasourceName: datasource,
|
||||||
exploreDatasources: [],
|
exploreDatasources: [],
|
||||||
graphRange: initialRange,
|
graphInterval: 15 * 1000,
|
||||||
|
graphResult: [],
|
||||||
initialQueries,
|
initialQueries,
|
||||||
history: [],
|
history: [],
|
||||||
|
logsResult: null,
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
range: initialRange,
|
range: initialRange,
|
||||||
scanning: false,
|
scanning: false,
|
||||||
@@ -135,6 +166,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
supportsGraph: null,
|
supportsGraph: null,
|
||||||
supportsLogs: null,
|
supportsLogs: null,
|
||||||
supportsTable: null,
|
supportsTable: null,
|
||||||
|
tableResult: new TableModel(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.modifiedQueries = initialQueries.slice();
|
this.modifiedQueries = initialQueries.slice();
|
||||||
@@ -176,6 +208,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setDatasource(datasource: any, origin?: DataSource) {
|
async setDatasource(datasource: any, origin?: DataSource) {
|
||||||
|
const { initialQueries, range } = this.state;
|
||||||
|
|
||||||
const supportsGraph = datasource.meta.metrics;
|
const supportsGraph = datasource.meta.metrics;
|
||||||
const supportsLogs = datasource.meta.logs;
|
const supportsLogs = datasource.meta.logs;
|
||||||
const supportsTable = datasource.meta.metrics;
|
const supportsTable = datasource.meta.metrics;
|
||||||
@@ -220,7 +254,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset edit state with new queries
|
// Reset edit state with new queries
|
||||||
const nextQueries = this.state.initialQueries.map((q, i) => ({
|
const nextQueries = initialQueries.map((q, i) => ({
|
||||||
...modifiedQueries[i],
|
...modifiedQueries[i],
|
||||||
...generateQueryKeys(i),
|
...generateQueryKeys(i),
|
||||||
}));
|
}));
|
||||||
@@ -229,11 +263,15 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
// Custom components
|
// Custom components
|
||||||
const StartPage = datasource.pluginExports.ExploreStartPage;
|
const StartPage = datasource.pluginExports.ExploreStartPage;
|
||||||
|
|
||||||
|
// Calculate graph bucketing interval
|
||||||
|
const graphInterval = getIntervals(range, datasource, this.el ? this.el.offsetWidth : 0).intervalMs;
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
StartPage,
|
StartPage,
|
||||||
datasource,
|
datasource,
|
||||||
datasourceError,
|
datasourceError,
|
||||||
|
graphInterval,
|
||||||
history,
|
history,
|
||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
@@ -414,12 +452,19 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
this.setState(
|
this.setState(
|
||||||
state => {
|
state => {
|
||||||
const showingTable = !state.showingTable;
|
const showingTable = !state.showingTable;
|
||||||
let nextQueryTransactions = state.queryTransactions;
|
if (showingTable) {
|
||||||
if (!showingTable) {
|
return { showingTable, queryTransactions: state.queryTransactions };
|
||||||
// Discard transactions related to Table query
|
|
||||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
|
|
||||||
}
|
}
|
||||||
return { queryTransactions: nextQueryTransactions, showingTable };
|
|
||||||
|
// Toggle off needs discarding of table queries
|
||||||
|
const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
|
||||||
|
const results = calulcateResultsFromQueryTransactions(
|
||||||
|
nextQueryTransactions,
|
||||||
|
state.datasource,
|
||||||
|
state.graphInterval
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...results, queryTransactions: nextQueryTransactions, showingTable };
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if (this.state.showingTable) {
|
if (this.state.showingTable) {
|
||||||
@@ -500,8 +545,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
|
|
||||||
// Discard transactions related to row query
|
// Discard transactions related to row query
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
||||||
|
const results = calulcateResultsFromQueryTransactions(
|
||||||
|
nextQueryTransactions,
|
||||||
|
state.datasource,
|
||||||
|
state.graphInterval
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...results,
|
||||||
initialQueries: nextQueries,
|
initialQueries: nextQueries,
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
};
|
};
|
||||||
@@ -609,7 +660,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
// Append new transaction
|
// Append new transaction
|
||||||
const nextQueryTransactions = [...remainingTransactions, transaction];
|
const nextQueryTransactions = [...remainingTransactions, transaction];
|
||||||
|
|
||||||
|
const results = calulcateResultsFromQueryTransactions(
|
||||||
|
nextQueryTransactions,
|
||||||
|
state.datasource,
|
||||||
|
state.graphInterval
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...results,
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
showingStartPage: false,
|
showingStartPage: false,
|
||||||
};
|
};
|
||||||
@@ -660,6 +718,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
return qt;
|
return qt;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const results = calulcateResultsFromQueryTransactions(
|
||||||
|
nextQueryTransactions,
|
||||||
|
state.datasource,
|
||||||
|
state.graphInterval
|
||||||
|
);
|
||||||
|
|
||||||
const nextHistory = updateHistory(history, datasourceId, queries);
|
const nextHistory = updateHistory(history, datasourceId, queries);
|
||||||
|
|
||||||
// Keep scanning for results if this was the last scanning transaction
|
// Keep scanning for results if this was the last scanning transaction
|
||||||
@@ -671,19 +735,13 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...results,
|
||||||
history: nextHistory,
|
history: nextHistory,
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
discardTransactions(rowIndex: number) {
|
|
||||||
this.setState(state => {
|
|
||||||
const remainingTransactions = state.queryTransactions.filter(qt => qt.rowIndex !== rowIndex);
|
|
||||||
return { queryTransactions: remainingTransactions };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
failQueryTransaction(transactionId: string, response: any, datasourceId: string) {
|
failQueryTransaction(transactionId: string, response: any, datasourceId: string) {
|
||||||
const { datasource } = this.state;
|
const { datasource } = this.state;
|
||||||
if (datasource.meta.id !== datasourceId || response.cancelled) {
|
if (datasource.meta.id !== datasourceId || response.cancelled) {
|
||||||
@@ -746,7 +804,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
const latency = Date.now() - now;
|
const latency = Date.now() - now;
|
||||||
const results = resultGetter ? resultGetter(res.data) : res.data;
|
const results = resultGetter ? resultGetter(res.data) : res.data;
|
||||||
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
||||||
this.setState({ graphRange: transaction.options.range });
|
|
||||||
} catch (response) {
|
} catch (response) {
|
||||||
this.failQueryTransaction(transaction.id, response, datasourceId);
|
this.failQueryTransaction(transaction.id, response, datasourceId);
|
||||||
}
|
}
|
||||||
@@ -776,9 +833,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
exploreDatasources,
|
exploreDatasources,
|
||||||
graphRange,
|
graphResult,
|
||||||
history,
|
history,
|
||||||
initialQueries,
|
initialQueries,
|
||||||
|
logsResult,
|
||||||
queryTransactions,
|
queryTransactions,
|
||||||
range,
|
range,
|
||||||
scanning,
|
scanning,
|
||||||
@@ -790,31 +848,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
|
tableResult,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const graphHeight = showingGraph && showingTable ? '200px' : '400px';
|
const graphHeight = showingGraph && showingTable ? '200px' : '400px';
|
||||||
const exploreClass = split ? 'explore explore-split' : 'explore';
|
const exploreClass = split ? 'explore explore-split' : 'explore';
|
||||||
const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
|
const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
|
||||||
const graphRangeIntervals = getIntervals(graphRange, datasource, this.el ? this.el.offsetWidth : 0);
|
|
||||||
const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
|
const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
|
||||||
const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
|
const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
|
||||||
const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
|
const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
|
||||||
// TODO don't recreate those on each re-render
|
|
||||||
const graphResult = _.flatten(
|
|
||||||
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
|
|
||||||
);
|
|
||||||
const tableResult = mergeTablesIntoModel(
|
|
||||||
new TableModel(),
|
|
||||||
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
|
|
||||||
);
|
|
||||||
const logsResult =
|
|
||||||
datasource && datasource.mergeStreams
|
|
||||||
? datasource.mergeStreams(
|
|
||||||
_.flatten(
|
|
||||||
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
|
|
||||||
),
|
|
||||||
graphRangeIntervals.intervalMs
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
const loading = queryTransactions.some(qt => !qt.done);
|
const loading = queryTransactions.some(qt => !qt.done);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -919,7 +960,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
height={graphHeight}
|
height={graphHeight}
|
||||||
id={`explore-graph-${position}`}
|
id={`explore-graph-${position}`}
|
||||||
onChangeTime={this.onChangeTime}
|
onChangeTime={this.onChangeTime}
|
||||||
range={graphRange}
|
range={range}
|
||||||
split={split}
|
split={split}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { Fragment, PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import Highlighter from 'react-highlight-words';
|
import Highlighter from 'react-highlight-words';
|
||||||
|
|
||||||
import * as rangeUtil from 'app/core/utils/rangeutil';
|
import * as rangeUtil from 'app/core/utils/rangeutil';
|
||||||
@@ -12,12 +12,15 @@ import {
|
|||||||
LogLevel,
|
LogLevel,
|
||||||
LogsStreamLabels,
|
LogsStreamLabels,
|
||||||
LogsMetaKind,
|
LogsMetaKind,
|
||||||
|
LogRow,
|
||||||
} from 'app/core/logs_model';
|
} from 'app/core/logs_model';
|
||||||
import { findHighlightChunksInText } from 'app/core/utils/text';
|
import { findHighlightChunksInText } from 'app/core/utils/text';
|
||||||
import { Switch } from 'app/core/components/Switch/Switch';
|
import { Switch } from 'app/core/components/Switch/Switch';
|
||||||
|
|
||||||
import Graph from './Graph';
|
import Graph from './Graph';
|
||||||
|
|
||||||
|
const RENDER_LIMIT = 100;
|
||||||
|
|
||||||
const graphOptions = {
|
const graphOptions = {
|
||||||
series: {
|
series: {
|
||||||
bars: {
|
bars: {
|
||||||
@@ -77,6 +80,58 @@ class Labels extends PureComponent<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
row: LogRow;
|
||||||
|
showLabels: boolean | null; // Tristate: null means auto
|
||||||
|
showLocalTime: boolean;
|
||||||
|
showUtc: boolean;
|
||||||
|
onClickLabel?: (label: string, value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row({ onClickLabel, row, showLabels, showLocalTime, showUtc }: RowProps) {
|
||||||
|
const needsHighlighter = row.searchWords && row.searchWords.length > 0;
|
||||||
|
return (
|
||||||
|
<div className="logs-row">
|
||||||
|
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''}>
|
||||||
|
{row.duplicates > 0 && (
|
||||||
|
<div className="logs-row-level__duplicates" title={`${row.duplicates} duplicates`}>
|
||||||
|
{Array.apply(null, { length: row.duplicates }).map((bogus, index) => (
|
||||||
|
<div className="logs-row-level__duplicate" key={`${index}`} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{showUtc && (
|
||||||
|
<div className="logs-row-time" title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>
|
||||||
|
{row.timestamp}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showLocalTime && (
|
||||||
|
<div className="logs-row-time" title={`${row.timestamp} (${row.timeFromNow})`}>
|
||||||
|
{row.timeLocal}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showLabels && (
|
||||||
|
<div className="logs-row-labels">
|
||||||
|
<Labels labels={row.uniqueLabels} onClickLabel={onClickLabel} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="logs-row-message">
|
||||||
|
{needsHighlighter ? (
|
||||||
|
<Highlighter
|
||||||
|
textToHighlight={row.entry}
|
||||||
|
searchWords={row.searchWords}
|
||||||
|
findChunks={findHighlightChunksInText}
|
||||||
|
highlightClassName="logs-row-match-highlight"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
row.entry
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface LogsProps {
|
interface LogsProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
data: LogsModel;
|
data: LogsModel;
|
||||||
@@ -93,21 +148,51 @@ interface LogsProps {
|
|||||||
|
|
||||||
interface LogsState {
|
interface LogsState {
|
||||||
dedup: LogsDedupStrategy;
|
dedup: LogsDedupStrategy;
|
||||||
|
deferLogs: boolean;
|
||||||
hiddenLogLevels: Set<LogLevel>;
|
hiddenLogLevels: Set<LogLevel>;
|
||||||
|
renderAll: boolean;
|
||||||
showLabels: boolean | null; // Tristate: null means auto
|
showLabels: boolean | null; // Tristate: null means auto
|
||||||
showLocalTime: boolean;
|
showLocalTime: boolean;
|
||||||
showUtc: boolean;
|
showUtc: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Logs extends PureComponent<LogsProps, LogsState> {
|
export default class Logs extends PureComponent<LogsProps, LogsState> {
|
||||||
|
deferLogsTimer: NodeJS.Timer;
|
||||||
|
renderAllTimer: NodeJS.Timer;
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
dedup: LogsDedupStrategy.none,
|
dedup: LogsDedupStrategy.none,
|
||||||
|
deferLogs: true,
|
||||||
hiddenLogLevels: new Set(),
|
hiddenLogLevels: new Set(),
|
||||||
|
renderAll: false,
|
||||||
showLabels: null,
|
showLabels: null,
|
||||||
showLocalTime: true,
|
showLocalTime: true,
|
||||||
showUtc: false,
|
showUtc: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
// Reset to render minimal only
|
||||||
|
if (nextProps.data !== this.props.data) {
|
||||||
|
this.setState({ deferLogs: true, renderAll: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
// Staged rendering
|
||||||
|
if (prevProps.data !== this.props.data && this.state.deferLogs) {
|
||||||
|
clearTimeout(this.deferLogsTimer);
|
||||||
|
this.deferLogsTimer = setTimeout(() => this.setState({ deferLogs: false }), 1000);
|
||||||
|
} else if (prevState.deferLogs && !this.state.deferLogs) {
|
||||||
|
clearTimeout(this.renderAllTimer);
|
||||||
|
this.renderAllTimer = setTimeout(() => this.setState({ renderAll: true }), 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearTimeout(this.deferLogsTimer);
|
||||||
|
clearTimeout(this.renderAllTimer);
|
||||||
|
}
|
||||||
|
|
||||||
onChangeDedup = (dedup: LogsDedupStrategy) => {
|
onChangeDedup = (dedup: LogsDedupStrategy) => {
|
||||||
this.setState(prevState => {
|
this.setState(prevState => {
|
||||||
if (prevState.dedup === dedup) {
|
if (prevState.dedup === dedup) {
|
||||||
@@ -155,7 +240,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className = '', data, loading = false, onClickLabel, position, range, scanning, scanRange } = this.props;
|
const { className = '', data, loading = false, onClickLabel, position, range, scanning, scanRange } = this.props;
|
||||||
const { dedup, hiddenLogLevels, showLocalTime, showUtc } = this.state;
|
const { dedup, deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc } = this.state;
|
||||||
let { showLabels } = this.state;
|
let { showLabels } = this.state;
|
||||||
const hasData = data && data.rows && data.rows.length > 0;
|
const hasData = data && data.rows && data.rows.length > 0;
|
||||||
|
|
||||||
@@ -172,26 +257,19 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Staged rendering
|
||||||
|
const firstRows = dedupedData.rows.slice(0, RENDER_LIMIT);
|
||||||
|
const lastRows = dedupedData.rows.slice(RENDER_LIMIT);
|
||||||
|
|
||||||
// Check for labels
|
// Check for labels
|
||||||
if (showLabels === null && hasData) {
|
if (showLabels === null) {
|
||||||
showLabels = data.rows.some(row => _.size(row.uniqueLabels) > 0);
|
if (hasData) {
|
||||||
|
showLabels = data.rows.some(row => _.size(row.uniqueLabels) > 0);
|
||||||
|
} else {
|
||||||
|
showLabels = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid options
|
|
||||||
const cssColumnSizes = ['3px']; // Log-level indicator line
|
|
||||||
if (showUtc) {
|
|
||||||
cssColumnSizes.push('minmax(100px, max-content)');
|
|
||||||
}
|
|
||||||
if (showLocalTime) {
|
|
||||||
cssColumnSizes.push('minmax(100px, max-content)');
|
|
||||||
}
|
|
||||||
if (showLabels) {
|
|
||||||
cssColumnSizes.push('minmax(100px, 25%)');
|
|
||||||
}
|
|
||||||
cssColumnSizes.push('1fr');
|
|
||||||
const logEntriesStyle = {
|
|
||||||
gridTemplateColumns: cssColumnSizes.join(' '),
|
|
||||||
};
|
|
||||||
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
|
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -251,36 +329,33 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="logs-entries" style={logEntriesStyle}>
|
<div className="logs-entries">
|
||||||
{hasData &&
|
{hasData &&
|
||||||
dedupedData.rows.map(row => (
|
!deferLogs &&
|
||||||
<Fragment key={row.key + row.duplicates}>
|
firstRows.map(row => (
|
||||||
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''}>
|
<Row
|
||||||
{row.duplicates > 0 && (
|
key={row.key + row.duplicates}
|
||||||
<div className="logs-row-level__duplicates" title={`${row.duplicates} duplicates`}>
|
row={row}
|
||||||
{Array.apply(null, { length: row.duplicates }).map((bogus, index) => (
|
showLabels={showLabels}
|
||||||
<div className="logs-row-level__duplicate" key={`${index}`} />
|
showLocalTime={showLocalTime}
|
||||||
))}
|
showUtc={showUtc}
|
||||||
</div>
|
onClickLabel={onClickLabel}
|
||||||
)}
|
/>
|
||||||
</div>
|
|
||||||
{showUtc && <div title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>{row.timestamp}</div>}
|
|
||||||
{showLocalTime && <div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>}
|
|
||||||
{showLabels && (
|
|
||||||
<div className="logs-row-labels">
|
|
||||||
<Labels labels={row.uniqueLabels} onClickLabel={onClickLabel} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<Highlighter
|
|
||||||
textToHighlight={row.entry}
|
|
||||||
searchWords={row.searchWords}
|
|
||||||
findChunks={findHighlightChunksInText}
|
|
||||||
highlightClassName="logs-row-match-highlight"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
))}
|
||||||
|
{hasData &&
|
||||||
|
!deferLogs &&
|
||||||
|
renderAll &&
|
||||||
|
lastRows.map(row => (
|
||||||
|
<Row
|
||||||
|
key={row.key + row.duplicates}
|
||||||
|
row={row}
|
||||||
|
showLabels={showLabels}
|
||||||
|
showLocalTime={showLocalTime}
|
||||||
|
showUtc={showUtc}
|
||||||
|
onClickLabel={onClickLabel}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{hasData && deferLogs && <span>Rendering {dedupedData.rows.length} rows...</span>}
|
||||||
</div>
|
</div>
|
||||||
{!loading &&
|
{!loading &&
|
||||||
!hasData &&
|
!hasData &&
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Value } from 'slate';
|
import { Value } from 'slate';
|
||||||
|
|
||||||
import { DataQuery, RawTimeRange } from './series';
|
import { DataQuery, RawTimeRange } from './series';
|
||||||
|
import TableModel from 'app/core/table_model';
|
||||||
|
import { LogsModel } from 'app/core/logs_model';
|
||||||
|
|
||||||
export interface CompletionItem {
|
export interface CompletionItem {
|
||||||
/**
|
/**
|
||||||
@@ -158,9 +160,11 @@ export interface ExploreState {
|
|||||||
datasourceMissing: boolean;
|
datasourceMissing: boolean;
|
||||||
datasourceName?: string;
|
datasourceName?: string;
|
||||||
exploreDatasources: ExploreDatasource[];
|
exploreDatasources: ExploreDatasource[];
|
||||||
graphRange: RawTimeRange;
|
graphInterval: number; // in ms
|
||||||
|
graphResult?: any[];
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
initialQueries: DataQuery[];
|
initialQueries: DataQuery[];
|
||||||
|
logsResult?: LogsModel;
|
||||||
queryTransactions: QueryTransaction[];
|
queryTransactions: QueryTransaction[];
|
||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
scanning?: boolean;
|
scanning?: boolean;
|
||||||
@@ -172,6 +176,7 @@ export interface ExploreState {
|
|||||||
supportsGraph: boolean | null;
|
supportsGraph: boolean | null;
|
||||||
supportsLogs: boolean | null;
|
supportsLogs: boolean | null;
|
||||||
supportsTable: boolean | null;
|
supportsTable: boolean | null;
|
||||||
|
tableResult?: TableModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExploreUrlState {
|
export interface ExploreUrlState {
|
||||||
|
|||||||
@@ -244,15 +244,6 @@
|
|||||||
|
|
||||||
.explore {
|
.explore {
|
||||||
.logs {
|
.logs {
|
||||||
.logs-entries {
|
|
||||||
display: grid;
|
|
||||||
grid-column-gap: 1rem;
|
|
||||||
grid-row-gap: 0.1rem;
|
|
||||||
grid-template-columns: 4px minmax(100px, max-content) minmax(100px, 25%) 1fr;
|
|
||||||
font-family: $font-family-monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs-controls {
|
.logs-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: $page-bg;
|
background-color: $page-bg;
|
||||||
@@ -302,6 +293,32 @@
|
|||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs-entries {
|
||||||
|
font-family: $font-family-monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
> div + div {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-row-level {
|
||||||
|
width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-row-labels {
|
||||||
|
flex: 0 0 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-row-message {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.logs-row-match-highlight {
|
.logs-row-match-highlight {
|
||||||
// Undoing mark styling
|
// Undoing mark styling
|
||||||
background: inherit;
|
background: inherit;
|
||||||
|
|||||||
Reference in New Issue
Block a user