mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Refactor deduplication, hiding of logs and Logs component (#33531)
* Move fitlering and deduplication to comnponent to enable future caching * Clean up LogsMetaInfo * Update * Memoize component * Fix type errors * Clean uo * Add tests for filtering in combination with deduplication
This commit is contained in:
parent
1e380e869e
commit
cf958e0b4f
@ -13,6 +13,7 @@ import {
|
||||
dedupLogRows,
|
||||
getSeriesProperties,
|
||||
logSeriesToLogsModel,
|
||||
filterLogLevels,
|
||||
LIMIT_LABEL,
|
||||
} from './logs_model';
|
||||
|
||||
@ -141,6 +142,63 @@ describe('dedupLogRows()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterLogLevels()', () => {
|
||||
test('should correctly filter out log levels', () => {
|
||||
const rows: LogRowModel[] = [
|
||||
{
|
||||
entry: 'DEBUG 1',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'ERROR 1',
|
||||
logLevel: LogLevel.error,
|
||||
},
|
||||
{
|
||||
entry: 'TRACE 1',
|
||||
logLevel: LogLevel.trace,
|
||||
},
|
||||
] as any;
|
||||
const filteredLogs = filterLogLevels(rows, new Set([LogLevel.debug]));
|
||||
expect(filteredLogs.length).toBe(2);
|
||||
expect(filteredLogs).toEqual([
|
||||
{ entry: 'ERROR 1', logLevel: 'error' },
|
||||
{ entry: 'TRACE 1', logLevel: 'trace' },
|
||||
]);
|
||||
});
|
||||
test('should correctly filter out log levels and then deduplicate', () => {
|
||||
const rows: LogRowModel[] = [
|
||||
{
|
||||
entry: 'DEBUG 1',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'DEBUG 2',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'DEBUG 2',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: 'ERROR 1',
|
||||
logLevel: LogLevel.error,
|
||||
},
|
||||
{
|
||||
entry: 'TRACE 1',
|
||||
logLevel: LogLevel.trace,
|
||||
},
|
||||
] as any;
|
||||
const filteredLogs = filterLogLevels(rows, new Set([LogLevel.error]));
|
||||
const deduplicatedLogs = dedupLogRows(filteredLogs, LogsDedupStrategy.exact);
|
||||
expect(deduplicatedLogs.length).toBe(3);
|
||||
expect(deduplicatedLogs).toEqual([
|
||||
{ duplicates: 0, entry: 'DEBUG 1', logLevel: 'debug' },
|
||||
{ duplicates: 1, entry: 'DEBUG 2', logLevel: 'debug' },
|
||||
{ duplicates: 0, entry: 'TRACE 1', logLevel: 'trace' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
const emptyLogsModel: any = {
|
||||
hasUniqueLabels: false,
|
||||
rows: [],
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
LogLevel,
|
||||
TimeZone,
|
||||
AbsoluteTimeRange,
|
||||
LogsMetaKind,
|
||||
LogsDedupStrategy,
|
||||
LogRowModel,
|
||||
LogsDedupDescription,
|
||||
@ -21,7 +20,6 @@ import {
|
||||
GrafanaTheme,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
LogLabels,
|
||||
RadioButtonGroup,
|
||||
LogRows,
|
||||
Button,
|
||||
@ -30,14 +28,12 @@ import {
|
||||
InlineSwitch,
|
||||
withTheme,
|
||||
stylesFactory,
|
||||
Icon,
|
||||
Tooltip,
|
||||
} from '@grafana/ui';
|
||||
import store from 'app/core/store';
|
||||
import { dedupLogRows, filterLogLevels } from 'app/core/logs_model';
|
||||
import { ExploreGraphPanel } from './ExploreGraphPanel';
|
||||
import { MetaInfoText } from './MetaInfoText';
|
||||
import { LogsMetaRow } from './LogsMetaRow';
|
||||
import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContextProvider';
|
||||
import { MAX_CHARACTERS } from '@grafana/ui/src/components/Logs/LogRowMessage';
|
||||
|
||||
const SETTINGS_KEYS = {
|
||||
showLabels: 'grafana.explore.logs.showLabels',
|
||||
@ -45,24 +41,10 @@ const SETTINGS_KEYS = {
|
||||
wrapLogMessage: 'grafana.explore.logs.wrapLogMessage',
|
||||
};
|
||||
|
||||
function renderMetaItem(value: any, kind: LogsMetaKind) {
|
||||
if (kind === LogsMetaKind.LabelsMap) {
|
||||
return (
|
||||
<span className="logs-meta-item__labels">
|
||||
<LogLabels labels={value} />
|
||||
</span>
|
||||
);
|
||||
} else if (kind === LogsMetaKind.Error) {
|
||||
return <span className="logs-meta-item__error">{value}</span>;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
logRows: LogRowModel[];
|
||||
logsMeta?: LogsMetaItem[];
|
||||
logsSeries?: GraphSeriesXY[];
|
||||
dedupedRows?: LogRowModel[];
|
||||
visibleRange?: AbsoluteTimeRange;
|
||||
width: number;
|
||||
theme: GrafanaTheme;
|
||||
@ -72,15 +54,12 @@ interface Props {
|
||||
timeZone: TimeZone;
|
||||
scanning?: boolean;
|
||||
scanRange?: RawTimeRange;
|
||||
dedupStrategy: LogsDedupStrategy;
|
||||
showContextToggle?: (row?: LogRowModel) => boolean;
|
||||
onChangeTime: (range: AbsoluteTimeRange) => void;
|
||||
onClickFilterLabel?: (key: string, value: string) => void;
|
||||
onClickFilterOutLabel?: (key: string, value: string) => void;
|
||||
onStartScanning?: () => void;
|
||||
onStopScanning?: () => void;
|
||||
onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
|
||||
onToggleLogLevel: (hiddenLogLevels: LogLevel[]) => void;
|
||||
getRowContext?: (row: LogRowModel, options?: RowContextOptions) => Promise<any>;
|
||||
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||
}
|
||||
@ -89,6 +68,8 @@ interface State {
|
||||
showLabels: boolean;
|
||||
showTime: boolean;
|
||||
wrapLogMessage: boolean;
|
||||
dedupStrategy: LogsDedupStrategy;
|
||||
hiddenLogLevels: LogLevel[];
|
||||
logsSortOrder: LogsSortOrder | null;
|
||||
isFlipping: boolean;
|
||||
showDetectedFields: string[];
|
||||
@ -103,6 +84,8 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
showLabels: store.getBool(SETTINGS_KEYS.showLabels, false),
|
||||
showTime: store.getBool(SETTINGS_KEYS.showTime, true),
|
||||
wrapLogMessage: store.getBool(SETTINGS_KEYS.wrapLogMessage, true),
|
||||
dedupStrategy: LogsDedupStrategy.none,
|
||||
hiddenLogLevels: [],
|
||||
logsSortOrder: null,
|
||||
isFlipping: false,
|
||||
showDetectedFields: [],
|
||||
@ -134,12 +117,8 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
}));
|
||||
};
|
||||
|
||||
onChangeDedup = (dedup: LogsDedupStrategy) => {
|
||||
const { onDedupStrategyChange } = this.props;
|
||||
if (this.props.dedupStrategy === dedup) {
|
||||
return onDedupStrategyChange(LogsDedupStrategy.none);
|
||||
}
|
||||
return onDedupStrategyChange(dedup);
|
||||
onChangeDedup = (dedupStrategy: LogsDedupStrategy) => {
|
||||
this.setState({ dedupStrategy });
|
||||
};
|
||||
|
||||
onChangeLabels = (event?: React.SyntheticEvent) => {
|
||||
@ -176,8 +155,8 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onToggleLogLevel = (hiddenRawLevels: string[]) => {
|
||||
const hiddenLogLevels: LogLevel[] = hiddenRawLevels.map((level) => LogLevel[level as LogLevel]);
|
||||
this.props.onToggleLogLevel(hiddenLogLevels);
|
||||
const hiddenLogLevels = hiddenRawLevels.map((level) => LogLevel[level as LogLevel]);
|
||||
this.setState({ hiddenLogLevels });
|
||||
};
|
||||
|
||||
onClickScan = (event: React.SyntheticEvent) => {
|
||||
@ -229,6 +208,16 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
return !!logRows.some((r) => r.hasUnescapedContent);
|
||||
});
|
||||
|
||||
dedupRows = memoizeOne((logRows: LogRowModel[], dedupStrategy: LogsDedupStrategy) => {
|
||||
const dedupedRows = dedupLogRows(logRows, dedupStrategy);
|
||||
const dedupCount = dedupedRows.reduce((sum, row) => (row.duplicates ? sum + row.duplicates : sum), 0);
|
||||
return { dedupedRows, dedupCount };
|
||||
});
|
||||
|
||||
filterRows = memoizeOne((logRows: LogRowModel[], hiddenLogLevels: LogLevel[]) => {
|
||||
return filterLogLevels(logRows, new Set(hiddenLogLevels));
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
logRows,
|
||||
@ -244,11 +233,9 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
scanRange,
|
||||
showContextToggle,
|
||||
width,
|
||||
dedupedRows,
|
||||
absoluteRange,
|
||||
onChangeTime,
|
||||
getFieldLinks,
|
||||
dedupStrategy,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
@ -256,43 +243,27 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
showLabels,
|
||||
showTime,
|
||||
wrapLogMessage,
|
||||
dedupStrategy,
|
||||
hiddenLogLevels,
|
||||
logsSortOrder,
|
||||
isFlipping,
|
||||
showDetectedFields,
|
||||
forceEscape,
|
||||
} = this.state;
|
||||
|
||||
const styles = getStyles(theme);
|
||||
const hasData = logRows && logRows.length > 0;
|
||||
const dedupCount = dedupedRows
|
||||
? dedupedRows.reduce((sum, row) => (row.duplicates ? sum + row.duplicates : sum), 0)
|
||||
: 0;
|
||||
const meta = logsMeta ? [...logsMeta] : [];
|
||||
const hasUnescapedContent = this.checkUnescapedContent(logRows);
|
||||
|
||||
if (dedupStrategy !== LogsDedupStrategy.none) {
|
||||
meta.push({
|
||||
label: 'Dedup count',
|
||||
value: dedupCount,
|
||||
kind: LogsMetaKind.Number,
|
||||
});
|
||||
}
|
||||
|
||||
if (logRows.some((r) => r.entry.length > MAX_CHARACTERS)) {
|
||||
meta.push({
|
||||
label: 'Info',
|
||||
value: 'Logs with more than 100,000 characters could not be parsed and highlighted',
|
||||
kind: LogsMetaKind.String,
|
||||
});
|
||||
}
|
||||
const filteredLogs = this.filterRows(logRows, hiddenLogLevels);
|
||||
const { dedupedRows, dedupCount } = this.dedupRows(filteredLogs, dedupStrategy);
|
||||
|
||||
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
|
||||
const series = logsSeries ? logsSeries : [];
|
||||
const styles = getStyles(theme);
|
||||
const hasUnescapedContent = this.checkUnescapedContent(logRows);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExploreGraphPanel
|
||||
series={series}
|
||||
series={logsSeries || []}
|
||||
width={width}
|
||||
onHiddenSeriesChanged={this.onToggleLogLevel}
|
||||
loading={loading}
|
||||
@ -340,56 +311,17 @@ export class UnthemedLogs extends PureComponent<Props, State> {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{meta && (
|
||||
<MetaInfoText
|
||||
metaItems={meta.map((item) => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: renderMetaItem(item.value, item.kind),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showDetectedFields?.length > 0 && (
|
||||
<MetaInfoText
|
||||
metaItems={[
|
||||
{
|
||||
label: 'Showing only detected fields',
|
||||
value: renderMetaItem(showDetectedFields, LogsMetaKind.LabelsMap),
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
value: (
|
||||
<Button variant="secondary" size="sm" onClick={this.clearDetectedFields}>
|
||||
Show all detected fields
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasUnescapedContent && (
|
||||
<MetaInfoText
|
||||
metaItems={[
|
||||
{
|
||||
label: 'Your logs might have incorrectly escaped content',
|
||||
value: (
|
||||
<Tooltip
|
||||
content="We suggest to try to fix the escaping of your log lines first. This is an experimental feature, your logs might not be correctly escaped."
|
||||
placement="right"
|
||||
>
|
||||
<Button variant="secondary" size="sm" onClick={this.onEscapeNewlines}>
|
||||
<span>{forceEscape ? 'Remove escaping' : 'Escape newlines'} </span>
|
||||
<Icon name="exclamation-triangle" className="muted" size="sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<LogsMetaRow
|
||||
logRows={logRows}
|
||||
meta={logsMeta || []}
|
||||
dedupStrategy={dedupStrategy}
|
||||
dedupCount={dedupCount}
|
||||
hasUnescapedContent={hasUnescapedContent}
|
||||
forceEscape={forceEscape}
|
||||
showDetectedFields={showDetectedFields}
|
||||
onEscapeNewlines={this.onEscapeNewlines}
|
||||
clearDetectedFields={this.clearDetectedFields}
|
||||
/>
|
||||
|
||||
<LogRows
|
||||
logRows={logRows}
|
||||
|
@ -3,15 +3,13 @@ import { hot } from 'react-hot-loader';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { Collapse } from '@grafana/ui';
|
||||
|
||||
import { AbsoluteTimeRange, Field, LogLevel, LogRowModel, LogsDedupStrategy, RawTimeRange } from '@grafana/data';
|
||||
import { AbsoluteTimeRange, Field, LogRowModel, RawTimeRange } from '@grafana/data';
|
||||
|
||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||
import { StoreState } from 'app/types';
|
||||
|
||||
import { splitOpen } from './state/main';
|
||||
import { updateTimeRange } from './state/time';
|
||||
import { toggleLogLevelAction, changeDedupStrategy } from './state/explorePane';
|
||||
import { deduplicatedRowsSelector } from './state/selectors';
|
||||
import { getTimeZone } from '../profile/state/selectors';
|
||||
import { LiveLogsWithTheme } from './LiveLogs';
|
||||
import { Logs } from './Logs';
|
||||
@ -36,18 +34,6 @@ export class LogsContainer extends PureComponent<PropsFromRedux & LogsContainerP
|
||||
updateTimeRange({ exploreId, absoluteRange });
|
||||
};
|
||||
|
||||
handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => {
|
||||
this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy);
|
||||
};
|
||||
|
||||
handleToggleLogLevel = (hiddenLogLevels: LogLevel[]) => {
|
||||
const { exploreId } = this.props;
|
||||
this.props.toggleLogLevelAction({
|
||||
exploreId,
|
||||
hiddenLogLevels,
|
||||
});
|
||||
};
|
||||
|
||||
getLogRowContext = async (row: LogRowModel, options?: any): Promise<any> => {
|
||||
const { datasourceInstance } = this.props;
|
||||
|
||||
@ -80,7 +66,6 @@ export class LogsContainer extends PureComponent<PropsFromRedux & LogsContainerP
|
||||
logRows,
|
||||
logsMeta,
|
||||
logsSeries,
|
||||
dedupedRows,
|
||||
onClickFilterLabel,
|
||||
onClickFilterOutLabel,
|
||||
onStartScanning,
|
||||
@ -120,11 +105,9 @@ export class LogsContainer extends PureComponent<PropsFromRedux & LogsContainerP
|
||||
<LogsCrossFadeTransition visible={!isLive}>
|
||||
<Collapse label="Logs" loading={loading} isOpen>
|
||||
<Logs
|
||||
dedupStrategy={this.props.dedupStrategy || LogsDedupStrategy.none}
|
||||
logRows={logRows}
|
||||
logsMeta={logsMeta}
|
||||
logsSeries={logsSeries}
|
||||
dedupedRows={dedupedRows}
|
||||
highlighterExpressions={logsHighlighterExpressions}
|
||||
loading={loading}
|
||||
onChangeTime={this.onChangeTime}
|
||||
@ -132,8 +115,6 @@ export class LogsContainer extends PureComponent<PropsFromRedux & LogsContainerP
|
||||
onClickFilterOutLabel={onClickFilterOutLabel}
|
||||
onStartScanning={onStartScanning}
|
||||
onStopScanning={onStopScanning}
|
||||
onDedupStrategyChange={this.handleDedupStrategyChange}
|
||||
onToggleLogLevel={this.handleToggleLogLevel}
|
||||
absoluteRange={absoluteRange}
|
||||
visibleRange={visibleRange}
|
||||
timeZone={timeZone}
|
||||
@ -165,9 +146,7 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
|
||||
isPaused,
|
||||
range,
|
||||
absoluteRange,
|
||||
dedupStrategy,
|
||||
} = item;
|
||||
const dedupedRows = deduplicatedRowsSelector(item) || undefined;
|
||||
const timeZone = getTimeZone(state.user);
|
||||
|
||||
return {
|
||||
@ -179,8 +158,6 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
|
||||
visibleRange: logsResult?.visibleRange,
|
||||
scanning,
|
||||
timeZone,
|
||||
dedupStrategy,
|
||||
dedupedRows,
|
||||
datasourceInstance,
|
||||
isLive,
|
||||
isPaused,
|
||||
@ -190,8 +167,6 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
changeDedupStrategy,
|
||||
toggleLogLevelAction,
|
||||
updateTimeRange,
|
||||
splitOpen,
|
||||
};
|
||||
|
116
public/app/features/explore/LogsMetaRow.tsx
Normal file
116
public/app/features/explore/LogsMetaRow.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React from 'react';
|
||||
import { LogsDedupStrategy, LogsMetaItem, LogsMetaKind, LogRowModel } from '@grafana/data';
|
||||
import { Button, Tooltip, Icon, LogLabels } from '@grafana/ui';
|
||||
import { MAX_CHARACTERS } from '@grafana/ui/src/components/Logs/LogRowMessage';
|
||||
import { MetaInfoText, MetaItemProps } from './MetaInfoText';
|
||||
|
||||
export type Props = {
|
||||
meta: LogsMetaItem[];
|
||||
dedupStrategy: LogsDedupStrategy;
|
||||
dedupCount: number;
|
||||
showDetectedFields: string[];
|
||||
hasUnescapedContent: boolean;
|
||||
forceEscape: boolean;
|
||||
logRows: LogRowModel[];
|
||||
onEscapeNewlines: () => void;
|
||||
clearDetectedFields: () => void;
|
||||
};
|
||||
|
||||
export const LogsMetaRow: React.FC<Props> = React.memo(
|
||||
({
|
||||
meta,
|
||||
dedupStrategy,
|
||||
dedupCount,
|
||||
showDetectedFields,
|
||||
clearDetectedFields,
|
||||
hasUnescapedContent,
|
||||
forceEscape,
|
||||
onEscapeNewlines,
|
||||
logRows,
|
||||
}) => {
|
||||
const logsMetaItem: Array<LogsMetaItem | MetaItemProps> = [...meta];
|
||||
|
||||
// Add deduplication info
|
||||
if (dedupStrategy !== LogsDedupStrategy.none) {
|
||||
logsMetaItem.push({
|
||||
label: 'Dedup count',
|
||||
value: dedupCount,
|
||||
kind: LogsMetaKind.Number,
|
||||
});
|
||||
}
|
||||
// Add info about limit for highlighting
|
||||
if (logRows.some((r) => r.entry.length > MAX_CHARACTERS)) {
|
||||
logsMetaItem.push({
|
||||
label: 'Info',
|
||||
value: 'Logs with more than 100,000 characters could not be parsed and highlighted',
|
||||
kind: LogsMetaKind.String,
|
||||
});
|
||||
}
|
||||
|
||||
// Add detected fields info
|
||||
if (showDetectedFields?.length > 0) {
|
||||
logsMetaItem.push(
|
||||
{
|
||||
label: 'Showing only detected fields',
|
||||
value: renderMetaItem(showDetectedFields, LogsMetaKind.LabelsMap),
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
value: (
|
||||
<Button variant="secondary" size="sm" onClick={clearDetectedFields}>
|
||||
Show all detected fields
|
||||
</Button>
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Add unescaped content info
|
||||
if (hasUnescapedContent) {
|
||||
logsMetaItem.push({
|
||||
label: 'Your logs might have incorrectly escaped content',
|
||||
value: (
|
||||
<Tooltip
|
||||
content="We suggest to try to fix the escaping of your log lines first. This is an experimental feature, your logs might not be correctly escaped."
|
||||
placement="right"
|
||||
>
|
||||
<Button variant="secondary" size="sm" onClick={onEscapeNewlines}>
|
||||
<span>{forceEscape ? 'Remove escaping' : 'Escape newlines'} </span>
|
||||
<Icon name="exclamation-triangle" className="muted" size="sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{logsMetaItem && (
|
||||
<MetaInfoText
|
||||
metaItems={logsMetaItem.map((item) => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: 'kind' in item ? renderMetaItem(item.value, item.kind) : item.value,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
LogsMetaRow.displayName = 'LogsMetaRow';
|
||||
|
||||
function renderMetaItem(value: any, kind: LogsMetaKind) {
|
||||
if (kind === LogsMetaKind.LabelsMap) {
|
||||
return (
|
||||
<span className="logs-meta-item__labels">
|
||||
<LogLabels labels={value} />
|
||||
</span>
|
||||
);
|
||||
} else if (kind === LogsMetaKind.Error) {
|
||||
return <span className="logs-meta-item__error">{value}</span>;
|
||||
}
|
||||
return value;
|
||||
}
|
@ -21,16 +21,7 @@ import {
|
||||
getUrlStateFromPaneState,
|
||||
} from './utils';
|
||||
import { createAction, PayloadAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
EventBusExtended,
|
||||
DataQuery,
|
||||
ExploreUrlState,
|
||||
LogLevel,
|
||||
LogsDedupStrategy,
|
||||
TimeRange,
|
||||
HistoryItem,
|
||||
DataSourceApi,
|
||||
} from '@grafana/data';
|
||||
import { EventBusExtended, DataQuery, ExploreUrlState, TimeRange, HistoryItem, DataSourceApi } from '@grafana/data';
|
||||
// Types
|
||||
import { ThunkResult } from 'app/types';
|
||||
import { getTimeZone } from 'app/features/profile/state/selectors';
|
||||
@ -53,15 +44,6 @@ export interface ChangeSizePayload {
|
||||
}
|
||||
export const changeSizeAction = createAction<ChangeSizePayload>('explore/changeSize');
|
||||
|
||||
/**
|
||||
* Change deduplication strategy for logs.
|
||||
*/
|
||||
export interface ChangeDedupStrategyPayload {
|
||||
exploreId: ExploreId;
|
||||
dedupStrategy: LogsDedupStrategy;
|
||||
}
|
||||
export const changeDedupStrategyAction = createAction<ChangeDedupStrategyPayload>('explore/changeDedupStrategyAction');
|
||||
|
||||
/**
|
||||
* Highlight expressions in the log results
|
||||
*/
|
||||
@ -89,12 +71,6 @@ export interface InitializeExplorePayload {
|
||||
}
|
||||
export const initializeExploreAction = createAction<InitializeExplorePayload>('explore/initializeExplore');
|
||||
|
||||
export interface ToggleLogLevelPayload {
|
||||
exploreId: ExploreId;
|
||||
hiddenLogLevels: LogLevel[];
|
||||
}
|
||||
export const toggleLogLevelAction = createAction<ToggleLogLevelPayload>('explore/toggleLogLevel');
|
||||
|
||||
export interface SetUrlReplacedPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
@ -111,16 +87,6 @@ export function changeSize(
|
||||
return changeSizeAction({ exploreId, height, width });
|
||||
}
|
||||
|
||||
/**
|
||||
* Change logs deduplication strategy.
|
||||
*/
|
||||
export const changeDedupStrategy = (
|
||||
exploreId: ExploreId,
|
||||
dedupStrategy: LogsDedupStrategy
|
||||
): PayloadAction<ChangeDedupStrategyPayload> => {
|
||||
return changeDedupStrategyAction({ exploreId, dedupStrategy });
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize Explore state with state from the URL and the React component.
|
||||
* Call this only on components for with the Explore state has not been initialized.
|
||||
@ -255,14 +221,6 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac
|
||||
};
|
||||
}
|
||||
|
||||
if (changeDedupStrategyAction.match(action)) {
|
||||
const { dedupStrategy } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
dedupStrategy,
|
||||
};
|
||||
}
|
||||
|
||||
if (initializeExploreAction.match(action)) {
|
||||
const { containerWidth, eventBridge, queries, range, originPanelId, datasourceInstance, history } = action.payload;
|
||||
|
||||
@ -283,14 +241,6 @@ export const paneReducer = (state: ExploreItemState = makeExplorePaneState(), ac
|
||||
};
|
||||
}
|
||||
|
||||
if (toggleLogLevelAction.match(action)) {
|
||||
const { hiddenLogLevels } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
hiddenLogLevels: Array.from(hiddenLogLevels),
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
|
@ -1,92 +0,0 @@
|
||||
import { deduplicatedRowsSelector } from './selectors';
|
||||
import { LogLevel, LogsDedupStrategy } from '@grafana/data';
|
||||
import { ExploreItemState } from 'app/types';
|
||||
|
||||
const state: any = {
|
||||
logsResult: {
|
||||
rows: [
|
||||
{
|
||||
entry: '2019-03-05T11:00:56Z sntpc sntpc[1]: offset=-0.033938, delay=0.000649',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T11:00:26Z sntpc sntpc[1]: offset=-0.033730, delay=0.000581',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:59:56Z sntpc sntpc[1]: offset=-0.034184, delay=0.001089',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:59:26Z sntpc sntpc[1]: offset=-0.033972, delay=0.000582',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:58:56Z sntpc sntpc[1]: offset=-0.033955, delay=0.000606',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:58:26Z sntpc sntpc[1]: offset=-0.034067, delay=0.000616',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:57:56Z sntpc sntpc[1]: offset=-0.034155, delay=0.001021',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:57:26Z sntpc sntpc[1]: offset=-0.035797, delay=0.000883',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:56:56Z sntpc sntpc[1]: offset=-0.046818, delay=0.000605',
|
||||
logLevel: LogLevel.debug,
|
||||
},
|
||||
{
|
||||
entry: '2019-03-05T10:56:26Z sntpc sntpc[1]: offset=-0.049200, delay=0.000584',
|
||||
logLevel: LogLevel.error,
|
||||
},
|
||||
{
|
||||
entry:
|
||||
'2019-11-01T14:53:02Z lifecycle-server time="2019-11-01T14:53:02.563571300Z" level=debug msg="Calling GET /v1.30/containers/c8defad4025e23f503d91b66610f93b5380622c8e871b31a71e29ff0e67653e7/stats?stream=0"',
|
||||
logLevel: LogLevel.trace,
|
||||
},
|
||||
],
|
||||
},
|
||||
hiddenLogLevels: undefined,
|
||||
dedupStrategy: LogsDedupStrategy.none,
|
||||
};
|
||||
|
||||
describe('Deduplication selector', () => {
|
||||
it('returns the same rows if no deduplication', () => {
|
||||
const dedups = deduplicatedRowsSelector(state as ExploreItemState);
|
||||
expect(dedups?.length).toBe(11);
|
||||
expect(dedups).toBe(state.logsResult.rows);
|
||||
});
|
||||
|
||||
it('should correctly extracts rows and deduplicates them', () => {
|
||||
const dedups = deduplicatedRowsSelector({
|
||||
...state,
|
||||
dedupStrategy: LogsDedupStrategy.numbers,
|
||||
} as ExploreItemState);
|
||||
expect(dedups?.length).toBe(2);
|
||||
expect(dedups).not.toBe(state.logsResult.rows);
|
||||
});
|
||||
|
||||
it('should filter out log levels', () => {
|
||||
let dedups = deduplicatedRowsSelector({
|
||||
...state,
|
||||
hiddenLogLevels: [LogLevel.debug],
|
||||
} as ExploreItemState);
|
||||
expect(dedups?.length).toBe(2);
|
||||
expect(dedups).not.toBe(state.logsResult.rows);
|
||||
|
||||
dedups = deduplicatedRowsSelector({
|
||||
...state,
|
||||
dedupStrategy: LogsDedupStrategy.numbers,
|
||||
hiddenLogLevels: [LogLevel.debug],
|
||||
} as ExploreItemState);
|
||||
|
||||
expect(dedups?.length).toBe(2);
|
||||
expect(dedups).not.toBe(state.logsResult.rows);
|
||||
});
|
||||
});
|
@ -1,21 +1,3 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { ExploreId, ExploreItemState, StoreState } from 'app/types';
|
||||
import { filterLogLevels, dedupLogRows } from 'app/core/logs_model';
|
||||
|
||||
const logsRowsSelector = (state: ExploreItemState) => state.logsResult && state.logsResult.rows;
|
||||
const hiddenLogLevelsSelector = (state: ExploreItemState) => state.hiddenLogLevels;
|
||||
const dedupStrategySelector = (state: ExploreItemState) => state.dedupStrategy;
|
||||
export const deduplicatedRowsSelector = createSelector(
|
||||
logsRowsSelector,
|
||||
hiddenLogLevelsSelector,
|
||||
dedupStrategySelector,
|
||||
function dedupRows(rows, hiddenLogLevels, dedupStrategy) {
|
||||
if (!(rows && rows.length)) {
|
||||
return rows;
|
||||
}
|
||||
const filteredRows = filterLogLevels(rows, new Set(hiddenLogLevels));
|
||||
return dedupLogRows(filteredRows, dedupStrategy);
|
||||
}
|
||||
);
|
||||
import { ExploreId, StoreState } from 'app/types';
|
||||
|
||||
export const isSplit = (state: StoreState) => Boolean(state.explore[ExploreId.left] && state.explore[ExploreId.right]);
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
getDefaultTimeRange,
|
||||
HistoryItem,
|
||||
LoadingState,
|
||||
LogsDedupStrategy,
|
||||
PanelData,
|
||||
} from '@grafana/data';
|
||||
|
||||
@ -49,7 +48,6 @@ export const makeExplorePaneState = (): ExploreItemState => ({
|
||||
tableResult: null,
|
||||
graphResult: null,
|
||||
logsResult: null,
|
||||
dedupStrategy: LogsDedupStrategy.none,
|
||||
eventBridge: (null as unknown) as EventBusExtended,
|
||||
});
|
||||
|
||||
|
@ -6,8 +6,6 @@ import {
|
||||
DataQueryRequest,
|
||||
DataSourceApi,
|
||||
HistoryItem,
|
||||
LogLevel,
|
||||
LogsDedupStrategy,
|
||||
LogsModel,
|
||||
PanelData,
|
||||
QueryHint,
|
||||
@ -119,16 +117,6 @@ export interface ExploreItemState {
|
||||
*/
|
||||
queryKeys: string[];
|
||||
|
||||
/**
|
||||
* Current logs deduplication strategy
|
||||
*/
|
||||
dedupStrategy: LogsDedupStrategy;
|
||||
|
||||
/**
|
||||
* Currently hidden log series
|
||||
*/
|
||||
hiddenLogLevels?: LogLevel[];
|
||||
|
||||
/**
|
||||
* How often query should be refreshed
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user