-
-
-
- {
- if (isSaveable && lastKnownDocRef.current) {
- docStorage
- .save(lastKnownDocRef.current)
- .then(({ id }) => {
- // Prevents unnecessary network request and disables save button
- const newDoc = { ...lastKnownDocRef.current!, id };
- setState({
- ...state,
- isDirty: false,
- persistedDoc: newDoc,
- });
- if (docId !== id) {
- redirectTo(id);
- }
- })
- .catch(reason => {
- core.notifications.toasts.addDanger(
- i18n.translate('xpack.lens.editorFrame.docSavingError', {
- defaultMessage: 'Error saving document {reason}',
- values: { reason },
- })
- );
- });
- }
- }}
- color={isSaveable ? 'primary' : 'subdued'}
- disabled={!isSaveable}
- >
- {i18n.translate('xpack.lens.editorFrame.save', {
- defaultMessage: 'Save',
- })}
-
-
-
-
- {
+ if (isSaveable && lastKnownDocRef.current) {
+ docStorage
+ .save(lastKnownDocRef.current)
+ .then(({ id }) => {
+ // Prevents unnecessary network request and disables save button
+ const newDoc = { ...lastKnownDocRef.current!, id };
+ setState(s => ({
+ ...s,
+ isDirty: false,
+ persistedDoc: newDoc,
+ }));
+ if (docId !== id) {
+ redirectTo(id);
+ }
+ })
+ .catch(() => {
+ core.notifications.toasts.addDanger(
+ i18n.translate('xpack.lens.editorFrame.docSavingError', {
+ defaultMessage: 'Error saving document',
+ })
+ );
+ });
+ }
+ },
+ testId: 'lnsApp_saveButton',
+ disableButton: !isSaveable,
+ },
+ ]}
+ data-test-subj="lnsApp_topNav"
screenTitle={'lens'}
- onSubmit={payload => {
+ onQuerySubmit={payload => {
const { dateRange, query } = payload;
- setState({
- ...state,
+ setState(s => ({
+ ...s,
dateRange: {
fromDate: dateRange.from,
toDate: dateRange.to,
},
- query: query || state.query,
- localQueryBarState: payload,
- });
- }}
- onChange={localQueryBarState => {
- setState({ ...state, localQueryBarState });
+ query: query || s.query,
+ }));
}}
- isDirty={isLocalStateDirty(state.localQueryBarState, state.query, state.dateRange)}
- indexPatterns={state.indexPatternTitles}
+ appName={'lens'}
+ indexPatterns={state.indexPatternsForTopNav}
+ showSearchBar={true}
showDatePicker={true}
- showQueryInput={true}
- query={state.localQueryBarState.query}
- dateRangeFrom={
- state.localQueryBarState.dateRange && state.localQueryBarState.dateRange.from
- }
- dateRangeTo={
- state.localQueryBarState.dateRange && state.localQueryBarState.dateRange.to
- }
+ showQueryBar={true}
+ showFilterBar={true}
+ showSaveQuery={core.application.capabilities.lens.saveQuery as boolean}
+ savedQuery={state.savedQuery}
+ onSaved={savedQuery => {
+ setState(s => ({ ...s, savedQuery }));
+ }}
+ onSavedQueryUpdated={savedQuery => {
+ data.filter.filterManager.setFilters(
+ savedQuery.attributes.filters || state.filters
+ );
+ setState(s => ({
+ ...s,
+ savedQuery: { ...savedQuery }, // Shallow query for reference issues
+ dateRange: savedQuery.attributes.timefilter
+ ? {
+ fromDate: savedQuery.attributes.timefilter.from,
+ toDate: savedQuery.attributes.timefilter.to,
+ }
+ : s.dateRange,
+ }));
+ }}
+ onClearSavedQuery={() => {
+ data.filter.filterManager.removeAll();
+ setState(s => ({
+ ...s,
+ savedQuery: undefined,
+ filters: [],
+ query: {
+ query: '',
+ language:
+ store.get('kibana.userQueryLanguage') ||
+ core.uiSettings.get('search:queryLanguage'),
+ },
+ }));
+ }}
+ query={state.query}
/>
@@ -247,22 +270,35 @@ export function App({
nativeProps={{
dateRange: state.dateRange,
query: state.query,
+ filters: state.filters,
+ savedQuery: state.savedQuery,
doc: state.persistedDoc,
onError,
- onChange: ({ indexPatternTitles, doc }) => {
- const indexPatternChange = !_.isEqual(
- state.indexPatternTitles,
- indexPatternTitles
- );
- const docChange = !_.isEqual(state.persistedDoc, doc);
- if (indexPatternChange || docChange) {
- setState({
- ...state,
- indexPatternTitles,
- isDirty: docChange,
+ onChange: ({ filterableIndexPatterns, doc }) => {
+ lastKnownDocRef.current = doc;
+
+ if (!_.isEqual(state.persistedDoc, doc)) {
+ setState(s => ({ ...s, isDirty: true }));
+ }
+
+ // Update the cached index patterns if the user made a change to any of them
+ if (
+ state.indexPatternsForTopNav.length !== filterableIndexPatterns.length ||
+ filterableIndexPatterns.find(
+ ({ id }) =>
+ !state.indexPatternsForTopNav.find(indexPattern => indexPattern.id === id)
+ )
+ ) {
+ getAllIndexPatterns(
+ filterableIndexPatterns,
+ data.indexPatterns.indexPatterns,
+ core.notifications
+ ).then(indexPatterns => {
+ if (indexPatterns) {
+ setState(s => ({ ...s, indexPatternsForTopNav: indexPatterns }));
+ }
});
}
- lastKnownDocRef.current = doc;
},
}}
/>
@@ -272,3 +308,21 @@ export function App({
);
}
+
+export async function getAllIndexPatterns(
+ ids: Array<{ id: string }>,
+ indexPatternsService: IndexPatternsService,
+ notifications: NotificationsStart
+): Promise
{
+ try {
+ return await Promise.all(ids.map(({ id }) => indexPatternsService.get(id)));
+ } catch (e) {
+ notifications.toasts.addDanger(
+ i18n.translate('xpack.lens.editorFrame.indexPatternLoadingError', {
+ defaultMessage: 'Error loading index patterns',
+ })
+ );
+
+ throw new Error(e);
+ }
+}
diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
index 5e81785132616..3b3b12533d74b 100644
--- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
@@ -11,7 +11,8 @@ import chrome from 'ui/chrome';
import { Storage } from 'ui/storage';
import { CoreSetup, CoreStart } from 'src/core/public';
import { npSetup, npStart } from 'ui/new_platform';
-import { DataPublicPluginStart } from 'src/plugins/data/public';
+import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public';
+import { start as dataStart } from '../../../../../../src/legacy/core_plugins/data/public/legacy';
import { editorFrameSetup, editorFrameStart, editorFrameStop } from '../editor_frame_plugin';
import { indexPatternDatasourceSetup, indexPatternDatasourceStop } from '../indexpattern_plugin';
import { SavedObjectIndexStore } from '../persistence';
@@ -25,7 +26,7 @@ import { App } from './app';
import { EditorFrameInstance } from '../types';
export interface LensPluginStartDependencies {
- data: DataPublicPluginStart;
+ data: DataStart;
}
export class AppPlugin {
private instance: EditorFrameInstance | null = null;
@@ -33,7 +34,7 @@ export class AppPlugin {
constructor() {}
- setup(core: CoreSetup) {
+ setup(core: CoreSetup, plugins: {}) {
// TODO: These plugins should not be called from the top level, but since this is the
// entry point to the app we have no choice until the new platform is ready
const indexPattern = indexPatternDatasourceSetup();
@@ -43,10 +44,10 @@ export class AppPlugin {
const editorFrameSetupInterface = editorFrameSetup();
this.store = new SavedObjectIndexStore(chrome!.getSavedObjectsClient());
- editorFrameSetupInterface.registerDatasource('indexpattern', indexPattern);
editorFrameSetupInterface.registerVisualization(xyVisualization);
editorFrameSetupInterface.registerVisualization(datatableVisualization);
editorFrameSetupInterface.registerVisualization(metricVisualization);
+ editorFrameSetupInterface.registerDatasource('indexpattern', indexPattern);
}
start(core: CoreStart, { data }: LensPluginStartDependencies) {
@@ -113,6 +114,6 @@ export class AppPlugin {
const app = new AppPlugin();
-export const appSetup = () => app.setup(npSetup.core);
-export const appStart = () => app.start(npStart.core, { data: npStart.plugins.data });
+export const appSetup = () => app.setup(npSetup.core, {});
+export const appStart = () => app.start(npStart.core, { data: dataStart });
export const appStop = () => app.stop();
diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx
index 177dfc9577028..f649564b2231a 100644
--- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/visualization.test.tsx
@@ -27,6 +27,7 @@ function mockFrame(): FramePublicAPI {
fromDate: 'now-7d',
toDate: 'now',
},
+ filters: [],
};
}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/data_panel_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/data_panel_wrapper.tsx
index 6229e558d1dab..115e8cbf002c3 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/data_panel_wrapper.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/data_panel_wrapper.tsx
@@ -6,6 +6,7 @@
import React, { useMemo, memo, useContext, useState } from 'react';
import { i18n } from '@kbn/i18n';
+import { Filter } from '@kbn/es-query';
import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
import { Query } from 'src/plugins/data/common';
import { DatasourceDataPanelProps, Datasource } from '../../../public';
@@ -23,6 +24,7 @@ interface DataPanelWrapperProps {
core: DatasourceDataPanelProps['core'];
query: Query;
dateRange: FramePublicAPI['dateRange'];
+ filters: Filter[];
}
export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
@@ -45,6 +47,7 @@ export const DataPanelWrapper = memo((props: DataPanelWrapperProps) => {
core: props.core,
query: props.query,
dateRange: props.dateRange,
+ filters: props.filters,
};
const [showDatasourceSwitcher, setDatasourceSwitcher] = useState(false);
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx
index 0b4d7ba217532..22766b86a4b15 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx
@@ -4,8 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { ReactElement } from 'react';
import { ReactWrapper } from 'enzyme';
+import { EuiPanel, EuiToolTip } from '@elastic/eui';
import { mountWithIntl as mount } from 'test_utils/enzyme_helpers';
import { EditorFrame } from './editor_frame';
import { Visualization, DatasourcePublicAPI, DatasourceSuggestion } from '../../types';
@@ -19,7 +20,7 @@ import {
} from '../mocks';
import { ExpressionRenderer } from 'src/legacy/core_plugins/expressions/public';
import { DragDrop } from '../../drag_drop';
-import { EuiPanel, EuiToolTip } from '@elastic/eui';
+import { FrameLayout } from './frame_layout';
// calling this function will wait for all pending Promises from mock
// datasources to be processed by its callers.
@@ -48,6 +49,7 @@ function getDefaultProps() {
onChange: jest.fn(),
dateRange: { fromDate: '', toDate: '' },
query: { query: '', language: 'lucene' },
+ filters: [],
core: coreMock.createSetup(),
};
}
@@ -256,6 +258,7 @@ describe('editor_frame', () => {
addNewLayer: expect.any(Function),
removeLayers: expect.any(Function),
query: { query: '', language: 'lucene' },
+ filters: [],
dateRange: { fromDate: 'now-7d', toDate: 'now' },
});
});
@@ -409,56 +412,58 @@ describe('editor_frame', () => {
instance.update();
expect(instance.find(expressionRendererMock).prop('expression')).toMatchInlineSnapshot(`
- Object {
- "chain": Array [
- Object {
- "arguments": Object {},
- "function": "kibana",
- "type": "function",
- },
- Object {
- "arguments": Object {
- "filters": Array [],
- "query": Array [
- "{\\"query\\":\\"\\",\\"language\\":\\"lucene\\"}",
- ],
- "timeRange": Array [
- "{\\"from\\":\\"\\",\\"to\\":\\"\\"}",
- ],
- },
- "function": "kibana_context",
- "type": "function",
- },
- Object {
- "arguments": Object {
- "layerIds": Array [
- "first",
- ],
- "tables": Array [
- Object {
- "chain": Array [
- Object {
- "arguments": Object {},
- "function": "datasource",
- "type": "function",
- },
- ],
- "type": "expression",
- },
- ],
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {},
+ "function": "kibana",
+ "type": "function",
+ },
+ Object {
+ "arguments": Object {
+ "filters": Array [
+ "[]",
+ ],
+ "query": Array [
+ "{\\"query\\":\\"\\",\\"language\\":\\"lucene\\"}",
+ ],
+ "timeRange": Array [
+ "{\\"from\\":\\"\\",\\"to\\":\\"\\"}",
+ ],
+ },
+ "function": "kibana_context",
+ "type": "function",
+ },
+ Object {
+ "arguments": Object {
+ "layerIds": Array [
+ "first",
+ ],
+ "tables": Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {},
+ "function": "datasource",
+ "type": "function",
},
- "function": "lens_merge_tables",
- "type": "function",
- },
- Object {
- "arguments": Object {},
- "function": "vis",
- "type": "function",
- },
- ],
- "type": "expression",
- }
- `);
+ ],
+ "type": "expression",
+ },
+ ],
+ },
+ "function": "lens_merge_tables",
+ "type": "function",
+ },
+ Object {
+ "arguments": Object {},
+ "function": "vis",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ }
+ `);
});
it('should render individual expression for each given layer', async () => {
@@ -525,7 +530,9 @@ describe('editor_frame', () => {
},
Object {
"arguments": Object {
- "filters": Array [],
+ "filters": Array [
+ "[]",
+ ],
"query": Array [
"{\\"query\\":\\"\\",\\"language\\":\\"lucene\\"}",
],
@@ -1491,7 +1498,7 @@ describe('editor_frame', () => {
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenNthCalledWith(1, {
- indexPatternTitles: ['resolved'],
+ filterableIndexPatterns: [{ id: '1', title: 'resolved' }],
doc: {
expression: '',
id: undefined,
@@ -1501,6 +1508,7 @@ describe('editor_frame', () => {
datasourceStates: { testDatasource: undefined },
query: { query: '', language: 'lucene' },
filters: [],
+ dateRange: { fromDate: '', toDate: '' },
},
title: 'New visualization',
type: 'lens',
@@ -1508,7 +1516,7 @@ describe('editor_frame', () => {
},
});
expect(onChange).toHaveBeenLastCalledWith({
- indexPatternTitles: ['resolved'],
+ filterableIndexPatterns: [{ id: '1', title: 'resolved' }],
doc: {
expression: '',
id: undefined,
@@ -1520,6 +1528,7 @@ describe('editor_frame', () => {
datasourceStates: { testDatasource: undefined },
query: { query: '', language: 'lucene' },
filters: [],
+ dateRange: { fromDate: '', toDate: '' },
},
title: 'New visualization',
type: 'lens',
@@ -1567,7 +1576,7 @@ describe('editor_frame', () => {
await waitForPromises();
expect(onChange).toHaveBeenCalledTimes(3);
expect(onChange).toHaveBeenNthCalledWith(3, {
- indexPatternTitles: [],
+ filterableIndexPatterns: [],
doc: {
expression: expect.stringContaining('vis "expression"'),
id: undefined,
@@ -1577,6 +1586,7 @@ describe('editor_frame', () => {
visualization: { initialState: true },
query: { query: 'new query', language: 'lucene' },
filters: [],
+ dateRange: { fromDate: '', toDate: '' },
},
title: 'New visualization',
type: 'lens',
@@ -1584,5 +1594,44 @@ describe('editor_frame', () => {
},
});
});
+
+ it('should call onChange when the datasource makes an internal state change', async () => {
+ const onChange = jest.fn();
+
+ mockDatasource.initialize.mockResolvedValue({});
+ mockDatasource.getLayers.mockReturnValue(['first']);
+ mockDatasource.getMetaData.mockReturnValue({
+ filterableIndexPatterns: [{ id: '1', title: 'resolved' }],
+ });
+ mockVisualization.initialize.mockReturnValue({ initialState: true });
+
+ act(() => {
+ instance = mount(
+
+ );
+ });
+
+ await waitForPromises();
+ expect(onChange).toHaveBeenCalledTimes(2);
+
+ (instance.find(FrameLayout).prop('dataPanel') as ReactElement)!.props.dispatch({
+ type: 'UPDATE_DATASOURCE_STATE',
+ updater: () => ({
+ newState: true,
+ }),
+ datasourceId: 'testDatasource',
+ });
+ await waitForPromises();
+
+ expect(onChange).toHaveBeenCalledTimes(3);
+ });
});
});
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx
index 054229bde98fb..5d623fa86cd86 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.tsx
@@ -6,9 +6,16 @@
import React, { useEffect, useReducer } from 'react';
import { CoreSetup, CoreStart } from 'src/core/public';
-import { Query } from '../../../../../../../src/legacy/core_plugins/data/public';
+import { Filter } from '@kbn/es-query';
+import { Query, SavedQuery } from '../../../../../../../src/legacy/core_plugins/data/public';
import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/expressions/public';
-import { Datasource, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../../types';
+import {
+ Datasource,
+ DatasourcePublicAPI,
+ FramePublicAPI,
+ Visualization,
+ DatasourceMetaData,
+} from '../../types';
import { reducer, getInitialState } from './state_management';
import { DataPanelWrapper } from './data_panel_wrapper';
import { ConfigPanelWrapper } from './config_panel_wrapper';
@@ -34,7 +41,12 @@ export interface EditorFrameProps {
toDate: string;
};
query: Query;
- onChange: (arg: { indexPatternTitles: string[]; doc: Document }) => void;
+ filters: Filter[];
+ savedQuery?: SavedQuery;
+ onChange: (arg: {
+ filterableIndexPatterns: DatasourceMetaData['filterableIndexPatterns'];
+ doc: Document;
+ }) => void;
}
export function EditorFrame(props: EditorFrameProps) {
@@ -98,6 +110,7 @@ export function EditorFrame(props: EditorFrameProps) {
datasourceLayers,
dateRange: props.dateRange,
query: props.query,
+ filters: props.filters,
addNewLayer() {
const newLayerId = generateId();
@@ -170,7 +183,7 @@ export function EditorFrame(props: EditorFrameProps) {
return;
}
- const indexPatternTitles: string[] = [];
+ const indexPatterns: DatasourceMetaData['filterableIndexPatterns'] = [];
Object.entries(props.datasourceMap)
.filter(([id, datasource]) => {
const stateWrapper = state.datasourceStates[id];
@@ -181,10 +194,8 @@ export function EditorFrame(props: EditorFrameProps) {
);
})
.forEach(([id, datasource]) => {
- indexPatternTitles.push(
- ...datasource
- .getMetaData(state.datasourceStates[id].state)
- .filterableIndexPatterns.map(pattern => pattern.title)
+ indexPatterns.push(
+ ...datasource.getMetaData(state.datasourceStates[id].state).filterableIndexPatterns
);
});
@@ -201,8 +212,16 @@ export function EditorFrame(props: EditorFrameProps) {
framePublicAPI,
});
- props.onChange({ indexPatternTitles, doc });
- }, [state.datasourceStates, state.visualization, props.query, props.dateRange, state.title]);
+ props.onChange({ filterableIndexPatterns: indexPatterns, doc });
+ }, [
+ state.datasourceStates,
+ state.visualization,
+ props.query,
+ props.dateRange,
+ props.filters,
+ props.savedQuery,
+ state.title,
+ ]);
return (
}
configPanel={
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts
index da7ddee67453e..1ddfc54cc187b 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/expression_helpers.ts
@@ -86,7 +86,7 @@ export function prependKibanaContext(
arguments: {
timeRange: timeRange ? [JSON.stringify(timeRange)] : [],
query: query ? [JSON.stringify(query)] : [],
- filters: filters ? [JSON.stringify(filters)] : [],
+ filters: [JSON.stringify(filters || [])],
},
},
...parsedExpression.chain,
@@ -121,13 +121,14 @@ export function buildExpression({
const visualizationExpression = visualization.toExpression(visualizationState, framePublicAPI);
const expressionContext = removeDateRange
- ? { query: framePublicAPI.query }
+ ? { query: framePublicAPI.query, filters: framePublicAPI.filters }
: {
query: framePublicAPI.query,
timeRange: {
from: framePublicAPI.dateRange.fromDate,
to: framePublicAPI.dateRange.toDate,
},
+ filters: framePublicAPI.filters,
};
const completeExpression = prependDatasourceExpression(
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.test.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.test.ts
index 6bfe8f70d93c4..b898d33f7a7b1 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.test.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.test.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { buildExistsFilter } from '@kbn/es-query';
import { getSavedObjectFormat, Props } from './save';
import { createMockDatasource, createMockVisualization } from '../mocks';
@@ -36,6 +37,7 @@ describe('save editor frame state', () => {
},
query: { query: '', language: 'lucene' },
dateRange: { fromDate: 'now-7d', toDate: 'now' },
+ filters: [buildExistsFilter({ name: '@timestamp' }, { id: 'indexpattern' })],
},
};
@@ -83,7 +85,13 @@ describe('save editor frame state', () => {
},
visualization: { things: '4_vis_persisted' },
query: { query: '', language: 'lucene' },
- filters: [],
+ filters: [
+ {
+ meta: { index: 'indexpattern' },
+ exists: { field: '@timestamp' },
+ },
+ ],
+ dateRange: { fromDate: 'now-7d', toDate: 'now' },
},
title: 'bbb',
type: 'lens',
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.ts
index 6c414d9866033..fc567f2d5dab8 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/save.ts
@@ -58,7 +58,8 @@ export function getSavedObjectFormat({
},
visualization: visualization.getPersistableState(state.visualization.state),
query: framePublicAPI.query,
- filters: [], // TODO: Support filters
+ filters: framePublicAPI.filters,
+ dateRange: framePublicAPI.dateRange,
},
};
}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.test.ts b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.test.ts
index aa6d7ded87ed9..5168059a33258 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.test.ts
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/state_management.test.ts
@@ -26,6 +26,7 @@ describe('editor_frame state management', () => {
core: coreMock.createSetup(),
dateRange: { fromDate: 'now-7d', toDate: 'now' },
query: { query: '', language: 'lucene' },
+ filters: [],
};
});
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx
index 93283534e1186..82ca3d01b73ca 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.test.tsx
@@ -279,7 +279,7 @@ describe('suggestion_panel', () => {
expect(passedExpression).toMatchInlineSnapshot(`
"kibana
- | kibana_context timeRange=\\"{\\\\\\"from\\\\\\":\\\\\\"now-7d\\\\\\",\\\\\\"to\\\\\\":\\\\\\"now\\\\\\"}\\" query=\\"{\\\\\\"query\\\\\\":\\\\\\"\\\\\\",\\\\\\"language\\\\\\":\\\\\\"lucene\\\\\\"}\\"
+ | kibana_context timeRange=\\"{\\\\\\"from\\\\\\":\\\\\\"now-7d\\\\\\",\\\\\\"to\\\\\\":\\\\\\"now\\\\\\"}\\" query=\\"{\\\\\\"query\\\\\\":\\\\\\"\\\\\\",\\\\\\"language\\\\\\":\\\\\\"lucene\\\\\\"}\\" filters=\\"[]\\"
| lens_merge_tables layerIds=\\"first\\" tables={datasource_expression}
| test
| expression"
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx
index fc224db743dca..ddb82565e4b8b 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx
@@ -5,6 +5,8 @@
*/
import React from 'react';
+
+import { buildExistsFilter } from '@kbn/es-query';
import { ExpressionRendererProps } from '../../../../../../../src/legacy/core_plugins/expressions/public';
import { Visualization, FramePublicAPI, TableSuggestion } from '../../types';
import {
@@ -153,7 +155,9 @@ describe('workspace_panel', () => {
},
Object {
"arguments": Object {
- "filters": Array [],
+ "filters": Array [
+ "[]",
+ ],
"query": Array [
"{\\"query\\":\\"\\",\\"language\\":\\"lucene\\"}",
],
@@ -244,39 +248,39 @@ describe('workspace_panel', () => {
expect(
(instance.find(expressionRendererMock).prop('expression') as Ast).chain[2].arguments.tables
).toMatchInlineSnapshot(`
- Array [
- Object {
- "chain": Array [
- Object {
- "arguments": Object {},
- "function": "datasource",
- "type": "function",
- },
- ],
- "type": "expression",
- },
- Object {
- "chain": Array [
- Object {
- "arguments": Object {},
- "function": "datasource2",
- "type": "function",
- },
- ],
- "type": "expression",
- },
- Object {
- "chain": Array [
- Object {
- "arguments": Object {},
- "function": "datasource2",
- "type": "function",
- },
- ],
- "type": "expression",
- },
- ]
- `);
+ Array [
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {},
+ "function": "datasource",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {},
+ "function": "datasource2",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ Object {
+ "chain": Array [
+ Object {
+ "arguments": Object {},
+ "function": "datasource2",
+ "type": "function",
+ },
+ ],
+ "type": "expression",
+ },
+ ]
+ `);
});
it('should run the expression again if the date range changes', async () => {
@@ -332,6 +336,62 @@ describe('workspace_panel', () => {
expect(expressionRendererMock).toHaveBeenCalledTimes(2);
});
+ it('should run the expression again if the filters change', async () => {
+ const framePublicAPI = createMockFramePublicAPI();
+ framePublicAPI.datasourceLayers = {
+ first: mockDatasource.publicAPIMock,
+ };
+ mockDatasource.getLayers.mockReturnValue(['first']);
+
+ mockDatasource.toExpression
+ .mockReturnValueOnce('datasource')
+ .mockReturnValueOnce('datasource second');
+
+ expressionRendererMock = jest.fn(_arg => );
+
+ instance = mount(
+ 'vis' },
+ }}
+ visualizationState={{}}
+ dispatch={() => {}}
+ ExpressionRenderer={expressionRendererMock}
+ core={coreMock.createSetup()}
+ />
+ );
+
+ // "wait" for the expression to execute
+ await waitForPromises();
+ instance.update();
+
+ expect(expressionRendererMock).toHaveBeenCalledTimes(1);
+
+ instance.setProps({
+ framePublicAPI: {
+ ...framePublicAPI,
+ filters: [buildExistsFilter({ name: 'myfield' }, { id: 'index1' })],
+ },
+ });
+
+ await waitForPromises();
+ instance.update();
+
+ expect(expressionRendererMock).toHaveBeenCalledTimes(2);
+ });
+
describe('expression failures', () => {
it('should show an error message if the expression fails to parse', () => {
mockDatasource.toExpression.mockReturnValue('|||');
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx
index 6d0ab402a2971..66fac5d6cf705 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx
@@ -142,6 +142,7 @@ export function InnerWorkspacePanel({
datasourceStates,
framePublicAPI.dateRange,
framePublicAPI.query,
+ framePublicAPI.filters,
]);
useEffect(() => {
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx
index 97e1fe8393fc3..f349585ce88a4 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/mocks.tsx
@@ -77,6 +77,7 @@ export function createMockFramePublicAPI(): FrameMock {
removeLayers: jest.fn(),
dateRange: { fromDate: 'now-7d', toDate: 'now' },
query: { query: '', language: 'lucene' },
+ filters: [],
};
}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx
index 7b21ec0cac1c2..f48a8b467e728 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx
@@ -59,6 +59,7 @@ describe('editor_frame plugin', () => {
onChange: jest.fn(),
dateRange: { fromDate: '', toDate: '' },
query: { query: '', language: 'lucene' },
+ filters: [],
});
instance.unmount();
}).not.toThrowError();
@@ -73,6 +74,7 @@ describe('editor_frame plugin', () => {
onChange: jest.fn(),
dateRange: { fromDate: '', toDate: '' },
query: { query: '', language: 'lucene' },
+ filters: [],
});
instance.unmount();
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
index e27c2e54500cf..cb81ec3d69985 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
@@ -76,7 +76,7 @@ export class EditorFramePlugin {
const createInstance = (): EditorFrameInstance => {
let domElement: Element;
return {
- mount: (element, { doc, onError, dateRange, query, onChange }) => {
+ mount: (element, { doc, onError, dateRange, query, filters, savedQuery, onChange }) => {
domElement = element;
const firstDatasourceId = Object.keys(this.datasources)[0];
const firstVisualizationId = Object.keys(this.visualizations)[0];
@@ -97,6 +97,8 @@ export class EditorFramePlugin {
doc={doc}
dateRange={dateRange}
query={query}
+ filters={filters}
+ savedQuery={savedQuery}
onChange={onChange}
/>
,
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx
index 9e20db9276ae3..891eb9415d3a4 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.test.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { shallow, mount } from 'enzyme';
import React, { ChangeEvent } from 'react';
import { IndexPatternPrivateState, IndexPatternColumn } from './indexpattern';
import { createMockedDragDropContext } from './mocks';
@@ -12,6 +11,7 @@ import { InnerIndexPatternDataPanel, IndexPatternDataPanel, MemoizedDataPanel }
import { FieldItem } from './field_item';
import { act } from 'react-dom/test-utils';
import { coreMock } from 'src/core/public/mocks';
+import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { ChangeIndexPattern } from './change_indexpattern';
jest.mock('ui/new_platform');
@@ -220,6 +220,7 @@ describe('IndexPattern Data Panel', () => {
toDate: 'now',
},
query: { query: '', language: 'lucene' },
+ filters: [],
showEmptyFields: false,
onToggleEmptyFields: jest.fn(),
};
@@ -231,7 +232,7 @@ describe('IndexPattern Data Panel', () => {
...initialState,
layers: { first: { indexPatternId: '1', columnOrder: [], columns: {} } },
};
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
{
second: { indexPatternId: '1', columnOrder: [], columns: {} },
},
};
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
{
},
},
};
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
{
});
it('should render a warning if there are no index patterns', () => {
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
);
expect(wrapper.find('[data-test-subj="indexPattern-no-indexpatterns"]')).toHaveLength(1);
});
it('should call setState when the index pattern is switched', async () => {
- const wrapper = shallow( );
+ const wrapper = shallowWithIntl( );
wrapper.find(ChangeIndexPattern).prop('onChangeIndexPattern')('2');
@@ -333,7 +334,9 @@ describe('IndexPattern Data Panel', () => {
},
});
const updateFields = jest.fn();
- mount( );
+ mountWithIntl(
+
+ );
await waitForPromises();
@@ -400,7 +403,9 @@ describe('IndexPattern Data Panel', () => {
const props = { ...defaultProps, indexPatterns: newIndexPatterns };
- mount( );
+ mountWithIntl(
+
+ );
await waitForPromises();
@@ -410,7 +415,7 @@ describe('IndexPattern Data Panel', () => {
describe('while showing empty fields', () => {
it('should list all supported fields in the pattern sorted alphabetically', async () => {
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
);
@@ -424,7 +429,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should filter down by name', () => {
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
);
@@ -440,7 +445,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should filter down by type', () => {
- const wrapper = mount(
+ const wrapper = mountWithIntl(
);
@@ -461,7 +466,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should toggle type if clicked again', () => {
- const wrapper = mount(
+ const wrapper = mountWithIntl(
);
@@ -489,7 +494,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should filter down by type and by name', () => {
- const wrapper = mount(
+ const wrapper = mountWithIntl(
);
@@ -537,7 +542,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should list all supported fields in the pattern sorted alphabetically', async () => {
- const wrapper = shallow( );
+ const wrapper = shallowWithIntl( );
expect(wrapper.find(FieldItem).map(fieldItem => fieldItem.prop('field').name)).toEqual([
'bytes',
@@ -546,7 +551,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should filter down by name', () => {
- const wrapper = shallow(
+ const wrapper = shallowWithIntl(
);
@@ -562,7 +567,7 @@ describe('IndexPattern Data Panel', () => {
});
it('should allow removing the filter for data', () => {
- const wrapper = mount( );
+ const wrapper = mountWithIntl( );
wrapper
.find('[data-test-subj="lnsIndexPatternFiltersToggle"]')
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx
index da42113b4e7b4..11d6228251025 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/datapanel.tsx
@@ -26,7 +26,6 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { Query } from 'src/plugins/data/common';
import { DatasourceDataPanelProps, DataType } from '../types';
import { IndexPatternPrivateState, IndexPatternField, IndexPattern } from './indexpattern';
import { ChildDragDropProvider, DragContextState } from '../drag_drop';
@@ -66,6 +65,7 @@ export function IndexPatternDataPanel({
dragDropContext,
core,
query,
+ filters,
dateRange,
}: DatasourceDataPanelProps) {
const { indexPatterns, currentIndexPatternId } = state;
@@ -114,6 +114,7 @@ export function IndexPatternDataPanel({
indexPatterns={indexPatterns}
query={query}
dateRange={dateRange}
+ filters={filters}
dragDropContext={dragDropContext}
showEmptyFields={state.showEmptyFields}
onToggleEmptyFields={onToggleEmptyFields}
@@ -146,18 +147,16 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
indexPatterns,
query,
dateRange,
+ filters,
dragDropContext,
onChangeIndexPattern,
updateFieldsWithCounts,
showEmptyFields,
onToggleEmptyFields,
core,
-}: Partial & {
+}: Pick> & {
currentIndexPatternId: string;
indexPatterns: Record;
- dateRange: DatasourceDataPanelProps['dateRange'];
- query: Query;
- core: DatasourceDataPanelProps['core'];
dragDropContext: DragContextState;
showEmptyFields: boolean;
onToggleEmptyFields: () => void;
@@ -487,6 +486,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
exists={overallField ? !!overallField.exists : false}
dateRange={dateRange}
query={query}
+ filters={filters}
/>
);
})}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx
new file mode 100644
index 0000000000000..9956c0ec33061
--- /dev/null
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.test.tsx
@@ -0,0 +1,215 @@
+/*
+ * 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';
+import { act } from 'react-dom/test-utils';
+import { EuiLoadingSpinner, EuiPopover } from '@elastic/eui';
+import { FieldItem, FieldItemProps } from './field_item';
+import { coreMock } from 'src/core/public/mocks';
+import { mountWithIntl } from 'test_utils/enzyme_helpers';
+
+jest.mock('ui/new_platform');
+
+// Formatter must be mocked to return a string, or the rendering will fail
+jest.mock('../../../../../../src/legacy/ui/public/registry/field_formats', () => ({
+ fieldFormats: {
+ getDefaultInstance: jest.fn().mockReturnValue({
+ convert: jest.fn().mockReturnValue((s: unknown) => JSON.stringify(s)),
+ }),
+ },
+}));
+
+const waitForPromises = () => new Promise(resolve => setTimeout(resolve));
+
+const indexPattern = {
+ id: '1',
+ title: 'my-fake-index-pattern',
+ timeFieldName: 'timestamp',
+ fields: [
+ {
+ name: 'timestamp',
+ type: 'date',
+ aggregatable: true,
+ searchable: true,
+ },
+ {
+ name: 'bytes',
+ type: 'number',
+ aggregatable: true,
+ searchable: true,
+ },
+ {
+ name: 'memory',
+ type: 'number',
+ aggregatable: true,
+ searchable: true,
+ },
+ {
+ name: 'unsupported',
+ type: 'geo',
+ aggregatable: true,
+ searchable: true,
+ },
+ {
+ name: 'source',
+ type: 'string',
+ aggregatable: true,
+ searchable: true,
+ },
+ ],
+};
+
+describe('IndexPattern Field Item', () => {
+ let defaultProps: FieldItemProps;
+ let core: ReturnType;
+
+ beforeEach(() => {
+ core = coreMock.createSetup();
+ core.http.post.mockClear();
+ defaultProps = {
+ indexPattern,
+ core,
+ highlight: '',
+ dateRange: {
+ fromDate: 'now-7d',
+ toDate: 'now',
+ },
+ query: { query: '', language: 'lucene' },
+ filters: [],
+ field: {
+ name: 'bytes',
+ type: 'number',
+ aggregatable: true,
+ searchable: true,
+ },
+ exists: true,
+ };
+ });
+
+ it('should request field stats every time the button is clicked', async () => {
+ let resolveFunction: (arg: unknown) => void;
+
+ core.http.post.mockImplementation(() => {
+ return new Promise(resolve => {
+ resolveFunction = resolve;
+ });
+ });
+
+ const wrapper = mountWithIntl( );
+
+ wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+
+ expect(core.http.post).toHaveBeenCalledWith(
+ `/api/lens/index_stats/my-fake-index-pattern/field`,
+ {
+ body: JSON.stringify({
+ dslQuery: {
+ bool: {
+ must: [{ match_all: {} }],
+ filter: [],
+ should: [],
+ must_not: [],
+ },
+ },
+ fromDate: 'now-7d',
+ toDate: 'now',
+ timeFieldName: 'timestamp',
+ field: {
+ name: 'bytes',
+ type: 'number',
+ aggregatable: true,
+ searchable: true,
+ },
+ }),
+ }
+ );
+
+ expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(true);
+
+ expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1);
+
+ resolveFunction!({
+ totalDocuments: 4633,
+ sampledDocuments: 4633,
+ sampledValues: 4633,
+ histogram: {
+ buckets: [{ count: 705, key: 0 }],
+ },
+ topValues: {
+ buckets: [{ count: 147, key: 0 }],
+ },
+ });
+
+ await waitForPromises();
+ wrapper.update();
+
+ expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0);
+
+ wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+ expect(core.http.post).toHaveBeenCalledTimes(1);
+
+ act(() => {
+ const closePopover = wrapper.find(EuiPopover).prop('closePopover');
+
+ closePopover();
+ });
+
+ expect(wrapper.find(EuiPopover).prop('isOpen')).toEqual(false);
+
+ act(() => {
+ wrapper.setProps({
+ dateRange: {
+ fromDate: 'now-14d',
+ toDate: 'now-7d',
+ },
+ query: { query: 'geo.src : "US"', language: 'kuery' },
+ filters: [
+ {
+ match: { phrase: { 'geo.dest': 'US' } },
+ },
+ ],
+ });
+ });
+
+ wrapper.find('[data-test-subj="lnsFieldListPanelField-bytes"]').simulate('click');
+
+ expect(core.http.post).toHaveBeenCalledTimes(2);
+ expect(core.http.post).toHaveBeenLastCalledWith(
+ `/api/lens/index_stats/my-fake-index-pattern/field`,
+ {
+ body: JSON.stringify({
+ dslQuery: {
+ bool: {
+ must: [],
+ filter: [
+ {
+ bool: {
+ should: [{ match_phrase: { 'geo.src': 'US' } }],
+ minimum_should_match: 1,
+ },
+ },
+ {
+ match: { phrase: { 'geo.dest': 'US' } },
+ },
+ ],
+ should: [],
+ must_not: [],
+ },
+ },
+ fromDate: 'now-14d',
+ toDate: 'now-7d',
+ timeFieldName: 'timestamp',
+ field: {
+ name: 'bytes',
+ type: 'number',
+ aggregatable: true,
+ searchable: true,
+ },
+ }),
+ }
+ );
+ });
+});
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx
index 62591bdf1e081..af0612be8dc2f 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx
@@ -34,7 +34,7 @@ import {
niceTimeFormatter,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
-import { toElasticsearchQuery } from '@kbn/es-query';
+import { Filter, buildEsQuery, getEsQueryConfig } from '@kbn/es-query';
import { Query } from 'src/plugins/data/common';
// @ts-ignore
import { fieldFormats } from '../../../../../../src/legacy/ui/public/registry/field_formats';
@@ -52,6 +52,7 @@ export interface FieldItemProps {
exists: boolean;
query: Query;
dateRange: DatasourceDataPanelProps['dateRange'];
+ filters: Filter[];
}
interface State {
@@ -71,7 +72,7 @@ function wrapOnDot(str?: string) {
}
export function FieldItem(props: FieldItemProps) {
- const { core, field, indexPattern, highlight, exists, query, dateRange } = props;
+ const { core, field, indexPattern, highlight, exists, query, dateRange, filters } = props;
const [infoIsOpen, setOpen] = useState(false);
@@ -112,7 +113,7 @@ export function FieldItem(props: FieldItemProps) {
core.http
.post(`/api/lens/index_stats/${indexPattern.title}/field`, {
body: JSON.stringify({
- query: toElasticsearchQuery(query, indexPattern),
+ dslQuery: buildEsQuery(indexPattern, query, filters, getEsQueryConfig(core.uiSettings)),
fromDate: dateRange.fromDate,
toDate: dateRange.toDate,
timeFieldName: indexPattern.timeFieldName,
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts
index 3576916e81868..98d6ed6f26869 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts
@@ -5,10 +5,7 @@
*/
import chromeMock from 'ui/chrome';
-import { data as dataMock } from '../../../../../../src/legacy/core_plugins/data/public/setup';
import { Storage } from 'ui/storage';
-import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries';
-import { SavedObjectsClientContract } from 'src/core/public';
import {
getIndexPatternDatasource,
IndexPatternPersistedState,
@@ -25,7 +22,6 @@ jest.mock('../id_generator');
jest.mock('ui/chrome');
// Contains old and new platform data plugins, used for interpreter and filter ratio
jest.mock('ui/new_platform');
-jest.mock('plugins/data/setup', () => ({ data: { query: { ui: {} } } }));
const expectedIndexPatterns = {
1: {
@@ -138,10 +134,7 @@ describe('IndexPattern Data Source', () => {
indexPatternDatasource = getIndexPatternDatasource({
chrome: chromeMock,
storage: {} as Storage,
- interpreter: { functionsRegistry },
- core: coreMock.createSetup(),
- data: dataMock,
- savedObjectsClient: {} as SavedObjectsClientContract,
+ core: coreMock.createStart(),
});
persistedState = {
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx
index afdbcf5b684c7..f285de4dcbf9d 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx
@@ -8,7 +8,7 @@ import _ from 'lodash';
import React from 'react';
import { render } from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
-import { CoreSetup, SavedObjectsClientContract } from 'src/core/public';
+import { CoreStart } from 'src/core/public';
import { Storage } from 'ui/storage';
import { i18n } from '@kbn/i18n';
import {
@@ -20,7 +20,7 @@ import {
import { getIndexPatterns } from './loader';
import { toExpression } from './to_expression';
import { IndexPatternDimensionPanel } from './dimension_panel';
-import { IndexPatternDatasourcePluginPlugins } from './plugin';
+import { IndexPatternDatasourceSetupPlugins } from './plugin';
import { IndexPatternDataPanel } from './datapanel';
import {
getDatasourceSuggestionsForField,
@@ -182,14 +182,14 @@ function removeProperty(prop: string, object: Record): Record & {
+ // Core start is being required here because it contains the savedObject client
+ // In the new platform, this plugin wouldn't be initialized until after setup
+ core: CoreStart;
storage: Storage;
- savedObjectsClient: SavedObjectsClientContract;
}) {
const uiSettings = chrome.getUiSettingsClient();
// Not stateful. State is persisted to the frame
@@ -307,7 +307,7 @@ export function getIndexPatternDatasource({
setState={setState}
uiSettings={uiSettings}
storage={storage}
- savedObjectsClient={savedObjectsClient}
+ savedObjectsClient={core.savedObjects.client}
layerId={props.layerId}
http={core.http}
uniqueLabel={columnLabelMap[props.columnId]}
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx
index 841d59b602ee8..b7e23b36d55c3 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern_suggestions.test.tsx
@@ -5,9 +5,6 @@
*/
import chromeMock from 'ui/chrome';
-import { data as dataMock } from '../../../../../../src/legacy/core_plugins/data/public/setup';
-import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries';
-import { SavedObjectsClientContract } from 'src/core/public';
import {
getIndexPatternDatasource,
IndexPatternPersistedState,
@@ -135,12 +132,9 @@ describe('IndexPattern Data Source suggestions', () => {
beforeEach(() => {
indexPatternDatasource = getIndexPatternDatasource({
- core: coreMock.createSetup(),
+ core: coreMock.createStart(),
chrome: chromeMock,
storage: {} as Storage,
- interpreter: { functionsRegistry },
- data: dataMock,
- savedObjectsClient: {} as SavedObjectsClientContract,
});
persistedState = {
diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx
index 581c08f832b67..7e2956cf2bb4b 100644
--- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx
@@ -9,22 +9,20 @@ import { CoreSetup } from 'src/core/public';
// The following dependencies on ui/* and src/legacy/core_plugins must be mocked when testing
import chrome, { Chrome } from 'ui/chrome';
import { Storage } from 'ui/storage';
-import { npSetup } from 'ui/new_platform';
+import { npSetup, npStart } from 'ui/new_platform';
import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public';
import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries';
import { getIndexPatternDatasource } from './indexpattern';
import { renameColumns } from './rename_columns';
import { calculateFilterRatio } from './filter_ratio';
-import { setup as dataSetup } from '../../../../../../src/legacy/core_plugins/data/public/legacy';
// TODO these are intermediary types because interpreter is not typed yet
// They can get replaced by references to the real interfaces as soon as they
// are available
-export interface IndexPatternDatasourcePluginPlugins {
+export interface IndexPatternDatasourceSetupPlugins {
chrome: Chrome;
interpreter: InterpreterSetup;
- data: typeof dataSetup;
}
export interface InterpreterSetup {
@@ -37,17 +35,9 @@ export interface InterpreterSetup {
class IndexPatternDatasourcePlugin {
constructor() {}
- setup(core: CoreSetup, { interpreter, data }: IndexPatternDatasourcePluginPlugins) {
+ setup(core: CoreSetup, { interpreter }: IndexPatternDatasourceSetupPlugins) {
interpreter.functionsRegistry.register(() => renameColumns);
interpreter.functionsRegistry.register(() => calculateFilterRatio);
- return getIndexPatternDatasource({
- core,
- chrome,
- interpreter,
- data,
- storage: new Storage(localStorage),
- savedObjectsClient: chrome.getSavedObjectsClient(),
- });
}
stop() {}
@@ -55,12 +45,18 @@ class IndexPatternDatasourcePlugin {
const plugin = new IndexPatternDatasourcePlugin();
-export const indexPatternDatasourceSetup = () =>
+export const indexPatternDatasourceSetup = () => {
plugin.setup(npSetup.core, {
chrome,
interpreter: {
functionsRegistry,
},
- data: dataSetup,
});
+
+ return getIndexPatternDatasource({
+ core: npStart.core,
+ chrome,
+ storage: new Storage(localStorage),
+ });
+};
export const indexPatternDatasourceStop = () => plugin.stop();
diff --git a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts
index 5fa7e3f0aca4a..77155b2add87a 100644
--- a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts
+++ b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts
@@ -8,6 +8,7 @@
import { SavedObjectAttributes } from 'src/core/server';
import { Filter } from '@kbn/es-query';
import { Query } from 'src/plugins/data/common';
+import { FramePublicAPI } from '../types';
export interface Document {
id?: string;
@@ -23,6 +24,7 @@ export interface Document {
visualization: unknown;
query: Query;
filters: Filter[];
+ dateRange?: FramePublicAPI['dateRange'];
};
}
diff --git a/x-pack/legacy/plugins/lens/public/types.ts b/x-pack/legacy/plugins/lens/public/types.ts
index 217a694a1861c..71ee2d4c25963 100644
--- a/x-pack/legacy/plugins/lens/public/types.ts
+++ b/x-pack/legacy/plugins/lens/public/types.ts
@@ -5,9 +5,11 @@
*/
import { Ast } from '@kbn/interpreter/common';
+import { Filter } from '@kbn/es-query';
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { CoreSetup } from 'src/core/public';
import { Query } from 'src/plugins/data/common';
+import { SavedQuery } from 'src/legacy/core_plugins/data/public';
import { KibanaDatatable } from '../../../../../src/legacy/core_plugins/interpreter/common';
import { DragContextState } from './drag_drop';
import { Document } from './persistence';
@@ -25,9 +27,14 @@ export interface EditorFrameProps {
toDate: string;
};
query: Query;
+ filters: Filter[];
+ savedQuery?: SavedQuery;
// Frame loader (app or embeddable) is expected to call this when it loads and updates
- onChange: (newState: { indexPatternTitles: string[]; doc: Document }) => void;
+ onChange: (newState: {
+ filterableIndexPatterns: DatasourceMetaData['filterableIndexPatterns'];
+ doc: Document;
+ }) => void;
}
export interface EditorFrameInstance {
mount: (element: Element, props: EditorFrameProps) => void;
@@ -165,6 +172,7 @@ export interface DatasourceDataPanelProps {
core: Pick;
query: Query;
dateRange: FramePublicAPI['dateRange'];
+ filters: Filter[];
}
// The only way a visualization has to restrict the query building
@@ -278,11 +286,13 @@ export interface VisualizationSuggestion {
export interface FramePublicAPI {
datasourceLayers: Record;
+
dateRange: {
fromDate: string;
toDate: string;
};
query: Query;
+ filters: Filter[];
// Adds a new layer. This has a side effect of updating the datasource state
addNewLayer: () => string;
diff --git a/x-pack/legacy/plugins/lens/readme.md b/x-pack/legacy/plugins/lens/readme.md
index 0ea0778dd17ef..60b4266edadb3 100644
--- a/x-pack/legacy/plugins/lens/readme.md
+++ b/x-pack/legacy/plugins/lens/readme.md
@@ -7,7 +7,7 @@ Run all tests from the `x-pack` root directory
- Unit tests: `node scripts/jest --watch lens`
- Functional tests:
- Run `node scripts/functional_tests_server`
- - Run `node ../scripts/functional_test_runner.js --config ./test/functional/config.js`
+ - Run `node ../scripts/functional_test_runner.js --config ./test/functional/config.js --grep="lens app"`
- You may want to comment out all imports except for Lens in the config file.
- API Functional tests:
- Run `node scripts/functional_tests_server`
diff --git a/x-pack/legacy/plugins/lens/server/routes/field_stats.ts b/x-pack/legacy/plugins/lens/server/routes/field_stats.ts
index a57811362c6cf..b1b4cdccc3538 100644
--- a/x-pack/legacy/plugins/lens/server/routes/field_stats.ts
+++ b/x-pack/legacy/plugins/lens/server/routes/field_stats.ts
@@ -24,7 +24,7 @@ export async function initFieldsRoute(setup: CoreSetup) {
}),
body: schema.object(
{
- query: schema.object({}, { allowUnknowns: true }),
+ dslQuery: schema.object({}, { allowUnknowns: true }),
fromDate: schema.string(),
toDate: schema.string(),
timeFieldName: schema.string(),
@@ -43,10 +43,10 @@ export async function initFieldsRoute(setup: CoreSetup) {
},
async (context, req, res) => {
const requestClient = context.core.elasticsearch.dataClient;
- const { fromDate, toDate, timeFieldName, field, query } = req.body;
+ const { fromDate, toDate, timeFieldName, field, dslQuery } = req.body;
try {
- const filters = {
+ const query = {
bool: {
filter: [
{
@@ -57,7 +57,7 @@ export async function initFieldsRoute(setup: CoreSetup) {
},
},
},
- query,
+ dslQuery,
],
},
};
@@ -66,7 +66,7 @@ export async function initFieldsRoute(setup: CoreSetup) {
requestClient.callAsCurrentUser('search', {
index: req.params.indexPatternTitle,
body: {
- query: filters,
+ query,
aggs,
},
// The hits total changed in 7.0 from number to object, unless this flag is set
diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap
new file mode 100644
index 0000000000000..eec24b23c6e23
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap
@@ -0,0 +1,2217 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Flyout apm part one should render normally 1`] = `
+
+
+
+
+ Monitor \`APM\` server with Metricbeat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Flyout apm part two should show instructions to disable internal collection 1`] = `
+
+
+
+
+
+ apm-server.yml
+ ,
+ }
+ }
+ />
+
+
+
+
+ monitoring.enabled: false
+
+
+
+
+
+
+
+ ,
+ "title": "Disable self monitoring of the APM server's monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout apm part two should show instructions to migrate to metricbeat 1`] = `
+
+
+
+
+
+
+
+ ,
+ "title": "Install Metricbeat on the same server as the APM server",
+ },
+ Object {
+ "children":
+
+ metricbeat modules enable beat-xpack
+
+
+
+
+
+ modules.d/beat-xpack.yml
+ ,
+ "hosts":
+ hosts
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Enable and configure the Beat x-pack module in Metricbeat",
+ },
+ Object {
+ "children":
+
+
+ metricbeat.yml
+ ,
+ }
+ }
+ />
+
+
+
+ output.elasticsearch:
+ hosts: ["http://localhost:9200"] ## Monitoring cluster
+
+ # Optional protocol and basic auth credentials.
+ #protocol: "https"
+ #username: "elastic"
+ #password: "changeme"
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Configure Metricbeat to send to the monitoring cluster",
+ },
+ Object {
+ "children":
+
+
+
+
+
+ ,
+ "title": "Start Metricbeat",
+ },
+ Object {
+ "children": ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout beats part one should render normally 1`] = `
+
+
+
+
+ Monitor \`Beats\` instance with Metricbeat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Flyout beats part two should show instructions to disable internal collection 1`] = `
+
+
+
+
+
+ .yml
+ ,
+ }
+ }
+ />
+
+
+
+
+ monitoring.enabled: false
+
+
+
+
+
+
+
+ ,
+ "title": "Disable self monitoring of beat's monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout beats part two should show instructions to migrate to metricbeat 1`] = `
+
+
+
+
+
+
+
+ ,
+ "title": "Install Metricbeat on the same server as this beat",
+ },
+ Object {
+ "children":
+
+ metricbeat modules enable beat-xpack
+
+
+
+
+
+ modules.d/beat-xpack.yml
+ ,
+ "hosts":
+ hosts
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Enable and configure the Beat x-pack module in Metricbeat",
+ },
+ Object {
+ "children":
+
+
+ metricbeat.yml
+ ,
+ }
+ }
+ />
+
+
+
+ output.elasticsearch:
+ hosts: ["http://localhost:9200"] ## Monitoring cluster
+
+ # Optional protocol and basic auth credentials.
+ #protocol: "https"
+ #username: "elastic"
+ #password: "changeme"
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Configure Metricbeat to send to the monitoring cluster",
+ },
+ Object {
+ "children":
+
+
+
+
+
+ ,
+ "title": "Start Metricbeat",
+ },
+ Object {
+ "children": ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout elasticsearch part one should render normally 1`] = `
+
+
+
+
+ Monitor \`Elasticsearch\` node with Metricbeat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Flyout elasticsearch part two should show instructions to disable internal collection 1`] = `
+
+
+
+
+
+ xpack.monitoring.elasticsearch.collection.enabled
+ ,
+ }
+ }
+ />
+
+
+
+
+ PUT _cluster/settings
+{
+ "persistent": {
+ "xpack.monitoring.elasticsearch.collection.enabled": false
+ }
+}
+
+
+ ,
+ "title": "Disable self monitoring of Elasticsearch monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout elasticsearch part two should show instructions to migrate to metricbeat 1`] = `
+
+
+
+
+
+
+
+ ,
+ "title": "Install Metricbeat on the same server as Elasticsearch",
+ },
+ Object {
+ "children":
+
+
+ From the installation directory, run:
+
+
+
+
+ metricbeat modules enable elasticsearch-xpack
+
+
+
+
+
+ modules.d/elasticsearch-xpack.yml
+ ,
+ "url":
+ http://localhost:9200
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Enable and configure the Elasticsearch x-pack module in Metricbeat",
+ },
+ Object {
+ "children":
+
+
+ metricbeat.yml
+ ,
+ }
+ }
+ />
+
+
+
+ output.elasticsearch:
+ hosts: ["http://localhost:9200"] ## Monitoring cluster
+
+ # Optional protocol and basic auth credentials.
+ #protocol: "https"
+ #username: "elastic"
+ #password: "changeme"
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Configure Metricbeat to send data to the monitoring cluster",
+ },
+ Object {
+ "children":
+
+
+
+
+
+ ,
+ "title": "Start Metricbeat",
+ },
+ Object {
+ "children": ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout kibana part one should render normally 1`] = `
+
+
+
+
+ Monitor \`Kibana\` instance with Metricbeat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Flyout kibana part two should show instructions to disable internal collection 1`] = `
+
+
+
+
+
+ kibana.yml
+ ,
+ }
+ }
+ />
+
+
+
+
+ xpack.monitoring.kibana.collection.enabled: false
+
+
+
+
+
+ xpack.monitoring.enabled
+ ,
+ "defaultValue":
+ true
+ ,
+ }
+ }
+ />
+
+
+ ,
+ "title": "Disable self monitoring of Kibana monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout kibana part two should show instructions to migrate to metricbeat 1`] = `
+
+
+
+
+
+
+
+ ,
+ "title": "Install Metricbeat on the same server as Kibana",
+ },
+ Object {
+ "children":
+
+ metricbeat modules enable kibana-xpack
+
+
+
+
+
+ modules.d/kibana-xpack.yml
+ ,
+ "hosts":
+ hosts
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Enable and configure the Kibana x-pack module in Metricbeat",
+ },
+ Object {
+ "children":
+
+
+ metricbeat.yml
+ ,
+ }
+ }
+ />
+
+
+
+ output.elasticsearch:
+ hosts: ["http://localhost:9200"] ## Monitoring cluster
+
+ # Optional protocol and basic auth credentials.
+ #protocol: "https"
+ #username: "elastic"
+ #password: "changeme"
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Configure Metricbeat to send to the monitoring cluster",
+ },
+ Object {
+ "children":
+
+
+
+
+
+ ,
+ "title": "Start Metricbeat",
+ },
+ Object {
+ "children": ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout logstash part one should render normally 1`] = `
+
+
+
+
+ Monitor \`Logstash\` node with Metricbeat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+ Next
+
+
+
+
+
+`;
+
+exports[`Flyout logstash part two should show instructions to disable internal collection 1`] = `
+
+
+
+
+
+ logstash.yml
+ ,
+ }
+ }
+ />
+
+
+
+
+ xpack.monitoring.enabled: false
+
+
+
+
+
+
+
+ ,
+ "title": "Disable self monitoring of Logstash monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout logstash part two should show instructions to migrate to metricbeat 1`] = `
+
+
+
+
+
+
+
+ ,
+ "title": "Install Metricbeat on the same server as Logstash",
+ },
+ Object {
+ "children":
+
+ metricbeat modules enable logstash-xpack
+
+
+
+
+
+ modules.d/logstash-xpack.yml
+ ,
+ "hosts":
+ hosts
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Enable and configure the Logstash x-pack module in Metricbeat",
+ },
+ Object {
+ "children":
+
+
+ metricbeat.yml
+ ,
+ }
+ }
+ />
+
+
+
+ output.elasticsearch:
+ hosts: ["http://localhost:9200"] ## Monitoring cluster
+
+ # Optional protocol and basic auth credentials.
+ #protocol: "https"
+ #username: "elastic"
+ #password: "changeme"
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Configure Metricbeat to send to the monitoring cluster",
+ },
+ Object {
+ "children":
+
+
+
+
+
+ ,
+ "title": "Start Metricbeat",
+ },
+ Object {
+ "children": ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout should render a consistent completion state for all products 1`] = `
+Object {
+ "children":
+
+ Metricbeat is shipping monitoring data.
+
+ ,
+ "status": "complete",
+ "title": "Migration status",
+}
+`;
+
+exports[`Flyout should render the beat type for beats for the disabling internal collection step 1`] = `
+
+
+
+
+
+ filebeat
+ .yml
+ ,
+ }
+ }
+ />
+
+
+
+
+ monitoring.enabled: false
+
+
+
+
+
+
+
+ ,
+ "title": "Disable self monitoring of filebeat's monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout should render the beat type for beats for the enabling metricbeat step 1`] = `
+
+
+
+
+
+
+
+ ,
+ "title": "Install Metricbeat on the same server as this filebeat",
+ },
+ Object {
+ "children":
+
+ metricbeat modules enable beat-xpack
+
+
+
+
+
+ modules.d/beat-xpack.yml
+ ,
+ "hosts":
+ hosts
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Enable and configure the Beat x-pack module in Metricbeat",
+ },
+ Object {
+ "children":
+
+
+ metricbeat.yml
+ ,
+ }
+ }
+ />
+
+
+
+ output.elasticsearch:
+ hosts: ["http://localhost:9200"] ## Monitoring cluster
+
+ # Optional protocol and basic auth credentials.
+ #protocol: "https"
+ #username: "elastic"
+ #password: "changeme"
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+ }
+ />
+
+ ,
+ "title": "Configure Metricbeat to send to the monitoring cluster",
+ },
+ Object {
+ "children":
+
+
+
+
+
+ ,
+ "title": "Start Metricbeat",
+ },
+ Object {
+ "children": ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
+
+exports[`Flyout should show a restart warning for restarting the primary Kibana 1`] = `
+
+
+
+
+
+ kibana.yml
+ ,
+ }
+ }
+ />
+
+
+
+
+ xpack.monitoring.kibana.collection.enabled: false
+
+
+
+
+
+ xpack.monitoring.enabled
+ ,
+ "defaultValue":
+ true
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+ "title": "Disable self monitoring of Kibana monitoring metrics",
+ },
+ Object {
+ "children":
+
+ It can take up to 30 seconds to detect data.
+
+
+ Last self monitoring was 0 seconds ago.
+
+ ,
+ "status": "incomplete",
+ "title": "Migration status",
+ },
+ ]
+ }
+ />
+
+`;
diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.test.js b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.test.js
new file mode 100644
index 0000000000000..9b4ac21548bb8
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.test.js
@@ -0,0 +1,183 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import { Flyout } from './flyout';
+import { INSTRUCTION_STEP_ENABLE_METRICBEAT } from '../constants';
+import {
+ ELASTICSEARCH_SYSTEM_ID,
+ KIBANA_SYSTEM_ID,
+ BEATS_SYSTEM_ID,
+ APM_SYSTEM_ID,
+ LOGSTASH_SYSTEM_ID
+} from '../../../../common/constants';
+
+jest.mock('ui/documentation_links', () => ({
+ ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
+ DOC_LINK_VERSION: 'current'
+}));
+
+jest.mock('../../../../common', () => ({
+ formatTimestampToDuration: () => `0 seconds`,
+}));
+
+const PRODUCTS = [
+ {
+ name: ELASTICSEARCH_SYSTEM_ID
+ },
+ {
+ name: KIBANA_SYSTEM_ID
+ },
+ {
+ name: LOGSTASH_SYSTEM_ID
+ },
+ {
+ name: BEATS_SYSTEM_ID
+ },
+ {
+ name: APM_SYSTEM_ID
+ }
+];
+
+describe('Flyout', () => {
+ for (const { name } of PRODUCTS) {
+ describe(`${name}`, () => {
+ describe('part one', () => {
+ it('should render normally', () => {
+ const component = shallow(
+ {}}
+ product={{}}
+ productName={name}
+ />
+ );
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('part two', () => {
+ it('should show instructions to migrate to metricbeat', () => {
+ const component = shallow(
+ {}}
+ product={{
+ isInternalCollector: true
+ }}
+ productName={name}
+ />
+ );
+ component.find('EuiButton').simulate('click');
+ component.update();
+ expect(component.find('EuiFlyoutBody')).toMatchSnapshot();
+ });
+
+ it('should show instructions to disable internal collection', () => {
+ const component = shallow(
+ {}}
+ product={{
+ isPartiallyMigrated: true,
+ lastInternallyCollectedTimestamp: 0,
+ }}
+ meta={{
+ secondsAgo: 30
+ }}
+ productName={name}
+ />
+ );
+ component.find('EuiButton').simulate('click');
+ component.update();
+ expect(component.find('EuiFlyoutBody')).toMatchSnapshot();
+ });
+ });
+ });
+ }
+
+ it('should render a consistent completion state for all products', () => {
+ let template = null;
+ for (const { name } of PRODUCTS) {
+ const component = shallow(
+ {}}
+ product={{
+ isPartiallyMigrated: true
+ }}
+ meta={{
+ secondsAgo: 10
+ }}
+ productName={name}
+ />
+ );
+ component.setState({
+ activeStep: INSTRUCTION_STEP_ENABLE_METRICBEAT,
+ });
+ component.update();
+ const steps = component.find('EuiSteps').prop('steps');
+ const step = steps[steps.length - 1];
+ if (!template) {
+ template = step;
+ expect(template).toMatchSnapshot();
+ } else {
+ expect(template).toEqual(step);
+ }
+ }
+ });
+
+ it('should render the beat type for beats for the enabling metricbeat step', () => {
+ const component = shallow(
+ {}}
+ product={{
+ isInternalCollector: true,
+ beatType: 'filebeat'
+ }}
+ productName={BEATS_SYSTEM_ID}
+ />
+ );
+ component.find('EuiButton').simulate('click');
+ component.update();
+ expect(component.find('EuiFlyoutBody')).toMatchSnapshot();
+ });
+
+ it('should render the beat type for beats for the disabling internal collection step', () => {
+ const component = shallow(
+ {}}
+ product={{
+ isPartiallyMigrated: true,
+ beatType: 'filebeat'
+ }}
+ meta={{
+ secondsAgo: 30
+ }}
+ productName={BEATS_SYSTEM_ID}
+ />
+ );
+ component.find('EuiButton').simulate('click');
+ component.update();
+ expect(component.find('EuiFlyoutBody')).toMatchSnapshot();
+ });
+
+ it('should show a restart warning for restarting the primary Kibana', () => {
+ const component = shallow(
+ {}}
+ product={{
+ isPartiallyMigrated: true,
+ isPrimary: true
+ }}
+ meta={{
+ secondsAgo: 30
+ }}
+ productName={KIBANA_SYSTEM_ID}
+ />
+ );
+ component.find('EuiButton').simulate('click');
+ component.update();
+ expect(component.find('EuiFlyoutBody')).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/__snapshots__/setup_mode.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/renderers/__snapshots__/setup_mode.test.js.snap
new file mode 100644
index 0000000000000..12b82be333703
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/__snapshots__/setup_mode.test.js.snap
@@ -0,0 +1,187 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SetupModeRenderer should render the flyout open 1`] = `
+
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+ Exit setup mode
+
+
+
+
+
+
+
+`;
+
+exports[`SetupModeRenderer should render with setup mode disabled 1`] = `
+
+
+
+`;
+
+exports[`SetupModeRenderer should render with setup mode enabled 1`] = `
+
+
+
+
+
+
+
+
+
+ ,
+ }
+ }
+ />
+
+
+
+
+
+
+
+
+ Exit setup mode
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js
index 079a3e7eeae09..a07a26f64acff 100644
--- a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js
+++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js
@@ -125,7 +125,7 @@ export class SetupModeRenderer extends React.Component {
-
+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.test.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.test.js
new file mode 100644
index 0000000000000..fbcf8db382614
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.test.js
@@ -0,0 +1,277 @@
+/*
+ * 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, { Fragment } from 'react';
+import { shallow } from 'enzyme';
+import { ELASTICSEARCH_SYSTEM_ID } from '../../../common/constants';
+
+describe('SetupModeRenderer', () => {
+ beforeEach(() => jest.resetModules());
+
+ it('should render with setup mode disabled', () => {
+ jest.doMock('../../lib/setup_mode', () => ({
+ getSetupModeState: () => ({
+ enabled: false
+ }),
+ initSetupModeState: () => {},
+ updateSetupModeData: () => {},
+ setSetupModeMenuItem: () => {}
+ }));
+ const SetupModeRenderer = require('./setup_mode').SetupModeRenderer;
+
+ const ChildComponent = () => Hi ;
+ const scope = {};
+ const injector = {};
+ const component = shallow(
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should render with setup mode enabled', () => {
+ jest.doMock('../../lib/setup_mode', () => ({
+ getSetupModeState: () => ({
+ enabled: true,
+ data: {
+ elasticsearch: {},
+ _meta: {}
+ }
+ }),
+ initSetupModeState: () => {},
+ updateSetupModeData: () => {},
+ setSetupModeMenuItem: () => {}
+ }));
+ const SetupModeRenderer = require('./setup_mode').SetupModeRenderer;
+
+ const ChildComponent = () => Hi ;
+ const scope = {};
+ const injector = {};
+ const component = shallow(
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should render the flyout open', () => {
+ jest.doMock('../../lib/setup_mode', () => ({
+ getSetupModeState: () => ({
+ enabled: true,
+ data: {
+ elasticsearch: {
+ byUuid: {
+
+ }
+ },
+ _meta: {}
+ }
+ }),
+ initSetupModeState: () => {},
+ updateSetupModeData: () => {},
+ setSetupModeMenuItem: () => {}
+ }));
+ const SetupModeRenderer = require('./setup_mode').SetupModeRenderer;
+
+ const ChildComponent = () => Hi ;
+ const scope = {};
+ const injector = {};
+ const component = shallow(
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+ );
+
+ component.setState({ isFlyoutOpen: true });
+ component.update();
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should handle a new node/instance scenario', () => {
+ jest.doMock('../../lib/setup_mode', () => ({
+ getSetupModeState: () => ({
+ enabled: true,
+ data: {
+ elasticsearch: {
+ byUuid: {}
+ },
+ _meta: {}
+ }
+ }),
+ initSetupModeState: () => {},
+ updateSetupModeData: () => {},
+ setSetupModeMenuItem: () => {}
+ }));
+ const SetupModeRenderer = require('./setup_mode').SetupModeRenderer;
+
+ const ChildComponent = () => Hi ;
+ const scope = {};
+ const injector = {};
+ const component = shallow(
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+ );
+
+ component.setState({ isFlyoutOpen: true, instance: null, isSettingUpNew: true });
+ component.update();
+ expect(component.find('Flyout').prop('product')).toEqual({ isNetNewUser: true });
+ });
+
+ it('should use a new product found in the api response', () => {
+ const newProduct = { id: 1 };
+
+ jest.useFakeTimers();
+ jest.doMock('../../lib/setup_mode', () => ({
+ getSetupModeState: () => ({
+ enabled: true,
+ data: {
+ elasticsearch: {
+ byUuid: {
+ 2: newProduct
+ }
+ },
+ _meta: {}
+ }
+ }),
+ initSetupModeState: (_scope, _injectir, cb) => {
+ setTimeout(() => {
+ cb({
+ elasticsearch: {
+ byUuid: {
+ 1: {}
+ }
+ }
+ });
+ }, 500);
+ },
+ updateSetupModeData: () => {},
+ setSetupModeMenuItem: () => {}
+ }));
+ const SetupModeRenderer = require('./setup_mode').SetupModeRenderer;
+
+ const ChildComponent = () => Hi ;
+ const scope = {};
+ const injector = {};
+ const component = shallow(
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+ );
+
+ component.setState({ isFlyoutOpen: true });
+ component.update();
+
+ jest.advanceTimersByTime(1000);
+ expect(component.state('renderState')).toBe(true);
+ expect(component.state('newProduct')).toBe(newProduct);
+ expect(component.find('Flyout').prop('product')).toBe(newProduct);
+ });
+
+ it('should set the top menu items', () => {
+ const newProduct = { id: 1 };
+
+ const setSetupModeMenuItem = jest.fn();
+ jest.doMock('../../lib/setup_mode', () => ({
+ getSetupModeState: () => ({
+ enabled: true,
+ data: {
+ elasticsearch: {
+ byUuid: {
+ 2: newProduct
+ }
+ },
+ _meta: {}
+ }
+ }),
+ initSetupModeState: (_scope, _injectir, cb) => {
+ setTimeout(() => {
+ cb({
+ elasticsearch: {
+ byUuid: {
+ 1: {}
+ }
+ }
+ });
+ }, 500);
+ },
+ updateSetupModeData: () => {},
+ setSetupModeMenuItem,
+ }));
+ const SetupModeRenderer = require('./setup_mode').SetupModeRenderer;
+
+ const ChildComponent = () => Hi ;
+ const scope = {};
+ const injector = {};
+ const component = shallow(
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+ );
+
+ component.setState({ isFlyoutOpen: true });
+ component.update();
+ expect(setSetupModeMenuItem).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/badge.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/badge.test.js.snap
new file mode 100644
index 0000000000000..8aba968f8384d
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/badge.test.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`setupMode SetupModeBadge internal collection should render each product consistently 1`] = `
+
+ Monitor with Metricbeat
+
+`;
+
+exports[`setupMode SetupModeBadge metricbeat collection should render each product consistently 1`] = `
+
+ Monitored with Metricbeat
+
+`;
+
+exports[`setupMode SetupModeBadge net new user should render each product consistently 1`] = `
+
+ Monitor with Metricbeat
+
+`;
+
+exports[`setupMode SetupModeBadge partially migrated should render each product consistently 1`] = `
+
+ Disable self monitoring
+
+`;
+
+exports[`setupMode SetupModeBadge should use a text status if internal collection cannot be disabled yet for elasticsearch 1`] = `
+
+
+
+
+ Some nodes use only self monitoring
+
+
+`;
+
+exports[`setupMode SetupModeBadge unknown should render each product consistently 1`] = `
+
+ N/A
+
+`;
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/formatting.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/formatting.test.js.snap
new file mode 100644
index 0000000000000..6ec8575a2ab39
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/formatting.test.js.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`setupMode formatting formatProductName should format the product name for apm 1`] = `"APM"`;
+
+exports[`setupMode formatting formatProductName should format the product name for beats 1`] = `"Beats"`;
+
+exports[`setupMode formatting formatProductName should format the product name for elasticsearch 1`] = `"Elasticsearch"`;
+
+exports[`setupMode formatting formatProductName should format the product name for kibana 1`] = `"Kibana"`;
+
+exports[`setupMode formatting formatProductName should format the product name for logstash 1`] = `"Logstash"`;
+
+exports[`setupMode formatting getIdentifier should get the plural identifier for apm 1`] = `"servers"`;
+
+exports[`setupMode formatting getIdentifier should get the plural identifier for beats 1`] = `"instances"`;
+
+exports[`setupMode formatting getIdentifier should get the plural identifier for elasticsearch 1`] = `"nodes"`;
+
+exports[`setupMode formatting getIdentifier should get the plural identifier for kibana 1`] = `"instances"`;
+
+exports[`setupMode formatting getIdentifier should get the plural identifier for logstash 1`] = `"nodes"`;
+
+exports[`setupMode formatting getIdentifier should get the singular identifier for apm 1`] = `"server"`;
+
+exports[`setupMode formatting getIdentifier should get the singular identifier for beats 1`] = `"instance"`;
+
+exports[`setupMode formatting getIdentifier should get the singular identifier for elasticsearch 1`] = `"node"`;
+
+exports[`setupMode formatting getIdentifier should get the singular identifier for kibana 1`] = `"instance"`;
+
+exports[`setupMode formatting getIdentifier should get the singular identifier for logstash 1`] = `"node"`;
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/listing_callout.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/listing_callout.test.js.snap
new file mode 100644
index 0000000000000..6f6861a53bf3f
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/listing_callout.test.js.snap
@@ -0,0 +1,406 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`setupMode ListingCallOut all internally collected should render for apm 1`] = `
+
+
+
+ These APM servers are self monitored.
+ Click 'Monitor with Metricbeat' to migrate.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all internally collected should render for beats 1`] = `
+
+
+
+ These Beats instances are self monitored.
+ Click 'Monitor with Metricbeat' to migrate.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all internally collected should render for elasticsearch 1`] = `
+
+
+
+ These Elasticsearch nodes are self monitored.
+ Click 'Monitor with Metricbeat' to migrate.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all internally collected should render for kibana 1`] = `
+
+
+
+ These Kibana instances are self monitored.
+ Click 'Monitor with Metricbeat' to migrate.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all internally collected should render for logstash 1`] = `
+
+
+
+ These Logstash nodes are self monitored.
+ Click 'Monitor with Metricbeat' to migrate.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all migrated should render for apm 1`] = `
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all migrated should render for beats 1`] = `
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all migrated should render for elasticsearch 1`] = `
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all migrated should render for kibana 1`] = `
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all migrated should render for logstash 1`] = `
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all partially migrated should render for apm 1`] = `
+
+
+
+ Metricbeat is now monitoring your APM servers. Disable self monitoring to finish the migration.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all partially migrated should render for beats 1`] = `
+
+
+
+ Metricbeat is now monitoring your Beats instances. Disable self monitoring to finish the migration.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all partially migrated should render for elasticsearch 1`] = `
+
+
+
+ Metricbeat is now monitoring your Elasticsearch nodes. Disable self monitoring to finish the migration.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all partially migrated should render for kibana 1`] = `
+
+
+
+ Metricbeat is now monitoring your Kibana instances. Disable self monitoring to finish the migration.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut all partially migrated should render for logstash 1`] = `
+
+
+
+ Metricbeat is now monitoring your Logstash nodes. Disable self monitoring to finish the migration.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut no detectable instances should render for apm 1`] = `
+
+
+
+ Click 'Set up monitoring' to start monitoring with Metricbeat.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut no detectable instances should render for beats 1`] = `
+
+
+
+ Click 'Set up monitoring' to start monitoring with Metricbeat.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut no detectable instances should render for elasticsearch 1`] = `
+
+
+
+ Click 'Set up monitoring' to start monitoring with Metricbeat.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut no detectable instances should render for kibana 1`] = `
+
+
+
+ Click 'Set up monitoring' to start monitoring with Metricbeat.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut no detectable instances should render for logstash 1`] = `
+
+
+
+ Click 'Set up monitoring' to start monitoring with Metricbeat.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut only detectable instances should render for apm 1`] = `
+
+
+
+ Click 'Set up monitoring' below to start monitoring this server.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut only detectable instances should render for beats 1`] = `
+
+
+
+ Click 'Set up monitoring' below to start monitoring this instance.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut only detectable instances should render for elasticsearch 1`] = `
+
+
+
+ Click 'Set up monitoring' below to start monitoring this node.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut only detectable instances should render for kibana 1`] = `
+
+
+
+ Click 'Set up monitoring' below to start monitoring this instance.
+
+
+
+
+`;
+
+exports[`setupMode ListingCallOut only detectable instances should render for logstash 1`] = `
+
+
+
+ Click 'Set up monitoring' below to start monitoring this node.
+
+
+
+
+`;
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/tooltip.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/tooltip.test.js.snap
new file mode 100644
index 0000000000000..49ce0e84fdacc
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/__snapshots__/tooltip.test.js.snap
@@ -0,0 +1,526 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`setupMode SetupModeTooltip allInternalCollection should render for apm 1`] = `
+
+
+
+ Self monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allInternalCollection should render for beats 1`] = `
+
+
+
+ Self monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allInternalCollection should render for elasticsearch 1`] = `
+
+
+
+ Self monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allInternalCollection should render for kibana 1`] = `
+
+
+
+ Self monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allInternalCollection should render for logstash 1`] = `
+
+
+
+ Self monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allMonitoredByMetricbeat should render for apm 1`] = `
+
+
+
+ Metricbeat monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allMonitoredByMetricbeat should render for beats 1`] = `
+
+
+
+ Metricbeat monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allMonitoredByMetricbeat should render for elasticsearch 1`] = `
+
+
+
+ Metricbeat monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allMonitoredByMetricbeat should render for kibana 1`] = `
+
+
+
+ Metricbeat monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip allMonitoredByMetricbeat should render for logstash 1`] = `
+
+
+
+ Metricbeat monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip internalCollectionOn should render for apm 1`] = `
+
+
+
+ Self monitoring is on
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip internalCollectionOn should render for beats 1`] = `
+
+
+
+ Self monitoring is on
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip internalCollectionOn should render for elasticsearch 1`] = `
+
+
+
+ Self monitoring is on
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip internalCollectionOn should render for kibana 1`] = `
+
+
+
+ Self monitoring is on
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip internalCollectionOn should render for logstash 1`] = `
+
+
+
+ Self monitoring is on
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip no detectable instances should render for apm 1`] = `
+
+
+
+ No usage
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip no detectable instances should render for beats 1`] = `
+
+
+
+ No usage
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip no detectable instances should render for elasticsearch 1`] = `
+
+
+
+ No usage
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip no detectable instances should render for kibana 1`] = `
+
+
+
+ No usage
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip no detectable instances should render for logstash 1`] = `
+
+
+
+ No usage
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip only detectable instances should render for apm 1`] = `
+
+
+
+ No monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip only detectable instances should render for beats 1`] = `
+
+
+
+ No monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip only detectable instances should render for elasticsearch 1`] = `
+
+
+
+ No monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip only detectable instances should render for kibana 1`] = `
+
+
+
+ No monitoring
+
+
+
+`;
+
+exports[`setupMode SetupModeTooltip only detectable instances should render for logstash 1`] = `
+
+
+
+ No monitoring
+
+
+
+`;
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/badge.test.js b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/badge.test.js
new file mode 100644
index 0000000000000..55cc9eebb9531
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/badge.test.js
@@ -0,0 +1,168 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import { SetupModeBadge } from './badge';
+import {
+ ELASTICSEARCH_SYSTEM_ID,
+ KIBANA_SYSTEM_ID,
+ BEATS_SYSTEM_ID,
+ APM_SYSTEM_ID,
+ LOGSTASH_SYSTEM_ID
+} from '../../../common/constants';
+
+const STATUSES = [
+ {
+ name: 'internal collection',
+ status: {
+ isInternalCollector: true
+ }
+ },
+ {
+ name: 'partially migrated',
+ status: {
+ isPartiallyMigrated: true
+ }
+ },
+ {
+ name: 'metricbeat collection',
+ status: {
+ isFullyMigrated: true
+ }
+ },
+ {
+ name: 'net new user',
+ status: {
+ isNetNewUser: true
+ }
+ },
+ {
+ name: 'unknown',
+ status: {}
+ }
+];
+
+const PRODUCTS = [
+ {
+ name: ELASTICSEARCH_SYSTEM_ID
+ },
+ {
+ name: KIBANA_SYSTEM_ID
+ },
+ {
+ name: LOGSTASH_SYSTEM_ID
+ },
+ {
+ name: BEATS_SYSTEM_ID
+ },
+ {
+ name: APM_SYSTEM_ID
+ }
+];
+
+describe('setupMode SetupModeBadge', () => {
+ for (const status of STATUSES) {
+ describe(`${status.name}`, () => {
+ it('should render each product consistently', () => {
+ let template = null;
+ for (const { name } of PRODUCTS) {
+ const component = shallow(
+
+ );
+ if (!template) {
+ template = component;
+ expect(component).toMatchSnapshot();
+ } else {
+ expect(template.debug()).toEqual(component.debug());
+ }
+ }
+ });
+ });
+ }
+
+ it('should call openFlyout when clicked', () => {
+ const openFlyout = jest.fn();
+ const instance = {
+ id: 1
+ };
+ const component = shallow(
+
+ );
+
+ component.find('EuiBadge').simulate('click');
+ expect(openFlyout).toHaveBeenCalledWith(instance);
+ });
+
+ it('should use a custom action for the live elasticsearch cluster', () => {
+ const shortcutToFinishMigration = jest.fn();
+ const component = shallow(
+
+ );
+ component.find('EuiBadge').simulate('click');
+ expect(shortcutToFinishMigration).toHaveBeenCalled();
+ });
+
+ it('should use a text status if internal collection cannot be disabled yet for elasticsearch', () => {
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/formatting.test.js b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/formatting.test.js
new file mode 100644
index 0000000000000..06d72bdb0665e
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/formatting.test.js
@@ -0,0 +1,52 @@
+/*
+ * 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 { formatProductName, getIdentifier } from './formatting';
+import {
+ ELASTICSEARCH_SYSTEM_ID,
+ KIBANA_SYSTEM_ID,
+ BEATS_SYSTEM_ID,
+ APM_SYSTEM_ID,
+ LOGSTASH_SYSTEM_ID
+} from '../../../common/constants';
+
+const PRODUCTS = [
+ {
+ name: ELASTICSEARCH_SYSTEM_ID
+ },
+ {
+ name: KIBANA_SYSTEM_ID
+ },
+ {
+ name: LOGSTASH_SYSTEM_ID
+ },
+ {
+ name: BEATS_SYSTEM_ID
+ },
+ {
+ name: APM_SYSTEM_ID
+ }
+];
+
+describe('setupMode formatting', () => {
+ describe('formatProductName', () => {
+ for (const { name } of PRODUCTS) {
+ it(`should format the product name for ${name}`, () => {
+ expect(formatProductName(name)).toMatchSnapshot();
+ });
+ }
+ });
+ describe('getIdentifier', () => {
+ for (const { name } of PRODUCTS) {
+ it(`should get the singular identifier for ${name}`, () => {
+ expect(getIdentifier(name)).toMatchSnapshot();
+ });
+ it(`should get the plural identifier for ${name}`, () => {
+ expect(getIdentifier(name, true)).toMatchSnapshot();
+ });
+ }
+ });
+});
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.js b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.js
index adede59d384d6..69f2d07c2d2da 100644
--- a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.js
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.js
@@ -109,8 +109,7 @@ export function ListingCallOut({ setupModeData, productName, customRenderer = nu
>
{i18n.translate('xpack.monitoring.setupMode.disableInternalCollectionDescription', {
- defaultMessage: `Metricbeat is now monitoring your {product} {identifier}.
- Disable self monitoring to finish the migration.`,
+ defaultMessage: `Metricbeat is now monitoring your {product} {identifier}. Disable self monitoring to finish the migration.`,
values: {
product: formatProductName(productName),
identifier: getIdentifier(productName, true)
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.test.js b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.test.js
new file mode 100644
index 0000000000000..5eb223a071965
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/listing_callout.test.js
@@ -0,0 +1,110 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import { ListingCallOut } from './listing_callout';
+import {
+ ELASTICSEARCH_SYSTEM_ID,
+ KIBANA_SYSTEM_ID,
+ BEATS_SYSTEM_ID,
+ APM_SYSTEM_ID,
+ LOGSTASH_SYSTEM_ID
+} from '../../../common/constants';
+
+const SCENARIOS = [
+ {
+ name: 'no detectable instances',
+ data: {
+ totalUniqueInstanceCount: 0,
+ detected: {
+ mightExist: false
+ }
+ }
+ },
+ {
+ name: 'only detectable instances',
+ data: {
+ totalUniqueInstanceCount: 0,
+ detected: {
+ mightExist: true
+ }
+ }
+ },
+ {
+ name: 'all migrated',
+ data: {
+ totalUniqueInstanceCount: 1,
+ totalUniqueFullyMigratedCount: 1
+ }
+ },
+ {
+ name: 'all partially migrated',
+ data: {
+ totalUniqueInstanceCount: 1,
+ totalUniquePartiallyMigratedCount: 1
+ }
+ },
+ {
+ name: 'all internally collected',
+ data: {
+ totalUniqueInstanceCount: 1,
+ totalUniquePartiallyMigratedCount: 0,
+ totalUniqueFullyMigratedCount: 0
+ }
+ },
+];
+
+const PRODUCTS = [
+ {
+ name: ELASTICSEARCH_SYSTEM_ID
+ },
+ {
+ name: KIBANA_SYSTEM_ID
+ },
+ {
+ name: LOGSTASH_SYSTEM_ID
+ },
+ {
+ name: BEATS_SYSTEM_ID
+ },
+ {
+ name: APM_SYSTEM_ID
+ }
+];
+
+describe('setupMode ListingCallOut', () => {
+ for (const scenario of SCENARIOS) {
+ describe(`${scenario.name}`, () => {
+ for (const { name } of PRODUCTS) {
+ it(`should render for ${name}`, () => {
+ const component = shallow(
+
+ );
+ expect(component).toMatchSnapshot();
+ });
+ }
+ });
+ }
+
+ it('should render a custom renderer', () => {
+ const MyComponent =
Hi ;
+ const component = shallow(
+ ({
+ shouldRender: true,
+ componentToRender: MyComponent
+ })}
+ />
+ );
+ expect(component.equals(MyComponent)).toBe(true);
+ });
+});
diff --git a/x-pack/legacy/plugins/monitoring/public/components/setup_mode/tooltip.test.js b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/tooltip.test.js
new file mode 100644
index 0000000000000..9c72773ef0d7c
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/components/setup_mode/tooltip.test.js
@@ -0,0 +1,96 @@
+/*
+ * 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';
+import { shallow } from 'enzyme';
+import { SetupModeTooltip } from './tooltip';
+import {
+ ELASTICSEARCH_SYSTEM_ID,
+ KIBANA_SYSTEM_ID,
+ BEATS_SYSTEM_ID,
+ APM_SYSTEM_ID,
+ LOGSTASH_SYSTEM_ID
+} from '../../../common/constants';
+
+const SCENARIOS = [
+ {
+ name: 'no detectable instances',
+ data: {
+ totalUniqueInstanceCount: 0,
+ detected: {
+ mightExist: false
+ }
+ }
+ },
+ {
+ name: 'only detectable instances',
+ data: {
+ totalUniqueInstanceCount: 0,
+ detected: {
+ mightExist: true
+ }
+ }
+ },
+ {
+ name: 'allMonitoredByMetricbeat',
+ data: {
+ totalUniqueInstanceCount: 1,
+ totalUniqueFullyMigratedCount: 1,
+ }
+ },
+ {
+ name: 'internalCollectionOn',
+ data: {
+ totalUniqueInstanceCount: 1,
+ totalUniquePartiallyMigratedCount: 1
+ }
+ },
+ {
+ name: 'allInternalCollection',
+ data: {
+ totalUniqueInstanceCount: 1,
+ totalUniqueFullyMigratedCount: 0,
+ totalUniquePartiallyMigratedCount: 0,
+ }
+ }
+];
+
+const PRODUCTS = [
+ {
+ name: ELASTICSEARCH_SYSTEM_ID
+ },
+ {
+ name: KIBANA_SYSTEM_ID
+ },
+ {
+ name: LOGSTASH_SYSTEM_ID
+ },
+ {
+ name: BEATS_SYSTEM_ID
+ },
+ {
+ name: APM_SYSTEM_ID
+ }
+];
+
+describe('setupMode SetupModeTooltip', () => {
+ for (const scenario of SCENARIOS) {
+ describe(`${scenario.name}`, () => {
+ for (const { name } of PRODUCTS) {
+ it(`should render for ${name}`, () => {
+ const component = shallow(
+ {}}
+ />
+ );
+ expect(component).toMatchSnapshot();
+ });
+ }
+ });
+ }
+});
diff --git a/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js b/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js
index 53b16c29143bc..862e609cd2fcb 100644
--- a/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js
+++ b/x-pack/legacy/plugins/monitoring/public/components/table/eui_table.js
@@ -50,7 +50,7 @@ export class EuiMonitoringTable extends React.PureComponent {
setupMode.openFlyout({}, true)}>
{i18n.translate('xpack.monitoring.euiTable.setupNewButtonLabel', {
- defaultMessage: 'Set up monitoring for new {identifier}',
+ defaultMessage: 'Monitor another {identifier} with Metricbeat',
values: {
identifier: getIdentifier(productName)
}
diff --git a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js
new file mode 100644
index 0000000000000..b5878c7ec5181
--- /dev/null
+++ b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js
@@ -0,0 +1,196 @@
+/*
+ * 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 {
+ toggleSetupMode,
+ initSetupModeState,
+ getSetupModeState,
+ updateSetupModeData,
+ setSetupModeMenuItem
+} from './setup_mode';
+
+jest.mock('./ajax_error_handler', () => ({
+ ajaxErrorHandlersProvider: err => {
+ throw err;
+ }
+}));
+
+let data = {};
+
+const injectorModulesMock = {
+ globalState: {
+ save: jest.fn()
+ },
+ Private: module => module,
+ $http: {
+ post: jest.fn().mockImplementation(() => {
+ return { data };
+ })
+ },
+ $executor: {
+ run: jest.fn()
+ }
+};
+
+const angularStateMock = {
+ injector: {
+ get: module => {
+ return injectorModulesMock[module] || {};
+ }
+ },
+ scope: {
+ $apply: fn => fn && fn()
+ }
+};
+
+// We are no longer waiting for setup mode data to be fetched when enabling
+// so we need to wait for the next tick for the async action to finish
+function waitForSetupModeData(action) {
+ process.nextTick(action);
+}
+
+describe('setup_mode', () => {
+ describe('setup', () => {
+ afterEach(async () => {
+ try {
+ toggleSetupMode(false);
+ } catch (err) {
+ // Do nothing...
+ }
+ });
+
+ it('should require angular state', async () => {
+ let error;
+ try {
+ toggleSetupMode(true);
+ }
+ catch (err) {
+ error = err;
+ }
+ expect(error).toEqual('Unable to interact with setup '
+ + 'mode because the angular injector was not previously set. This needs to be '
+ + 'set by calling `initSetupModeState`.');
+ });
+
+ it('should enable toggle mode', async () => {
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(true);
+ expect(injectorModulesMock.globalState.inSetupMode).toBe(true);
+ });
+
+ it('should disable toggle mode', async () => {
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(false);
+ expect(injectorModulesMock.globalState.inSetupMode).toBe(false);
+ });
+
+ it('should set top nav config', async () => {
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ setSetupModeMenuItem();
+ expect(angularStateMock.scope.topNavMenu.length).toBe(1);
+ await toggleSetupMode(true);
+ expect(angularStateMock.scope.topNavMenu.length).toBe(0);
+ });
+ });
+
+ describe('in setup mode', () => {
+ afterEach(async () => {
+ data = {};
+ toggleSetupMode(false);
+ });
+
+ it('should enable it through clicking top nav item', async () => {
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await angularStateMock.scope.topNavMenu[0].run();
+ expect(injectorModulesMock.globalState.inSetupMode).toBe(true);
+ });
+
+ it('should not fetch data if on cloud', async (done) => {
+ data = {
+ _meta: {
+ isOnCloud: true
+ }
+ };
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(true);
+ waitForSetupModeData(() => {
+ const state = getSetupModeState();
+ expect(state.enabled).toBe(false);
+ done();
+ });
+ });
+
+ it('should set the newly discovered cluster uuid', async (done) => {
+ const clusterUuid = '1ajy';
+ data = {
+ _meta: {
+ liveClusterUuid: clusterUuid
+ },
+ elasticsearch: {
+ byUuid: {
+ 123: {
+ isPartiallyMigrated: true
+ }
+ }
+ }
+ };
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(true);
+ waitForSetupModeData(() => {
+ expect(injectorModulesMock.globalState.cluster_uuid).toBe(clusterUuid);
+ done();
+ });
+ });
+
+ it('should fetch data for a given cluster', async (done) => {
+ const clusterUuid = '1ajy';
+ data = {
+ _meta: {
+ liveClusterUuid: clusterUuid
+ },
+ elasticsearch: {
+ byUuid: {
+ 123: {
+ isPartiallyMigrated: true
+ }
+ }
+ }
+ };
+
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(true);
+ waitForSetupModeData(() => {
+ expect(injectorModulesMock.$http.post).toHaveBeenCalledWith(
+ `../api/monitoring/v1/setup/collection/cluster/${clusterUuid}`,
+ { ccs: undefined }
+ );
+ done();
+ });
+ });
+
+ it('should fetch data for a single node', async () => {
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(true);
+ injectorModulesMock.$http.post.mockClear();
+ await updateSetupModeData('45asd');
+ expect(injectorModulesMock.$http.post).toHaveBeenCalledWith(
+ '../api/monitoring/v1/setup/collection/node/45asd',
+ { ccs: undefined }
+ );
+ });
+
+ it('should fetch data without a cluster uuid', async () => {
+ initSetupModeState(angularStateMock.scope, angularStateMock.injector);
+ await toggleSetupMode(true);
+ injectorModulesMock.$http.post.mockClear();
+ await updateSetupModeData(undefined, true);
+ expect(injectorModulesMock.$http.post).toHaveBeenCalledWith(
+ '../api/monitoring/v1/setup/collection/cluster',
+ { ccs: undefined }
+ );
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts
index fee17baecce28..da2c184d2d6e0 100644
--- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts
+++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts
@@ -6,7 +6,6 @@
import { Request } from 'hapi';
-// @ts-ignore no module definition
import { buildEsQuery } from '@kbn/es-query';
// @ts-ignore no module definition
import { createGenerateCsv } from '../../../csv/server/lib/generate_csv';
diff --git a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts
index e0597d19a59da..24589822f0250 100644
--- a/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts
+++ b/x-pack/legacy/plugins/siem/server/graphql/source_status/resolvers.ts
@@ -31,9 +31,21 @@ export const createSourceStatusResolvers = (libs: {
} => ({
SourceStatus: {
async indicesExist(source, args, { req }) {
+ if (
+ args.defaultIndex.length === 1 &&
+ (args.defaultIndex[0] === '' || args.defaultIndex[0] === '_all')
+ ) {
+ return false;
+ }
return libs.sourceStatus.hasIndices(req, args.defaultIndex);
},
async indexFields(source, args, { req }) {
+ if (
+ args.defaultIndex.length === 1 &&
+ (args.defaultIndex[0] === '' || args.defaultIndex[0] === '_all')
+ ) {
+ return [];
+ }
return libs.fields.getFields(req, args.defaultIndex);
},
},
diff --git a/x-pack/legacy/plugins/uptime/common/constants/plugin.ts b/x-pack/legacy/plugins/uptime/common/constants/plugin.ts
index 01f38d37189d1..47f77ea985f32 100644
--- a/x-pack/legacy/plugins/uptime/common/constants/plugin.ts
+++ b/x-pack/legacy/plugins/uptime/common/constants/plugin.ts
@@ -5,6 +5,7 @@
*/
export const PLUGIN = {
+ APP_ROOT_ID: 'react-uptime-root',
ID: 'uptime',
ROUTER_BASE_NAME: '/app/uptime#/',
LOCAL_STORAGE_KEY: 'xpack.uptime',
diff --git a/x-pack/legacy/plugins/uptime/public/app.ts b/x-pack/legacy/plugins/uptime/public/app.ts
index 255c51c9e48ce..b068f8a9becda 100644
--- a/x-pack/legacy/plugins/uptime/public/app.ts
+++ b/x-pack/legacy/plugins/uptime/public/app.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import './apps/kibana_app';
+import './apps/index';
diff --git a/x-pack/legacy/plugins/uptime/public/apps/index.ts b/x-pack/legacy/plugins/uptime/public/apps/index.ts
new file mode 100644
index 0000000000000..2fa548c3c2717
--- /dev/null
+++ b/x-pack/legacy/plugins/uptime/public/apps/index.ts
@@ -0,0 +1,11 @@
+/*
+ * 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 chrome from 'ui/chrome';
+import { npStart } from 'ui/new_platform';
+import { Plugin } from './plugin';
+
+new Plugin({ opaqueId: Symbol('uptime') }, chrome).start(npStart);
diff --git a/x-pack/legacy/plugins/uptime/public/apps/plugin.ts b/x-pack/legacy/plugins/uptime/public/apps/plugin.ts
new file mode 100644
index 0000000000000..bc4e30b79cb15
--- /dev/null
+++ b/x-pack/legacy/plugins/uptime/public/apps/plugin.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 { LegacyCoreStart, PluginInitializerContext } from 'src/core/public';
+import { PluginsStart } from 'ui/new_platform/new_platform';
+import { Chrome } from 'ui/chrome';
+import { UMFrontendLibs } from '../lib/lib';
+import { PLUGIN } from '../../common/constants';
+import { getKibanaFrameworkAdapter } from '../lib/adapters/framework/new_platform_adapter';
+import template from './template.html';
+import { UptimeApp } from '../uptime_app';
+import { createApolloClient } from '../lib/adapters/framework/apollo_client_adapter';
+
+export interface StartObject {
+ core: LegacyCoreStart;
+ plugins: PluginsStart;
+}
+
+export class Plugin {
+ constructor(
+ // @ts-ignore this is added to satisfy the New Platform typing constraint,
+ // but we're not leveraging any of its functionality yet.
+ private readonly initializerContext: PluginInitializerContext,
+ private readonly chrome: Chrome
+ ) {
+ this.chrome = chrome;
+ }
+
+ public start(start: StartObject): void {
+ const {
+ core,
+ plugins: {
+ data: { autocomplete },
+ },
+ } = start;
+ const libs: UMFrontendLibs = {
+ framework: getKibanaFrameworkAdapter(core, autocomplete),
+ };
+ // @ts-ignore improper type description
+ this.chrome.setRootTemplate(template);
+ const checkForRoot = () => {
+ return new Promise(resolve => {
+ const ready = !!document.getElementById(PLUGIN.APP_ROOT_ID);
+ if (ready) {
+ resolve();
+ } else {
+ setTimeout(() => resolve(checkForRoot()), 10);
+ }
+ });
+ };
+ checkForRoot().then(() => {
+ libs.framework.render(UptimeApp, createApolloClient);
+ });
+ }
+}
diff --git a/x-pack/legacy/plugins/uptime/public/apps/start_app.tsx b/x-pack/legacy/plugins/uptime/public/apps/start_app.tsx
deleted file mode 100644
index 4b07f936ae363..0000000000000
--- a/x-pack/legacy/plugins/uptime/public/apps/start_app.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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-vis/dist/style.css';
-import 'ui/angular-bootstrap';
-import 'ui/autoload/all';
-import 'ui/autoload/styles';
-import 'ui/courier';
-import 'ui/persisted_log';
-import { createApolloClient } from '../lib/adapters/framework/apollo_client_adapter';
-import { UMFrontendLibs } from '../lib/lib';
-import { UptimeApp } from '../uptime_app';
-
-export async function startApp(libs: UMFrontendLibs) {
- libs.framework.render(UptimeApp, createApolloClient);
-}
diff --git a/x-pack/legacy/plugins/uptime/public/apps/template.html b/x-pack/legacy/plugins/uptime/public/apps/template.html
new file mode 100644
index 0000000000000..a6fb47048a9b1
--- /dev/null
+++ b/x-pack/legacy/plugins/uptime/public/apps/template.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/uptime/public/badge.ts b/x-pack/legacy/plugins/uptime/public/badge.ts
index 76c2b72f5ead6..a1b4f85348f06 100644
--- a/x-pack/legacy/plugins/uptime/public/badge.ts
+++ b/x-pack/legacy/plugins/uptime/public/badge.ts
@@ -4,5 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Badge } from 'ui/chrome/api/badge';
-export type UMBadge = Badge | undefined;
+import { ChromeBadge } from 'src/core/public';
+export type UMBadge = ChromeBadge | undefined;
diff --git a/x-pack/legacy/plugins/uptime/public/breadcrumbs.ts b/x-pack/legacy/plugins/uptime/public/breadcrumbs.ts
index 31aa1e491a999..ff0dca3887ff2 100644
--- a/x-pack/legacy/plugins/uptime/public/breadcrumbs.ts
+++ b/x-pack/legacy/plugins/uptime/public/breadcrumbs.ts
@@ -5,24 +5,20 @@
*/
import { i18n } from '@kbn/i18n';
+import { ChromeBreadcrumb } from 'src/core/public';
-export interface UMBreadcrumb {
- text: string;
- href?: string;
-}
-
-const makeOverviewBreadcrumb = (search?: string): UMBreadcrumb => ({
+const makeOverviewBreadcrumb = (search?: string): ChromeBreadcrumb => ({
text: i18n.translate('xpack.uptime.breadcrumbs.overviewBreadcrumbText', {
defaultMessage: 'Uptime',
}),
href: `#/${search ? search : ''}`,
});
-export const getOverviewPageBreadcrumbs = (search?: string): UMBreadcrumb[] => [
+export const getOverviewPageBreadcrumbs = (search?: string): ChromeBreadcrumb[] => [
makeOverviewBreadcrumb(search),
];
-export const getMonitorPageBreadcrumb = (name: string, search?: string): UMBreadcrumb[] => [
+export const getMonitorPageBreadcrumb = (name: string, search?: string): ChromeBreadcrumb[] => [
makeOverviewBreadcrumb(search),
{ text: name },
];
diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx
index b979cbf2456bd..f529c9cd9d53f 100644
--- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx
+++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx
@@ -6,18 +6,17 @@
import React, { useState, useEffect, useContext } from 'react';
import { uniqueId, startsWith } from 'lodash';
-import { npStart } from 'ui/new_platform';
import { EuiCallOut } from '@elastic/eui';
import styled from 'styled-components';
import { FormattedMessage } from '@kbn/i18n/react';
-import { StaticIndexPattern } from 'ui/index_patterns';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
+import { AutocompleteProviderRegister, AutocompleteSuggestion } from 'src/plugins/data/public';
+import { StaticIndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns';
import { Typeahead } from './typeahead';
import { getIndexPattern } from '../../../lib/adapters/index_pattern';
import { UptimeSettingsContext } from '../../../contexts';
import { useUrlParams } from '../../../hooks';
import { toStaticIndexPattern } from '../../../lib/helper';
-import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public';
const Container = styled.div`
margin-bottom: 10px;
@@ -28,10 +27,7 @@ interface State {
isLoadingIndexPattern: boolean;
}
-const getAutocompleteProvider = (language: string) =>
- npStart.plugins.data.autocomplete.getProvider(language);
-
-function convertKueryToEsQuery(kuery: string, indexPattern: StaticIndexPattern) {
+function convertKueryToEsQuery(kuery: string, indexPattern: unknown) {
const ast = fromKueryExpression(kuery);
return toElasticsearchQuery(ast, indexPattern);
}
@@ -39,9 +35,10 @@ function convertKueryToEsQuery(kuery: string, indexPattern: StaticIndexPattern)
function getSuggestions(
query: string,
selectionStart: number,
- apmIndexPattern: StaticIndexPattern
+ apmIndexPattern: StaticIndexPattern,
+ autocomplete: Pick
) {
- const autocompleteProvider = getAutocompleteProvider('kuery');
+ const autocompleteProvider = autocomplete.getProvider('kuery');
if (!autocompleteProvider) {
return [];
}
@@ -62,7 +59,11 @@ function getSuggestions(
return suggestions;
}
-export function KueryBar() {
+interface Props {
+ autocomplete: Pick;
+}
+
+export function KueryBar({ autocomplete }: Props) {
const [state, setState] = useState({
suggestions: [],
isLoadingIndexPattern: true,
@@ -94,7 +95,12 @@ export function KueryBar() {
currentRequestCheck = currentRequest;
try {
- let suggestions = await getSuggestions(inputValue, selectionStart, indexPattern);
+ let suggestions = await getSuggestions(
+ inputValue,
+ selectionStart,
+ indexPattern,
+ autocomplete
+ );
suggestions = suggestions
.filter((suggestion: AutocompleteSuggestion) => !startsWith(suggestion.text, 'span.'))
.slice(0, 15);
diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/capabilities_adapter.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/capabilities_adapter.ts
index 135648d7b6fa3..e3faf34b7696d 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/capabilities_adapter.ts
+++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/capabilities_adapter.ts
@@ -4,16 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { capabilities as uiCapabilities } from 'ui/capabilities';
-
interface IntegratedAppsAvailability {
[key: string]: boolean;
}
export const getIntegratedAppAvailability = (
+ capabilities: any,
integratedApps: string[]
): IntegratedAppsAvailability => {
- const capabilities = uiCapabilities.get();
return integratedApps.reduce((supportedSolutions: IntegratedAppsAvailability, solutionName) => {
supportedSolutions[solutionName] =
capabilities[solutionName] && capabilities[solutionName].show === true;
diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts
deleted file mode 100644
index dee8a957e54da..0000000000000
--- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/kibana_framework_adapter.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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 ReactDOM from 'react-dom';
-import { unmountComponentAtNode } from 'react-dom';
-import chrome from 'ui/chrome';
-import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
-import { PLUGIN, INTEGRATED_SOLUTIONS } from '../../../../common/constants';
-import { BootstrapUptimeApp, UMFrameworkAdapter } from '../../lib';
-import { CreateGraphQLClient } from './framework_adapter_types';
-import { renderUptimeKibanaGlobalHelp } from './kibana_global_help';
-import { getTelemetryMonitorPageLogger, getTelemetryOverviewPageLogger } from '../telemetry';
-import { getIntegratedAppAvailability } from './capabilities_adapter';
-
-export class UMKibanaFrameworkAdapter implements UMFrameworkAdapter {
- private uiRoutes: any;
- private xsrfHeader: string;
- private uriPath: string;
-
- constructor(uiRoutes: any) {
- this.uiRoutes = uiRoutes;
- this.xsrfHeader = chrome.getXsrfToken();
- this.uriPath = `${chrome.getBasePath()}/api/uptime/graphql`;
- }
-
- /**
- * This function will acquire all the existing data from Kibana
- * services and persisted state expected by the plugin's props
- * interface. It then renders the plugin.
- */
- public render = (
- renderComponent: BootstrapUptimeApp,
- createGraphQLClient: CreateGraphQLClient
- ) => {
- const route = {
- controllerAs: 'uptime',
- // @ts-ignore angular
- controller: ($scope, $route, config, $location, $window) => {
- const graphQLClient = createGraphQLClient(this.uriPath, this.xsrfHeader);
- $scope.$$postDigest(() => {
- const elem = document.getElementById('uptimeReactRoot');
-
- // set up route with current base path
- const basePath = chrome.getBasePath();
- const routerBasename = basePath.endsWith('/')
- ? `${basePath}/${PLUGIN.ROUTER_BASE_NAME}`
- : basePath + PLUGIN.ROUTER_BASE_NAME;
-
- /**
- * TODO: this is a redirect hack to deal with a problem that largely
- * in testing but rarely occurs in the real world, where the specified
- * URL contains `.../app/uptime{SOME_URL_PARAM_TEXT}#` instead of
- * a path like `.../app/uptime#{SOME_URL_PARAM_TEXT}`.
- *
- * This redirect will almost never be triggered in practice, but it makes more
- * sense to include it here rather than altering the existing testing
- * infrastructure underlying the rest of Kibana.
- *
- * We welcome a more permanent solution that will result in the deletion of the
- * block below.
- */
- if ($location.absUrl().indexOf(PLUGIN.ROUTER_BASE_NAME) === -1) {
- $window.location.replace(routerBasename);
- }
-
- // determine whether dark mode is enabled
- const darkMode = config.get('theme:darkMode', false) || false;
-
- /**
- * We pass this global help setup as a prop to the app, because for
- * localization it's necessary to have the provider mounted before
- * we can render our help links, as they rely on i18n.
- */
- const renderGlobalHelpControls = () =>
- // render Uptime feedback link in global help menu
- chrome.helpExtension.set((element: HTMLDivElement) => {
- ReactDOM.render(
- renderUptimeKibanaGlobalHelp(ELASTIC_WEBSITE_URL, DOC_LINK_VERSION),
- element
- );
- return () => ReactDOM.unmountComponentAtNode(element);
- });
-
- /**
- * These values will let Uptime know if the integrated solutions
- * are available. If any/all of them are unavaialble, we should not show
- * links/integrations to those apps.
- */
- const {
- apm: isApmAvailable,
- infrastructure: isInfraAvailable,
- logs: isLogsAvailable,
- } = getIntegratedAppAvailability(INTEGRATED_SOLUTIONS);
-
- ReactDOM.render(
- renderComponent({
- basePath,
- client: graphQLClient,
- darkMode,
- isApmAvailable,
- isInfraAvailable,
- isLogsAvailable,
- logMonitorPageLoad: getTelemetryMonitorPageLogger(this.xsrfHeader, basePath),
- logOverviewPageLoad: getTelemetryOverviewPageLogger(this.xsrfHeader, basePath),
- renderGlobalHelpControls,
- routerBasename,
- setBadge: chrome.badge.set,
- setBreadcrumbs: chrome.breadcrumbs.set,
- }),
- elem
- );
- this.manageAngularLifecycle($scope, $route, elem);
- });
- },
- template:
- ' ',
- };
- this.uiRoutes.enable();
- // TODO: hack to refer all routes to same endpoint, use a more proper way of achieving this
- this.uiRoutes.otherwise(route);
- };
-
- // @ts-ignore angular params
- private manageAngularLifecycle = ($scope, $route, elem) => {
- const lastRoute = $route.current;
- const deregister = $scope.$on('$locationChangeSuccess', () => {
- const currentRoute = $route.current;
- if (lastRoute.$$route && lastRoute.$$route.template === currentRoute.$$route.template) {
- $route.current = lastRoute;
- }
- });
- $scope.$on('$destroy', () => {
- deregister();
- unmountComponentAtNode(elem);
- });
- };
-}
diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx
new file mode 100644
index 0000000000000..e35d8e93ca4a7
--- /dev/null
+++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 { ChromeBreadcrumb, CoreStart } from 'src/core/public';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { get } from 'lodash';
+import { AutocompleteProviderRegister } from 'src/plugins/data/public';
+import { CreateGraphQLClient } from './framework_adapter_types';
+import { UptimeApp, UptimeAppProps } from '../../../uptime_app';
+import { getIntegratedAppAvailability } from './capabilities_adapter';
+import { INTEGRATED_SOLUTIONS, PLUGIN } from '../../../../common/constants';
+import { getTelemetryMonitorPageLogger, getTelemetryOverviewPageLogger } from '../telemetry';
+import { renderUptimeKibanaGlobalHelp } from './kibana_global_help';
+import { UMFrameworkAdapter, BootstrapUptimeApp } from '../../lib';
+import { createApolloClient } from './apollo_client_adapter';
+
+export const getKibanaFrameworkAdapter = (
+ core: CoreStart,
+ autocomplete: Pick
+): UMFrameworkAdapter => {
+ const {
+ application: { capabilities },
+ chrome: { setBadge, setHelpExtension },
+ docLinks: { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL },
+ http: { basePath },
+ i18n,
+ } = core;
+ let breadcrumbs: ChromeBreadcrumb[] = [];
+ core.chrome.getBreadcrumbs$().subscribe((nextBreadcrumbs?: ChromeBreadcrumb[]) => {
+ breadcrumbs = nextBreadcrumbs || [];
+ });
+ const { apm, infrastructure, logs } = getIntegratedAppAvailability(
+ capabilities,
+ INTEGRATED_SOLUTIONS
+ );
+ const canSave = get(capabilities, 'uptime.save', false);
+ const props: UptimeAppProps = {
+ basePath: basePath.get(),
+ canSave,
+ client: createApolloClient(`${basePath.get()}/api/uptime/graphql`, 'true'),
+ darkMode: core.uiSettings.get('theme:darkMode'),
+ autocomplete,
+ i18n,
+ isApmAvailable: apm,
+ isInfraAvailable: infrastructure,
+ isLogsAvailable: logs,
+ kibanaBreadcrumbs: breadcrumbs,
+ logMonitorPageLoad: getTelemetryMonitorPageLogger('true', basePath.get()),
+ logOverviewPageLoad: getTelemetryOverviewPageLogger('true', basePath.get()),
+ renderGlobalHelpControls: () =>
+ setHelpExtension((element: HTMLElement) => {
+ ReactDOM.render(
+ renderUptimeKibanaGlobalHelp(ELASTIC_WEBSITE_URL, DOC_LINK_VERSION),
+ element
+ );
+ return () => ReactDOM.unmountComponentAtNode(element);
+ }),
+ routerBasename: basePath.prepend(PLUGIN.ROUTER_BASE_NAME),
+ setBadge,
+ setBreadcrumbs: core.chrome.setBreadcrumbs,
+ };
+
+ return {
+ // TODO: these parameters satisfy the interface but are no longer needed
+ render: async (createComponent: BootstrapUptimeApp, cgc: CreateGraphQLClient) => {
+ const node = await document.getElementById('react-uptime-root');
+ if (node) {
+ ReactDOM.render( , node);
+ }
+ },
+ };
+};
diff --git a/x-pack/legacy/plugins/uptime/public/lib/compose/kibana_compose.ts b/x-pack/legacy/plugins/uptime/public/lib/compose/kibana_compose.ts
deleted file mode 100644
index 759c161d32cdd..0000000000000
--- a/x-pack/legacy/plugins/uptime/public/lib/compose/kibana_compose.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * 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 uiRoutes from 'ui/routes';
-import { UMKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
-import { UMFrontendLibs } from '../lib';
-
-export function compose(): UMFrontendLibs {
- const libs: UMFrontendLibs = {
- framework: new UMKibanaFrameworkAdapter(uiRoutes),
- };
-
- return libs;
-}
diff --git a/x-pack/legacy/plugins/uptime/public/lib/lib.ts b/x-pack/legacy/plugins/uptime/public/lib/lib.ts
index 65a7f18711fd0..0a744bff815c7 100644
--- a/x-pack/legacy/plugins/uptime/public/lib/lib.ts
+++ b/x-pack/legacy/plugins/uptime/public/lib/lib.ts
@@ -7,8 +7,8 @@
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import React from 'react';
+import { ChromeBreadcrumb } from 'src/core/public';
import { UMBadge } from '../badge';
-import { UMBreadcrumb } from '../breadcrumbs';
import { UptimeAppProps } from '../uptime_app';
import { CreateGraphQLClient } from './adapters/framework/framework_adapter_types';
@@ -16,7 +16,7 @@ export interface UMFrontendLibs {
framework: UMFrameworkAdapter;
}
-export type UMUpdateBreadcrumbs = (breadcrumbs: UMBreadcrumb[]) => void;
+export type UMUpdateBreadcrumbs = (breadcrumbs: ChromeBreadcrumb[]) => void;
export type UMUpdateBadge = (badge: UMBadge) => void;
diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx
index 3bd09bcba143c..7c1b81b0e15c8 100644
--- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx
+++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx
@@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import React, { Fragment, useContext, useEffect, useState } from 'react';
import styled from 'styled-components';
+import { AutocompleteProviderRegister } from 'src/plugins/data/public';
import { getOverviewPageBreadcrumbs } from '../breadcrumbs';
import {
EmptyState,
@@ -29,12 +30,13 @@ import { combineFiltersAndUserSearch, stringifyKueries, toStaticIndexPattern } f
interface OverviewPageProps {
basePath: string;
- logOverviewPageLoad: () => void;
+ autocomplete: Pick;
history: any;
location: {
pathname: string;
search: string;
};
+ logOverviewPageLoad: () => void;
setBreadcrumbs: UMUpdateBreadcrumbs;
}
@@ -54,7 +56,12 @@ const EuiFlexItemStyled = styled(EuiFlexItem)`
}
`;
-export const OverviewPage = ({ basePath, logOverviewPageLoad, setBreadcrumbs }: Props) => {
+export const OverviewPage = ({
+ basePath,
+ autocomplete,
+ logOverviewPageLoad,
+ setBreadcrumbs,
+}: Props) => {
const { colors, setHeadingText } = useContext(UptimeSettingsContext);
const [getUrlParams, updateUrl] = useUrlParams();
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams();
@@ -129,7 +136,7 @@ export const OverviewPage = ({ basePath, logOverviewPageLoad, setBreadcrumbs }:
-
+
;
+ i18n: I18nStart;
isApmAvailable: boolean;
isInfraAvailable: boolean;
isLogsAvailable: boolean;
+ kibanaBreadcrumbs: ChromeBreadcrumb[];
logMonitorPageLoad: () => void;
logOverviewPageLoad: () => void;
routerBasename: string;
@@ -50,8 +54,11 @@ export interface UptimeAppProps {
const Application = (props: UptimeAppProps) => {
const {
basePath,
+ canSave,
client,
darkMode,
+ autocomplete,
+ i18n: i18nCore,
isApmAvailable,
isInfraAvailable,
isLogsAvailable,
@@ -89,7 +96,7 @@ const Application = (props: UptimeAppProps) => {
useEffect(() => {
renderGlobalHelpControls();
setBadge(
- !capabilities.get().uptime.save
+ !canSave
? {
text: i18n.translate('xpack.uptime.badge.readOnly.text', {
defaultMessage: 'Read only',
@@ -140,7 +147,7 @@ const Application = (props: UptimeAppProps) => {
};
return (
-
+
{
render={routerProps => (
{
/>
-
+
);
};
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index dc42138352783..676cf78de9ff9 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5316,11 +5316,8 @@
"xpack.infra.header.logsTitle": "ログ",
"xpack.infra.homePage.settingsTabTitle": "設定",
"xpack.infra.kibanaMetrics.cloudIdMissingErrorMessage": "{metricId} のモデルには cloudId が必要ですが、{nodeId} に cloudId が指定されていません。",
- "xpack.infra.logs.analysis.logRateSectionAnomalySeriesName": "異常",
- "xpack.infra.logs.analysis.logRateSectionAreaSeriesName": "期待値",
"xpack.infra.logs.analysis.logRateSectionLineSeriesName": "15 分ごとのログエントリー (平均)",
"xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel": "ログレートの結果を読み込み中",
- "xpack.infra.logs.analysis.logRateSectionModelBoundsCheckboxLabel": "モデルバウンドを表示",
"xpack.infra.logs.analysis.logRateSectionNoDataBody": "時間範囲を調整する必要があるかもしれません。",
"xpack.infra.logs.analysis.logRateSectionNoDataTitle": "表示するデータがありません。",
"xpack.infra.logs.analysis.logRateSectionTitle": "ログレート",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 3f647a16d4cb1..c2054497c96f6 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -5319,11 +5319,8 @@
"xpack.infra.header.logsTitle": "Logs",
"xpack.infra.homePage.settingsTabTitle": "设置",
"xpack.infra.kibanaMetrics.cloudIdMissingErrorMessage": "{metricId} 的模型需要云 ID,但没有为 {nodeId} 提供。",
- "xpack.infra.logs.analysis.logRateSectionAnomalySeriesName": "异常",
- "xpack.infra.logs.analysis.logRateSectionAreaSeriesName": "预期",
"xpack.infra.logs.analysis.logRateSectionLineSeriesName": "每 15 分钟日志条目数(平均值)",
"xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel": "正在加载日志速率结果",
- "xpack.infra.logs.analysis.logRateSectionModelBoundsCheckboxLabel": "显示模型边界",
"xpack.infra.logs.analysis.logRateSectionNoDataBody": "您可能想调整时间范围。",
"xpack.infra.logs.analysis.logRateSectionNoDataTitle": "没有可显示的数据。",
"xpack.infra.logs.analysis.logRateSectionTitle": "日志速率",
diff --git a/x-pack/test/api_integration/apis/infra/log_analysis.ts b/x-pack/test/api_integration/apis/infra/log_analysis.ts
index bd09cdf6ff56e..fe7d55649d1d6 100644
--- a/x-pack/test/api_integration/apis/infra/log_analysis.ts
+++ b/x-pack/test/api_integration/apis/infra/log_analysis.ts
@@ -20,8 +20,8 @@ import {
} from '../../../../legacy/plugins/infra/common/runtime_types';
import { FtrProviderContext } from '../../ftr_provider_context';
-const TIME_BEFORE_START = 1564315100000;
-const TIME_AFTER_END = 1565040700000;
+const TIME_BEFORE_START = 1569934800000;
+const TIME_AFTER_END = 1570016700000;
const COMMON_HEADERS = {
'kbn-xsrf': 'some-xsrf-token',
};
@@ -32,8 +32,8 @@ export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
describe('log analysis apis', () => {
- before(() => esArchiver.load('infra/8.0.0/ml_anomalies_log_rate'));
- after(() => esArchiver.unload('infra/8.0.0/ml_anomalies_log_rate'));
+ before(() => esArchiver.load('infra/8.0.0/ml_anomalies_partitioned_log_rate'));
+ after(() => esArchiver.unload('infra/8.0.0/ml_anomalies_partitioned_log_rate'));
describe('log rate results', () => {
describe('with the default source', () => {
@@ -62,11 +62,12 @@ export default ({ getService }: FtrProviderContext) => {
getLogEntryRateSuccessReponsePayloadRT.decode(body),
fold(throwErrors(createPlainError), identity)
);
-
expect(logEntryRateBuckets.data.bucketDuration).to.be(15 * 60 * 1000);
expect(logEntryRateBuckets.data.histogramBuckets).to.not.be.empty();
expect(
- logEntryRateBuckets.data.histogramBuckets.some(bucket => bucket.anomalies.length > 0)
+ logEntryRateBuckets.data.histogramBuckets.some(bucket => {
+ return bucket.dataSets.some(dataSet => dataSet.anomalies.length > 0);
+ })
).to.be(true);
});
diff --git a/x-pack/test/api_integration/apis/lens/field_stats.ts b/x-pack/test/api_integration/apis/lens/field_stats.ts
index 9eba9392c4f7f..b2bb791e2da7f 100644
--- a/x-pack/test/api_integration/apis/lens/field_stats.ts
+++ b/x-pack/test/api_integration/apis/lens/field_stats.ts
@@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext) => {
.post('/api/lens/index_stats/logstash/field')
.set(COMMON_HEADERS)
.send({
- query: { match_all: {} },
+ dslQuery: { match_all: {} },
fromDate: TEST_START_TIME,
toDate: TEST_END_TIME,
timeFieldName: '@timestamp',
@@ -52,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => {
.post('/api/lens/index_stats/logstash-2015.09.22/field')
.set(COMMON_HEADERS)
.send({
- query: { match_all: {} },
+ dslQuery: { match_all: {} },
fromDate: TEST_START_TIME,
toDate: TEST_END_TIME,
timeFieldName: '@timestamp',
@@ -163,7 +163,7 @@ export default ({ getService }: FtrProviderContext) => {
.post('/api/lens/index_stats/logstash-2015.09.22/field')
.set(COMMON_HEADERS)
.send({
- query: { match_all: {} },
+ dslQuery: { match_all: {} },
fromDate: TEST_START_TIME,
toDate: TEST_END_TIME,
timeFieldName: '@timestamp',
@@ -200,7 +200,7 @@ export default ({ getService }: FtrProviderContext) => {
.post('/api/lens/index_stats/logstash-2015.09.22/field')
.set(COMMON_HEADERS)
.send({
- query: { match_all: {} },
+ dslQuery: { match_all: {} },
fromDate: TEST_START_TIME,
toDate: TEST_END_TIME,
timeFieldName: '@timestamp',
@@ -261,6 +261,29 @@ export default ({ getService }: FtrProviderContext) => {
},
});
});
+
+ it('should apply filters and queries', async () => {
+ const { body } = await supertest
+ .post('/api/lens/index_stats/logstash-2015.09.22/field')
+ .set(COMMON_HEADERS)
+ .send({
+ dslQuery: {
+ bool: {
+ filter: [{ match: { 'geo.src': 'US' } }],
+ },
+ },
+ fromDate: TEST_START_TIME,
+ toDate: TEST_END_TIME,
+ timeFieldName: '@timestamp',
+ field: {
+ name: 'bytes',
+ type: 'number',
+ },
+ })
+ .expect(200);
+
+ expect(body.totalDocuments).to.eql(425);
+ });
});
});
};
diff --git a/x-pack/test/functional/es_archives/infra/8.0.0/ml_anomalies_partitioned_log_rate/data.json.gz b/x-pack/test/functional/es_archives/infra/8.0.0/ml_anomalies_partitioned_log_rate/data.json.gz
new file mode 100644
index 0000000000000..8d15ff8ccb022
Binary files /dev/null and b/x-pack/test/functional/es_archives/infra/8.0.0/ml_anomalies_partitioned_log_rate/data.json.gz differ
diff --git a/x-pack/test/functional/es_archives/infra/8.0.0/ml_anomalies_partitioned_log_rate/mappings.json b/x-pack/test/functional/es_archives/infra/8.0.0/ml_anomalies_partitioned_log_rate/mappings.json
new file mode 100644
index 0000000000000..69ffc922ede7d
--- /dev/null
+++ b/x-pack/test/functional/es_archives/infra/8.0.0/ml_anomalies_partitioned_log_rate/mappings.json
@@ -0,0 +1,513 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ ".ml-anomalies-.write-kibana-logs-ui-default-default-log-entry-rate": {
+ },
+ ".ml-anomalies-kibana-logs-ui-default-default-log-entry-rate": {
+ "filter": {
+ "term": {
+ "job_id": {
+ "boost": 1,
+ "value": "kibana-logs-ui-default-default-log-entry-rate"
+ }
+ }
+ }
+ }
+ },
+ "index": ".ml-anomalies-shared",
+ "mappings": {
+ "_meta": {
+ "version": "8.0.0"
+ },
+ "dynamic_templates": [
+ {
+ "strings_as_keywords": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match": "*"
+ }
+ }
+ ],
+ "properties": {
+ "actual": {
+ "type": "double"
+ },
+ "all_field_values": {
+ "analyzer": "whitespace",
+ "type": "text"
+ },
+ "anomaly_score": {
+ "type": "double"
+ },
+ "average_bucket_processing_time_ms": {
+ "type": "double"
+ },
+ "bucket_allocation_failures_count": {
+ "type": "long"
+ },
+ "bucket_count": {
+ "type": "long"
+ },
+ "bucket_influencers": {
+ "properties": {
+ "anomaly_score": {
+ "type": "double"
+ },
+ "bucket_span": {
+ "type": "long"
+ },
+ "influencer_field_name": {
+ "type": "keyword"
+ },
+ "initial_anomaly_score": {
+ "type": "double"
+ },
+ "is_interim": {
+ "type": "boolean"
+ },
+ "job_id": {
+ "type": "keyword"
+ },
+ "probability": {
+ "type": "double"
+ },
+ "raw_anomaly_score": {
+ "type": "double"
+ },
+ "result_type": {
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ }
+ },
+ "type": "nested"
+ },
+ "bucket_span": {
+ "type": "long"
+ },
+ "by_field_name": {
+ "type": "keyword"
+ },
+ "by_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "category_id": {
+ "type": "long"
+ },
+ "causes": {
+ "properties": {
+ "actual": {
+ "type": "double"
+ },
+ "by_field_name": {
+ "type": "keyword"
+ },
+ "by_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "correlated_by_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "field_name": {
+ "type": "keyword"
+ },
+ "function": {
+ "type": "keyword"
+ },
+ "function_description": {
+ "type": "keyword"
+ },
+ "over_field_name": {
+ "type": "keyword"
+ },
+ "over_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "partition_field_name": {
+ "type": "keyword"
+ },
+ "partition_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "probability": {
+ "type": "double"
+ },
+ "typical": {
+ "type": "double"
+ }
+ },
+ "type": "nested"
+ },
+ "description": {
+ "type": "text"
+ },
+ "detector_index": {
+ "type": "integer"
+ },
+ "earliest_record_timestamp": {
+ "type": "date"
+ },
+ "empty_bucket_count": {
+ "type": "long"
+ },
+ "event": {
+ "properties": {
+ "dataset": {
+ "type": "keyword"
+ }
+ }
+ },
+ "event_count": {
+ "type": "long"
+ },
+ "examples": {
+ "type": "text"
+ },
+ "exponential_average_bucket_processing_time_ms": {
+ "type": "double"
+ },
+ "exponential_average_calculation_context": {
+ "properties": {
+ "incremental_metric_value_ms": {
+ "type": "double"
+ },
+ "latest_timestamp": {
+ "type": "date"
+ },
+ "previous_exponential_average_ms": {
+ "type": "double"
+ }
+ }
+ },
+ "field_name": {
+ "type": "keyword"
+ },
+ "forecast_create_timestamp": {
+ "type": "date"
+ },
+ "forecast_end_timestamp": {
+ "type": "date"
+ },
+ "forecast_expiry_timestamp": {
+ "type": "date"
+ },
+ "forecast_id": {
+ "type": "keyword"
+ },
+ "forecast_lower": {
+ "type": "double"
+ },
+ "forecast_memory_bytes": {
+ "type": "long"
+ },
+ "forecast_messages": {
+ "type": "keyword"
+ },
+ "forecast_prediction": {
+ "type": "double"
+ },
+ "forecast_progress": {
+ "type": "double"
+ },
+ "forecast_start_timestamp": {
+ "type": "date"
+ },
+ "forecast_status": {
+ "type": "keyword"
+ },
+ "forecast_upper": {
+ "type": "double"
+ },
+ "function": {
+ "type": "keyword"
+ },
+ "function_description": {
+ "type": "keyword"
+ },
+ "influencer_field_name": {
+ "type": "keyword"
+ },
+ "influencer_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "influencer_score": {
+ "type": "double"
+ },
+ "influencers": {
+ "properties": {
+ "influencer_field_name": {
+ "type": "keyword"
+ },
+ "influencer_field_values": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ }
+ },
+ "type": "nested"
+ },
+ "initial_anomaly_score": {
+ "type": "double"
+ },
+ "initial_influencer_score": {
+ "type": "double"
+ },
+ "initial_record_score": {
+ "type": "double"
+ },
+ "input_bytes": {
+ "type": "long"
+ },
+ "input_field_count": {
+ "type": "long"
+ },
+ "input_record_count": {
+ "type": "long"
+ },
+ "invalid_date_count": {
+ "type": "long"
+ },
+ "is_interim": {
+ "type": "boolean"
+ },
+ "job_id": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "last_data_time": {
+ "type": "date"
+ },
+ "latest_empty_bucket_timestamp": {
+ "type": "date"
+ },
+ "latest_record_time_stamp": {
+ "type": "date"
+ },
+ "latest_record_timestamp": {
+ "type": "date"
+ },
+ "latest_result_time_stamp": {
+ "type": "date"
+ },
+ "latest_sparse_bucket_timestamp": {
+ "type": "date"
+ },
+ "log_time": {
+ "type": "date"
+ },
+ "max_matching_length": {
+ "type": "long"
+ },
+ "maximum_bucket_processing_time_ms": {
+ "type": "double"
+ },
+ "memory_status": {
+ "type": "keyword"
+ },
+ "min_version": {
+ "type": "keyword"
+ },
+ "minimum_bucket_processing_time_ms": {
+ "type": "double"
+ },
+ "missing_field_count": {
+ "type": "long"
+ },
+ "model_bytes": {
+ "type": "long"
+ },
+ "model_bytes_exceeded": {
+ "type": "keyword"
+ },
+ "model_bytes_memory_limit": {
+ "type": "keyword"
+ },
+ "model_feature": {
+ "type": "keyword"
+ },
+ "model_lower": {
+ "type": "double"
+ },
+ "model_median": {
+ "type": "double"
+ },
+ "model_size_stats": {
+ "properties": {
+ "bucket_allocation_failures_count": {
+ "type": "long"
+ },
+ "job_id": {
+ "type": "keyword"
+ },
+ "log_time": {
+ "type": "date"
+ },
+ "memory_status": {
+ "type": "keyword"
+ },
+ "model_bytes": {
+ "type": "long"
+ },
+ "model_bytes_exceeded": {
+ "type": "keyword"
+ },
+ "model_bytes_memory_limit": {
+ "type": "keyword"
+ },
+ "result_type": {
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "total_by_field_count": {
+ "type": "long"
+ },
+ "total_over_field_count": {
+ "type": "long"
+ },
+ "total_partition_field_count": {
+ "type": "long"
+ }
+ }
+ },
+ "model_upper": {
+ "type": "double"
+ },
+ "multi_bucket_impact": {
+ "type": "double"
+ },
+ "out_of_order_timestamp_count": {
+ "type": "long"
+ },
+ "over_field_name": {
+ "type": "keyword"
+ },
+ "over_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "partition_field_name": {
+ "type": "keyword"
+ },
+ "partition_field_value": {
+ "copy_to": [
+ "all_field_values"
+ ],
+ "type": "keyword"
+ },
+ "probability": {
+ "type": "double"
+ },
+ "processed_field_count": {
+ "type": "long"
+ },
+ "processed_record_count": {
+ "type": "long"
+ },
+ "processing_time_ms": {
+ "type": "long"
+ },
+ "quantiles": {
+ "enabled": false,
+ "type": "object"
+ },
+ "raw_anomaly_score": {
+ "type": "double"
+ },
+ "record_score": {
+ "type": "double"
+ },
+ "regex": {
+ "type": "keyword"
+ },
+ "result_type": {
+ "type": "keyword"
+ },
+ "retain": {
+ "type": "boolean"
+ },
+ "scheduled_events": {
+ "type": "keyword"
+ },
+ "search_count": {
+ "type": "long"
+ },
+ "snapshot_doc_count": {
+ "type": "integer"
+ },
+ "snapshot_id": {
+ "type": "keyword"
+ },
+ "sparse_bucket_count": {
+ "type": "long"
+ },
+ "terms": {
+ "type": "text"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "total_by_field_count": {
+ "type": "long"
+ },
+ "total_over_field_count": {
+ "type": "long"
+ },
+ "total_partition_field_count": {
+ "type": "long"
+ },
+ "total_search_time_ms": {
+ "type": "double"
+ },
+ "typical": {
+ "type": "double"
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "auto_expand_replicas": "0-1",
+ "number_of_replicas": "0",
+ "number_of_shards": "1",
+ "query": {
+ "default_field": "all_field_values"
+ },
+ "translog": {
+ "durability": "async"
+ },
+ "unassigned": {
+ "node_left": {
+ "delayed_timeout": "1m"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file