Skip to content

Commit

Permalink
add test for sync_query
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Jan 24, 2020
1 parent 7203901 commit 1ebb3a1
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 11 deletions.
166 changes: 166 additions & 0 deletions src/plugins/data/public/query/state_sync/sync_query.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Subscription } from 'rxjs';
import { createBrowserHistory, History } from 'history';
import { FilterManager } from '../filter_manager';
import { getFilter } from '../filter_manager/test_helpers/get_stub_filter';
import { esFilters } from '../../../common';
import { coreMock } from '../../../../../core/public/mocks';
import {
createKbnUrlStateStorage,
IKbnUrlStateStorage,
Storage,
} from '../../../../kibana_utils/public';
import { QueryService, QueryStart } from '../query_service';
import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
import { TimefilterContract } from '../timefilter';
import { QuerySyncState, syncQuery } from './sync_query';

const setupMock = coreMock.createSetup();
const startMock = coreMock.createStart();

setupMock.uiSettings.get.mockImplementation((key: string) => {
switch (key) {
case 'filters:pinnedByDefault':
return true;
case 'timepicker:timeDefaults':
return { from: 'now-15m', to: 'now' };
case 'timepicker:refreshIntervalDefaults':
return { pause: false, value: 0 };
default:
throw new Error(`sync_query test: not mocked uiSetting: ${key}`);
}
});

describe('sync_query', () => {
let queryServiceStart: QueryStart;
let filterManager: FilterManager;
let timefilter: TimefilterContract;
let kbnUrlStateStorage: IKbnUrlStateStorage;
let history: History;

let filterManagerChangeSub: Subscription;
let filterManagerChangeTriggered = jest.fn();

let gF: esFilters.Filter;
let aF: esFilters.Filter;

const pathWithFilter =
"/#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!t,index:'logstash-*',key:query,negate:!t,type:custom,value:'%7B%22match%22:%7B%22key1%22:%22value1%22%7D%7D'),query:(match:(key1:value1)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";

beforeEach(() => {
const queryService = new QueryService();
queryService.setup({
uiSettings: setupMock.uiSettings,
storage: new Storage(new StubBrowserStorage()),
});
queryServiceStart = queryService.start(startMock.savedObjects);
filterManager = queryServiceStart.filterManager;
timefilter = queryServiceStart.timefilter.timefilter;

filterManagerChangeTriggered = jest.fn();
filterManagerChangeSub = filterManager.getUpdates$().subscribe(filterManagerChangeTriggered);

window.location.href = '/';
history = createBrowserHistory();
kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, history });

gF = getFilter(esFilters.FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1');
aF = getFilter(esFilters.FilterStateStore.APP_STATE, true, true, 'key3', 'value3');
});
afterEach(() => {
filterManagerChangeSub.unsubscribe();
});

test('url is actually changed when data in services changes', () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
filterManager.setFilters([gF, aF]);
kbnUrlStateStorage.flush(); // sync force location change
expect(history.location.hash).toMatchInlineSnapshot(
`"#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!t,index:'logstash-*',key:query,negate:!t,type:custom,value:'%7B%22match%22:%7B%22key1%22:%22value1%22%7D%7D'),query:(match:(key1:value1)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))"`
);
stop();
});

test('when filters change, global filters synced to urlStorage', () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
filterManager.setFilters([gF, aF]);
expect(kbnUrlStateStorage.get<QuerySyncState>('_g')?.filters).toHaveLength(1);
stop();
});

test('when time range changes, time synced to urlStorage', () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
timefilter.setTime({ from: 'now-30m', to: 'now' });
expect(kbnUrlStateStorage.get<QuerySyncState>('_g')?.time).toEqual({
from: 'now-30m',
to: 'now',
});
stop();
});

test('when refresh interval changes, refresh interval is synced to urlStorage', () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
timefilter.setRefreshInterval({ pause: true, value: 100 });
expect(kbnUrlStateStorage.get<QuerySyncState>('_g')?.refreshInterval).toEqual({
pause: true,
value: 100,
});
stop();
});

test('when url is changed, filters synced back to filterManager', () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
kbnUrlStateStorage.cancel(); // stop initial syncing pending update
history.push(pathWithFilter);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
stop();
});

test('initial url should be synced with services', () => {
history.push(pathWithFilter);

const { stop, hasInheritedQueryFromUrl } = syncQuery(queryServiceStart, kbnUrlStateStorage);
expect(hasInheritedQueryFromUrl).toBe(true);
expect(filterManager.getGlobalFilters()).toHaveLength(1);
stop();
});

test("url changes shouldn't trigger services updates if data didn't change", () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
filterManagerChangeTriggered.mockClear();

history.push(pathWithFilter);
history.push(pathWithFilter);
history.push(pathWithFilter);

expect(filterManagerChangeTriggered).not.toBeCalled();
stop();
});

test("if data didn't change, kbnUrlStateStorage.set shouldn't be called", () => {
const { stop } = syncQuery(queryServiceStart, kbnUrlStateStorage);
filterManager.setFilters([gF, aF]);
const spy = jest.spyOn(kbnUrlStateStorage, 'set');
filterManager.setFilters([gF]); // global filters didn't change
expect(spy).not.toBeCalled();
stop();
});
});
22 changes: 11 additions & 11 deletions src/plugins/data/public/query/state_sync/sync_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { Subscription } from 'rxjs';
import _ from 'lodash';
import { filter, map } from 'rxjs/operators';
import {
createStateContainer,
Expand All @@ -30,7 +31,7 @@ import { QueryStart } from '../query_service';

const GLOBAL_STATE_STORAGE_KEY = '_g';

interface QuerySyncState {
export interface QuerySyncState {
time?: TimeRange;
refreshInterval?: RefreshInterval;
filters?: esFilters.Filter[];
Expand Down Expand Up @@ -64,7 +65,7 @@ export const syncQuery = (
};

// create state container, which will be used for syncing with syncState() util
const filtersSyncStateContainer = createStateContainer(
const querySyncStateContainer = createStateContainer(
initialState,
{
setTime: (state: QuerySyncState) => (time: TimeRange) => ({ ...state, time }),
Expand All @@ -86,10 +87,10 @@ export const syncQuery = (

const subs: Subscription[] = [
timefilter.getTimeUpdate$().subscribe(() => {
filtersSyncStateContainer.transitions.setTime(timefilter.getTime());
querySyncStateContainer.transitions.setTime(timefilter.getTime());
}),
timefilter.getRefreshIntervalUpdate$().subscribe(() => {
filtersSyncStateContainer.transitions.setRefreshInterval(timefilter.getRefreshInterval());
querySyncStateContainer.transitions.setRefreshInterval(timefilter.getRefreshInterval());
}),
filterManager
.getUpdates$()
Expand All @@ -98,21 +99,20 @@ export const syncQuery = (
filter(newGlobalFilters => {
// continue only if global filters changed
// and ignore app state filters
const oldGlobalFilters = filtersSyncStateContainer.get().filters;
const oldGlobalFilters = querySyncStateContainer.get().filters;
return (
!oldGlobalFilters ||
!compareFilters(newGlobalFilters, oldGlobalFilters, COMPARE_ALL_OPTIONS)
);
})
)
.subscribe(newGlobalFilters => {
filtersSyncStateContainer.transitions.setFilters(newGlobalFilters);
querySyncStateContainer.transitions.setFilters(newGlobalFilters);
}),
filtersSyncStateContainer.state$.subscribe(
querySyncStateContainer.state$.subscribe(
({ time, filters: globalFilters, refreshInterval }) => {
// cloneDeep is required because services are mutating passed objects
// and state in state container is frozen

if (time && !_.isEqual(time, timefilter.getTime())) {
timefilter.setTime(_.cloneDeep(time));
}
Expand Down Expand Up @@ -140,16 +140,16 @@ export const syncQuery = (
}

// trigger initial syncing from state container to services if needed
filtersSyncStateContainer.set(initialState);
querySyncStateContainer.set(initialState);

const { start, stop } = syncState({
stateStorage: urlStateStorage,
stateContainer: {
...filtersSyncStateContainer,
...querySyncStateContainer,
set: state => {
if (state) {
// syncState utils requires to handle incoming "null" value
filtersSyncStateContainer.set(state);
querySyncStateContainer.set(state);
}
},
},
Expand Down

0 comments on commit 1ebb3a1

Please sign in to comment.