2019-09-17 20:57:53 +02:00
|
|
|
/*
|
|
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
|
|
* or more contributor license agreements. Licensed under the Elastic License;
|
|
|
|
* you may not use this file except in compliance with the Elastic License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import React from 'react';
|
2019-10-03 17:24:45 +02:00
|
|
|
import { ReactWrapper } from 'enzyme';
|
|
|
|
import { act } from 'react-dom/test-utils';
|
2019-09-17 20:57:53 +02:00
|
|
|
import { App } from './app';
|
|
|
|
import { EditorFrameInstance } from '../types';
|
2020-06-01 21:05:27 +02:00
|
|
|
import { AppMountParameters } from 'kibana/public';
|
2020-04-15 12:22:37 +02:00
|
|
|
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
2019-09-17 20:57:53 +02:00
|
|
|
import { Document, SavedObjectStore } from '../persistence';
|
|
|
|
import { mount } from 'enzyme';
|
2020-06-10 15:44:59 +02:00
|
|
|
import { createMemoryHistory, History } from 'history';
|
2020-04-15 20:57:44 +02:00
|
|
|
import { SavedObjectSaveModal } from '../../../../../src/plugins/saved_objects/public';
|
2020-01-21 22:33:53 +01:00
|
|
|
import {
|
|
|
|
esFilters,
|
|
|
|
FilterManager,
|
|
|
|
IFieldType,
|
|
|
|
IIndexPattern,
|
2020-06-05 09:55:42 +02:00
|
|
|
UI_SETTINGS,
|
2020-04-15 12:22:37 +02:00
|
|
|
} from '../../../../../src/plugins/data/public';
|
|
|
|
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
|
2019-10-06 16:25:24 +02:00
|
|
|
const dataStartMock = dataPluginMock.createStartContract();
|
|
|
|
|
2020-04-15 12:22:37 +02:00
|
|
|
import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks';
|
|
|
|
import { TopNavMenuData } from '../../../../../src/plugins/navigation/public';
|
2019-09-17 20:57:53 +02:00
|
|
|
import { coreMock } from 'src/core/public/mocks';
|
2020-06-10 15:44:59 +02:00
|
|
|
import { Observable } from 'rxjs';
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
jest.mock('../persistence');
|
|
|
|
jest.mock('src/core/public');
|
|
|
|
|
2020-04-15 12:22:37 +02:00
|
|
|
const navigationStartMock = navigationPluginMock.createStartContract();
|
|
|
|
|
|
|
|
jest.spyOn(navigationStartMock.ui.TopNavMenu.prototype, 'constructor').mockImplementation(() => {
|
|
|
|
return <div className="topNavMenu" />;
|
|
|
|
});
|
2019-12-16 14:56:51 +01:00
|
|
|
|
2020-04-15 12:22:37 +02:00
|
|
|
const { TopNavMenu } = navigationStartMock.ui;
|
2019-12-16 14:56:51 +01:00
|
|
|
|
2019-09-17 20:57:53 +02:00
|
|
|
function createMockFrame(): jest.Mocked<EditorFrameInstance> {
|
|
|
|
return {
|
|
|
|
mount: jest.fn((el, props) => {}),
|
|
|
|
unmount: jest.fn(() => {}),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
function createMockFilterManager() {
|
|
|
|
const unsubscribe = jest.fn();
|
|
|
|
|
|
|
|
let subscriber: () => void;
|
|
|
|
let filters: unknown = [];
|
|
|
|
|
|
|
|
return {
|
|
|
|
getUpdates$: () => ({
|
|
|
|
subscribe: ({ next }: { next: () => void }) => {
|
|
|
|
subscriber = next;
|
|
|
|
return unsubscribe;
|
|
|
|
},
|
|
|
|
}),
|
2020-02-03 17:19:41 +01:00
|
|
|
setFilters: jest.fn((newFilters: unknown[]) => {
|
2019-10-03 17:24:45 +02:00
|
|
|
filters = newFilters;
|
2020-02-03 17:19:41 +01:00
|
|
|
if (subscriber) subscriber();
|
|
|
|
}),
|
|
|
|
setAppFilters: jest.fn((newFilters: unknown[]) => {
|
|
|
|
filters = newFilters;
|
|
|
|
if (subscriber) subscriber();
|
|
|
|
}),
|
2019-10-03 17:24:45 +02:00
|
|
|
getFilters: () => filters,
|
2020-01-21 22:33:53 +01:00
|
|
|
getGlobalFilters: () => {
|
|
|
|
// @ts-ignore
|
|
|
|
return filters.filter(esFilters.isFilterPinned);
|
|
|
|
},
|
2019-10-03 17:24:45 +02:00
|
|
|
removeAll: () => {
|
|
|
|
filters = [];
|
|
|
|
subscriber();
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-03-24 20:51:00 +01:00
|
|
|
function createMockTimefilter() {
|
|
|
|
const unsubscribe = jest.fn();
|
|
|
|
|
|
|
|
return {
|
|
|
|
getTime: jest.fn(() => ({ from: 'now-7d', to: 'now' })),
|
|
|
|
setTime: jest.fn(),
|
|
|
|
getTimeUpdate$: () => ({
|
|
|
|
subscribe: ({ next }: { next: () => void }) => {
|
|
|
|
return unsubscribe;
|
|
|
|
},
|
|
|
|
}),
|
2020-06-10 15:44:59 +02:00
|
|
|
getRefreshInterval: () => {},
|
|
|
|
getRefreshIntervalDefaults: () => {},
|
2020-03-24 20:51:00 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-09-17 20:57:53 +02:00
|
|
|
describe('Lens App', () => {
|
|
|
|
let frame: jest.Mocked<EditorFrameInstance>;
|
|
|
|
let core: ReturnType<typeof coreMock['createStart']>;
|
2020-03-10 22:10:35 +01:00
|
|
|
let instance: ReactWrapper;
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
function makeDefaultArgs(): jest.Mocked<{
|
|
|
|
editorFrame: EditorFrameInstance;
|
2019-10-06 16:25:24 +02:00
|
|
|
data: typeof dataStartMock;
|
2020-04-15 12:22:37 +02:00
|
|
|
navigation: typeof navigationStartMock;
|
2019-09-17 20:57:53 +02:00
|
|
|
core: typeof core;
|
2019-10-29 13:16:08 +01:00
|
|
|
storage: Storage;
|
2019-09-17 20:57:53 +02:00
|
|
|
docId?: string;
|
|
|
|
docStorage: SavedObjectStore;
|
2020-05-14 18:29:03 +02:00
|
|
|
redirectTo: (
|
|
|
|
id?: string,
|
|
|
|
returnToOrigin?: boolean,
|
|
|
|
originatingApp?: string | undefined,
|
|
|
|
newlyCreated?: boolean
|
|
|
|
) => void;
|
2020-05-05 22:58:36 +02:00
|
|
|
originatingApp: string | undefined;
|
2020-06-01 21:05:27 +02:00
|
|
|
onAppLeave: AppMountParameters['onAppLeave'];
|
2020-06-10 15:44:59 +02:00
|
|
|
history: History;
|
2019-09-17 20:57:53 +02:00
|
|
|
}> {
|
|
|
|
return ({
|
2020-04-15 12:22:37 +02:00
|
|
|
navigation: navigationStartMock,
|
2019-09-17 20:57:53 +02:00
|
|
|
editorFrame: createMockFrame(),
|
2019-10-03 17:24:45 +02:00
|
|
|
core: {
|
|
|
|
...core,
|
|
|
|
application: {
|
|
|
|
...core.application,
|
|
|
|
capabilities: {
|
|
|
|
...core.application.capabilities,
|
2019-10-15 21:03:12 +02:00
|
|
|
visualize: { save: true, saveQuery: true, show: true },
|
2019-10-03 17:24:45 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-10-29 10:55:24 +01:00
|
|
|
data: {
|
|
|
|
query: {
|
|
|
|
filterManager: createMockFilterManager(),
|
2019-11-20 12:06:53 +01:00
|
|
|
timefilter: {
|
2020-03-24 20:51:00 +01:00
|
|
|
timefilter: createMockTimefilter(),
|
2019-11-20 12:06:53 +01:00
|
|
|
},
|
2020-06-10 15:44:59 +02:00
|
|
|
state$: new Observable(),
|
2019-10-29 10:55:24 +01:00
|
|
|
},
|
2019-10-03 17:24:45 +02:00
|
|
|
indexPatterns: {
|
2020-05-22 09:08:58 +02:00
|
|
|
get: jest.fn((id) => {
|
|
|
|
return new Promise((resolve) => resolve({ id }));
|
2019-12-09 12:31:49 +01:00
|
|
|
}),
|
2019-10-03 17:24:45 +02:00
|
|
|
},
|
|
|
|
},
|
2019-10-29 13:16:08 +01:00
|
|
|
storage: {
|
2019-09-17 20:57:53 +02:00
|
|
|
get: jest.fn(),
|
|
|
|
},
|
|
|
|
docStorage: {
|
|
|
|
load: jest.fn(),
|
|
|
|
save: jest.fn(),
|
|
|
|
},
|
2020-05-14 18:29:03 +02:00
|
|
|
redirectTo: jest.fn(
|
|
|
|
(
|
|
|
|
id?: string,
|
|
|
|
returnToOrigin?: boolean,
|
|
|
|
originatingApp?: string | undefined,
|
|
|
|
newlyCreated?: boolean
|
|
|
|
) => {}
|
|
|
|
),
|
2020-06-01 21:05:27 +02:00
|
|
|
onAppLeave: jest.fn(),
|
2020-06-10 15:44:59 +02:00
|
|
|
history: createMemoryHistory(),
|
2019-09-17 20:57:53 +02:00
|
|
|
} as unknown) as jest.Mocked<{
|
2020-04-15 12:22:37 +02:00
|
|
|
navigation: typeof navigationStartMock;
|
2019-09-17 20:57:53 +02:00
|
|
|
editorFrame: EditorFrameInstance;
|
2019-10-06 16:25:24 +02:00
|
|
|
data: typeof dataStartMock;
|
2019-09-17 20:57:53 +02:00
|
|
|
core: typeof core;
|
2019-10-29 13:16:08 +01:00
|
|
|
storage: Storage;
|
2019-09-17 20:57:53 +02:00
|
|
|
docId?: string;
|
|
|
|
docStorage: SavedObjectStore;
|
2020-05-14 18:29:03 +02:00
|
|
|
redirectTo: (
|
|
|
|
id?: string,
|
|
|
|
returnToOrigin?: boolean,
|
|
|
|
originatingApp?: string | undefined,
|
|
|
|
newlyCreated?: boolean
|
|
|
|
) => void;
|
2020-05-05 22:58:36 +02:00
|
|
|
originatingApp: string | undefined;
|
2020-06-01 21:05:27 +02:00
|
|
|
onAppLeave: AppMountParameters['onAppLeave'];
|
2020-06-10 15:44:59 +02:00
|
|
|
history: History;
|
2019-09-17 20:57:53 +02:00
|
|
|
}>;
|
|
|
|
}
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
frame = createMockFrame();
|
2019-10-24 17:59:45 +02:00
|
|
|
core = coreMock.createStart({ basePath: '/testbasepath' });
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
core.uiSettings.get.mockImplementation(
|
2020-05-22 09:08:58 +02:00
|
|
|
jest.fn((type) => {
|
2019-09-17 20:57:53 +02:00
|
|
|
if (type === 'timepicker:timeDefaults') {
|
|
|
|
return { from: 'now-7d', to: 'now' };
|
2020-06-05 09:55:42 +02:00
|
|
|
} else if (type === UI_SETTINGS.SEARCH_QUERY_LANGUAGE) {
|
2019-09-17 20:57:53 +02:00
|
|
|
return 'kuery';
|
2020-06-10 15:44:59 +02:00
|
|
|
} else if (type === 'state:storeInSessionStorage') {
|
|
|
|
return false;
|
2019-09-17 20:57:53 +02:00
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the editor frame', () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
|
|
|
mount(<App {...args} />);
|
|
|
|
|
|
|
|
expect(frame.mount.mock.calls).toMatchInlineSnapshot(`
|
|
|
|
Array [
|
|
|
|
Array [
|
|
|
|
<div
|
2019-09-23 20:57:35 +02:00
|
|
|
class="lnsApp__frame"
|
2019-09-17 20:57:53 +02:00
|
|
|
/>,
|
|
|
|
Object {
|
|
|
|
"dateRange": Object {
|
|
|
|
"fromDate": "now-7d",
|
|
|
|
"toDate": "now",
|
|
|
|
},
|
|
|
|
"doc": undefined,
|
2019-10-03 17:24:45 +02:00
|
|
|
"filters": Array [],
|
2019-09-17 20:57:53 +02:00
|
|
|
"onChange": [Function],
|
|
|
|
"onError": [Function],
|
|
|
|
"query": Object {
|
|
|
|
"language": "kuery",
|
|
|
|
"query": "",
|
|
|
|
},
|
2019-10-03 17:24:45 +02:00
|
|
|
"savedQuery": undefined,
|
2019-09-17 20:57:53 +02:00
|
|
|
},
|
|
|
|
],
|
|
|
|
]
|
|
|
|
`);
|
|
|
|
});
|
|
|
|
|
2020-02-03 17:19:41 +01:00
|
|
|
it('clears app filters on load', () => {
|
|
|
|
const defaultArgs = makeDefaultArgs();
|
|
|
|
mount(<App {...defaultArgs} />);
|
|
|
|
|
|
|
|
expect(defaultArgs.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]);
|
|
|
|
});
|
|
|
|
|
2019-09-17 20:57:53 +02:00
|
|
|
it('sets breadcrumbs when the document title changes', async () => {
|
|
|
|
const defaultArgs = makeDefaultArgs();
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...defaultArgs} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(core.chrome.setBreadcrumbs).toHaveBeenCalledWith([
|
2020-05-13 09:19:24 +02:00
|
|
|
{ text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() },
|
2019-09-17 20:57:53 +02:00
|
|
|
{ text: 'Create' },
|
|
|
|
]);
|
|
|
|
|
|
|
|
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
|
|
|
|
id: '1234',
|
|
|
|
title: 'Daaaaaaadaumching!',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'valid expression',
|
2019-09-17 20:57:53 +02:00
|
|
|
state: {
|
|
|
|
query: 'fake query',
|
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
|
|
|
},
|
|
|
|
});
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(defaultArgs.core.chrome.setBreadcrumbs).toHaveBeenCalledWith([
|
2020-05-13 09:19:24 +02:00
|
|
|
{ text: 'Visualize', href: '/testbasepath/app/visualize#/', onClick: expect.anything() },
|
2019-09-17 20:57:53 +02:00
|
|
|
{ text: 'Daaaaaaadaumching!' },
|
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('persistence', () => {
|
|
|
|
it('does not load a document if there is no document id', () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
|
|
|
|
mount(<App {...args} />);
|
|
|
|
|
|
|
|
expect(args.docStorage.load).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2020-02-03 17:19:41 +01:00
|
|
|
it('loads a document and uses query and filters if there is a document id', async () => {
|
2019-09-17 20:57:53 +02:00
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
(args.docStorage.load as jest.Mock).mockResolvedValue({
|
|
|
|
id: '1234',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'valid expression',
|
2019-09-17 20:57:53 +02:00
|
|
|
state: {
|
|
|
|
query: 'fake query',
|
2020-02-03 17:19:41 +01:00
|
|
|
filters: [{ query: { match_phrase: { src: 'test' } } }],
|
2019-09-17 20:57:53 +02:00
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(args.docStorage.load).toHaveBeenCalledWith('1234');
|
2019-12-09 12:31:49 +01:00
|
|
|
expect(args.data.indexPatterns.get).toHaveBeenCalledWith('1');
|
2020-02-03 17:19:41 +01:00
|
|
|
expect(args.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([
|
|
|
|
{ query: { match_phrase: { src: 'test' } } },
|
|
|
|
]);
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
2019-09-17 20:57:53 +02:00
|
|
|
expect.objectContaining({
|
|
|
|
query: 'fake query',
|
2019-10-03 17:24:45 +02:00
|
|
|
indexPatterns: [{ id: '1' }],
|
2019-09-17 20:57:53 +02:00
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
expect(frame.mount).toHaveBeenCalledWith(
|
|
|
|
expect.any(Element),
|
|
|
|
expect.objectContaining({
|
|
|
|
doc: {
|
|
|
|
id: '1234',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'valid expression',
|
2019-09-17 20:57:53 +02:00
|
|
|
state: {
|
|
|
|
query: 'fake query',
|
2020-02-03 17:19:41 +01:00
|
|
|
filters: [{ query: { match_phrase: { src: 'test' } } }],
|
2019-09-17 20:57:53 +02:00
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not load documents on sequential renders unless the id changes', async () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
(args.docStorage.load as jest.Mock).mockResolvedValue({ id: '1234' });
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(args.docStorage.load).toHaveBeenCalledTimes(1);
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '9876' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(args.docStorage.load).toHaveBeenCalledTimes(2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('handles document load errors', async () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
(args.docStorage.load as jest.Mock).mockRejectedValue('failed to load');
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(args.docStorage.load).toHaveBeenCalledWith('1234');
|
|
|
|
expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled();
|
|
|
|
expect(args.redirectTo).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('save button', () => {
|
2019-10-16 01:40:30 +02:00
|
|
|
interface SaveProps {
|
|
|
|
newCopyOnSave: boolean;
|
2020-05-05 22:58:36 +02:00
|
|
|
returnToOrigin?: boolean;
|
2019-10-16 01:40:30 +02:00
|
|
|
newTitle: string;
|
|
|
|
}
|
|
|
|
|
2020-06-01 21:05:27 +02:00
|
|
|
let defaultArgs: ReturnType<typeof makeDefaultArgs>;
|
2019-12-24 09:21:38 +01:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
defaultArgs = makeDefaultArgs();
|
|
|
|
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
|
|
|
|
id: '1234',
|
|
|
|
title: 'My cool doc',
|
|
|
|
expression: 'valid expression',
|
|
|
|
state: {
|
|
|
|
query: 'kuery',
|
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
|
|
|
},
|
|
|
|
} as jest.ResolvedValue<Document>);
|
|
|
|
});
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
function getButton(inst: ReactWrapper): TopNavMenuData {
|
|
|
|
return (inst
|
2019-10-03 17:24:45 +02:00
|
|
|
.find('[data-test-subj="lnsApp_topNav"]')
|
|
|
|
.prop('config') as TopNavMenuData[]).find(
|
2020-05-22 09:08:58 +02:00
|
|
|
(button) => button.testId === 'lnsApp_saveButton'
|
2019-10-03 17:24:45 +02:00
|
|
|
)!;
|
|
|
|
}
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
async function testSave(inst: ReactWrapper, saveProps: SaveProps) {
|
|
|
|
await getButton(inst).run(inst.getDOMNode());
|
|
|
|
inst.update();
|
2020-05-05 22:58:36 +02:00
|
|
|
const handler = inst.find('[data-test-subj="lnsApp_saveModalOrigin"]').prop('onSave') as (
|
2019-10-16 01:40:30 +02:00
|
|
|
p: unknown
|
2019-11-13 17:00:02 +01:00
|
|
|
) => void;
|
2019-10-16 01:40:30 +02:00
|
|
|
handler(saveProps);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function save({
|
2020-02-13 13:59:27 +01:00
|
|
|
lastKnownDoc = { expression: 'kibana 3' },
|
2020-05-05 22:58:36 +02:00
|
|
|
initialDocId,
|
2019-10-16 01:40:30 +02:00
|
|
|
...saveProps
|
|
|
|
}: SaveProps & {
|
2020-02-13 13:59:27 +01:00
|
|
|
lastKnownDoc?: object;
|
2019-10-16 01:40:30 +02:00
|
|
|
initialDocId?: string;
|
|
|
|
}) {
|
|
|
|
const args = {
|
2019-12-24 09:21:38 +01:00
|
|
|
...defaultArgs,
|
2019-10-16 01:40:30 +02:00
|
|
|
docId: initialDocId,
|
|
|
|
};
|
|
|
|
args.editorFrame = frame;
|
|
|
|
(args.docStorage.load as jest.Mock).mockResolvedValue({
|
|
|
|
id: '1234',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'kibana',
|
2019-10-16 01:40:30 +02:00
|
|
|
state: {
|
|
|
|
query: 'fake query',
|
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
2020-02-13 13:59:27 +01:00
|
|
|
filters: [],
|
2019-10-16 01:40:30 +02:00
|
|
|
},
|
|
|
|
});
|
|
|
|
(args.docStorage.save as jest.Mock).mockImplementation(async ({ id }) => ({
|
|
|
|
id: id || 'aaa',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'kibana 2',
|
2019-10-16 01:40:30 +02:00
|
|
|
}));
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
instance = mount(<App {...args} />);
|
|
|
|
});
|
2019-10-16 01:40:30 +02:00
|
|
|
|
|
|
|
if (initialDocId) {
|
|
|
|
expect(args.docStorage.load).toHaveBeenCalledTimes(1);
|
|
|
|
} else {
|
|
|
|
expect(args.docStorage.load).not.toHaveBeenCalled();
|
|
|
|
}
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: { id: initialDocId, ...lastKnownDoc } as Document,
|
|
|
|
})
|
|
|
|
);
|
2019-10-16 01:40:30 +02:00
|
|
|
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
expect(getButton(instance).disableButton).toEqual(false);
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
2020-05-05 22:58:36 +02:00
|
|
|
testSave(instance, { ...saveProps });
|
2020-03-10 22:10:35 +01:00
|
|
|
});
|
2019-10-16 01:40:30 +02:00
|
|
|
|
|
|
|
return { args, instance };
|
|
|
|
}
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
it('shows a disabled save button when the user does not have permissions', async () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-10-03 17:24:45 +02:00
|
|
|
args.core.application = {
|
|
|
|
...args.core.application,
|
|
|
|
capabilities: {
|
|
|
|
...args.core.application.capabilities,
|
2019-10-15 21:03:12 +02:00
|
|
|
visualize: { save: false, saveQuery: false, show: true },
|
2019-10-03 17:24:45 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
expect(getButton(instance).disableButton).toEqual(true);
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: 'will save this', expression: 'valid expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
2019-10-03 17:24:45 +02:00
|
|
|
instance.update();
|
2019-12-24 09:21:38 +01:00
|
|
|
expect(getButton(instance).disableButton).toEqual(true);
|
|
|
|
});
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
it('shows a save button that is enabled when the frame has provided its state', async () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-09-17 20:57:53 +02:00
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(getButton(instance).disableButton).toEqual(true);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: 'will save this', expression: 'valid expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
2019-09-17 20:57:53 +02:00
|
|
|
instance.update();
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(getButton(instance).disableButton).toEqual(false);
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
it('saves new docs', async () => {
|
2020-03-10 22:10:35 +01:00
|
|
|
const { args, instance: inst } = await save({
|
2019-10-16 01:40:30 +02:00
|
|
|
initialDocId: undefined,
|
|
|
|
newCopyOnSave: false,
|
|
|
|
newTitle: 'hello there',
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
expect(args.docStorage.save).toHaveBeenCalledWith({
|
|
|
|
id: undefined,
|
|
|
|
title: 'hello there',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'kibana 3',
|
2019-10-16 01:40:30 +02:00
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-05-14 18:29:03 +02:00
|
|
|
expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, undefined, true);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
inst.setProps({ docId: 'aaa' });
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
expect(args.docStorage.load).not.toHaveBeenCalled();
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
it('saves the latest doc as a copy', async () => {
|
2020-03-10 22:10:35 +01:00
|
|
|
const { args, instance: inst } = await save({
|
2019-10-16 01:40:30 +02:00
|
|
|
initialDocId: '1234',
|
|
|
|
newCopyOnSave: true,
|
|
|
|
newTitle: 'hello there',
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
expect(args.docStorage.save).toHaveBeenCalledWith({
|
|
|
|
id: undefined,
|
|
|
|
title: 'hello there',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'kibana 3',
|
2019-10-03 17:24:45 +02:00
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-05-14 18:29:03 +02:00
|
|
|
expect(args.redirectTo).toHaveBeenCalledWith('aaa', undefined, undefined, true);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
inst.setProps({ docId: 'aaa' });
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
expect(args.docStorage.load).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
it('saves existing docs', async () => {
|
2020-03-10 22:10:35 +01:00
|
|
|
const { args, instance: inst } = await save({
|
2019-10-16 01:40:30 +02:00
|
|
|
initialDocId: '1234',
|
|
|
|
newCopyOnSave: false,
|
|
|
|
newTitle: 'hello there',
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
expect(args.docStorage.save).toHaveBeenCalledWith({
|
|
|
|
id: '1234',
|
|
|
|
title: 'hello there',
|
2019-12-24 09:21:38 +01:00
|
|
|
expression: 'kibana 3',
|
2019-10-16 01:40:30 +02:00
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-16 01:40:30 +02:00
|
|
|
expect(args.redirectTo).not.toHaveBeenCalled();
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
inst.setProps({ docId: '1234' });
|
2019-10-16 01:40:30 +02:00
|
|
|
|
|
|
|
expect(args.docStorage.load).toHaveBeenCalledTimes(1);
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it('handles save failure by showing a warning, but still allows another save', async () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-09-17 20:57:53 +02:00
|
|
|
args.editorFrame = frame;
|
|
|
|
(args.docStorage.save as jest.Mock).mockRejectedValue({ message: 'failed' });
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: undefined, expression: 'new expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
instance.update();
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
await act(async () => {
|
|
|
|
testSave(instance, { newCopyOnSave: false, newTitle: 'hello there' });
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled();
|
|
|
|
expect(args.redirectTo).not.toHaveBeenCalled();
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(getButton(instance).disableButton).toEqual(false);
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|
2020-01-13 18:38:47 +01:00
|
|
|
|
2020-05-05 22:58:36 +02:00
|
|
|
it('saves new doc and redirects to originating app', async () => {
|
2020-01-13 18:38:47 +01:00
|
|
|
const { args } = await save({
|
|
|
|
initialDocId: undefined,
|
2020-05-05 22:58:36 +02:00
|
|
|
returnToOrigin: true,
|
2020-01-13 18:38:47 +01:00
|
|
|
newCopyOnSave: false,
|
|
|
|
newTitle: 'hello there',
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(args.docStorage.save).toHaveBeenCalledWith({
|
|
|
|
expression: 'kibana 3',
|
|
|
|
id: undefined,
|
|
|
|
title: 'hello there',
|
|
|
|
});
|
|
|
|
|
2020-05-14 18:29:03 +02:00
|
|
|
expect(args.redirectTo).toHaveBeenCalledWith('aaa', true, undefined, true);
|
2020-01-13 18:38:47 +01:00
|
|
|
});
|
2020-02-13 13:59:27 +01:00
|
|
|
|
|
|
|
it('saves app filters and does not save pinned filters', async () => {
|
|
|
|
const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern;
|
|
|
|
const field = ({ name: 'myfield' } as unknown) as IFieldType;
|
|
|
|
const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType;
|
|
|
|
|
|
|
|
const unpinned = esFilters.buildExistsFilter(field, indexPattern);
|
|
|
|
const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern);
|
2020-03-10 22:10:35 +01:00
|
|
|
|
|
|
|
await act(async () => {
|
|
|
|
FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE);
|
|
|
|
});
|
2020-02-13 13:59:27 +01:00
|
|
|
|
|
|
|
const { args } = await save({
|
|
|
|
initialDocId: '1234',
|
|
|
|
newCopyOnSave: false,
|
|
|
|
newTitle: 'hello there2',
|
|
|
|
lastKnownDoc: {
|
|
|
|
expression: 'kibana 3',
|
|
|
|
state: {
|
|
|
|
filters: [pinned, unpinned],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(args.docStorage.save).toHaveBeenCalledWith({
|
|
|
|
id: '1234',
|
|
|
|
title: 'hello there2',
|
|
|
|
expression: 'kibana 3',
|
|
|
|
state: {
|
|
|
|
filters: [unpinned],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
2020-04-15 20:57:44 +02:00
|
|
|
|
|
|
|
it('does not show the copy button on first save', async () => {
|
|
|
|
const args = defaultArgs;
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
|
|
|
instance = mount(<App {...args} />);
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
|
|
|
await act(async () =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ expression: 'valid expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
instance.update();
|
|
|
|
await act(async () => getButton(instance).run(instance.getDOMNode()));
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
expect(instance.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false);
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('query bar state management', () => {
|
2020-06-01 21:05:27 +02:00
|
|
|
let defaultArgs: ReturnType<typeof makeDefaultArgs>;
|
2019-12-24 09:21:38 +01:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
defaultArgs = makeDefaultArgs();
|
|
|
|
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
|
|
|
|
id: '1234',
|
|
|
|
title: 'My cool doc',
|
|
|
|
expression: 'valid expression',
|
|
|
|
state: {
|
|
|
|
query: 'kuery',
|
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
|
|
|
},
|
|
|
|
} as jest.ResolvedValue<Document>);
|
|
|
|
});
|
|
|
|
|
2019-09-17 20:57:53 +02:00
|
|
|
it('uses the default time and query language settings', () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-09-17 20:57:53 +02:00
|
|
|
args.editorFrame = frame;
|
|
|
|
|
|
|
|
mount(<App {...args} />);
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
2019-09-17 20:57:53 +02:00
|
|
|
expect.objectContaining({
|
|
|
|
query: { query: '', language: 'kuery' },
|
2019-10-28 20:04:03 +01:00
|
|
|
dateRangeFrom: 'now-7d',
|
|
|
|
dateRangeTo: 'now',
|
2019-09-17 20:57:53 +02:00
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
expect(frame.mount).toHaveBeenCalledWith(
|
|
|
|
expect.any(Element),
|
|
|
|
expect.objectContaining({
|
|
|
|
dateRange: { fromDate: 'now-7d', toDate: 'now' },
|
|
|
|
query: { query: '', language: 'kuery' },
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
it('updates the index patterns when the editor frame is changed', async () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-09-17 20:57:53 +02:00
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
2019-09-17 20:57:53 +02:00
|
|
|
expect.objectContaining({
|
|
|
|
indexPatterns: [],
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
2020-03-10 22:10:35 +01:00
|
|
|
|
|
|
|
await act(async () => {
|
2020-02-13 13:59:27 +01:00
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [{ id: '1', title: 'newIndex' }],
|
|
|
|
doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document,
|
2020-03-10 22:10:35 +01:00
|
|
|
});
|
|
|
|
});
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({
|
|
|
|
indexPatterns: [{ id: '1' }],
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Do it again to verify that the dirty checking is done right
|
2020-03-10 22:10:35 +01:00
|
|
|
|
|
|
|
await act(async () => {
|
2020-02-13 13:59:27 +01:00
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [{ id: '2', title: 'second index' }],
|
|
|
|
doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document,
|
2020-03-10 22:10:35 +01:00
|
|
|
});
|
|
|
|
});
|
2020-02-13 13:59:27 +01:00
|
|
|
|
2019-09-17 20:57:53 +02:00
|
|
|
instance.update();
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(TopNavMenu).toHaveBeenLastCalledWith(
|
2019-09-17 20:57:53 +02:00
|
|
|
expect.objectContaining({
|
2019-10-03 17:24:45 +02:00
|
|
|
indexPatterns: [{ id: '2' }],
|
2019-09-17 20:57:53 +02:00
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
});
|
2019-10-03 17:24:45 +02:00
|
|
|
it('updates the editor frame when the user changes query or time in the search bar', () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-09-17 20:57:53 +02:00
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
instance.find(TopNavMenu).prop('onQuerySubmit')!({
|
|
|
|
dateRange: { from: 'now-14d', to: 'now-7d' },
|
|
|
|
query: { query: 'new', language: 'lucene' },
|
|
|
|
})
|
|
|
|
);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
instance.update();
|
|
|
|
|
2019-10-03 17:24:45 +02:00
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
2019-09-17 20:57:53 +02:00
|
|
|
expect.objectContaining({
|
|
|
|
query: { query: 'new', language: 'lucene' },
|
2019-10-28 20:04:03 +01:00
|
|
|
dateRangeFrom: 'now-14d',
|
|
|
|
dateRangeTo: 'now-7d',
|
2019-09-17 20:57:53 +02:00
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
expect(frame.mount).toHaveBeenCalledWith(
|
|
|
|
expect.any(Element),
|
|
|
|
expect.objectContaining({
|
|
|
|
dateRange: { fromDate: 'now-14d', toDate: 'now-7d' },
|
|
|
|
query: { query: 'new', language: 'lucene' },
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
it('updates the filters when the user changes them', () => {
|
2019-12-24 09:21:38 +01:00
|
|
|
const args = defaultArgs;
|
2019-10-03 17:24:45 +02:00
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-11-15 21:35:21 +01:00
|
|
|
const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern;
|
|
|
|
const field = ({ name: 'myfield' } as unknown) as IFieldType;
|
2019-10-03 17:24:45 +02:00
|
|
|
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
args.data.query.filterManager.setFilters([esFilters.buildExistsFilter(field, indexPattern)])
|
|
|
|
);
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
expect(frame.mount).toHaveBeenCalledWith(
|
|
|
|
expect.any(Element),
|
|
|
|
expect.objectContaining({
|
2019-11-15 21:35:21 +01:00
|
|
|
filters: [esFilters.buildExistsFilter(field, indexPattern)],
|
2019-10-03 17:24:45 +02:00
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('saved query handling', () => {
|
|
|
|
it('does not allow saving when the user is missing the saveQuery permission', () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.core.application = {
|
|
|
|
...args.core.application,
|
|
|
|
capabilities: {
|
|
|
|
...args.core.application.capabilities,
|
2019-10-15 21:03:12 +02:00
|
|
|
visualize: { save: false, saveQuery: false, show: true },
|
2019-10-03 17:24:45 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
mount(<App {...args} />);
|
|
|
|
|
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({ showSaveQuery: false }),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('persists the saved query ID when the query is saved', () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({
|
|
|
|
showSaveQuery: true,
|
|
|
|
savedQuery: undefined,
|
|
|
|
onSaved: expect.any(Function),
|
|
|
|
onSavedQueryUpdated: expect.any(Function),
|
|
|
|
onClearSavedQuery: expect.any(Function),
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
instance.find(TopNavMenu).prop('onSaved')!({
|
|
|
|
id: '1',
|
|
|
|
attributes: {
|
|
|
|
title: '',
|
|
|
|
description: '',
|
|
|
|
query: { query: '', language: 'lucene' },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({
|
|
|
|
savedQuery: {
|
|
|
|
id: '1',
|
|
|
|
attributes: {
|
|
|
|
title: '',
|
|
|
|
description: '',
|
|
|
|
query: { query: '', language: 'lucene' },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('changes the saved query ID when the query is updated', () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-10-03 17:24:45 +02:00
|
|
|
|
|
|
|
act(() => {
|
|
|
|
instance.find(TopNavMenu).prop('onSaved')!({
|
|
|
|
id: '1',
|
|
|
|
attributes: {
|
|
|
|
title: '',
|
|
|
|
description: '',
|
|
|
|
query: { query: '', language: 'lucene' },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
instance.find(TopNavMenu).prop('onSavedQueryUpdated')!({
|
|
|
|
id: '2',
|
|
|
|
attributes: {
|
|
|
|
title: 'new title',
|
|
|
|
description: '',
|
|
|
|
query: { query: '', language: 'lucene' },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(TopNavMenu).toHaveBeenCalledWith(
|
|
|
|
expect.objectContaining({
|
|
|
|
savedQuery: {
|
|
|
|
id: '2',
|
|
|
|
attributes: {
|
|
|
|
title: 'new title',
|
|
|
|
description: '',
|
|
|
|
query: { query: '', language: 'lucene' },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2020-01-21 22:33:53 +01:00
|
|
|
it('clears all existing unpinned filters when the active saved query is cleared', () => {
|
2019-10-03 17:24:45 +02:00
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-10-03 17:24:45 +02:00
|
|
|
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() =>
|
|
|
|
instance.find(TopNavMenu).prop('onQuerySubmit')!({
|
|
|
|
dateRange: { from: 'now-14d', to: 'now-7d' },
|
|
|
|
query: { query: 'new', language: 'lucene' },
|
|
|
|
})
|
|
|
|
);
|
2019-10-03 17:24:45 +02:00
|
|
|
|
2019-11-15 21:35:21 +01:00
|
|
|
const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern;
|
|
|
|
const field = ({ name: 'myfield' } as unknown) as IFieldType;
|
2020-01-21 22:33:53 +01:00
|
|
|
const pinnedField = ({ name: 'pinnedField' } as unknown) as IFieldType;
|
2019-11-15 21:35:21 +01:00
|
|
|
|
2020-01-21 22:33:53 +01:00
|
|
|
const unpinned = esFilters.buildExistsFilter(field, indexPattern);
|
|
|
|
const pinned = esFilters.buildExistsFilter(pinnedField, indexPattern);
|
|
|
|
FilterManager.setFiltersStore([pinned], esFilters.FilterStateStore.GLOBAL_STATE);
|
|
|
|
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() => args.data.query.filterManager.setFilters([pinned, unpinned]));
|
2019-10-03 17:24:45 +02:00
|
|
|
instance.update();
|
|
|
|
|
2020-02-13 13:59:27 +01:00
|
|
|
act(() => instance.find(TopNavMenu).prop('onClearSavedQuery')!());
|
2019-10-03 17:24:45 +02:00
|
|
|
instance.update();
|
|
|
|
|
|
|
|
expect(frame.mount).toHaveBeenLastCalledWith(
|
|
|
|
expect.any(Element),
|
|
|
|
expect.objectContaining({
|
2020-01-21 22:33:53 +01:00
|
|
|
filters: [pinned],
|
2019-10-03 17:24:45 +02:00
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
it('displays errors from the frame in a toast', () => {
|
|
|
|
const args = makeDefaultArgs();
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
2020-03-10 22:10:35 +01:00
|
|
|
instance = mount(<App {...args} />);
|
2019-09-17 20:57:53 +02:00
|
|
|
|
|
|
|
const onError = frame.mount.mock.calls[0][1].onError;
|
|
|
|
onError({ message: 'error' });
|
|
|
|
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
expect(args.core.notifications.toasts.addDanger).toHaveBeenCalled();
|
|
|
|
});
|
2020-06-01 21:05:27 +02:00
|
|
|
|
|
|
|
describe('showing a confirm message when leaving', () => {
|
|
|
|
let defaultArgs: ReturnType<typeof makeDefaultArgs>;
|
|
|
|
let defaultLeave: jest.Mock;
|
|
|
|
let confirmLeave: jest.Mock;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
defaultArgs = makeDefaultArgs();
|
|
|
|
defaultLeave = jest.fn();
|
|
|
|
confirmLeave = jest.fn();
|
|
|
|
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
|
|
|
|
id: '1234',
|
|
|
|
title: 'My cool doc',
|
|
|
|
expression: 'valid expression',
|
|
|
|
state: {
|
|
|
|
query: 'kuery',
|
|
|
|
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
|
|
|
|
},
|
|
|
|
} as jest.ResolvedValue<Document>);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not show a confirm message if there is no expression to save', () => {
|
|
|
|
instance = mount(<App {...defaultArgs} />);
|
|
|
|
|
|
|
|
const lastCall =
|
|
|
|
defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0];
|
|
|
|
lastCall({ default: defaultLeave, confirm: confirmLeave });
|
|
|
|
|
|
|
|
expect(defaultLeave).toHaveBeenCalled();
|
|
|
|
expect(confirmLeave).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not confirm if the user is missing save permissions', () => {
|
|
|
|
const args = defaultArgs;
|
|
|
|
args.core.application = {
|
|
|
|
...args.core.application,
|
|
|
|
capabilities: {
|
|
|
|
...args.core.application.capabilities,
|
|
|
|
visualize: { save: false, saveQuery: false, show: true },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
args.editorFrame = frame;
|
|
|
|
|
|
|
|
instance = mount(<App {...args} />);
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
const lastCall =
|
|
|
|
defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0];
|
|
|
|
lastCall({ default: defaultLeave, confirm: confirmLeave });
|
|
|
|
|
|
|
|
expect(defaultLeave).toHaveBeenCalled();
|
|
|
|
expect(confirmLeave).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should confirm when leaving with an unsaved doc', () => {
|
|
|
|
defaultArgs.editorFrame = frame;
|
|
|
|
instance = mount(<App {...defaultArgs} />);
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
const lastCall =
|
|
|
|
defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0];
|
|
|
|
lastCall({ default: defaultLeave, confirm: confirmLeave });
|
|
|
|
|
|
|
|
expect(confirmLeave).toHaveBeenCalled();
|
|
|
|
expect(defaultLeave).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should confirm when leaving with unsaved changes to an existing doc', async () => {
|
|
|
|
defaultArgs.editorFrame = frame;
|
|
|
|
instance = mount(<App {...defaultArgs} />);
|
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: '1234', expression: 'different expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
const lastCall =
|
|
|
|
defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0];
|
|
|
|
lastCall({ default: defaultLeave, confirm: confirmLeave });
|
|
|
|
|
|
|
|
expect(confirmLeave).toHaveBeenCalled();
|
|
|
|
expect(defaultLeave).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not confirm when changes are saved', async () => {
|
|
|
|
defaultArgs.editorFrame = frame;
|
|
|
|
instance = mount(<App {...defaultArgs} />);
|
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: '1234', expression: 'valid expression' } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
const lastCall =
|
|
|
|
defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0];
|
|
|
|
lastCall({ default: defaultLeave, confirm: confirmLeave });
|
|
|
|
|
|
|
|
expect(defaultLeave).toHaveBeenCalled();
|
|
|
|
expect(confirmLeave).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should confirm when the latest doc is invalid', async () => {
|
|
|
|
defaultArgs.editorFrame = frame;
|
|
|
|
instance = mount(<App {...defaultArgs} />);
|
|
|
|
await act(async () => {
|
|
|
|
instance.setProps({ docId: '1234' });
|
|
|
|
});
|
|
|
|
|
|
|
|
const onChange = frame.mount.mock.calls[0][1].onChange;
|
|
|
|
act(() =>
|
|
|
|
onChange({
|
|
|
|
filterableIndexPatterns: [],
|
|
|
|
doc: ({ id: '1234', expression: null } as unknown) as Document,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
instance.update();
|
|
|
|
|
|
|
|
const lastCall =
|
|
|
|
defaultArgs.onAppLeave.mock.calls[defaultArgs.onAppLeave.mock.calls.length - 1][0];
|
|
|
|
lastCall({ default: defaultLeave, confirm: confirmLeave });
|
|
|
|
|
|
|
|
expect(confirmLeave).toHaveBeenCalled();
|
|
|
|
expect(defaultLeave).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2019-09-17 20:57:53 +02:00
|
|
|
});
|