Commit a79bd424 by Torkel Ödegaard Committed by GitHub

Merge pull request #15306 from grafana/explore/dedup-strategu-url

Persist deduplication strategy in url
parents 951e5932 85780eb3
......@@ -8,6 +8,7 @@ import {
} from './explore';
import { ExploreUrlState } from 'app/types/explore';
import store from 'app/core/store';
import { LogsDedupStrategy } from 'app/core/logs_model';
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
datasource: null,
......@@ -17,6 +18,7 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
showingGraph: true,
showingTable: true,
showingLogs: true,
dedupStrategy: LogsDedupStrategy.none,
}
};
......@@ -78,7 +80,7 @@ describe('state functions', () => {
expect(serializeStateToUrlParam(state)).toBe(
'{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
'{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
'"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
'"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true,"dedupStrategy":"none"}}'
);
});
......@@ -100,7 +102,7 @@ describe('state functions', () => {
},
};
expect(serializeStateToUrlParam(state, true)).toBe(
'["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]'
'["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true,"none"]}]'
);
});
});
......
......@@ -21,6 +21,7 @@ import {
QueryIntervals,
QueryOptions,
} from 'app/types/explore';
import { LogsDedupStrategy } from 'app/core/logs_model';
export const DEFAULT_RANGE = {
from: 'now-6h',
......@@ -31,6 +32,7 @@ export const DEFAULT_UI_STATE = {
showingTable: true,
showingGraph: true,
showingLogs: true,
dedupStrategy: LogsDedupStrategy.none,
};
const MAX_HISTORY_ITEMS = 100;
......@@ -183,6 +185,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
showingGraph: segment.ui[0],
showingLogs: segment.ui[1],
showingTable: segment.ui[2],
dedupStrategy: segment.ui[3],
};
}
});
......@@ -204,7 +207,7 @@ export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: bo
urlState.range.to,
urlState.datasource,
...urlState.queries,
{ ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] },
{ ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable, urlState.ui.dedupStrategy] },
]);
}
return JSON.stringify(urlState);
......
......@@ -25,7 +25,7 @@ interface GraphContainerProps {
export class GraphContainer extends PureComponent<GraphContainerProps> {
onClickGraphButton = () => {
this.props.toggleGraph(this.props.exploreId);
this.props.toggleGraph(this.props.exploreId, this.props.showingGraph);
};
onChangeTime = (timeRange: TimeRange) => {
......
......@@ -58,14 +58,15 @@ interface Props {
range?: RawTimeRange;
scanning?: boolean;
scanRange?: RawTimeRange;
dedupStrategy: LogsDedupStrategy;
onChangeTime?: (range: RawTimeRange) => void;
onClickLabel?: (label: string, value: string) => void;
onStartScanning?: () => void;
onStopScanning?: () => void;
onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
}
interface State {
dedup: LogsDedupStrategy;
deferLogs: boolean;
hiddenLogLevels: Set<LogLevel>;
renderAll: boolean;
......@@ -79,7 +80,6 @@ export default class Logs extends PureComponent<Props, State> {
renderAllTimer: NodeJS.Timer;
state = {
dedup: LogsDedupStrategy.none,
deferLogs: true,
hiddenLogLevels: new Set(),
renderAll: false,
......@@ -112,12 +112,11 @@ export default class Logs extends PureComponent<Props, State> {
}
onChangeDedup = (dedup: LogsDedupStrategy) => {
this.setState(prevState => {
if (prevState.dedup === dedup) {
return { dedup: LogsDedupStrategy.none };
}
return { dedup };
});
const { onDedupStrategyChange } = this.props;
if (this.props.dedupStrategy === dedup) {
return onDedupStrategyChange(LogsDedupStrategy.none);
}
return onDedupStrategyChange(dedup);
};
onChangeLabels = (event: React.SyntheticEvent) => {
......@@ -173,17 +172,19 @@ export default class Logs extends PureComponent<Props, State> {
return null;
}
const { dedup, deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc } = this.state;
const { deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc, } = this.state;
let { showLabels } = this.state;
const { dedupStrategy } = this.props;
const hasData = data && data.rows && data.rows.length > 0;
const showDuplicates = dedup !== LogsDedupStrategy.none;
const showDuplicates = dedupStrategy !== LogsDedupStrategy.none;
// Filtering
const filteredData = filterLogLevels(data, hiddenLogLevels);
const dedupedData = dedupLogRows(filteredData, dedup);
const dedupedData = dedupLogRows(filteredData, dedupStrategy);
const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
const meta = [...data.meta];
if (dedup !== LogsDedupStrategy.none) {
if (dedupStrategy !== LogsDedupStrategy.none) {
meta.push({
label: 'Dedup count',
value: dedupCount,
......@@ -236,7 +237,7 @@ export default class Logs extends PureComponent<Props, State> {
key={i}
value={dedupType}
onChange={this.onChangeDedup}
selected={dedup === dedupType}
selected={dedupStrategy === dedupType}
tooltip={LogsDedupDescription[dedupType]}
>
{dedupType}
......
......@@ -4,10 +4,10 @@ import { connect } from 'react-redux';
import { RawTimeRange, TimeRange } from '@grafana/ui';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { LogsModel } from 'app/core/logs_model';
import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
import { StoreState } from 'app/types';
import { toggleLogs } from './state/actions';
import { toggleLogs, changeDedupStrategy } from './state/actions';
import Logs from './Logs';
import Panel from './Panel';
......@@ -25,12 +25,18 @@ interface LogsContainerProps {
scanRange?: RawTimeRange;
showingLogs: boolean;
toggleLogs: typeof toggleLogs;
changeDedupStrategy: typeof changeDedupStrategy;
dedupStrategy: LogsDedupStrategy;
width: number;
}
export class LogsContainer extends PureComponent<LogsContainerProps> {
onClickLogsButton = () => {
this.props.toggleLogs(this.props.exploreId);
this.props.toggleLogs(this.props.exploreId, this.props.showingLogs);
};
handleDedupStrategyChange = (dedupStrategy: LogsDedupStrategy) => {
this.props.changeDedupStrategy(this.props.exploreId, dedupStrategy);
};
render() {
......@@ -53,6 +59,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
return (
<Panel label="Logs" loading={loading} isOpen={showingLogs} onToggle={this.onClickLogsButton}>
<Logs
dedupStrategy={this.props.dedupStrategy || LogsDedupStrategy.none}
data={logsResult}
exploreId={exploreId}
key={logsResult && logsResult.id}
......@@ -62,6 +69,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
onClickLabel={onClickLabel}
onStartScanning={onStartScanning}
onStopScanning={onStopScanning}
onDedupStrategyChange={this.handleDedupStrategyChange}
range={range}
scanning={scanning}
scanRange={scanRange}
......@@ -72,11 +80,23 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
}
}
const selectItemUIState = (itemState: ExploreItemState) => {
const { showingGraph, showingLogs, showingTable, showingStartPage, dedupStrategy } = itemState;
return {
showingGraph,
showingLogs,
showingTable,
showingStartPage,
dedupStrategy,
};
};
function mapStateToProps(state: StoreState, { exploreId }) {
const explore = state.explore;
const item: ExploreItemState = explore[exploreId];
const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, showingLogs, range } = item;
const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item;
const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
const {showingLogs, dedupStrategy} = selectItemUIState(item);
return {
loading,
logsHighlighterExpressions,
......@@ -85,11 +105,13 @@ function mapStateToProps(state: StoreState, { exploreId }) {
scanRange,
showingLogs,
range,
dedupStrategy,
};
}
const mapDispatchToProps = {
toggleLogs,
changeDedupStrategy,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(LogsContainer));
......@@ -21,7 +21,7 @@ interface TableContainerProps {
export class TableContainer extends PureComponent<TableContainerProps> {
onClickTableButton = () => {
this.props.toggleTable(this.props.exploreId);
this.props.toggleTable(this.props.exploreId, this.props.showingTable);
};
render() {
......
......@@ -192,6 +192,10 @@ export interface ToggleLogsPayload {
exploreId: ExploreId;
}
export interface UpdateUIStatePayload extends Partial<ExploreUIState>{
exploreId: ExploreId;
}
export interface UpdateDatasourceInstancePayload {
exploreId: ExploreId;
datasourceInstance: DataSourceApi;
......@@ -367,6 +371,11 @@ export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/S
export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create();
/**
* Update state of Explores UI elements (panels visiblity and deduplication strategy)
*/
export const updateUIStateAction = actionCreatorFactory<UpdateUIStatePayload>('explore/UPDATE_UI_STATE').create();
/**
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
*/
export const toggleTableAction = actionCreatorFactory<ToggleTablePayload>('explore/TOGGLE_TABLE').create();
......
......@@ -67,14 +67,26 @@ import {
ToggleGraphPayload,
ToggleLogsPayload,
ToggleTablePayload,
updateUIStateAction,
} from './actionTypes';
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
import { LogsDedupStrategy } from 'app/core/logs_model';
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
// /**
// * Adds a query row after the row with the given index.
// */
/**
* Updates UI state and save it to the URL
*/
const updateExploreUIState = (exploreId, uiStateFragment: Partial<ExploreUIState>) => {
return dispatch => {
dispatch(updateUIStateAction({ exploreId, ...uiStateFragment }));
dispatch(stateSave());
};
};
/**
* Adds a query row after the row with the given index.
*/
export function addQueryRow(exploreId: ExploreId, index: number): ActionOf<AddQueryRowPayload> {
const query = generateEmptyQuery(index + 1);
return addQueryRowAction({ exploreId, index, query });
......@@ -669,6 +681,7 @@ export function stateSave() {
showingGraph: left.showingGraph,
showingLogs: left.showingLogs,
showingTable: left.showingTable,
dedupStrategy: left.dedupStrategy,
},
};
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
......@@ -677,7 +690,12 @@ export function stateSave() {
datasource: right.datasourceInstance.name,
queries: right.queries.map(clearQueryKeys),
range: right.range,
ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable },
ui: {
showingGraph: right.showingGraph,
showingLogs: right.showingLogs,
showingTable: right.showingTable,
dedupStrategy: right.dedupStrategy,
},
};
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
......@@ -696,24 +714,26 @@ const togglePanelActionCreator = (
| ActionCreator<ToggleGraphPayload>
| ActionCreator<ToggleLogsPayload>
| ActionCreator<ToggleTablePayload>
) => (exploreId: ExploreId) => {
return (dispatch, getState) => {
let shouldRunQueries;
dispatch(actionCreator({ exploreId }));
dispatch(stateSave());
) => (exploreId: ExploreId, isPanelVisible: boolean) => {
return dispatch => {
let uiFragmentStateUpdate: Partial<ExploreUIState>;
const shouldRunQueries = !isPanelVisible;
switch (actionCreator.type) {
case toggleGraphAction.type:
shouldRunQueries = getState().explore[exploreId].showingGraph;
uiFragmentStateUpdate = { showingGraph: !isPanelVisible };
break;
case toggleLogsAction.type:
shouldRunQueries = getState().explore[exploreId].showingLogs;
uiFragmentStateUpdate = { showingLogs: !isPanelVisible };
break;
case toggleTableAction.type:
shouldRunQueries = getState().explore[exploreId].showingTable;
uiFragmentStateUpdate = { showingTable: !isPanelVisible };
break;
}
dispatch(actionCreator({ exploreId }));
dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate));
if (shouldRunQueries) {
dispatch(runQueries(exploreId));
}
......@@ -734,3 +754,12 @@ export const toggleLogs = togglePanelActionCreator(toggleLogsAction);
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
*/
export const toggleTable = togglePanelActionCreator(toggleTableAction);
/**
* Change logs deduplication strategy and update URL.
*/
export const changeDedupStrategy = (exploreId, dedupStrategy: LogsDedupStrategy) => {
return dispatch => {
dispatch(updateExploreUIState(exploreId, { dedupStrategy }));
};
};
......@@ -37,6 +37,7 @@ import {
toggleLogsAction,
toggleTableAction,
queriesImportedAction,
updateUIStateAction,
} from './actionTypes';
export const DEFAULT_RANGE = {
......@@ -407,6 +408,12 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
},
})
.addMapper({
filter: updateUIStateAction,
mapper: (state, action): ExploreItemState => {
return { ...state, ...action.payload };
},
})
.addMapper({
filter: toggleGraphAction,
mapper: (state): ExploreItemState => {
const showingGraph = !state.showingGraph;
......@@ -415,7 +422,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
// Discard transactions related to Graph query
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
}
return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
return { ...state, queryTransactions: nextQueryTransactions };
},
})
.addMapper({
......@@ -427,7 +434,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
// Discard transactions related to Logs query
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
}
return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
return { ...state, queryTransactions: nextQueryTransactions };
},
})
.addMapper({
......@@ -435,7 +442,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
mapper: (state): ExploreItemState => {
const showingTable = !state.showingTable;
if (showingTable) {
return { ...state, showingTable, queryTransactions: state.queryTransactions };
return { ...state, queryTransactions: state.queryTransactions };
}
// Toggle off needs discarding of table queries and results
......@@ -446,7 +453,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
state.queryIntervals.intervalMs
);
return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
return { ...state, ...results, queryTransactions: nextQueryTransactions };
},
})
.addMapper({
......
jest.mock('app/core/core', () => ({}));
jest.mock('app/core/config', () => {
return {
bootData: {
user: {},
},
panels: {
test: {
id: 'test',
......
......@@ -11,7 +11,7 @@ import {
} from '@grafana/ui';
import { Emitter } from 'app/core/core';
import { LogsModel } from 'app/core/logs_model';
import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
import TableModel from 'app/core/table_model';
export interface CompletionItem {
......@@ -237,12 +237,18 @@ export interface ExploreItemState {
* React keys for rendering of QueryRows
*/
queryKeys: string[];
/**
* Current logs deduplication strategy
*/
dedupStrategy?: LogsDedupStrategy;
}
export interface ExploreUIState {
showingTable: boolean;
showingGraph: boolean;
showingLogs: boolean;
dedupStrategy?: LogsDedupStrategy;
}
export interface ExploreUrlState {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment