The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/explore/state/reducers.test.ts

540 lines
17 KiB

import {
itemReducer,
makeExploreItemState,
exploreReducer,
makeInitialUpdateState,
initialExploreState,
} from './reducers';
import {
ExploreId,
ExploreItemState,
ExploreUrlState,
ExploreState,
QueryTransaction,
RangeScanner,
} from 'app/types/explore';
import { reducerTester } from 'test/core/redux/reducerTester';
import {
scanStartAction,
scanStopAction,
testDataSourcePendingAction,
testDataSourceSuccessAction,
testDataSourceFailureAction,
updateDatasourceInstanceAction,
splitOpenAction,
splitCloseAction,
} from './actionTypes';
import { Reducer } from 'redux';
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
import { updateLocation } from 'app/core/actions/location';
import { LogsDedupStrategy, LogsModel } from 'app/core/logs_model';
import { serializeStateToUrlParam } from 'app/core/utils/explore';
import TableModel from 'app/core/table_model';
import { DataSourceApi, DataQuery } from '@grafana/ui';
describe('Explore item reducer', () => {
describe('scanning', () => {
test('should start scanning', () => {
const scanner = jest.fn();
const initalState = {
...makeExploreItemState(),
scanning: false,
scanner: undefined as RangeScanner,
};
reducerTester()
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
.whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner }))
.thenStateShouldEqual({
...makeExploreItemState(),
scanning: true,
scanner,
});
});
test('should stop scanning', () => {
const scanner = jest.fn();
const initalState = {
...makeExploreItemState(),
scanning: true,
scanner,
scanRange: {},
};
reducerTester()
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
.whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual({
...makeExploreItemState(),
scanning: false,
scanner: undefined,
scanRange: undefined,
});
});
});
describe('testing datasource', () => {
describe('when testDataSourcePendingAction is dispatched', () => {
it('then it should set datasourceError', () => {
reducerTester()
.givenReducer(itemReducer, { datasourceError: {} })
.whenActionIsDispatched(testDataSourcePendingAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual({ datasourceError: null });
});
});
describe('when testDataSourceSuccessAction is dispatched', () => {
it('then it should set datasourceError', () => {
reducerTester()
.givenReducer(itemReducer, { datasourceError: {} })
.whenActionIsDispatched(testDataSourceSuccessAction({ exploreId: ExploreId.left }))
.thenStateShouldEqual({ datasourceError: null });
});
});
describe('when testDataSourceFailureAction is dispatched', () => {
it('then it should set correct state', () => {
const error = 'some error';
const queryTransactions: QueryTransaction[] = [];
const initalState: Partial<ExploreItemState> = {
datasourceError: null,
queryTransactions: [{} as QueryTransaction],
graphResult: [],
tableResult: {} as TableModel,
logsResult: {} as LogsModel,
update: {
datasource: true,
queries: true,
range: true,
ui: true,
},
};
const expectedState = {
datasourceError: error,
queryTransactions,
graphResult: undefined as any[],
tableResult: undefined as TableModel,
logsResult: undefined as LogsModel,
update: makeInitialUpdateState(),
};
reducerTester()
.givenReducer(itemReducer, initalState)
.whenActionIsDispatched(testDataSourceFailureAction({ exploreId: ExploreId.left, error }))
.thenStateShouldEqual(expectedState);
});
});
});
describe('changing datasource', () => {
describe('when updateDatasourceInstanceAction is dispatched', () => {
describe('and datasourceInstance supports graph, logs, table and has a startpage', () => {
it('then it should set correct state', () => {
const StartPage = {};
const datasourceInstance = {
meta: {
metrics: {},
logs: {},
tables: {},
},
components: {
ExploreStartPage: StartPage,
},
} as DataSourceApi;
const queries: DataQuery[] = [];
const queryKeys: string[] = [];
const initalState: Partial<ExploreItemState> = {
datasourceInstance: null,
supportsGraph: false,
supportsLogs: false,
supportsTable: false,
StartPage: null,
showingStartPage: false,
queries,
queryKeys,
};
const expectedState = {
datasourceInstance,
supportsGraph: true,
supportsLogs: true,
supportsTable: true,
StartPage,
showingStartPage: true,
queries,
queryKeys,
};
reducerTester()
.givenReducer(itemReducer, initalState)
.whenActionIsDispatched(updateDatasourceInstanceAction({ exploreId: ExploreId.left, datasourceInstance }))
.thenStateShouldEqual(expectedState);
});
});
});
});
});
export const setup = (urlStateOverrides?: any) => {
const update = makeInitialUpdateState();
const urlStateDefaults: ExploreUrlState = {
datasource: 'some-datasource',
queries: [],
range: {
from: '',
to: '',
},
ui: {
dedupStrategy: LogsDedupStrategy.none,
showingGraph: false,
showingTable: false,
showingLogs: false,
},
};
const urlState: ExploreUrlState = { ...urlStateDefaults, ...urlStateOverrides };
const serializedUrlState = serializeStateToUrlParam(urlState);
const initalState = { split: false, left: { urlState, update }, right: { urlState, update } };
return {
initalState,
serializedUrlState,
};
};
describe('Explore reducer', () => {
describe('split view', () => {
it("should make right pane a duplicate of the given item's state on split open", () => {
const leftItemMock = {
containerWidth: 100,
} as ExploreItemState;
const initalState = {
split: null,
left: leftItemMock as ExploreItemState,
right: makeExploreItemState(),
} as ExploreState;
reducerTester()
.givenReducer(exploreReducer as Reducer<ExploreState, ActionOf<any>>, initalState)
.whenActionIsDispatched(splitOpenAction({ itemState: leftItemMock }))
.thenStateShouldEqual({
split: true,
left: leftItemMock,
right: leftItemMock,
});
});
describe('split close', () => {
it('should keep right pane as left when left is closed', () => {
const leftItemMock = {
containerWidth: 100,
} as ExploreItemState;
const rightItemMock = {
containerWidth: 200,
} as ExploreItemState;
const initalState = {
split: null,
left: leftItemMock,
right: rightItemMock,
} as ExploreState;
// closing left item
reducerTester()
.givenReducer(exploreReducer as Reducer<ExploreState, ActionOf<any>>, initalState)
.whenActionIsDispatched(splitCloseAction({ itemId: ExploreId.left }))
.thenStateShouldEqual({
split: false,
left: rightItemMock,
right: initialExploreState.right,
});
});
it('should reset right pane when it is closed ', () => {
const leftItemMock = {
containerWidth: 100,
} as ExploreItemState;
const rightItemMock = {
containerWidth: 200,
} as ExploreItemState;
const initalState = {
split: null,
left: leftItemMock,
right: rightItemMock,
} as ExploreState;
// closing left item
reducerTester()
.givenReducer(exploreReducer as Reducer<ExploreState, ActionOf<any>>, initalState)
.whenActionIsDispatched(splitCloseAction({ itemId: ExploreId.right }))
.thenStateShouldEqual({
split: false,
left: leftItemMock,
right: initialExploreState.right,
});
});
});
});
describe('when updateLocation is dispatched', () => {
describe('and payload does not contain a query', () => {
it('then it should just return state', () => {
reducerTester()
.givenReducer(exploreReducer, {})
.whenActionIsDispatched(updateLocation({ query: null }))
.thenStateShouldEqual({});
});
});
describe('and payload contains a query', () => {
describe("but does not contain 'left'", () => {
it('then it should just return state', () => {
reducerTester()
.givenReducer(exploreReducer, {})
.whenActionIsDispatched(updateLocation({ query: {} }))
.thenStateShouldEqual({});
});
});
describe("and query contains a 'right'", () => {
it('then it should add split in state', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = { ...initalState, split: true };
reducerTester()
.givenReducer(exploreReducer, initalState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
right: serializedUrlState,
},
})
)
.thenStateShouldEqual(expectedState);
});
});
describe("and query contains a 'left'", () => {
describe('but urlState is not set in state', () => {
it('then it should just add urlState and update in state', () => {
const { initalState, serializedUrlState } = setup();
const urlState: ExploreUrlState = null;
const stateWithoutUrlState = { ...initalState, left: { urlState } };
const expectedState = { ...initalState };
reducerTester()
.givenReducer(exploreReducer, stateWithoutUrlState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe("but '/explore' is missing in path", () => {
it('then it should just add urlState and update in state', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = { ...initalState };
reducerTester()
.givenReducer(exploreReducer, initalState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/dashboard',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe("and '/explore' is in path", () => {
describe('and datasource differs', () => {
it('then it should return update datasource', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = {
...initalState,
left: {
...initalState.left,
update: {
...initalState.left.update,
datasource: true,
},
},
};
const stateWithDifferentDataSource = {
...initalState,
left: {
...initalState.left,
urlState: {
...initalState.left.urlState,
datasource: 'different datasource',
},
},
};
reducerTester()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and range differs', () => {
it('then it should return update range', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = {
...initalState,
left: {
...initalState.left,
update: {
...initalState.left.update,
range: true,
},
},
};
const stateWithDifferentDataSource = {
...initalState,
left: {
...initalState.left,
urlState: {
...initalState.left.urlState,
range: {
from: 'now',
to: 'now-6h',
},
},
},
};
reducerTester()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and queries differs', () => {
it('then it should return update queries', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = {
...initalState,
left: {
...initalState.left,
update: {
...initalState.left.update,
queries: true,
},
},
};
const stateWithDifferentDataSource = {
...initalState,
left: {
...initalState.left,
urlState: {
...initalState.left.urlState,
queries: [{ expr: '{__filename__="some.log"}' }],
},
},
};
reducerTester()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and ui differs', () => {
it('then it should return update ui', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = {
...initalState,
left: {
...initalState.left,
update: {
...initalState.left.update,
ui: true,
},
},
};
const stateWithDifferentDataSource = {
...initalState,
left: {
...initalState.left,
urlState: {
...initalState.left.urlState,
ui: {
...initalState.left.urlState.ui,
showingGraph: true,
},
},
},
};
reducerTester()
.givenReducer(exploreReducer, stateWithDifferentDataSource)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
describe('and nothing differs', () => {
fit('then it should return update ui', () => {
const { initalState, serializedUrlState } = setup();
const expectedState = { ...initalState };
reducerTester()
.givenReducer(exploreReducer, initalState)
.whenActionIsDispatched(
updateLocation({
query: {
left: serializedUrlState,
},
path: '/explore',
})
)
.thenStateShouldEqual(expectedState);
});
});
});
});
});
});
});