From b07db9dec824469d3cc10efa941cf47534341bef Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Mon, 17 Aug 2020 09:41:08 -0600
Subject: [PATCH 001/177] [Maps] convert kibana_services to TS (#74901)
* [Maps] convert kibana_services to TS
* tslint
* fix jest test
* tslint
Co-authored-by: Elastic Machine
---
src/plugins/maps_legacy/public/index.ts | 4 +
x-pack/plugins/file_upload/public/index.ts | 1 +
x-pack/plugins/file_upload/public/plugin.ts | 26 ++-
.../ems_boundaries_layer_wizard.tsx | 2 +-
.../ems_base_map_layer_wizard.tsx | 2 +-
.../es_geo_grid_source.test.ts | 95 +++++----
.../classes/sources/es_source/es_source.d.ts | 13 ++
.../classes/sources/es_source/es_source.js | 24 ++-
.../__snapshots__/view.test.js.snap | 2 +-
.../layer_panel/view.test.js | 11 +
.../plugins/maps/public/kibana_services.d.ts | 92 ---------
x-pack/plugins/maps/public/kibana_services.js | 192 ------------------
x-pack/plugins/maps/public/kibana_services.ts | 83 ++++++++
.../maps/public/lazy_load_bundle/index.ts | 4 +-
.../maps/public/maps_vis_type_alias.js | 6 +-
x-pack/plugins/maps/public/meta.ts | 4 +-
x-pack/plugins/maps/public/plugin.ts | 125 ++++--------
.../routes/maps_app/top_nav_config.tsx | 4 +-
18 files changed, 236 insertions(+), 454 deletions(-)
delete mode 100644 x-pack/plugins/maps/public/kibana_services.d.ts
delete mode 100644 x-pack/plugins/maps/public/kibana_services.js
create mode 100644 x-pack/plugins/maps/public/kibana_services.ts
diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts
index 6b9c7d1c52db9..14612ab1b2a57 100644
--- a/src/plugins/maps_legacy/public/index.ts
+++ b/src/plugins/maps_legacy/public/index.ts
@@ -48,6 +48,10 @@ export interface MapsLegacyConfigType {
includeElasticMapsService: boolean;
proxyElasticMapsServiceInMaps: boolean;
tilemap: any;
+ emsFontLibraryUrl: string;
+ emsFileApiUrl: string;
+ emsTileApiUrl: string;
+ emsLandingPageUrl: string;
}
export function plugin(initializerContext: PluginInitializerContext) {
diff --git a/x-pack/plugins/file_upload/public/index.ts b/x-pack/plugins/file_upload/public/index.ts
index 1e39fb4dc8596..71853488eba89 100644
--- a/x-pack/plugins/file_upload/public/index.ts
+++ b/x-pack/plugins/file_upload/public/index.ts
@@ -10,4 +10,5 @@ export function plugin() {
return new FileUploadPlugin();
}
+export { StartContract } from './plugin';
export { FileUploadComponentProps } from './get_file_upload_component';
diff --git a/x-pack/plugins/file_upload/public/plugin.ts b/x-pack/plugins/file_upload/public/plugin.ts
index ff74be659aeca..0431e660abe88 100644
--- a/x-pack/plugins/file_upload/public/plugin.ts
+++ b/x-pack/plugins/file_upload/public/plugin.ts
@@ -4,33 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React from 'react';
import { CoreSetup, CoreStart, Plugin } from 'kibana/server';
-import { getFileUploadComponent } from './get_file_upload_component';
+import { FileUploadComponentProps, getFileUploadComponent } from './get_file_upload_component';
// @ts-ignore
import { setupInitServicesAndConstants, startInitServicesAndConstants } from './kibana_services';
import { IDataPluginServices } from '../../../../src/plugins/data/public';
-/**
- * These are the interfaces with your public contracts. You should export these
- * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces.
- * @public
- */
-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface FileUploadPluginSetupDependencies {}
-export interface FileUploadPluginStartDependencies {
+export interface SetupDependencies {}
+export interface StartDependencies {
data: IDataPluginServices;
}
-export type FileUploadPluginSetup = ReturnType;
-export type FileUploadPluginStart = ReturnType;
+export type SetupContract = ReturnType;
+export interface StartContract {
+ getFileUploadComponent: () => Promise>;
+}
-export class FileUploadPlugin implements Plugin {
- public setup(core: CoreSetup, plugins: FileUploadPluginSetupDependencies) {
+export class FileUploadPlugin
+ implements Plugin {
+ public setup(core: CoreSetup, plugins: SetupDependencies) {
setupInitServicesAndConstants(core);
}
- public start(core: CoreStart, plugins: FileUploadPluginStartDependencies) {
+ public start(core: CoreStart, plugins: StartDependencies) {
startInitServicesAndConstants(core, plugins);
return {
getFileUploadComponent,
diff --git a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx
index c53a7a4facb0c..8d4d57e524276 100644
--- a/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/ems_file_source/ems_boundaries_layer_wizard.tsx
@@ -17,7 +17,7 @@ import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants';
export const emsBoundariesLayerWizardConfig: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.REFERENCE],
- checkVisibility: () => {
+ checkVisibility: async () => {
return getIsEmsEnabled();
},
description: i18n.translate('xpack.maps.source.emsFileDescription', {
diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx
index 5cc2a1225bbd7..315759a2eba29 100644
--- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_base_map_layer_wizard.tsx
@@ -18,7 +18,7 @@ import { LAYER_WIZARD_CATEGORY } from '../../../../common/constants';
export const emsBaseMapLayerWizardConfig: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.REFERENCE],
- checkVisibility: () => {
+ checkVisibility: async () => {
return getIsEmsEnabled();
},
description: i18n.translate('xpack.maps.source.emsTileDescription', {
diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
index 2707b2ac23e58..37193e148bdc7 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts
@@ -8,11 +8,7 @@ import { MapExtent, MapFilters } from '../../../../common/descriptor_types';
jest.mock('../../../kibana_services');
jest.mock('ui/new_platform');
-import {
- getIndexPatternService,
- getSearchService,
- fetchSearchSourceAndRecordWithInspector,
-} from '../../../kibana_services';
+import { getIndexPatternService, getSearchService } from '../../../kibana_services';
import { ESGeoGridSource } from './es_geo_grid_source';
import {
ES_GEO_FIELD_TYPE,
@@ -54,6 +50,51 @@ describe('ESGeoGridSource', () => {
},
{}
);
+ geogridSource._runEsQuery = async (args: unknown) => {
+ return {
+ took: 71,
+ timed_out: false,
+ _shards: {
+ total: 1,
+ successful: 1,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 748 + 683,
+ max_score: null,
+ hits: [],
+ },
+ aggregations: {
+ gridSplit: {
+ buckets: [
+ {
+ key: '4/4/6',
+ doc_count: 748,
+ gridCentroid: {
+ location: {
+ lat: 35.64189018148127,
+ lon: -82.84314106196105,
+ },
+ count: 748,
+ },
+ },
+ {
+ key: '4/3/6',
+ doc_count: 683,
+ gridCentroid: {
+ location: {
+ lat: 35.24134021274211,
+ lon: -98.45945192042787,
+ },
+ count: 683,
+ },
+ },
+ ],
+ },
+ },
+ };
+ };
describe('getGeoJsonWithMeta', () => {
let mockSearchSource: unknown;
@@ -71,50 +112,6 @@ describe('ESGeoGridSource', () => {
getIndexPatternService.mockReturnValue(mockIndexPatternService);
// @ts-expect-error
getSearchService.mockReturnValue(mockSearchService);
- // @ts-expect-error
- fetchSearchSourceAndRecordWithInspector.mockReturnValue({
- took: 71,
- timed_out: false,
- _shards: {
- total: 1,
- successful: 1,
- skipped: 0,
- failed: 0,
- },
- hits: {
- total: 748 + 683,
- max_score: null,
- hits: [],
- },
- aggregations: {
- gridSplit: {
- buckets: [
- {
- key: '4/4/6',
- doc_count: 748,
- gridCentroid: {
- location: {
- lat: 35.64189018148127,
- lon: -82.84314106196105,
- },
- count: 748,
- },
- },
- {
- key: '4/3/6',
- doc_count: 683,
- gridCentroid: {
- location: {
- lat: 35.24134021274211,
- lon: -98.45945192042787,
- },
- count: 683,
- },
- },
- ],
- },
- },
- });
});
const extent: MapExtent = {
diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts
index 1f2985ffcc27c..01fde589dcb84 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts
+++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.d.ts
@@ -52,4 +52,17 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource
registerCancelCallback: (requestToken: symbol, callback: () => void) => void,
searchFilters: VectorSourceRequestMeta
): Promise;
+ _runEsQuery: ({
+ requestId,
+ requestName,
+ requestDescription,
+ searchSource,
+ registerCancelCallback,
+ }: {
+ requestId: string;
+ requestName: string;
+ requestDescription: string;
+ searchSource: ISearchSource;
+ registerCancelCallback: () => void;
+ }) => Promise;
}
diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js
index c043e6d6994ab..866e3c76c2a3f 100644
--- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js
@@ -7,7 +7,6 @@
import { AbstractVectorSource } from '../vector_source';
import {
getAutocompleteService,
- fetchSearchSourceAndRecordWithInspector,
getIndexPatternService,
getTimeFilter,
getSearchService,
@@ -20,6 +19,7 @@ import uuid from 'uuid/v4';
import { copyPersistentState } from '../../../reducers/util';
import { DataRequestAbortError } from '../../util/data_request';
import { expandToTileBoundaries } from '../es_geo_grid_source/geo_tile_utils';
+import { search } from '../../../../../../../src/plugins/data/public';
export class AbstractESSource extends AbstractVectorSource {
constructor(descriptor, inspectorAdapters) {
@@ -84,16 +84,22 @@ export class AbstractESSource extends AbstractVectorSource {
const abortController = new AbortController();
registerCancelCallback(() => abortController.abort());
+ const inspectorRequest = this._inspectorAdapters.requests.start(requestName, {
+ id: requestId,
+ description: requestDescription,
+ });
+ let resp;
try {
- return await fetchSearchSourceAndRecordWithInspector({
- inspectorAdapters: this._inspectorAdapters,
- searchSource,
- requestName,
- requestId,
- requestDesc: requestDescription,
- abortSignal: abortController.signal,
+ inspectorRequest.stats(search.getRequestInspectorStats(searchSource));
+ searchSource.getSearchRequestBody().then((body) => {
+ inspectorRequest.json(body);
});
+ resp = await searchSource.fetch({ abortSignal: abortController.signal });
+ inspectorRequest
+ .stats(search.getResponseInspectorStats(resp, searchSource))
+ .ok({ json: resp });
} catch (error) {
+ inspectorRequest.error({ error });
if (error.name === 'AbortError') {
throw new DataRequestAbortError();
}
@@ -105,6 +111,8 @@ export class AbstractESSource extends AbstractVectorSource {
})
);
}
+
+ return resp;
}
async makeSearchSource(searchFilters, limit, initialSearchContext) {
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
index 2cf5287ae6594..b005e3ca6b17d 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap
@@ -5,7 +5,7 @@ exports[`LayerPanel is rendered 1`] = `
services={
Object {
"appName": "maps",
- "data": undefined,
+ "data": Object {},
"storage": Storage {
"clear": [Function],
"get": [Function],
diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js b/x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js
index 33ca80b00c451..1a0eda102986f 100644
--- a/x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js
+++ b/x-pack/plugins/maps/public/connected_components/layer_panel/view.test.js
@@ -40,6 +40,17 @@ jest.mock('./layer_settings', () => ({
},
}));
+jest.mock('../../kibana_services', () => {
+ return {
+ getData() {
+ return {};
+ },
+ getCore() {
+ return {};
+ },
+ };
+});
+
import React from 'react';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
diff --git a/x-pack/plugins/maps/public/kibana_services.d.ts b/x-pack/plugins/maps/public/kibana_services.d.ts
deleted file mode 100644
index 1f3a1cd665366..0000000000000
--- a/x-pack/plugins/maps/public/kibana_services.d.ts
+++ /dev/null
@@ -1,92 +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 from 'react';
-import { DataPublicPluginStart } from 'src/plugins/data/public';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { IndexPatternsService } from 'src/plugins/data/public/index_patterns';
-import { NavigateToAppOptions } from 'kibana/public';
-import { MapsConfigType } from '../config';
-import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public';
-import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
-import { FileUploadComponentProps } from '../../file_upload/public';
-
-export function getLicenseId(): any;
-export function getInspector(): any;
-export function getFileUploadComponent(): Promise>;
-export function getIndexPatternSelectComponent(): any;
-export function getHttp(): any;
-export function getTimeFilter(): any;
-export function getToasts(): any;
-export function getIndexPatternService(): IndexPatternsService;
-export function getAutocompleteService(): any;
-export function getSavedObjectsClient(): any;
-export function getMapsCapabilities(): any;
-export function getVisualizations(): any;
-export function getDocLinks(): any;
-export function getCoreChrome(): any;
-export function getUiSettings(): any;
-export function getIsDarkMode(): boolean;
-export function getCoreOverlays(): any;
-export function getData(): any;
-export function getUiActions(): any;
-export function getCore(): any;
-export function getNavigation(): any;
-export function getCoreI18n(): any;
-export function getSearchService(): DataPublicPluginStart['search'];
-export function getKibanaCommonConfig(): MapsLegacyConfigType;
-export function getMapAppConfig(): MapsConfigType;
-export function getIsEmsEnabled(): any;
-export function getEmsFontLibraryUrl(): any;
-export function getEmsTileLayerId(): any;
-export function getEmsFileApiUrl(): any;
-export function getEmsTileApiUrl(): any;
-export function getEmsLandingPageUrl(): any;
-export function getRegionmapLayers(): any;
-export function getTilemap(): any;
-export function getKibanaVersion(): string;
-export function getEnabled(): boolean;
-export function getShowMapVisualizationTypes(): boolean;
-export function getShowMapsInspectorAdapter(): boolean;
-export function getPreserveDrawingBuffer(): boolean;
-export function getProxyElasticMapsServiceInMaps(): boolean;
-export function getIsGoldPlus(): boolean;
-export function fetchSearchSourceAndRecordWithInspector(args: unknown): any;
-
-export function setLicenseId(args: unknown): void;
-export function setInspector(args: unknown): void;
-export function setFileUpload(args: unknown): void;
-export function setIndexPatternSelect(args: unknown): void;
-export function setHttp(args: unknown): void;
-export function setTimeFilter(args: unknown): void;
-export function setToasts(args: unknown): void;
-export function setIndexPatternService(args: unknown): void;
-export function setAutocompleteService(args: unknown): void;
-export function setSavedObjectsClient(args: unknown): void;
-export function setMapsCapabilities(args: unknown): void;
-export function setVisualizations(args: unknown): void;
-export function setDocLinks(args: unknown): void;
-export function setCoreChrome(args: unknown): void;
-export function setUiSettings(args: unknown): void;
-export function setCoreOverlays(args: unknown): void;
-export function setData(args: unknown): void;
-export function setUiActions(args: unknown): void;
-export function setCore(args: unknown): void;
-export function setNavigation(args: unknown): void;
-export function setCoreI18n(args: unknown): void;
-export function setSearchService(args: DataPublicPluginStart['search']): void;
-export function setKibanaCommonConfig(config: MapsLegacyConfigType): void;
-export function setMapAppConfig(config: MapsConfigType): void;
-export function setKibanaVersion(version: string): void;
-export function setIsGoldPlus(isGoldPlus: boolean): void;
-export function setEmbeddableService(embeddableService: EmbeddableStart): void;
-export function getEmbeddableService(): EmbeddableStart;
-export function setNavigateToApp(
- navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise
-): void;
-export const navigateToApp: (
- appId: string,
- options?: NavigateToAppOptions | undefined
-) => Promise;
diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js
deleted file mode 100644
index 64aa0e07ffafb..0000000000000
--- a/x-pack/plugins/maps/public/kibana_services.js
+++ /dev/null
@@ -1,192 +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 { esFilters, search } from '../../../../src/plugins/data/public';
-import _ from 'lodash';
-
-export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER;
-const { getRequestInspectorStats, getResponseInspectorStats } = search;
-
-let indexPatternService;
-export const setIndexPatternService = (dataIndexPatterns) =>
- (indexPatternService = dataIndexPatterns);
-export const getIndexPatternService = () => indexPatternService;
-
-let autocompleteService;
-export const setAutocompleteService = (dataAutoComplete) =>
- (autocompleteService = dataAutoComplete);
-export const getAutocompleteService = () => autocompleteService;
-
-let licenseId;
-export const setLicenseId = (latestLicenseId) => (licenseId = latestLicenseId);
-export const getLicenseId = () => {
- return licenseId;
-};
-
-let inspector;
-export const setInspector = (newInspector) => (inspector = newInspector);
-export const getInspector = () => {
- return inspector;
-};
-
-let fileUploadPlugin;
-export const setFileUpload = (fileUpload) => (fileUploadPlugin = fileUpload);
-export const getFileUploadComponent = async () => {
- return await fileUploadPlugin.getFileUploadComponent();
-};
-
-let uiSettings;
-export const setUiSettings = (coreUiSettings) => (uiSettings = coreUiSettings);
-export const getUiSettings = () => uiSettings;
-export const getIsDarkMode = () => {
- return getUiSettings().get('theme:darkMode', false);
-};
-
-let indexPatternSelectComponent;
-export const setIndexPatternSelect = (indexPatternSelect) =>
- (indexPatternSelectComponent = indexPatternSelect);
-export const getIndexPatternSelectComponent = () => indexPatternSelectComponent;
-
-let coreHttp;
-export const setHttp = (http) => (coreHttp = http);
-export const getHttp = () => coreHttp;
-
-let dataTimeFilter;
-export const setTimeFilter = (timeFilter) => (dataTimeFilter = timeFilter);
-export const getTimeFilter = () => dataTimeFilter;
-
-let toast;
-export const setToasts = (notificationToast) => (toast = notificationToast);
-export const getToasts = () => toast;
-
-export async function fetchSearchSourceAndRecordWithInspector({
- searchSource,
- requestId,
- requestName,
- requestDesc,
- inspectorAdapters,
- abortSignal,
-}) {
- const inspectorRequest = inspectorAdapters.requests.start(requestName, {
- id: requestId,
- description: requestDesc,
- });
- let resp;
- try {
- inspectorRequest.stats(getRequestInspectorStats(searchSource));
- searchSource.getSearchRequestBody().then((body) => {
- inspectorRequest.json(body);
- });
- resp = await searchSource.fetch({ abortSignal });
- inspectorRequest.stats(getResponseInspectorStats(resp, searchSource)).ok({ json: resp });
- } catch (error) {
- inspectorRequest.error({ error });
- throw error;
- }
-
- return resp;
-}
-
-let savedObjectsClient;
-export const setSavedObjectsClient = (coreSavedObjectsClient) =>
- (savedObjectsClient = coreSavedObjectsClient);
-export const getSavedObjectsClient = () => savedObjectsClient;
-
-let chrome;
-export const setCoreChrome = (coreChrome) => (chrome = coreChrome);
-export const getCoreChrome = () => chrome;
-
-let mapsCapabilities;
-export const setMapsCapabilities = (coreAppMapsCapabilities) =>
- (mapsCapabilities = coreAppMapsCapabilities);
-export const getMapsCapabilities = () => mapsCapabilities;
-
-let visualizations;
-export const setVisualizations = (visPlugin) => (visualizations = visPlugin);
-export const getVisualizations = () => visualizations;
-
-let docLinks;
-export const setDocLinks = (coreDocLinks) => (docLinks = coreDocLinks);
-export const getDocLinks = () => docLinks;
-
-let overlays;
-export const setCoreOverlays = (coreOverlays) => (overlays = coreOverlays);
-export const getCoreOverlays = () => overlays;
-
-let data;
-export const setData = (dataPlugin) => (data = dataPlugin);
-export const getData = () => data;
-
-let uiActions;
-export const setUiActions = (pluginUiActions) => (uiActions = pluginUiActions);
-export const getUiActions = () => uiActions;
-
-let core;
-export const setCore = (kibanaCore) => (core = kibanaCore);
-export const getCore = () => core;
-
-let navigation;
-export const setNavigation = (pluginNavigation) => (navigation = pluginNavigation);
-export const getNavigation = () => navigation;
-
-let coreI18n;
-export const setCoreI18n = (kibanaCoreI18n) => (coreI18n = kibanaCoreI18n);
-export const getCoreI18n = () => coreI18n;
-
-let dataSearchService;
-export const setSearchService = (searchService) => (dataSearchService = searchService);
-export const getSearchService = () => dataSearchService;
-
-let kibanaVersion;
-export const setKibanaVersion = (version) => (kibanaVersion = version);
-export const getKibanaVersion = () => kibanaVersion;
-
-// xpack.maps.* kibana.yml settings from this plugin
-let mapAppConfig;
-export const setMapAppConfig = (config) => (mapAppConfig = config);
-export const getMapAppConfig = () => mapAppConfig;
-
-export const getEnabled = () => getMapAppConfig().enabled;
-export const getShowMapVisualizationTypes = () => getMapAppConfig().showMapVisualizationTypes;
-export const getShowMapsInspectorAdapter = () => getMapAppConfig().showMapsInspectorAdapter;
-export const getPreserveDrawingBuffer = () => getMapAppConfig().preserveDrawingBuffer;
-
-// map.* kibana.yml settings from maps_legacy plugin that are shared between OSS map visualizations and maps app
-let kibanaCommonConfig;
-export const setKibanaCommonConfig = (config) => (kibanaCommonConfig = config);
-export const getKibanaCommonConfig = () => kibanaCommonConfig;
-
-export const getIsEmsEnabled = () => getKibanaCommonConfig().includeElasticMapsService;
-export const getEmsFontLibraryUrl = () => getKibanaCommonConfig().emsFontLibraryUrl;
-export const getEmsTileLayerId = () => getKibanaCommonConfig().emsTileLayerId;
-export const getEmsFileApiUrl = () => getKibanaCommonConfig().emsFileApiUrl;
-export const getEmsTileApiUrl = () => getKibanaCommonConfig().emsTileApiUrl;
-export const getEmsLandingPageUrl = () => getKibanaCommonConfig().emsLandingPageUrl;
-export const getProxyElasticMapsServiceInMaps = () =>
- getKibanaCommonConfig().proxyElasticMapsServiceInMaps;
-export const getRegionmapLayers = () => _.get(getKibanaCommonConfig(), 'regionmap.layers', []);
-export const getTilemap = () => _.get(getKibanaCommonConfig(), 'tilemap', []);
-
-let isGoldPlus = false;
-export const setIsGoldPlus = (igp) => {
- isGoldPlus = igp;
-};
-
-export const getIsGoldPlus = () => {
- return isGoldPlus;
-};
-
-let embeddableService;
-export const setEmbeddableService = (_embeddableService) => {
- embeddableService = _embeddableService;
-};
-export const getEmbeddableService = () => {
- return embeddableService;
-};
-
-export let navigateToApp;
-export function setNavigateToApp(_navigateToApp) {
- navigateToApp = _navigateToApp;
-}
diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts
new file mode 100644
index 0000000000000..3b004e2cda67b
--- /dev/null
+++ b/x-pack/plugins/maps/public/kibana_services.ts
@@ -0,0 +1,83 @@
+/*
+ * 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 _ from 'lodash';
+import { esFilters } from '../../../../src/plugins/data/public';
+import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public';
+import { MapsConfigType } from '../config';
+import { MapsPluginStartDependencies } from './plugin';
+import { CoreStart } from '../../../../src/core/public';
+
+export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER;
+
+let licenseId: string | undefined;
+export const setLicenseId = (latestLicenseId: string | undefined) => (licenseId = latestLicenseId);
+export const getLicenseId = () => licenseId;
+let isGoldPlus: boolean = false;
+export const setIsGoldPlus = (igp: boolean) => (isGoldPlus = igp);
+export const getIsGoldPlus = () => isGoldPlus;
+
+let kibanaVersion: string;
+export const setKibanaVersion = (version: string) => (kibanaVersion = version);
+export const getKibanaVersion = () => kibanaVersion;
+
+let coreStart: CoreStart;
+let pluginsStart: MapsPluginStartDependencies;
+export function setStartServices(core: CoreStart, plugins: MapsPluginStartDependencies) {
+ coreStart = core;
+ pluginsStart = plugins;
+}
+export const getIndexPatternService = () => pluginsStart.data.indexPatterns;
+export const getAutocompleteService = () => pluginsStart.data.autocomplete;
+export const getInspector = () => pluginsStart.inspector;
+export const getFileUploadComponent = async () => {
+ return await pluginsStart.fileUpload.getFileUploadComponent();
+};
+export const getUiSettings = () => coreStart.uiSettings;
+export const getIsDarkMode = () => getUiSettings().get('theme:darkMode', false);
+export const getIndexPatternSelectComponent = (): any => pluginsStart.data.ui.IndexPatternSelect;
+export const getHttp = () => coreStart.http;
+export const getTimeFilter = () => pluginsStart.data.query.timefilter.timefilter;
+export const getToasts = () => coreStart.notifications.toasts;
+export const getSavedObjectsClient = () => coreStart.savedObjects.client;
+export const getCoreChrome = () => coreStart.chrome;
+export const getMapsCapabilities = () => coreStart.application.capabilities.maps;
+export const getDocLinks = () => coreStart.docLinks;
+export const getCoreOverlays = () => coreStart.overlays;
+export const getData = () => pluginsStart.data;
+export const getUiActions = () => pluginsStart.uiActions;
+export const getCore = () => coreStart;
+export const getNavigation = () => pluginsStart.navigation;
+export const getCoreI18n = () => coreStart.i18n;
+export const getSearchService = () => pluginsStart.data.search;
+export const getEmbeddableService = () => pluginsStart.embeddable;
+export const getNavigateToApp = () => coreStart.application.navigateToApp;
+
+// xpack.maps.* kibana.yml settings from this plugin
+let mapAppConfig: MapsConfigType;
+export const setMapAppConfig = (config: MapsConfigType) => (mapAppConfig = config);
+export const getMapAppConfig = () => mapAppConfig;
+
+export const getEnabled = () => getMapAppConfig().enabled;
+export const getShowMapsInspectorAdapter = () => getMapAppConfig().showMapsInspectorAdapter;
+export const getPreserveDrawingBuffer = () => getMapAppConfig().preserveDrawingBuffer;
+
+// map.* kibana.yml settings from maps_legacy plugin that are shared between OSS map visualizations and maps app
+let kibanaCommonConfig: MapsLegacyConfigType;
+export const setKibanaCommonConfig = (config: MapsLegacyConfigType) =>
+ (kibanaCommonConfig = config);
+export const getKibanaCommonConfig = () => kibanaCommonConfig;
+
+export const getIsEmsEnabled = () => getKibanaCommonConfig().includeElasticMapsService;
+export const getEmsFontLibraryUrl = () => getKibanaCommonConfig().emsFontLibraryUrl;
+export const getEmsTileLayerId = () => getKibanaCommonConfig().emsTileLayerId;
+export const getEmsFileApiUrl = () => getKibanaCommonConfig().emsFileApiUrl;
+export const getEmsTileApiUrl = () => getKibanaCommonConfig().emsTileApiUrl;
+export const getEmsLandingPageUrl = () => getKibanaCommonConfig().emsLandingPageUrl;
+export const getProxyElasticMapsServiceInMaps = () =>
+ getKibanaCommonConfig().proxyElasticMapsServiceInMaps;
+export const getRegionmapLayers = () => _.get(getKibanaCommonConfig(), 'regionmap.layers', []);
+export const getTilemap = () => _.get(getKibanaCommonConfig(), 'tilemap', []);
diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts
index b77bf208c5865..5f2a640aa9d0f 100644
--- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts
+++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts
@@ -6,7 +6,7 @@
import { AnyAction } from 'redux';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { IndexPatternsService } from 'src/plugins/data/public/index_patterns';
+import { IndexPatternsContract } from 'src/plugins/data/public/index_patterns';
import { ReactElement } from 'react';
import { IndexPattern } from 'src/plugins/data/public';
import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public';
@@ -29,7 +29,7 @@ interface LazyLoadedMapModules {
renderTooltipContent?: RenderToolTipContent,
eventHandlers?: EventHandlers
) => Embeddable;
- getIndexPatternService: () => IndexPatternsService;
+ getIndexPatternService: () => IndexPatternsContract;
getHttp: () => any;
getMapsCapabilities: () => any;
createMapStore: () => MapStore;
diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.js b/x-pack/plugins/maps/public/maps_vis_type_alias.js
index d90674f0f7725..b7e95cdf987db 100644
--- a/x-pack/plugins/maps/public/maps_vis_type_alias.js
+++ b/x-pack/plugins/maps/public/maps_vis_type_alias.js
@@ -6,12 +6,10 @@
import { i18n } from '@kbn/i18n';
import { APP_ID, APP_ICON, MAP_PATH } from '../common/constants';
-import { getShowMapVisualizationTypes, getVisualizations } from './kibana_services';
-export function getMapsVisTypeAlias() {
- const showMapVisualizationTypes = getShowMapVisualizationTypes();
+export function getMapsVisTypeAlias(visualizations, showMapVisualizationTypes) {
if (!showMapVisualizationTypes) {
- getVisualizations().hideTypes(['region_map', 'tile_map']);
+ visualizations.hideTypes(['region_map', 'tile_map']);
}
const description = i18n.translate('xpack.maps.visTypeAlias.description', {
diff --git a/x-pack/plugins/maps/public/meta.ts b/x-pack/plugins/maps/public/meta.ts
index 34c5f004fd7f3..5142793bede34 100644
--- a/x-pack/plugins/maps/public/meta.ts
+++ b/x-pack/plugins/maps/public/meta.ts
@@ -61,7 +61,7 @@ function relativeToAbsolute(url: string): string {
}
let emsClient: EMSClient | null = null;
-let latestLicenseId: string | null = null;
+let latestLicenseId: string | undefined;
export function getEMSClient(): EMSClient {
if (!emsClient) {
const proxyElasticMapsServiceInMaps = getProxyElasticMapsServiceInMaps();
@@ -93,7 +93,7 @@ export function getEMSClient(): EMSClient {
const licenseId = getLicenseId();
if (latestLicenseId !== licenseId) {
latestLicenseId = licenseId;
- emsClient.addQueryParams({ license: licenseId });
+ emsClient.addQueryParams({ license: licenseId ? licenseId : '' });
}
return emsClient;
}
diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts
index c374d3cb59b34..e2b40e22bfe7d 100644
--- a/x-pack/plugins/maps/public/plugin.ts
+++ b/x-pack/plugins/maps/public/plugin.ts
@@ -5,6 +5,9 @@
*/
import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public';
+import { UiActionsStart } from 'src/plugins/ui_actions/public';
+import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
+import { Start as InspectorStartContract } from 'src/plugins/inspector/public';
import {
CoreSetup,
CoreStart,
@@ -15,34 +18,12 @@ import {
// @ts-ignore
import { MapView } from './inspector/views/map_view';
import {
- setAutocompleteService,
- setCore,
- setCoreChrome,
- setCoreI18n,
- setCoreOverlays,
- setData,
- setDocLinks,
- setFileUpload,
- setHttp,
- setIndexPatternSelect,
- setIndexPatternService,
- setInspector,
setIsGoldPlus,
setKibanaCommonConfig,
setKibanaVersion,
setLicenseId,
setMapAppConfig,
- setMapsCapabilities,
- setNavigation,
- setSavedObjectsClient,
- setSearchService,
- setTimeFilter,
- setToasts,
- setUiActions,
- setUiSettings,
- setVisualizations,
- setEmbeddableService,
- setNavigateToApp,
+ setStartServices,
} from './kibana_services';
import { featureCatalogueEntry } from './feature_catalogue_entry';
// @ts-ignore
@@ -58,66 +39,29 @@ import { ILicense } from '../../licensing/common/types';
import { lazyLoadMapModules } from './lazy_load_bundle';
import { MapsStartApi } from './api';
import { createSecurityLayerDescriptors, registerLayerWizard, registerSource } from './api';
+import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
+import { MapsLegacyConfigType } from '../../../../src/plugins/maps_legacy/public';
+import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
+import { LicensingPluginStart } from '../../licensing/public';
+import { StartContract as FileUploadStartContract } from '../../file_upload/public';
export interface MapsPluginSetupDependencies {
inspector: InspectorSetupContract;
home: HomePublicPluginSetup;
visualizations: VisualizationsSetup;
embeddable: EmbeddableSetup;
- mapsLegacy: { config: unknown };
+ mapsLegacy: { config: MapsLegacyConfigType };
}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface MapsPluginStartDependencies {}
-export const bindSetupCoreAndPlugins = (
- core: CoreSetup,
- plugins: any,
- config: MapsConfigType,
- kibanaVersion: string
-) => {
- const { licensing, mapsLegacy } = plugins;
- const { uiSettings, http, notifications } = core;
- if (licensing) {
- licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid));
- }
- setHttp(http);
- setToasts(notifications.toasts);
- setVisualizations(plugins.visualizations);
- setUiSettings(uiSettings);
- setKibanaCommonConfig(mapsLegacy.config);
- setMapAppConfig(config);
- setKibanaVersion(kibanaVersion);
-};
-
-export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => {
- const { fileUpload, data, inspector, licensing } = plugins;
- if (licensing) {
- licensing.license$.subscribe((license: ILicense) => {
- const gold = license.check(APP_ID, 'gold');
- setIsGoldPlus(gold.state === 'valid');
- });
- }
-
- setInspector(inspector);
- setFileUpload(fileUpload);
- setIndexPatternSelect(data.ui.IndexPatternSelect);
- setTimeFilter(data.query.timefilter.timefilter);
- setSearchService(data.search);
- setIndexPatternService(data.indexPatterns);
- setAutocompleteService(data.autocomplete);
- setCore(core);
- setSavedObjectsClient(core.savedObjects.client);
- setCoreChrome(core.chrome);
- setCoreOverlays(core.overlays);
- setMapsCapabilities(core.application.capabilities.maps);
- setDocLinks(core.docLinks);
- setData(plugins.data);
- setUiActions(plugins.uiActions);
- setNavigation(plugins.navigation);
- setCoreI18n(core.i18n);
- setEmbeddableService(plugins.embeddable);
- setNavigateToApp(core.application.navigateToApp);
-};
+export interface MapsPluginStartDependencies {
+ data: DataPublicPluginStart;
+ embeddable: EmbeddableStart;
+ fileUpload: FileUploadStartContract;
+ inspector: InspectorStartContract;
+ licensing: LicensingPluginStart;
+ navigation: NavigationPublicPluginStart;
+ uiActions: UiActionsStart;
+}
/**
* These are the interfaces with your public contracts. You should export these
@@ -144,14 +88,16 @@ export class MapsPlugin
public setup(core: CoreSetup, plugins: MapsPluginSetupDependencies) {
const config = this._initializerContext.config.get();
- const kibanaVersion = this._initializerContext.env.packageInfo.version;
- const { inspector, home, visualizations, embeddable } = plugins;
- bindSetupCoreAndPlugins(core, plugins, config, kibanaVersion);
+ setKibanaCommonConfig(plugins.mapsLegacy.config);
+ setMapAppConfig(config);
+ setKibanaVersion(this._initializerContext.env.packageInfo.version);
- inspector.registerView(MapView);
- home.featureCatalogue.register(featureCatalogueEntry);
- visualizations.registerAlias(getMapsVisTypeAlias());
- embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory());
+ plugins.inspector.registerView(MapView);
+ plugins.home.featureCatalogue.register(featureCatalogueEntry);
+ plugins.visualizations.registerAlias(
+ getMapsVisTypeAlias(plugins.visualizations, config.showMapVisualizationTypes)
+ );
+ plugins.embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory());
core.application.register({
id: APP_ID,
@@ -162,16 +108,23 @@ export class MapsPlugin
category: DEFAULT_APP_CATEGORIES.kibana,
// @ts-expect-error
async mount(context, params) {
- const [coreStart, startPlugins] = await core.getStartServices();
- bindStartCoreAndPlugins(coreStart, startPlugins);
const { renderApp } = await lazyLoadMapModules();
return renderApp(context, params);
},
});
}
- public start(core: CoreStart, plugins: any): MapsStartApi {
- bindStartCoreAndPlugins(core, plugins);
+ public start(core: CoreStart, plugins: MapsPluginStartDependencies): MapsStartApi {
+ if (plugins.licensing) {
+ plugins.licensing.license$.subscribe((license: ILicense) => {
+ const gold = license.check(APP_ID, 'gold');
+ setIsGoldPlus(gold.state === 'valid');
+ setLicenseId(license.uid);
+ });
+ }
+
+ setStartServices(core, plugins);
+
return {
createSecurityLayerDescriptors,
registerLayerWizard,
diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx
index 8a4d8ae555895..35d8490f1a886 100644
--- a/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx
+++ b/x-pack/plugins/maps/public/routing/routes/maps_app/top_nav_config.tsx
@@ -13,7 +13,7 @@ import {
getInspector,
getToasts,
getCoreI18n,
- navigateToApp,
+ getNavigateToApp,
} from '../../../kibana_services';
import {
SavedObjectSaveModalOrigin,
@@ -117,7 +117,7 @@ export function getTopNavConfig({
state: { id, type: MAP_SAVED_OBJECT_TYPE },
});
} else {
- navigateToApp(originatingApp);
+ getNavigateToApp()(originatingApp);
}
}
From 4ac0e81554049d2fb900d286a2e7ec29698a4333 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Mon, 17 Aug 2020 09:44:08 -0600
Subject: [PATCH 002/177] [Maps] fix double fetch when filters are modified
(#74893)
* [Maps] fix double fetch when filters are modified
* add unit tests
Co-authored-by: Elastic Machine
---
.../maps/public/actions/map_actions.test.js | 93 ++++++++++++++++++-
.../maps/public/actions/map_actions.ts | 28 ++++--
.../maps/public/embeddable/map_embeddable.tsx | 8 +-
.../public/routing/routes/maps_app/index.js | 4 +-
.../routing/routes/maps_app/maps_app_view.js | 8 +-
5 files changed, 124 insertions(+), 17 deletions(-)
diff --git a/x-pack/plugins/maps/public/actions/map_actions.test.js b/x-pack/plugins/maps/public/actions/map_actions.test.js
index d860f413df27b..50e583f00ae81 100644
--- a/x-pack/plugins/maps/public/actions/map_actions.test.js
+++ b/x-pack/plugins/maps/public/actions/map_actions.test.js
@@ -11,7 +11,7 @@ jest.mock('./data_request_actions', () => {
};
});
-import { mapExtentChanged, setMouseCoordinates } from './map_actions';
+import { mapExtentChanged, setMouseCoordinates, setQuery } from './map_actions';
const getStoreMock = jest.fn();
const dispatchMock = jest.fn();
@@ -226,4 +226,95 @@ describe('map_actions', () => {
});
});
});
+
+ describe('setQuery', () => {
+ const query = {
+ language: 'kuery',
+ query: '',
+ queryLastTriggeredAt: '2020-08-14T15:07:12.276Z',
+ };
+ const timeFilters = { from: 'now-1y', to: 'now' };
+ const filters = [
+ {
+ meta: {
+ index: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ alias: null,
+ negate: false,
+ disabled: false,
+ type: 'phrase',
+ key: 'extension',
+ params: { query: 'png' },
+ },
+ query: { match_phrase: { extension: 'png' } },
+ $state: { store: 'appState' },
+ },
+ ];
+
+ beforeEach(() => {
+ //Mocks the "previous" state
+ require('../selectors/map_selectors').getQuery = () => {
+ return query;
+ };
+ require('../selectors/map_selectors').getTimeFilters = () => {
+ return timeFilters;
+ };
+ require('../selectors/map_selectors').getFilters = () => {
+ return filters;
+ };
+ require('../selectors/map_selectors').getMapSettings = () => {
+ return {
+ autoFitToDataBounds: false,
+ };
+ };
+ });
+
+ it('should dispatch query action and resync when query changes', async () => {
+ const newQuery = {
+ language: 'kuery',
+ query: 'foobar',
+ queryLastTriggeredAt: '2020-08-14T15:07:12.276Z',
+ };
+ const setQueryAction = await setQuery({
+ query: newQuery,
+ filters,
+ });
+ await setQueryAction(dispatchMock, getStoreMock);
+
+ expect(dispatchMock.mock.calls).toEqual([
+ [
+ {
+ timeFilters,
+ query: newQuery,
+ filters,
+ type: 'SET_QUERY',
+ },
+ ],
+ [undefined], // dispatch(syncDataForAllLayers());
+ ]);
+ });
+
+ it('should not dispatch query action when nothing changes', async () => {
+ const setQueryAction = await setQuery({
+ timeFilters,
+ query,
+ filters,
+ });
+ await setQueryAction(dispatchMock, getStoreMock);
+
+ expect(dispatchMock.mock.calls.length).toEqual(0);
+ });
+
+ it('should dispatch query action when nothing changes and force refresh', async () => {
+ const setQueryAction = await setQuery({
+ timeFilters,
+ query,
+ filters,
+ forceRefresh: true,
+ });
+ await setQueryAction(dispatchMock, getStoreMock);
+
+ // Only checking calls length instead of calls because queryLastTriggeredAt changes on this run
+ expect(dispatchMock.mock.calls.length).toEqual(2);
+ });
+ });
});
diff --git a/x-pack/plugins/maps/public/actions/map_actions.ts b/x-pack/plugins/maps/public/actions/map_actions.ts
index 08826276c12ad..7ba58307e1952 100644
--- a/x-pack/plugins/maps/public/actions/map_actions.ts
+++ b/x-pack/plugins/maps/public/actions/map_actions.ts
@@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import _ from 'lodash';
import { Dispatch } from 'redux';
import turfBboxPolygon from '@turf/bbox-polygon';
import turfBooleanContains from '@turf/boolean-contains';
@@ -204,12 +205,12 @@ export function setQuery({
query,
timeFilters,
filters = [],
- refresh = false,
+ forceRefresh = false,
}: {
- filters: Filter[];
+ filters?: Filter[];
query?: Query;
timeFilters?: TimeRange;
- refresh?: boolean;
+ forceRefresh?: boolean;
}) {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const prevQuery = getQuery(getState());
@@ -218,15 +219,30 @@ export function setQuery({
? prevQuery.queryLastTriggeredAt
: generateQueryTimestamp();
- dispatch({
- type: SET_QUERY,
+ const nextQueryContext = {
timeFilters: timeFilters ? timeFilters : getTimeFilters(getState()),
query: {
...(query ? query : getQuery(getState())),
// ensure query changes to trigger re-fetch when "Refresh" clicked
- queryLastTriggeredAt: refresh ? generateQueryTimestamp() : prevTriggeredAt,
+ queryLastTriggeredAt: forceRefresh ? generateQueryTimestamp() : prevTriggeredAt,
},
filters: filters ? filters : getFilters(getState()),
+ };
+
+ const prevQueryContext = {
+ timeFilters: getTimeFilters(getState()),
+ query: getQuery(getState()),
+ filters: getFilters(getState()),
+ };
+
+ if (_.isEqual(nextQueryContext, prevQueryContext)) {
+ // do nothing if query context has not changed
+ return;
+ }
+
+ dispatch({
+ type: SET_QUERY,
+ ...nextQueryContext,
});
if (getMapSettings(getState()).autoFitToDataBounds) {
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
index 616d06a5c7b19..43ff274b1353f 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
@@ -129,12 +129,12 @@ export class MapEmbeddable extends Embeddable !filter.meta.disabled),
query,
timeFilters: timeRange,
- refresh,
+ forceRefresh,
})
);
}
@@ -270,7 +270,7 @@ export class MapEmbeddable extends Embeddable {
+ dispatchSetQuery: ({ forceRefresh, filters, query, timeFilters }) => {
dispatch(
setQuery({
filters,
query,
timeFilters,
- refresh,
+ forceRefresh,
})
);
},
diff --git a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js
index 23625b4591db7..58f0bf16e93f2 100644
--- a/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js
+++ b/x-pack/plugins/maps/public/routing/routes/maps_app/maps_app_view.js
@@ -142,7 +142,7 @@ export class MapsAppView extends React.Component {
return;
}
- this._onQueryChange({ time: globalState.time, refresh: true });
+ this._onQueryChange({ time: globalState.time });
};
async _updateIndexPatterns() {
@@ -160,7 +160,7 @@ export class MapsAppView extends React.Component {
}
}
- _onQueryChange = ({ filters, query, time, refresh = false }) => {
+ _onQueryChange = ({ filters, query, time, forceRefresh = false }) => {
const { filterManager } = getData().query;
if (filters) {
@@ -168,7 +168,7 @@ export class MapsAppView extends React.Component {
}
this.props.dispatchSetQuery({
- refresh,
+ forceRefresh,
filters: filterManager.getFilters(),
query,
timeFilters: time,
@@ -336,7 +336,7 @@ export class MapsAppView extends React.Component {
this._onQueryChange({
query,
time: dateRange,
- refresh: true,
+ forceRefresh: true,
});
}}
onFiltersUpdated={this._onFiltersChange}
From 0b28dc2f4845ec672a0f7e9521c44116051af7ea Mon Sep 17 00:00:00 2001
From: Constance
Date: Mon, 17 Aug 2020 09:37:21 -0700
Subject: [PATCH 003/177] [Enterprise Search][tech debt] Refactor external URL
generation to a single DRY helper (#75093)
* Set up App Search routes constants file
* Create new helper/generator for external URLs
* Update plugin to pass externalUrl helper in KibanaContext
* Update AS & WS navs to use new external url generator
* Update App Search views to use new externalUrl.getAppSearchUrl helper
* Update Workplace Search to use externalUrl.getWorkplaceSearchUrl helper
+ remove old useRoutes.getWSRoute helper, which was the inspiration for this
* Rename top-level enterpriseSearchUrl to config.host
- This allows us to more clearly separate concerns between the URL set in config.host and externalUrl.enterpriseSearchUrl (used for front-end links, may be a vanity/public URL)
- This change also enables us to not mutate Kibana's config obj, which is much cleaner
Misc tech debt:
- Reorder renderApp args (from least to most likely to change)
- Rename our public url methods/vars to more generic "init data" names - this is in preparation for upcoming changes where we pull more than just external_url from our client endpoint
* Fix broken Workplace Search nav links
- that require a hash for routing to work
Co-authored-by: Elastic Machine
---
.../__mocks__/kibana_context.mock.ts | 4 +-
.../__mocks__/mount_with_context.mock.tsx | 2 +-
.../__mocks__/shallow_usecontext.mock.ts | 2 +-
.../components/empty_states/empty_state.tsx | 8 +++-
.../engine_overview/engine_table.tsx | 9 ++++-
.../engine_overview_header.tsx | 7 +++-
.../applications/app_search/index.test.tsx | 4 +-
.../public/applications/app_search/index.tsx | 37 +++++++++++-------
.../public/applications/app_search/routes.ts | 17 +++++++++
.../public/applications/index.test.tsx | 9 +++--
.../public/applications/index.tsx | 14 ++++---
.../generate_external_url.test.ts | 25 ++++++++++++
.../generate_external_url.ts | 38 +++++++++++++++++++
.../shared/enterprise_search_url/index.ts | 1 +
.../shared/error_state/error_state_prompt.tsx | 4 +-
.../components/layout/nav.tsx | 19 +++++-----
.../components/overview/onboarding_card.tsx | 9 +++--
.../components/overview/onboarding_steps.tsx | 9 +++--
.../components/overview/recent_activity.tsx | 9 +++--
.../components/overview/statistic_card.tsx | 10 +++--
.../shared/product_button/product_button.tsx | 7 +++-
.../components/shared/use_routes/index.ts | 7 ----
.../shared/use_routes/use_routes.tsx | 15 --------
.../workplace_search/index.test.tsx | 17 +++------
.../applications/workplace_search/index.tsx | 4 +-
.../enterprise_search/public/plugin.ts | 36 +++++++++++-------
26 files changed, 212 insertions(+), 111 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.test.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts
delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/index.ts
delete mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/use_routes.tsx
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts
index b1d7341d51a4c..ef3bf54053b5c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_context.mock.ts
@@ -5,6 +5,7 @@
*/
import { httpServiceMock } from 'src/core/public/mocks';
+import { ExternalUrl } from '../shared/enterprise_search_url';
/**
* A set of default Kibana context values to use across component tests.
@@ -14,5 +15,6 @@ export const mockKibanaContext = {
http: httpServiceMock.createSetupContract(),
setBreadcrumbs: jest.fn(),
setDocTitle: jest.fn(),
- enterpriseSearchUrl: 'http://localhost:3002',
+ config: { host: 'http://localhost:3002' },
+ externalUrl: new ExternalUrl('http://localhost:3002'),
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
index 1e0df1326c177..9f8fda856eed6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_context.mock.tsx
@@ -21,7 +21,7 @@ import { mockLicenseContext } from './license_context.mock';
*
* Example usage:
*
- * const wrapper = mountWithContext(, { enterpriseSearchUrl: 'someOverride', license: {} });
+ * const wrapper = mountWithContext(, { config: { host: 'someOverride' } });
*/
export const mountWithContext = (children: React.ReactNode, context?: object) => {
return mount(
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
index 2bcdd42c38055..792be49a49c48 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_usecontext.mock.ts
@@ -35,6 +35,6 @@ jest.mock('react', () => ({
* // ... etc.
*
* it('some test', () => {
- * useContext.mockImplementationOnce(() => ({ enterpriseSearchUrl: 'someOverride' }));
+ * useContext.mockImplementationOnce(() => ({ config: { host: 'someOverride' } }));
* });
*/
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
index 4d2b790e7fb97..9b0edb423bc52 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/empty_states/empty_state.tsx
@@ -11,16 +11,20 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { sendTelemetry } from '../../../shared/telemetry';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { KibanaContext, IKibanaContext } from '../../../index';
+import { CREATE_ENGINES_PATH } from '../../routes';
import { EngineOverviewHeader } from '../engine_overview_header';
import './empty_states.scss';
export const EmptyState: React.FC = () => {
- const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+ const {
+ externalUrl: { getAppSearchUrl },
+ http,
+ } = useContext(KibanaContext) as IKibanaContext;
const buttonProps = {
- href: `${enterpriseSearchUrl}/as/engines/new`,
+ href: getAppSearchUrl(CREATE_ENGINES_PATH),
target: '_blank',
onClick: () =>
sendTelemetry({
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
index 1e58d820dc83b..9c6122c88c7d7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_table.tsx
@@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
+import { getEngineRoute } from '../../routes';
import { ENGINES_PAGE_SIZE } from '../../../../../common/constants';
@@ -39,9 +40,13 @@ export const EngineTable: React.FC = ({
data,
pagination: { totalEngines, pageIndex, onPaginate },
}) => {
- const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+ const {
+ externalUrl: { getAppSearchUrl },
+ http,
+ } = useContext(KibanaContext) as IKibanaContext;
+
const engineLinkProps = (name: string) => ({
- href: `${enterpriseSearchUrl}/as/engines/${name}`,
+ href: getAppSearchUrl(getEngineRoute(name)),
target: '_blank',
onClick: () =>
sendTelemetry({
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx
index cc480d241ad50..7f67d00f5df91 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview_header/engine_overview_header.tsx
@@ -19,13 +19,16 @@ import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
export const EngineOverviewHeader: React.FC = () => {
- const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+ const {
+ externalUrl: { getAppSearchUrl },
+ http,
+ } = useContext(KibanaContext) as IKibanaContext;
const buttonProps = {
fill: true,
iconType: 'popout',
'data-test-subj': 'launchButton',
- href: `${enterpriseSearchUrl}/as`,
+ href: getAppSearchUrl(),
target: '_blank',
onClick: () =>
sendTelemetry({
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
index 9e660d10053ec..fa9a761a966e1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx
@@ -20,8 +20,8 @@ describe('AppSearch', () => {
expect(wrapper.find(Layout)).toHaveLength(1);
});
- it('redirects to Setup Guide when enterpriseSearchUrl is not set', () => {
- (useContext as jest.Mock).mockImplementationOnce(() => ({ enterpriseSearchUrl: '' }));
+ it('redirects to Setup Guide when config.host is not set', () => {
+ (useContext as jest.Mock).mockImplementationOnce(() => ({ config: { host: '' } }));
const wrapper = shallow();
expect(wrapper.find(Redirect)).toHaveLength(1);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
index d69b3ba29b0ca..7ebd35ff35ee1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx
@@ -13,19 +13,29 @@ import { APP_SEARCH_PLUGIN } from '../../../common/constants';
import { KibanaContext, IKibanaContext } from '../index';
import { Layout, SideNav, SideNavLink } from '../shared/layout';
+import {
+ ROOT_PATH,
+ SETUP_GUIDE_PATH,
+ SETTINGS_PATH,
+ CREDENTIALS_PATH,
+ ROLE_MAPPINGS_PATH,
+ ENGINES_PATH,
+} from './routes';
+
import { SetupGuide } from './components/setup_guide';
import { EngineOverview } from './components/engine_overview';
export const AppSearch: React.FC = () => {
- const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
- if (!enterpriseSearchUrl)
+ const { config } = useContext(KibanaContext) as IKibanaContext;
+
+ if (!config.host)
return (
-
+
-
+
{/* Kibana displays a blank page on redirect if this isn't included */}
@@ -33,17 +43,17 @@ export const AppSearch: React.FC = () => {
return (
-
+
}>
-
+
{/* For some reason a Redirect to /engines just doesn't work here - it shows a blank page */}
-
+
@@ -54,27 +64,28 @@ export const AppSearch: React.FC = () => {
};
export const AppSearchNav: React.FC = () => {
- const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
- const externalUrl = `${enterpriseSearchUrl}/as#`;
+ const {
+ externalUrl: { getAppSearchUrl },
+ } = useContext(KibanaContext) as IKibanaContext;
return (
-
+
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.engines', {
defaultMessage: 'Engines',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.settings', {
defaultMessage: 'Account Settings',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.credentials', {
defaultMessage: 'Credentials',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.appSearch.nav.roleMappings', {
defaultMessage: 'Role Mappings',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
new file mode 100644
index 0000000000000..51e2497365dd7
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+export const ROOT_PATH = '/';
+export const SETUP_GUIDE_PATH = '/setup_guide';
+export const SETTINGS_PATH = '/settings/account';
+export const CREDENTIALS_PATH = '/credentials';
+export const ROLE_MAPPINGS_PATH = '#/role-mappings'; // This page seems to 404 if the # isn't included
+
+export const ENGINES_PATH = '/engines';
+export const CREATE_ENGINES_PATH = `${ENGINES_PATH}/new`;
+
+export const ENGINE_PATH = '/engines/:engineName';
+export const getEngineRoute = (engineName: string) => `${ENGINES_PATH}/${engineName}`;
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
index 70e16e61846b4..e0cf2814b46b4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.test.tsx
@@ -17,10 +17,11 @@ import { WorkplaceSearch } from './workplace_search';
describe('renderApp', () => {
let params: AppMountParameters;
const core = coreMock.createStart();
- const config = {};
const plugins = {
licensing: licensingMock.createSetup(),
} as any;
+ const config = {};
+ const data = {} as any;
beforeEach(() => {
jest.clearAllMocks();
@@ -30,19 +31,19 @@ describe('renderApp', () => {
it('mounts and unmounts UI', () => {
const MockApp = () => Hello world!
;
- const unmount = renderApp(MockApp, core, params, config, plugins);
+ const unmount = renderApp(MockApp, params, core, plugins, config, data);
expect(params.element.querySelector('.hello-world')).not.toBeNull();
unmount();
expect(params.element.innerHTML).toEqual('');
});
it('renders AppSearch', () => {
- renderApp(AppSearch, core, params, config, plugins);
+ renderApp(AppSearch, params, core, plugins, config, data);
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
});
it('renders WorkplaceSearch', () => {
- renderApp(WorkplaceSearch, core, params, config, plugins);
+ renderApp(WorkplaceSearch, params, core, plugins, config, data);
expect(params.element.querySelector('.setupGuide')).not.toBeNull();
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx
index 0e43d86f5095d..f3ccbc126ae62 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx
@@ -10,11 +10,13 @@ import { Router } from 'react-router-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { CoreStart, AppMountParameters, HttpSetup, ChromeBreadcrumb } from 'src/core/public';
-import { ClientConfigType, PluginsSetup } from '../plugin';
+import { ClientConfigType, ClientData, PluginsSetup } from '../plugin';
import { LicenseProvider } from './shared/licensing';
+import { IExternalUrl } from './shared/enterprise_search_url';
export interface IKibanaContext {
- enterpriseSearchUrl?: string;
+ config: { host?: string };
+ externalUrl: IExternalUrl;
http: HttpSetup;
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
setDocTitle(title: string): void;
@@ -30,17 +32,19 @@ export const KibanaContext = React.createContext({});
export const renderApp = (
App: React.FC,
- core: CoreStart,
params: AppMountParameters,
+ core: CoreStart,
+ plugins: PluginsSetup,
config: ClientConfigType,
- plugins: PluginsSetup
+ data: ClientData
) => {
ReactDOM.render(
{
+ const externalUrl = new ExternalUrl('http://localhost:3002');
+
+ it('exposes a public enterpriseSearchUrl string', () => {
+ expect(externalUrl.enterpriseSearchUrl).toEqual('http://localhost:3002');
+ });
+
+ it('generates a public App Search URL', () => {
+ expect(externalUrl.getAppSearchUrl()).toEqual('http://localhost:3002/as');
+ expect(externalUrl.getAppSearchUrl('/path')).toEqual('http://localhost:3002/as/path');
+ });
+
+ it('generates a public Workplace Search URL', () => {
+ expect(externalUrl.getWorkplaceSearchUrl()).toEqual('http://localhost:3002/ws');
+ expect(externalUrl.getWorkplaceSearchUrl('/path')).toEqual('http://localhost:3002/ws/path');
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts
new file mode 100644
index 0000000000000..9db48d197f3bc
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/generate_external_url.ts
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+/**
+ * Small helper for generating external public-facing URLs
+ * to the legacy/standalone Enterprise Search app
+ */
+export interface IExternalUrl {
+ enterpriseSearchUrl?: string;
+ getAppSearchUrl(path?: string): string;
+ getWorkplaceSearchUrl(path?: string): string;
+}
+
+export class ExternalUrl {
+ public enterpriseSearchUrl: string;
+
+ constructor(externalUrl: string) {
+ this.enterpriseSearchUrl = externalUrl;
+
+ this.getAppSearchUrl = this.getAppSearchUrl.bind(this);
+ this.getWorkplaceSearchUrl = this.getWorkplaceSearchUrl.bind(this);
+ }
+
+ private getExternalUrl(path: string): string {
+ return this.enterpriseSearchUrl + path;
+ }
+
+ public getAppSearchUrl(path: string = ''): string {
+ return this.getExternalUrl('/as' + path);
+ }
+
+ public getWorkplaceSearchUrl(path: string = ''): string {
+ return this.getExternalUrl('/ws' + path);
+ }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts
index bbbb688b8ea7b..563d19f9fdeb5 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/enterprise_search_url/index.ts
@@ -5,3 +5,4 @@
*/
export { getPublicUrl } from './get_enterprise_search_url';
+export { ExternalUrl, IExternalUrl } from './generate_external_url';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx
index ccd5beff66e70..a2cb424dadee8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx
@@ -14,7 +14,7 @@ import { KibanaContext, IKibanaContext } from '../../index';
import './error_state_prompt.scss';
export const ErrorStatePrompt: React.FC = () => {
- const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
+ const { config } = useContext(KibanaContext) as IKibanaContext;
return (
{
id="xpack.enterpriseSearch.errorConnectingState.description1"
defaultMessage="We can’t establish a connection to Enterprise Search at the host URL: {enterpriseSearchUrl}"
values={{
- enterpriseSearchUrl: {enterpriseSearchUrl},
+ enterpriseSearchUrl: {config.host},
}}
/>
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
index 8f8edc61620ab..9fb627ed09791 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx
@@ -22,8 +22,9 @@ import {
} from '../../routes';
export const WorkplaceSearchNav: React.FC = () => {
- const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
- const legacyUrl = (path: string) => `${enterpriseSearchUrl}/ws#${path}`;
+ const {
+ externalUrl: { getWorkplaceSearchUrl },
+ } = useContext(KibanaContext) as IKibanaContext;
// TODO: icons
return (
@@ -33,38 +34,38 @@ export const WorkplaceSearchNav: React.FC = () => {
defaultMessage: 'Overview',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.sources', {
defaultMessage: 'Sources',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.groups', {
defaultMessage: 'Groups',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', {
defaultMessage: 'Role Mappings',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', {
defaultMessage: 'Security',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.settings', {
defaultMessage: 'Settings',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.personalDashboard', {
defaultMessage: 'View my personal dashboard',
})}
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.search', {
defaultMessage: 'Go to search application',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_card.tsx
index 288c0be84fa9a..786357358dfa6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_card.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_card.tsx
@@ -17,7 +17,6 @@ import {
EuiButtonEmptyProps,
EuiLinkProps,
} from '@elastic/eui';
-import { useRoutes } from '../shared/use_routes';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
@@ -40,8 +39,10 @@ export const OnboardingCard: React.FC = ({
actionPath,
complete,
}) => {
- const { http } = useContext(KibanaContext) as IKibanaContext;
- const { getWSRoute } = useRoutes();
+ const {
+ http,
+ externalUrl: { getWorkplaceSearchUrl },
+ } = useContext(KibanaContext) as IKibanaContext;
const onClick = () =>
sendTelemetry({
@@ -53,7 +54,7 @@ export const OnboardingCard: React.FC = ({
const buttonActionProps = actionPath
? {
onClick,
- href: getWSRoute(actionPath),
+ href: getWorkplaceSearchUrl(actionPath),
target: '_blank',
'data-test-subj': testSubj,
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx
index 7fe1eae502329..d0f5893bdb88a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/onboarding_steps.tsx
@@ -22,7 +22,6 @@ import {
EuiLinkProps,
} from '@elastic/eui';
import sharedSourcesIcon from '../shared/assets/share_circle.svg';
-import { useRoutes } from '../shared/use_routes';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes';
@@ -133,8 +132,10 @@ export const OnboardingSteps: React.FC = () => {
};
export const OrgNameOnboarding: React.FC = () => {
- const { http } = useContext(KibanaContext) as IKibanaContext;
- const { getWSRoute } = useRoutes();
+ const {
+ http,
+ externalUrl: { getWorkplaceSearchUrl },
+ } = useContext(KibanaContext) as IKibanaContext;
const onClick = () =>
sendTelemetry({
@@ -148,7 +149,7 @@ export const OrgNameOnboarding: React.FC = () => {
onClick,
target: '_blank',
color: 'primary',
- href: getWSRoute(ORG_SETTINGS_PATH),
+ href: getWorkplaceSearchUrl(ORG_SETTINGS_PATH),
'data-test-subj': 'orgNameChangeButton',
} as EuiButtonEmptyProps & EuiLinkProps;
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx
index 2c0fbe1275cbf..0f4f6c65d083c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/recent_activity.tsx
@@ -13,7 +13,6 @@ import { EuiEmptyPrompt, EuiLink, EuiPanel, EuiSpacer, EuiLinkProps } from '@ela
import { FormattedMessage } from '@kbn/i18n/react';
import { ContentSection } from '../shared/content_section';
-import { useRoutes } from '../shared/use_routes';
import { sendTelemetry } from '../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../index';
import { getSourcePath } from '../../routes';
@@ -92,8 +91,10 @@ export const RecentActivityItem: React.FC = ({
timestamp,
sourceId,
}) => {
- const { http } = useContext(KibanaContext) as IKibanaContext;
- const { getWSRoute } = useRoutes();
+ const {
+ http,
+ externalUrl: { getWorkplaceSearchUrl },
+ } = useContext(KibanaContext) as IKibanaContext;
const onClick = () =>
sendTelemetry({
@@ -106,7 +107,7 @@ export const RecentActivityItem: React.FC = ({
const linkProps = {
onClick,
target: '_blank',
- href: getWSRoute(getSourcePath(sourceId)),
+ href: getWorkplaceSearchUrl(getSourcePath(sourceId)),
external: true,
color: status === 'error' ? 'danger' : 'primary',
'data-test-subj': 'viewSourceDetailsLink',
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/statistic_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/statistic_card.tsx
index 9bc8f4f768073..3e1d285698c0c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/statistic_card.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/overview/statistic_card.tsx
@@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useContext } from 'react';
import { EuiCard, EuiFlexItem, EuiTitle, EuiTextColor } from '@elastic/eui';
-import { useRoutes } from '../shared/use_routes';
+import { KibanaContext, IKibanaContext } from '../../../index';
interface IStatisticCardProps {
title: string;
@@ -17,11 +17,13 @@ interface IStatisticCardProps {
}
export const StatisticCard: React.FC = ({ title, count = 0, actionPath }) => {
- const { getWSRoute } = useRoutes();
+ const {
+ externalUrl: { getWorkplaceSearchUrl },
+ } = useContext(KibanaContext) as IKibanaContext;
const linkProps = actionPath
? {
- href: getWSRoute(actionPath),
+ href: getWorkplaceSearchUrl(actionPath),
target: '_blank',
rel: 'noopener',
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx
index 5b86e14132e0f..a914000654165 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/product_button/product_button.tsx
@@ -13,14 +13,17 @@ import { sendTelemetry } from '../../../../shared/telemetry';
import { KibanaContext, IKibanaContext } from '../../../../index';
export const ProductButton: React.FC = () => {
- const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
+ const {
+ externalUrl: { getWorkplaceSearchUrl },
+ http,
+ } = useContext(KibanaContext) as IKibanaContext;
const buttonProps = {
fill: true,
iconType: 'popout',
'data-test-subj': 'launchButton',
} as EuiButtonProps & EuiLinkProps;
- buttonProps.href = `${enterpriseSearchUrl}/ws`;
+ buttonProps.href = getWorkplaceSearchUrl();
buttonProps.target = '_blank';
buttonProps.onClick = () =>
sendTelemetry({
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/index.ts
deleted file mode 100644
index cb9684408c459..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/index.ts
+++ /dev/null
@@ -1,7 +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.
- */
-
-export { useRoutes } from './use_routes';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/use_routes.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/use_routes.tsx
deleted file mode 100644
index 48b8695f82b43..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/use_routes/use_routes.tsx
+++ /dev/null
@@ -1,15 +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 { useContext } from 'react';
-
-import { KibanaContext, IKibanaContext } from '../../../../index';
-
-export const useRoutes = () => {
- const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
- const getWSRoute = (path: string): string => `${enterpriseSearchUrl}/ws${path}`;
- return { getWSRoute };
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx
index a4af405247f83..a55ff64014130 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.test.tsx
@@ -10,7 +10,6 @@ import React, { useContext } from 'react';
import { Redirect } from 'react-router-dom';
import { shallow } from 'enzyme';
-import { SetupGuide } from './components/setup_guide';
import { Overview } from './components/overview';
import { WorkplaceSearch } from './';
@@ -18,16 +17,18 @@ import { WorkplaceSearch } from './';
describe('Workplace Search', () => {
describe('/', () => {
it('redirects to Setup Guide when enterpriseSearchUrl is not set', () => {
- (useContext as jest.Mock).mockImplementationOnce(() => ({ enterpriseSearchUrl: '' }));
+ (useContext as jest.Mock).mockImplementationOnce(() => ({
+ config: { host: '' },
+ }));
const wrapper = shallow();
expect(wrapper.find(Redirect)).toHaveLength(1);
expect(wrapper.find(Overview)).toHaveLength(0);
});
- it('renders Engine Overview when enterpriseSearchUrl is set', () => {
+ it('renders the Overview when enterpriseSearchUrl is set', () => {
(useContext as jest.Mock).mockImplementationOnce(() => ({
- enterpriseSearchUrl: 'https://foo.bar',
+ config: { host: 'https://foo.bar' },
}));
const wrapper = shallow();
@@ -35,12 +36,4 @@ describe('Workplace Search', () => {
expect(wrapper.find(Redirect)).toHaveLength(0);
});
});
-
- describe('/setup_guide', () => {
- it('renders', () => {
- const wrapper = shallow();
-
- expect(wrapper.find(SetupGuide)).toHaveLength(1);
- });
- });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
index 6470a3b78c5f1..ca0d395c0d673 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/index.tsx
@@ -24,8 +24,8 @@ import { SetupGuide } from './components/setup_guide';
import { Overview } from './components/overview';
export const WorkplaceSearch: React.FC = () => {
- const { enterpriseSearchUrl } = useContext(KibanaContext) as IKibanaContext;
- if (!enterpriseSearchUrl)
+ const { config } = useContext(KibanaContext) as IKibanaContext;
+ if (!config.host)
return (
diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts
index 42ad7de93b00e..0d392eefe0aa2 100644
--- a/x-pack/plugins/enterprise_search/public/plugin.ts
+++ b/x-pack/plugins/enterprise_search/public/plugin.ts
@@ -21,13 +21,21 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { LicensingPluginSetup } from '../../licensing/public';
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../common/constants';
-import { getPublicUrl } from './applications/shared/enterprise_search_url';
+import {
+ getPublicUrl,
+ ExternalUrl,
+ IExternalUrl,
+} from './applications/shared/enterprise_search_url';
import AppSearchLogo from './applications/app_search/assets/logo.svg';
import WorkplaceSearchLogo from './applications/workplace_search/assets/logo.svg';
export interface ClientConfigType {
host?: string;
}
+export interface ClientData {
+ externalUrl: IExternalUrl;
+}
+
export interface PluginsSetup {
home: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
@@ -35,15 +43,15 @@ export interface PluginsSetup {
export class EnterpriseSearchPlugin implements Plugin {
private config: ClientConfigType;
- private hasCheckedPublicUrl: boolean = false;
+ private hasInitialized: boolean = false;
+ private data: ClientData = {} as ClientData;
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get();
+ this.data.externalUrl = new ExternalUrl(this.config.host || '');
}
public setup(core: CoreSetup, plugins: PluginsSetup) {
- const config = { host: this.config.host };
-
core.application.register({
id: APP_SEARCH_PLUGIN.ID,
title: APP_SEARCH_PLUGIN.NAME,
@@ -54,12 +62,12 @@ export class EnterpriseSearchPlugin implements Plugin {
const { chrome } = coreStart;
chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
- await this.setPublicUrl(config, coreStart.http);
+ await this.getInitialData(coreStart.http);
const { renderApp } = await import('./applications');
const { AppSearch } = await import('./applications/app_search');
- return renderApp(AppSearch, coreStart, params, config, plugins);
+ return renderApp(AppSearch, params, coreStart, plugins, this.config, this.data);
},
});
@@ -73,12 +81,12 @@ export class EnterpriseSearchPlugin implements Plugin {
const { chrome } = coreStart;
chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME);
- await this.setPublicUrl(config, coreStart.http);
+ await this.getInitialData(coreStart.http);
const { renderApp } = await import('./applications');
const { WorkplaceSearch } = await import('./applications/workplace_search');
- return renderApp(WorkplaceSearch, coreStart, params, config, plugins);
+ return renderApp(WorkplaceSearch, params, coreStart, plugins, this.config, this.data);
},
});
@@ -107,12 +115,14 @@ export class EnterpriseSearchPlugin implements Plugin {
public stop() {}
- private async setPublicUrl(config: ClientConfigType, http: HttpSetup) {
- if (!config.host) return; // No API to check
- if (this.hasCheckedPublicUrl) return; // We've already performed the check
+ private async getInitialData(http: HttpSetup) {
+ if (!this.config.host) return; // No API to call
+ if (this.hasInitialized) return; // We've already made an initial call
+ // TODO: Rename to something more generic once we start fetching more data than just external_url from this endpoint
const publicUrl = await getPublicUrl(http);
- if (publicUrl) config.host = publicUrl;
- this.hasCheckedPublicUrl = true;
+
+ if (publicUrl) this.data.externalUrl = new ExternalUrl(publicUrl);
+ this.hasInitialized = true;
}
}
From 7d3fe58dfad14458c0b9a5bf20afe7b27e8aadc2 Mon Sep 17 00:00:00 2001
From: Spencer
Date: Mon, 17 Aug 2020 09:54:59 -0700
Subject: [PATCH 004/177] [jenkins/security-cypress] send status emails to
entire team (#75171)
Co-authored-by: spalger
---
.ci/Jenkinsfile_security_cypress | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.ci/Jenkinsfile_security_cypress b/.ci/Jenkinsfile_security_cypress
index bdfef18024b78..d0167cfd18099 100644
--- a/.ci/Jenkinsfile_security_cypress
+++ b/.ci/Jenkinsfile_security_cypress
@@ -16,6 +16,6 @@ kibanaPipeline(timeoutMinutes: 180) {
}
if (params.NOTIFY_ON_FAILURE) {
- kibanaPipeline.sendMail(to: 'gloria.delatorre@elastic.co')
+ kibanaPipeline.sendMail(to: 'siem_dev_team@elastic.co')
}
}
From 22f0641ec3fae1864f318ba8c5fc18748485bb9f Mon Sep 17 00:00:00 2001
From: Peter Pisljar
Date: Mon, 17 Aug 2020 20:21:48 +0200
Subject: [PATCH 005/177] esdsl (#69254)
---
...ublic.esdslexpressionfunctiondefinition.md | 11 +
...c.esrawresponseexpressiontypedefinition.md | 11 +
.../kibana-plugin-plugins-data-public.md | 2 +
src/plugins/data/public/index.ts | 3 +
src/plugins/data/public/plugin.ts | 2 +-
src/plugins/data/public/public.api.md | 47 +-
.../es_raw_response.test.ts.snap | 193 ++++
.../__snapshots__/esdsl.test.ts.snap | 191 ++++
.../expressions/es_raw_response.test.ts | 827 ++++++++++++++++++
.../search/expressions/es_raw_response.ts | 102 +++
.../public/search/expressions/esdsl.test.ts | 170 ++++
.../data/public/search/expressions/esdsl.ts | 201 +++++
.../data/public/search/expressions/index.ts | 2 +
.../data/public/search/search_service.test.ts | 2 +-
.../data/public/search/search_service.ts | 13 +-
15 files changed, 1756 insertions(+), 21 deletions(-)
create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md
create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md
create mode 100644 src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap
create mode 100644 src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap
create mode 100644 src/plugins/data/public/search/expressions/es_raw_response.test.ts
create mode 100644 src/plugins/data/public/search/expressions/es_raw_response.ts
create mode 100644 src/plugins/data/public/search/expressions/esdsl.test.ts
create mode 100644 src/plugins/data/public/search/expressions/esdsl.ts
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md
new file mode 100644
index 0000000000000..0bd00e937eaaa
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md)
+
+## EsdslExpressionFunctionDefinition type
+
+Signature:
+
+```typescript
+export declare type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md
new file mode 100644
index 0000000000000..b95ae3c69bf20
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsRawResponseExpressionTypeDefinition](./kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md)
+
+## EsRawResponseExpressionTypeDefinition type
+
+Signature:
+
+```typescript
+export declare type EsRawResponseExpressionTypeDefinition = ExpressionTypeDefinition;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index 53c30b52cb985..dc83cfb930d7d 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -126,7 +126,9 @@
| [AggParam](./kibana-plugin-plugins-data-public.aggparam.md) | |
| [CustomFilter](./kibana-plugin-plugins-data-public.customfilter.md) | |
| [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esaggsexpressionfunctiondefinition.md) | |
+| [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md) | |
| [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | |
+| [EsRawResponseExpressionTypeDefinition](./kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md) | |
| [ExistsFilter](./kibana-plugin-plugins-data-public.existsfilter.md) | |
| [FieldFormatId](./kibana-plugin-plugins-data-public.fieldformatid.md) | id type is needed for creating custom converters. |
| [FieldFormatsContentType](./kibana-plugin-plugins-data-public.fieldformatscontenttype.md) | \* |
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index d35069207ee84..ecf076aa517fb 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -363,6 +363,9 @@ export {
SearchResponse,
SearchSourceFields,
SortDirection,
+ // expression functions and types
+ EsdslExpressionFunctionDefinition,
+ EsRawResponseExpressionTypeDefinition,
} from './search';
// Search namespace
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index e6a48794d8b0f..564c571b6ccd6 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -148,7 +148,7 @@ export class DataPublicPlugin
const searchService = this.searchService.setup(core, {
usageCollection,
packageInfo: this.packageInfo,
- registerFunction: expressions.registerFunction,
+ expressions,
});
return {
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 744376403e1a1..58c2bd9957ab8 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -348,6 +348,15 @@ export const ES_SEARCH_STRATEGY = "es";
// @public (undocumented)
export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition<'esaggs', Input, Arguments, Output>;
+// Warning: (ae-forgotten-export) The symbol "name" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "Input" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "Arguments" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "Output" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "EsdslExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition;
+
// Warning: (ae-missing-release-tag) "esFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -444,6 +453,14 @@ export interface EsQueryConfig {
// @public (undocumented)
export type EsQuerySortValue = Record;
+// Warning: (ae-forgotten-export) The symbol "ExpressionTypeDefinition" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "name" needs to be exported by the entry point index.d.ts
+// Warning: (ae-forgotten-export) The symbol "EsRawResponse" needs to be exported by the entry point index.d.ts
+// Warning: (ae-missing-release-tag) "EsRawResponseExpressionTypeDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type EsRawResponseExpressionTypeDefinition = ExpressionTypeDefinition;
+
// Warning: (ae-missing-release-tag) "ExistsFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1975,21 +1992,21 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:369:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:371:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:372:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:372:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:374:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:375:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:62:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap b/src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap
new file mode 100644
index 0000000000000..c43663a50a2ba
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/__snapshots__/es_raw_response.test.ts.snap
@@ -0,0 +1,193 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`esRawResponse converts aggregations to table simple aggregation response 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "2.buckets.key",
+ "meta": Object {
+ "field": "2.buckets.key",
+ "params": Object {},
+ "type": "string",
+ },
+ "name": "2.buckets.key",
+ },
+ Object {
+ "id": "2.buckets.doc_count",
+ "meta": Object {
+ "field": "2.buckets.doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.buckets.doc_count",
+ },
+ Object {
+ "id": "2.doc_count_error_upper_bound",
+ "meta": Object {
+ "field": "2.doc_count_error_upper_bound",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.doc_count_error_upper_bound",
+ },
+ Object {
+ "id": "2.sum_other_doc_count",
+ "meta": Object {
+ "field": "2.sum_other_doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.sum_other_doc_count",
+ },
+ ],
+ "meta": Object {
+ "source": "*",
+ "type": "esdsl",
+ },
+ "rows": Array [
+ Object {
+ "2.buckets.doc_count": 1033,
+ "2.buckets.key": "FEMALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ Object {
+ "2.buckets.doc_count": 944,
+ "2.buckets.key": "MALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ ],
+ "type": "datatable",
+}
+`;
+
+exports[`esRawResponse converts raw docs to table simple docs response 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "order_date",
+ "meta": Object {
+ "field": "order_date",
+ "params": Object {},
+ "type": "object",
+ },
+ "name": "order_date",
+ },
+ Object {
+ "id": "products.created_on",
+ "meta": Object {
+ "field": "products.created_on",
+ "params": Object {},
+ "type": "object",
+ },
+ "name": "products.created_on",
+ },
+ ],
+ "meta": Object {
+ "source": "*",
+ "type": "esdsl",
+ },
+ "rows": Array [
+ Object {
+ "order_date": Array [
+ "2020-07-13T09:27:22.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-12T09:27:22.000Z",
+ "2016-12-12T09:27:22.000Z",
+ ],
+ },
+ Object {
+ "order_date": Array [
+ "2020-07-15T08:12:29.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-14T08:12:29.000Z",
+ "2016-12-14T08:12:29.000Z",
+ ],
+ },
+ Object {
+ "order_date": Array [
+ "2020-07-15T01:26:24.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-14T01:26:24.000Z",
+ "2016-12-14T01:26:24.000Z",
+ ],
+ },
+ Object {
+ "order_date": Array [
+ "2020-07-10T19:55:12.000Z",
+ ],
+ "products.created_on": Array [
+ "2016-12-09T19:55:12.000Z",
+ "2016-12-09T19:55:12.000Z",
+ ],
+ },
+ ],
+ "type": "datatable",
+}
+`;
+
+exports[`esRawResponse returns aggs if both docs and aggs are present on response 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "2.buckets.key",
+ "meta": Object {
+ "field": "2.buckets.key",
+ "params": Object {},
+ "type": "string",
+ },
+ "name": "2.buckets.key",
+ },
+ Object {
+ "id": "2.buckets.doc_count",
+ "meta": Object {
+ "field": "2.buckets.doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.buckets.doc_count",
+ },
+ Object {
+ "id": "2.doc_count_error_upper_bound",
+ "meta": Object {
+ "field": "2.doc_count_error_upper_bound",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.doc_count_error_upper_bound",
+ },
+ Object {
+ "id": "2.sum_other_doc_count",
+ "meta": Object {
+ "field": "2.sum_other_doc_count",
+ "params": Object {},
+ "type": "number",
+ },
+ "name": "2.sum_other_doc_count",
+ },
+ ],
+ "meta": Object {
+ "source": "*",
+ "type": "esdsl",
+ },
+ "rows": Array [
+ Object {
+ "2.buckets.doc_count": 1033,
+ "2.buckets.key": "FEMALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ Object {
+ "2.buckets.doc_count": 944,
+ "2.buckets.key": "MALE",
+ "2.doc_count_error_upper_bound": 0,
+ "2.sum_other_doc_count": 0,
+ },
+ ],
+ "type": "datatable",
+}
+`;
diff --git a/src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap b/src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap
new file mode 100644
index 0000000000000..cd71217276373
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/__snapshots__/esdsl.test.ts.snap
@@ -0,0 +1,191 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`esdsl correctly handles filter, query and timerange on context 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "_source": false,
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "match_phrase": Object {
+ "gender": "male",
+ },
+ },
+ ],
+ "must": Array [
+ Object {
+ "query_string": Object {
+ "query": "*",
+ "time_zone": true,
+ },
+ },
+ Object {
+ "term": Object {
+ "machine.os.keyword": "osx",
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "kibana_sample_data_logs",
+ "size": 4,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds filters 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "match_phrase": Object {
+ "gender": "male",
+ },
+ },
+ ],
+ "must": Array [],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "test",
+ "size": 0,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds filters to query with filters 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "_source": false,
+ "query": Object {
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "match_phrase": Object {
+ "gender": "male",
+ },
+ },
+ ],
+ "must": Array [
+ Object {
+ "term": Object {
+ "machine.os.keyword": "osx",
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "kibana_sample_data_logs",
+ "size": 4,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds query 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ "must": Array [
+ Object {
+ "query_string": Object {
+ "query": "*",
+ "time_zone": true,
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "test",
+ "size": 0,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input adds query to a query with filters 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "_source": false,
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ "must": Array [
+ Object {
+ "query_string": Object {
+ "query": "*",
+ "time_zone": true,
+ },
+ },
+ Object {
+ "term": Object {
+ "machine.os.keyword": "osx",
+ },
+ },
+ ],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "kibana_sample_data_logs",
+ "size": 4,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
+
+exports[`esdsl correctly handles input ignores timerange 1`] = `
+Object {
+ "body": Object {
+ "params": Object {
+ "body": Object {
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ "must": Array [],
+ "must_not": Array [],
+ "should": Array [],
+ },
+ },
+ },
+ "index": "test",
+ "size": 0,
+ },
+ },
+ "type": "es_raw_response",
+}
+`;
diff --git a/src/plugins/data/public/search/expressions/es_raw_response.test.ts b/src/plugins/data/public/search/expressions/es_raw_response.test.ts
new file mode 100644
index 0000000000000..4acb75fa4a255
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/es_raw_response.test.ts
@@ -0,0 +1,827 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EsRawResponse, esRawResponse } from './es_raw_response';
+
+jest.mock('@kbn/i18n', () => {
+ return {
+ i18n: {
+ translate: (id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage,
+ },
+ };
+});
+
+describe('esRawResponse', () => {
+ describe('converts aggregations to table', () => {
+ test('simple aggregation response', () => {
+ const response: EsRawResponse = {
+ type: 'es_raw_response',
+ body: {
+ took: 7,
+ timed_out: false,
+ _shards: {
+ total: 7,
+ successful: 7,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 1977,
+ max_score: 0,
+ hits: [],
+ },
+ aggregations: {
+ '2': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'FEMALE',
+ doc_count: 1033,
+ },
+ {
+ key: 'MALE',
+ doc_count: 944,
+ },
+ ],
+ },
+ },
+ },
+ };
+ const result = esRawResponse.to!.datatable(response, {});
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ describe('converts raw docs to table', () => {
+ test('simple docs response', () => {
+ const response: EsRawResponse = {
+ type: 'es_raw_response',
+ body: {
+ took: 5,
+ timed_out: false,
+ _shards: {
+ total: 7,
+ successful: 7,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 1977,
+ max_score: 0,
+ hits: [
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'AncqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Rios',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Rios',
+ customer_phone: '',
+ day_of_week: 'Monday',
+ day_of_week_i: 0,
+ email: 'oliver@rios-family.zzz',
+ manufacturer: ['Low Tide Media', 'Elitelligence'],
+ order_date: '2020-07-13T09:27:22+00:00',
+ order_id: 565855,
+ products: [
+ {
+ base_price: 20.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 19919,
+ category: "Men's Clothing",
+ sku: 'ZO0417504175',
+ taxless_price: 20.99,
+ unit_discount_amount: 0,
+ min_price: 9.87,
+ _id: 'sold_product_565855_19919',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Shirt - dark blue white',
+ price: 20.99,
+ taxful_price: 20.99,
+ base_unit_price: 20.99,
+ },
+ {
+ base_price: 24.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 24502,
+ category: "Men's Clothing",
+ sku: 'ZO0535205352',
+ taxless_price: 24.99,
+ unit_discount_amount: 0,
+ min_price: 12.49,
+ _id: 'sold_product_565855_24502',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Slim fit jeans - raw blue',
+ price: 24.99,
+ taxful_price: 24.99,
+ base_unit_price: 24.99,
+ },
+ ],
+ sku: ['ZO0417504175', 'ZO0535205352'],
+ taxful_total_price: 45.98,
+ taxless_total_price: 45.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-13T09:27:22.000Z'],
+ 'products.created_on': ['2016-12-12T09:27:22.000Z', '2016-12-12T09:27:22.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'I3cqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Boris',
+ customer_full_name: 'Boris Bradley',
+ customer_gender: 'MALE',
+ customer_id: 36,
+ customer_last_name: 'Bradley',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'boris@bradley-family.zzz',
+ manufacturer: ['Microlutions', 'Elitelligence'],
+ order_date: '2020-07-15T08:12:29+00:00',
+ order_id: 568397,
+ products: [
+ {
+ base_price: 32.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 24419,
+ category: "Men's Clothing",
+ sku: 'ZO0112101121',
+ taxless_price: 32.99,
+ unit_discount_amount: 0,
+ min_price: 17.48,
+ _id: 'sold_product_568397_24419',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Cargo trousers - oliv',
+ price: 32.99,
+ taxful_price: 32.99,
+ base_unit_price: 32.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 20207,
+ category: "Men's Clothing",
+ sku: 'ZO0530405304',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 13.92,
+ _id: 'sold_product_568397_20207',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Trousers - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0112101121', 'ZO0530405304'],
+ taxful_total_price: 61.98,
+ taxless_total_price: 61.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'boris',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T08:12:29.000Z'],
+ 'products.created_on': ['2016-12-14T08:12:29.000Z', '2016-12-14T08:12:29.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'JHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Hubbard',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Hubbard',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'oliver@hubbard-family.zzz',
+ manufacturer: ['Spritechnologies', 'Microlutions'],
+ order_date: '2020-07-15T01:26:24+00:00',
+ order_id: 568044,
+ products: [
+ {
+ base_price: 14.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Spritechnologies',
+ tax_amount: 0,
+ product_id: 12799,
+ category: "Men's Clothing",
+ sku: 'ZO0630406304',
+ taxless_price: 14.99,
+ unit_discount_amount: 0,
+ min_price: 6.9,
+ _id: 'sold_product_568044_12799',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Undershirt - dark grey multicolor',
+ price: 14.99,
+ taxful_price: 14.99,
+ base_unit_price: 14.99,
+ },
+ {
+ base_price: 16.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 18008,
+ category: "Men's Clothing",
+ sku: 'ZO0120201202',
+ taxless_price: 16.99,
+ unit_discount_amount: 0,
+ min_price: 8.83,
+ _id: 'sold_product_568044_18008',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Long sleeved top - purple',
+ price: 16.99,
+ taxful_price: 16.99,
+ base_unit_price: 16.99,
+ },
+ ],
+ sku: ['ZO0630406304', 'ZO0120201202'],
+ taxful_total_price: 31.98,
+ taxless_total_price: 31.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T01:26:24.000Z'],
+ 'products.created_on': ['2016-12-14T01:26:24.000Z', '2016-12-14T01:26:24.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'LHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Women's Shoes", "Women's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Wilhemina St.',
+ customer_full_name: 'Wilhemina St. Parker',
+ customer_gender: 'FEMALE',
+ customer_id: 17,
+ customer_last_name: 'Parker',
+ customer_phone: '',
+ day_of_week: 'Friday',
+ day_of_week_i: 4,
+ email: 'wilhemina st.@parker-family.zzz',
+ manufacturer: ['Low Tide Media', 'Tigress Enterprises'],
+ order_date: '2020-07-10T19:55:12+00:00',
+ order_id: 562351,
+ products: [
+ {
+ base_price: 49.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 18495,
+ category: "Women's Shoes",
+ sku: 'ZO0376403764',
+ taxless_price: 49.99,
+ unit_discount_amount: 0,
+ min_price: 25,
+ _id: 'sold_product_562351_18495',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Ankle boots - cognac',
+ price: 49.99,
+ taxful_price: 49.99,
+ base_unit_price: 49.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Tigress Enterprises',
+ tax_amount: 0,
+ product_id: 22598,
+ category: "Women's Clothing",
+ sku: 'ZO0050800508',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 14.78,
+ _id: 'sold_product_562351_22598',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Shift dress - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0376403764', 'ZO0050800508'],
+ taxful_total_price: 78.98,
+ taxless_total_price: 78.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'wilhemina',
+ geoip: {
+ country_iso_code: 'MC',
+ location: {
+ lon: 7.4,
+ lat: 43.7,
+ },
+ continent_name: 'Europe',
+ city_name: 'Monte Carlo',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-10T19:55:12.000Z'],
+ 'products.created_on': ['2016-12-09T19:55:12.000Z', '2016-12-09T19:55:12.000Z'],
+ },
+ },
+ ],
+ },
+ },
+ };
+ const result = esRawResponse.to!.datatable(response, {});
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ test('returns aggs if both docs and aggs are present on response', () => {
+ const response: EsRawResponse = {
+ type: 'es_raw_response',
+ body: {
+ took: 5,
+ timed_out: false,
+ _shards: {
+ total: 7,
+ successful: 7,
+ skipped: 0,
+ failed: 0,
+ },
+ hits: {
+ total: 1977,
+ max_score: 0,
+ hits: [
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'AncqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Rios',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Rios',
+ customer_phone: '',
+ day_of_week: 'Monday',
+ day_of_week_i: 0,
+ email: 'oliver@rios-family.zzz',
+ manufacturer: ['Low Tide Media', 'Elitelligence'],
+ order_date: '2020-07-13T09:27:22+00:00',
+ order_id: 565855,
+ products: [
+ {
+ base_price: 20.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 19919,
+ category: "Men's Clothing",
+ sku: 'ZO0417504175',
+ taxless_price: 20.99,
+ unit_discount_amount: 0,
+ min_price: 9.87,
+ _id: 'sold_product_565855_19919',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Shirt - dark blue white',
+ price: 20.99,
+ taxful_price: 20.99,
+ base_unit_price: 20.99,
+ },
+ {
+ base_price: 24.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 24502,
+ category: "Men's Clothing",
+ sku: 'ZO0535205352',
+ taxless_price: 24.99,
+ unit_discount_amount: 0,
+ min_price: 12.49,
+ _id: 'sold_product_565855_24502',
+ discount_amount: 0,
+ created_on: '2016-12-12T09:27:22+00:00',
+ product_name: 'Slim fit jeans - raw blue',
+ price: 24.99,
+ taxful_price: 24.99,
+ base_unit_price: 24.99,
+ },
+ ],
+ sku: ['ZO0417504175', 'ZO0535205352'],
+ taxful_total_price: 45.98,
+ taxless_total_price: 45.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-13T09:27:22.000Z'],
+ 'products.created_on': ['2016-12-12T09:27:22.000Z', '2016-12-12T09:27:22.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'I3cqUnMBMY_orZma2mZy',
+ _type: 'document',
+ _score: 0,
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Boris',
+ customer_full_name: 'Boris Bradley',
+ customer_gender: 'MALE',
+ customer_id: 36,
+ customer_last_name: 'Bradley',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'boris@bradley-family.zzz',
+ manufacturer: ['Microlutions', 'Elitelligence'],
+ order_date: '2020-07-15T08:12:29+00:00',
+ order_id: 568397,
+ products: [
+ {
+ base_price: 32.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 24419,
+ category: "Men's Clothing",
+ sku: 'ZO0112101121',
+ taxless_price: 32.99,
+ unit_discount_amount: 0,
+ min_price: 17.48,
+ _id: 'sold_product_568397_24419',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Cargo trousers - oliv',
+ price: 32.99,
+ taxful_price: 32.99,
+ base_unit_price: 32.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Elitelligence',
+ tax_amount: 0,
+ product_id: 20207,
+ category: "Men's Clothing",
+ sku: 'ZO0530405304',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 13.92,
+ _id: 'sold_product_568397_20207',
+ discount_amount: 0,
+ created_on: '2016-12-14T08:12:29+00:00',
+ product_name: 'Trousers - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0112101121', 'ZO0530405304'],
+ taxful_total_price: 61.98,
+ taxless_total_price: 61.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'boris',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T08:12:29.000Z'],
+ 'products.created_on': ['2016-12-14T08:12:29.000Z', '2016-12-14T08:12:29.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'JHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Men's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Oliver',
+ customer_full_name: 'Oliver Hubbard',
+ customer_gender: 'MALE',
+ customer_id: 7,
+ customer_last_name: 'Hubbard',
+ customer_phone: '',
+ day_of_week: 'Wednesday',
+ day_of_week_i: 2,
+ email: 'oliver@hubbard-family.zzz',
+ manufacturer: ['Spritechnologies', 'Microlutions'],
+ order_date: '2020-07-15T01:26:24+00:00',
+ order_id: 568044,
+ products: [
+ {
+ base_price: 14.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Spritechnologies',
+ tax_amount: 0,
+ product_id: 12799,
+ category: "Men's Clothing",
+ sku: 'ZO0630406304',
+ taxless_price: 14.99,
+ unit_discount_amount: 0,
+ min_price: 6.9,
+ _id: 'sold_product_568044_12799',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Undershirt - dark grey multicolor',
+ price: 14.99,
+ taxful_price: 14.99,
+ base_unit_price: 14.99,
+ },
+ {
+ base_price: 16.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Microlutions',
+ tax_amount: 0,
+ product_id: 18008,
+ category: "Men's Clothing",
+ sku: 'ZO0120201202',
+ taxless_price: 16.99,
+ unit_discount_amount: 0,
+ min_price: 8.83,
+ _id: 'sold_product_568044_18008',
+ discount_amount: 0,
+ created_on: '2016-12-14T01:26:24+00:00',
+ product_name: 'Long sleeved top - purple',
+ price: 16.99,
+ taxful_price: 16.99,
+ base_unit_price: 16.99,
+ },
+ ],
+ sku: ['ZO0630406304', 'ZO0120201202'],
+ taxful_total_price: 31.98,
+ taxless_total_price: 31.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'oliver',
+ geoip: {
+ country_iso_code: 'GB',
+ location: {
+ lon: -0.1,
+ lat: 51.5,
+ },
+ continent_name: 'Europe',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-15T01:26:24.000Z'],
+ 'products.created_on': ['2016-12-14T01:26:24.000Z', '2016-12-14T01:26:24.000Z'],
+ },
+ },
+ {
+ _index: 'kibana_sample_data_ecommerce',
+ _id: 'LHcqUnMBMY_orZma2mZy',
+ _score: 0,
+ _type: 'document',
+ _source: {
+ category: ["Women's Shoes", "Women's Clothing"],
+ currency: 'EUR',
+ customer_first_name: 'Wilhemina St.',
+ customer_full_name: 'Wilhemina St. Parker',
+ customer_gender: 'FEMALE',
+ customer_id: 17,
+ customer_last_name: 'Parker',
+ customer_phone: '',
+ day_of_week: 'Friday',
+ day_of_week_i: 4,
+ email: 'wilhemina st.@parker-family.zzz',
+ manufacturer: ['Low Tide Media', 'Tigress Enterprises'],
+ order_date: '2020-07-10T19:55:12+00:00',
+ order_id: 562351,
+ products: [
+ {
+ base_price: 49.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Low Tide Media',
+ tax_amount: 0,
+ product_id: 18495,
+ category: "Women's Shoes",
+ sku: 'ZO0376403764',
+ taxless_price: 49.99,
+ unit_discount_amount: 0,
+ min_price: 25,
+ _id: 'sold_product_562351_18495',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Ankle boots - cognac',
+ price: 49.99,
+ taxful_price: 49.99,
+ base_unit_price: 49.99,
+ },
+ {
+ base_price: 28.99,
+ discount_percentage: 0,
+ quantity: 1,
+ manufacturer: 'Tigress Enterprises',
+ tax_amount: 0,
+ product_id: 22598,
+ category: "Women's Clothing",
+ sku: 'ZO0050800508',
+ taxless_price: 28.99,
+ unit_discount_amount: 0,
+ min_price: 14.78,
+ _id: 'sold_product_562351_22598',
+ discount_amount: 0,
+ created_on: '2016-12-09T19:55:12+00:00',
+ product_name: 'Shift dress - black',
+ price: 28.99,
+ taxful_price: 28.99,
+ base_unit_price: 28.99,
+ },
+ ],
+ sku: ['ZO0376403764', 'ZO0050800508'],
+ taxful_total_price: 78.98,
+ taxless_total_price: 78.98,
+ total_quantity: 2,
+ total_unique_products: 2,
+ type: 'order',
+ user: 'wilhemina',
+ geoip: {
+ country_iso_code: 'MC',
+ location: {
+ lon: 7.4,
+ lat: 43.7,
+ },
+ continent_name: 'Europe',
+ city_name: 'Monte Carlo',
+ },
+ event: {
+ dataset: 'sample_ecommerce',
+ },
+ },
+ fields: {
+ order_date: ['2020-07-10T19:55:12.000Z'],
+ 'products.created_on': ['2016-12-09T19:55:12.000Z', '2016-12-09T19:55:12.000Z'],
+ },
+ },
+ ],
+ },
+ aggregations: {
+ '2': {
+ doc_count_error_upper_bound: 0,
+ sum_other_doc_count: 0,
+ buckets: [
+ {
+ key: 'FEMALE',
+ doc_count: 1033,
+ },
+ {
+ key: 'MALE',
+ doc_count: 944,
+ },
+ ],
+ },
+ },
+ },
+ };
+ const result = esRawResponse.to!.datatable(response, {});
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/data/public/search/expressions/es_raw_response.ts b/src/plugins/data/public/search/expressions/es_raw_response.ts
new file mode 100644
index 0000000000000..bd0fcb3d49c54
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/es_raw_response.ts
@@ -0,0 +1,102 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SearchResponse } from 'elasticsearch';
+import { ExpressionTypeDefinition } from '../../../../expressions/common';
+
+const name = 'es_raw_response';
+
+export interface EsRawResponse {
+ type: typeof name;
+ body: SearchResponse;
+}
+
+// flattens elasticsearch object into table rows
+function flatten(obj: any, keyPrefix = '') {
+ let topLevelKeys: Record = {};
+ const nestedRows: any[] = [];
+ const prefix = keyPrefix ? keyPrefix + '.' : '';
+ Object.keys(obj).forEach((key) => {
+ if (Array.isArray(obj[key])) {
+ nestedRows.push(
+ ...obj[key]
+ .map((nestedRow: any) => flatten(nestedRow, prefix + key))
+ .reduce((acc: any, object: any) => [...acc, ...object], [])
+ );
+ } else if (typeof obj[key] === 'object' && obj[key] !== null) {
+ const subRows = flatten(obj[key], prefix + key);
+ if (subRows.length === 1) {
+ topLevelKeys = { ...topLevelKeys, ...subRows[0] };
+ } else {
+ nestedRows.push(...subRows);
+ }
+ } else {
+ topLevelKeys[prefix + key] = obj[key];
+ }
+ });
+ if (nestedRows.length === 0) {
+ return [topLevelKeys];
+ } else {
+ return nestedRows.map((nestedRow) => ({ ...nestedRow, ...topLevelKeys }));
+ }
+}
+
+const parseRawDocs = (hits: SearchResponse['hits']) => {
+ return hits.hits.map((hit) => hit.fields || hit._source).filter((hit) => hit);
+};
+
+const convertResult = (body: SearchResponse) => {
+ return !body.aggregations ? parseRawDocs(body.hits) : flatten(body.aggregations);
+};
+
+export type EsRawResponseExpressionTypeDefinition = ExpressionTypeDefinition<
+ typeof name,
+ EsRawResponse,
+ EsRawResponse
+>;
+
+export const esRawResponse: EsRawResponseExpressionTypeDefinition = {
+ name,
+ to: {
+ datatable: (context: EsRawResponse) => {
+ const rows = convertResult(context.body);
+ const columns = rows.length
+ ? Object.keys(rows[0]).map((key) => ({
+ id: key,
+ name: key,
+ meta: {
+ type: typeof rows[0][key],
+ field: key,
+ params: {},
+ },
+ }))
+ : [];
+
+ return {
+ type: 'datatable',
+ meta: {
+ type: 'esdsl',
+ source: '*',
+ },
+ columns,
+ rows,
+ };
+ },
+ },
+};
diff --git a/src/plugins/data/public/search/expressions/esdsl.test.ts b/src/plugins/data/public/search/expressions/esdsl.test.ts
new file mode 100644
index 0000000000000..9458962464f65
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/esdsl.test.ts
@@ -0,0 +1,170 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { esdsl } from './esdsl';
+
+jest.mock('@kbn/i18n', () => {
+ return {
+ i18n: {
+ translate: (id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage,
+ },
+ };
+});
+
+jest.mock('../../services', () => ({
+ getUiSettings: () => ({
+ get: () => true,
+ }),
+ getSearchService: () => ({
+ search: jest.fn((params: any) => {
+ return {
+ toPromise: async () => {
+ return { rawResponse: params };
+ },
+ };
+ }),
+ }),
+}));
+
+describe('esdsl', () => {
+ describe('correctly handles input', () => {
+ test('throws on invalid json input', async () => {
+ const fn = async function () {
+ await esdsl().fn(null, { dsl: 'invalid json', index: 'test', size: 0 }, {
+ inspectorAdapters: {},
+ } as any);
+ };
+
+ let errorMessage;
+ try {
+ await fn();
+ } catch (error) {
+ errorMessage = error.message;
+ }
+ expect(errorMessage).toEqual('Unexpected token i in JSON at position 0');
+ });
+
+ test('adds filters', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ filters: [
+ {
+ meta: { index: '1', alias: 'test', negate: false, disabled: false },
+ query: { match_phrase: { gender: 'male' } },
+ },
+ ],
+ },
+ { dsl: '{}', index: 'test', size: 0 },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('adds filters to query with filters', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ filters: [
+ {
+ meta: { index: '1', alias: 'test', negate: false, disabled: false },
+ query: { match_phrase: { gender: 'male' } },
+ },
+ ],
+ },
+ {
+ index: 'kibana_sample_data_logs',
+ size: 4,
+ dsl: '{"_source": false, "query": { "term": { "machine.os.keyword": "osx"}}}',
+ },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('adds query', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ query: { language: 'lucene', query: '*' },
+ },
+ { dsl: '{}', index: 'test', size: 0 },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('adds query to a query with filters', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ query: { language: 'lucene', query: '*' },
+ },
+ {
+ index: 'kibana_sample_data_logs',
+ size: 4,
+ dsl: '{ "_source": false, "query": { "term": { "machine.os.keyword": "osx"}}}',
+ },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+
+ test('ignores timerange', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ timeRange: { from: 'now-15m', to: 'now' },
+ },
+ { dsl: '{}', index: 'test', size: 0 },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+ });
+
+ test('correctly handles filter, query and timerange on context', async () => {
+ const result = await esdsl().fn(
+ {
+ type: 'kibana_context',
+ query: { language: 'lucene', query: '*' },
+ timeRange: { from: 'now-15m', to: 'now' },
+ filters: [
+ {
+ meta: { index: '1', alias: 'test', negate: false, disabled: false },
+ query: { match_phrase: { gender: 'male' } },
+ },
+ ],
+ },
+ {
+ index: 'kibana_sample_data_logs',
+ size: 4,
+ dsl: '{ "_source": false, "query": { "term": { "machine.os.keyword": "osx"}}}',
+ },
+ { inspectorAdapters: {} } as any
+ );
+
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/src/plugins/data/public/search/expressions/esdsl.ts b/src/plugins/data/public/search/expressions/esdsl.ts
new file mode 100644
index 0000000000000..d7b897ace29b4
--- /dev/null
+++ b/src/plugins/data/public/search/expressions/esdsl.ts
@@ -0,0 +1,201 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import {
+ KibanaContext,
+ ExpressionFunctionDefinition,
+} from '../../../../../plugins/expressions/public';
+
+import { getSearchService, getUiSettings } from '../../services';
+import { EsRawResponse } from './es_raw_response';
+import { RequestStatistics, RequestAdapter } from '../../../../inspector/common';
+import { IEsSearchResponse } from '../../../common/search/es_search';
+import { buildEsQuery, getEsQueryConfig } from '../../../common/es_query/es_query';
+import { DataPublicPluginStart } from '../../types';
+
+const name = 'esdsl';
+
+type Input = KibanaContext | null;
+type Output = Promise;
+
+interface Arguments {
+ dsl: string;
+ index: string;
+ size: number;
+}
+
+export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition<
+ typeof name,
+ Input,
+ Arguments,
+ Output
+>;
+
+export const esdsl = (): EsdslExpressionFunctionDefinition => ({
+ name,
+ type: 'es_raw_response',
+ inputTypes: ['kibana_context', 'null'],
+ help: i18n.translate('data.search.esdsl.help', {
+ defaultMessage: 'Run Elasticsearch request',
+ }),
+ args: {
+ dsl: {
+ types: ['string'],
+ aliases: ['_', 'q', 'query'],
+ help: i18n.translate('data.search.esdsl.q.help', {
+ defaultMessage: 'Query DSL',
+ }),
+ required: true,
+ },
+ index: {
+ types: ['string'],
+ help: i18n.translate('data.search.esdsl.index.help', {
+ defaultMessage: 'ElasticSearch index to query',
+ }),
+ required: true,
+ },
+ size: {
+ types: ['number'],
+ help: i18n.translate('data.search.esdsl.size.help', {
+ defaultMessage: 'ElasticSearch searchAPI size parameter',
+ }),
+ default: 10,
+ },
+ },
+ async fn(input, args, { inspectorAdapters, abortSignal }) {
+ const searchService: DataPublicPluginStart['search'] = getSearchService();
+
+ const dsl = JSON.parse(args.dsl);
+
+ if (input) {
+ const esQueryConfigs = getEsQueryConfig(getUiSettings());
+ const query = buildEsQuery(
+ undefined, // args.index,
+ input.query || [],
+ input.filters || [],
+ esQueryConfigs
+ );
+
+ if (!dsl.query) {
+ dsl.query = query;
+ } else {
+ query.bool.must.push(dsl.query);
+ dsl.query = query;
+ }
+ }
+
+ if (!inspectorAdapters.requests) {
+ inspectorAdapters.requests = new RequestAdapter();
+ }
+
+ const request = inspectorAdapters.requests.start(
+ i18n.translate('data.search.dataRequest.title', {
+ defaultMessage: 'Data',
+ }),
+ {
+ description: i18n.translate('data.search.es_search.dataRequest.description', {
+ defaultMessage:
+ 'This request queries Elasticsearch to fetch the data for the visualization.',
+ }),
+ }
+ );
+
+ request.stats({
+ indexPattern: {
+ label: i18n.translate('data.search.es_search.indexPatternLabel', {
+ defaultMessage: 'Index pattern',
+ }),
+ value: args.index,
+ description: i18n.translate('data.search.es_search.indexPatternDescription', {
+ defaultMessage: 'The index pattern that connected to the Elasticsearch indices.',
+ }),
+ },
+ });
+
+ let res: IEsSearchResponse;
+ try {
+ res = await searchService
+ .search(
+ {
+ params: {
+ index: args.index,
+ size: args.size,
+ body: dsl,
+ },
+ },
+ { signal: abortSignal }
+ )
+ .toPromise();
+
+ const stats: RequestStatistics = {};
+ const resp = res.rawResponse;
+
+ if (resp && resp.took) {
+ stats.queryTime = {
+ label: i18n.translate('data.search.es_search.queryTimeLabel', {
+ defaultMessage: 'Query time',
+ }),
+ value: i18n.translate('data.search.es_search.queryTimeValue', {
+ defaultMessage: '{queryTime}ms',
+ values: { queryTime: resp.took },
+ }),
+ description: i18n.translate('data.search.es_search.queryTimeDescription', {
+ defaultMessage:
+ 'The time it took to process the query. ' +
+ 'Does not include the time to send the request or parse it in the browser.',
+ }),
+ };
+ }
+
+ if (resp && resp.hits) {
+ stats.hitsTotal = {
+ label: i18n.translate('data.search.es_search.hitsTotalLabel', {
+ defaultMessage: 'Hits (total)',
+ }),
+ value: `${resp.hits.total}`,
+ description: i18n.translate('data.search.es_search.hitsTotalDescription', {
+ defaultMessage: 'The number of documents that match the query.',
+ }),
+ };
+
+ stats.hits = {
+ label: i18n.translate('data.search.es_search.hitsLabel', {
+ defaultMessage: 'Hits',
+ }),
+ value: `${resp.hits.hits.length}`,
+ description: i18n.translate('data.search.es_search.hitsDescription', {
+ defaultMessage: 'The number of documents returned by the query.',
+ }),
+ };
+ }
+
+ request.stats(stats).ok({ json: resp });
+ request.json(dsl);
+
+ return {
+ type: 'es_raw_response',
+ body: resp,
+ };
+ } catch (e) {
+ request.error({ json: e });
+ throw e;
+ }
+ },
+});
diff --git a/src/plugins/data/public/search/expressions/index.ts b/src/plugins/data/public/search/expressions/index.ts
index 25839a805d8c5..02df7986479ad 100644
--- a/src/plugins/data/public/search/expressions/index.ts
+++ b/src/plugins/data/public/search/expressions/index.ts
@@ -18,4 +18,6 @@
*/
export * from './esaggs';
+export * from './es_raw_response';
+export * from './esdsl';
export * from './utils';
diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts
index e6897a16a353a..4360a0caa7cde 100644
--- a/src/plugins/data/public/search/search_service.test.ts
+++ b/src/plugins/data/public/search/search_service.test.ts
@@ -37,7 +37,7 @@ describe('Search service', () => {
it('exposes proper contract', async () => {
const setup = searchService.setup(mockCoreSetup, ({
packageInfo: { version: '8' },
- registerFunction: jest.fn(),
+ expressions: { registerFunction: jest.fn(), registerType: jest.fn() },
} as unknown) as SearchServiceSetupDependencies);
expect(setup).toHaveProperty('aggs');
expect(setup).toHaveProperty('usageCollector');
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index bd9c1b1253fe2..04e1a46c84652 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -22,18 +22,20 @@ import { ISearchSetup, ISearchStart, SearchEnhancements } from './types';
import { createSearchSource, SearchSource, SearchSourceDependencies } from './search_source';
import { getEsClient, LegacyApiCaller } from './legacy';
-import { AggsService, AggsSetupDependencies, AggsStartDependencies } from './aggs';
+import { AggsService, AggsStartDependencies } from './aggs';
import { IndexPatternsContract } from '../index_patterns/index_patterns';
import { ISearchInterceptor, SearchInterceptor } from './search_interceptor';
import { ISearchGeneric } from './types';
import { SearchUsageCollector, createUsageCollector } from './collectors';
import { UsageCollectionSetup } from '../../../usage_collection/public';
+import { esdsl, esRawResponse } from './expressions';
+import { ExpressionsSetup } from '../../../expressions/public';
/** @internal */
export interface SearchServiceSetupDependencies {
packageInfo: PackageInfo;
- registerFunction: AggsSetupDependencies['registerFunction'];
usageCollection?: UsageCollectionSetup;
+ expressions: ExpressionsSetup;
}
/** @internal */
@@ -50,7 +52,7 @@ export class SearchService implements Plugin {
public setup(
core: CoreSetup,
- { packageInfo, registerFunction, usageCollection }: SearchServiceSetupDependencies
+ { packageInfo, usageCollection, expressions }: SearchServiceSetupDependencies
): ISearchSetup {
this.usageCollector = createUsageCollector(core, usageCollection);
this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo);
@@ -71,9 +73,12 @@ export class SearchService implements Plugin {
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
);
+ expressions.registerFunction(esdsl);
+ expressions.registerType(esRawResponse);
+
return {
aggs: this.aggsService.setup({
- registerFunction,
+ registerFunction: expressions.registerFunction,
uiSettings: core.uiSettings,
}),
usageCollector: this.usageCollector!,
From 156692cbfbf62309d23190baad5651544ed737c7 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Mon, 17 Aug 2020 14:25:26 -0400
Subject: [PATCH 006/177] [Ingest pipelines] Add KibanaContextProvider as
dependency of processor editor (#75018)
---
.../on_failure_processors_title.tsx | 9 +++--
.../pipeline_form/pipeline_form_fields.tsx | 7 +---
.../pipeline_form/processors_header.tsx | 9 +++--
.../pipeline_processors_editor.helpers.tsx | 34 +++++++++++++++++--
.../pipeline_processors_editor.test.tsx | 7 ----
.../manage_processor_form.container.tsx | 14 ++++----
.../test_pipeline/flyout_provider.tsx | 11 +++---
.../flyout_tabs/tab_documents.tsx | 7 ++--
.../context/context.tsx | 6 ----
.../context/processors_context.tsx | 13 -------
.../pipeline_processors_editor/types.ts | 9 -----
11 files changed, 62 insertions(+), 64 deletions(-)
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx
index d223653442819..d2c001b0aaa13 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/on_failure_processors_title.tsx
@@ -8,10 +8,10 @@ import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { usePipelineProcessorsContext } from '../pipeline_processors_editor/context';
+import { useKibana } from '../../../shared_imports';
export const OnFailureProcessorsTitle: FunctionComponent = () => {
- const { links } = usePipelineProcessorsContext();
+ const { services } = useKibana();
return (
{
values={{
learnMoreLink: (
{i18n.translate(
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx
index 32beb61039a90..3a97e6408b144 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_fields.tsx
@@ -10,7 +10,7 @@ import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Processor } from '../../../../common/types';
-import { getUseField, getFormRow, Field, useKibana } from '../../../shared_imports';
+import { getUseField, getFormRow, Field } from '../../../shared_imports';
import {
ProcessorsEditorContextProvider,
@@ -45,8 +45,6 @@ export const PipelineFormFields: React.FunctionComponent = ({
hasVersion,
onEditorFlyoutOpen,
}) => {
- const { services } = useKibana();
-
const [isVersionVisible, setIsVersionVisible] = useState(hasVersion);
return (
@@ -123,9 +121,6 @@ export const PipelineFormFields: React.FunctionComponent = ({
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx
index 3e8cd999a484a..1f27d611e54d4 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/processors_header.tsx
@@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiTitle } from '@elastic/
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { usePipelineProcessorsContext } from '../pipeline_processors_editor/context';
+import { useKibana } from '../../../shared_imports';
import {
LoadFromJsonButton,
@@ -22,7 +22,7 @@ export interface Props {
}
export const ProcessorsHeader: FunctionComponent = ({ onLoadJson }) => {
- const { links } = usePipelineProcessorsContext();
+ const { services } = useKibana();
return (
= ({ onLoadJson }) => {
defaultMessage="The processors used to pre-process documents before indexing. {learnMoreLink}"
values={{
learnMoreLink: (
-
+
{i18n.translate(
'xpack.ingestPipelines.pipelineEditor.processorsDocumentationLink',
{
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
index 5ac43953e79bc..2e7a47e0c93de 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
@@ -5,6 +5,11 @@
*/
import { act } from 'react-dom/test-utils';
import React from 'react';
+
+import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks';
+
+import { LocationDescriptorObject } from 'history';
+import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
import { registerTestBed, TestBed } from '../../../../../../../test_utils';
import {
ProcessorsEditorContextProvider,
@@ -13,6 +18,13 @@ import {
GlobalOnFailureProcessorsEditor,
} from '../';
+import {
+ breadcrumbService,
+ uiMetricService,
+ documentationService,
+ apiService,
+} from '../../../services';
+
jest.mock('@elastic/eui', () => {
const original = jest.requireActual('@elastic/eui');
@@ -60,11 +72,27 @@ jest.mock('react-virtualized', () => {
};
});
+const history = scopedHistoryMock.create();
+history.createHref.mockImplementation((location: LocationDescriptorObject) => {
+ return `${location.pathname}?${location.search}`;
+});
+
+const appServices = {
+ breadcrumbs: breadcrumbService,
+ metric: uiMetricService,
+ documentation: documentationService,
+ api: apiService,
+ notifications: notificationServiceMock.createSetupContract(),
+ history,
+};
+
const testBedSetup = registerTestBed(
(props: Props) => (
-
-
-
+
+
+
+
+
),
{
doMountAsync: false,
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
index d3c5df02c837e..b12f324528167 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
@@ -3,11 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { notificationServiceMock } from 'src/core/public/mocks';
import { setup, SetupResult } from './pipeline_processors_editor.helpers';
import { Pipeline } from '../../../../../common/types';
-import { apiService } from '../../../services';
const testProcessors: Pick = {
processors: [
@@ -46,11 +44,6 @@ describe('Pipeline Editor', () => {
},
onFlyoutOpen: jest.fn(),
onUpdate,
- links: {
- esDocsBasePath: 'test',
- },
- toasts: notificationServiceMock.createSetupContract().toasts,
- api: apiService,
});
});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx
index 84551ce152099..083529921b0a7 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx
@@ -6,11 +6,10 @@
import React, { FunctionComponent, useCallback, useEffect } from 'react';
-import { useForm, OnFormUpdateArg, FormData } from '../../../../../shared_imports';
+import { useForm, OnFormUpdateArg, FormData, useKibana } from '../../../../../shared_imports';
import { ProcessorInternal } from '../../types';
import { ManageProcessorForm as ViewComponent } from './manage_processor_form';
-import { usePipelineProcessorsContext } from '../../context';
export type ManageProcessorFormOnSubmitArg = Omit;
@@ -33,9 +32,7 @@ export const ManageProcessorForm: FunctionComponent = ({
onSubmit,
...rest
}) => {
- const {
- links: { esDocsBasePath },
- } = usePipelineProcessorsContext();
+ const { services } = useKibana();
const handleSubmit = useCallback(
async (data: FormData, isValid: boolean) => {
@@ -67,6 +64,11 @@ export const ManageProcessorForm: FunctionComponent = ({
}, [onFormUpdate]);
return (
-
+
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_provider.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_provider.tsx
index ad88259e3bcc4..53aeb9fdc08ba 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_provider.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_provider.tsx
@@ -17,6 +17,8 @@ import {
EuiCallOut,
} from '@elastic/eui';
+import { useKibana } from '../../../../../shared_imports';
+
import { usePipelineProcessorsContext, useTestConfigContext } from '../../context';
import { serialize } from '../../serialize';
@@ -27,10 +29,9 @@ export interface Props {
}
export const FlyoutProvider: React.FunctionComponent = ({ children }) => {
+ const { services } = useKibana();
const {
state: { processors },
- api,
- toasts,
} = usePipelineProcessorsContext();
const serializedProcessors = serialize(processors.state);
@@ -53,7 +54,7 @@ export const FlyoutProvider: React.FunctionComponent = ({ children }) =>
setIsExecuting(true);
setExecuteError(null);
- const { error, data: output } = await api.simulatePipeline({
+ const { error, data: output } = await services.api.simulatePipeline({
documents,
verbose,
pipeline: { ...serializedProcessors },
@@ -68,7 +69,7 @@ export const FlyoutProvider: React.FunctionComponent = ({ children }) =>
setExecuteOutput(output);
- toasts.addSuccess(
+ services.notifications.toasts.addSuccess(
i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', {
defaultMessage: 'Pipeline executed',
}),
@@ -79,7 +80,7 @@ export const FlyoutProvider: React.FunctionComponent = ({ children }) =>
setSelectedTab('output');
},
- [serializedProcessors, api, toasts]
+ [services.api, services.notifications.toasts, serializedProcessors]
);
useEffect(() => {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_tabs/tab_documents.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_tabs/tab_documents.tsx
index 593347f8b2343..794d935571210 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_tabs/tab_documents.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/flyout_tabs/tab_documents.tsx
@@ -17,9 +17,10 @@ import {
Form,
useForm,
FormConfig,
+ useKibana,
} from '../../../../../../shared_imports';
-import { usePipelineProcessorsContext, useTestConfigContext, TestConfig } from '../../../context';
+import { useTestConfigContext, TestConfig } from '../../../context';
import { documentsSchema } from './schema';
@@ -31,7 +32,7 @@ interface Props {
}
export const DocumentsTab: React.FunctionComponent = ({ handleExecute, isExecuting }) => {
- const { links } = usePipelineProcessorsContext();
+ const { services } = useKibana();
const { setCurrentTestConfig, testConfig } = useTestConfigContext();
const { verbose: cachedVerbose, documents: cachedDocuments } = testConfig;
@@ -71,7 +72,7 @@ export const DocumentsTab: React.FunctionComponent = ({ handleExecute, is
values={{
learnMoreLink: (
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/context.tsx
index 1ccfcc8e19755..a1ea0fd9d0b9e 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/context.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/context.tsx
@@ -18,9 +18,6 @@ interface Props extends ProcessorsContextProps {
export const ProcessorsEditorContextProvider: FunctionComponent = ({
children,
- links,
- api,
- toasts,
onUpdate,
value,
onFlyoutOpen,
@@ -29,9 +26,6 @@ export const ProcessorsEditorContextProvider: FunctionComponent = ({
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx
index 7124efc4905a7..f83803da7bf91 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx
@@ -15,10 +15,7 @@ import React, {
useRef,
} from 'react';
-import { NotificationsSetup } from 'src/core/public';
-
import { Processor } from '../../../../../common/types';
-import { ApiService } from '../../../services';
import {
EditorMode,
@@ -27,7 +24,6 @@ import {
OnUpdateHandlerArg,
ContextValue,
ContextValueState,
- Links,
ProcessorInternal,
} from '../types';
@@ -51,9 +47,6 @@ import { getValue } from '../utils';
const PipelineProcessorsContext = createContext({} as any);
export interface Props {
- links: Links;
- api: ApiService;
- toasts: NotificationsSetup['toasts'];
value: {
processors: Processor[];
onFailure?: Processor[];
@@ -66,9 +59,6 @@ export interface Props {
}
export const PipelineProcessorsContextProvider: FunctionComponent = ({
- links,
- api,
- toasts,
value: { processors: originalProcessors, onFailure: originalOnFailureProcessors },
onUpdate,
onFlyoutOpen,
@@ -211,9 +201,6 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({
return (
Date: Mon, 17 Aug 2020 21:08:27 +0200
Subject: [PATCH 007/177] expose es config on es setup contract (#73055)
* expose es config on es setup contract
* move config$ back to legacy
---
...kibana-plugin-core-public.doclinksstart.md | 2 +-
...server.elasticsearchservicesetup.legacy.md | 1 +
...n-core-server.elasticsearchservicesetup.md | 2 +-
...server.elasticsearchservicestart.legacy.md | 1 +
...n-core-server.elasticsearchservicestart.md | 2 +-
.../elasticsearch_service.mock.ts | 4 +++-
.../elasticsearch/elasticsearch_service.ts | 1 +
src/core/server/elasticsearch/types.ts | 21 ++++++++++++-------
src/core/server/legacy/legacy_service.ts | 5 +----
src/core/server/server.api.md | 2 ++
.../plugins/licensing/server/plugin.test.ts | 6 +++++-
11 files changed, 31 insertions(+), 16 deletions(-)
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 8f739950d249b..fa2d9090e3159 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly drilldowns: string;
};
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
}
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md
index c683f0ba33189..abcbbf18a8f9c 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md
@@ -13,6 +13,7 @@
```typescript
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md
index 0dd41a6154a1e..ca6134cd5ed65 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md
@@ -15,5 +15,5 @@ export interface ElasticsearchServiceSetup
| Property | Type | Description |
| --- | --- | --- |
-| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
+| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md
index 5f346d7887c2a..4026483894aa1 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md
@@ -13,6 +13,7 @@
```typescript
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md
index 860867d654435..8d9cd1be148cf 100644
--- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md
+++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md
@@ -17,5 +17,5 @@ export interface ElasticsearchServiceStart
| --- | --- | --- |
| [client](./kibana-plugin-core-server.elasticsearchservicestart.client.md) | IClusterClient
| A pre-configured [Elasticsearch client](./kibana-plugin-core-server.iclusterclient.md) |
| [createClient](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient
| Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). |
-| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
+| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly config$: Observable<ElasticsearchConfig>;
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
}
| |
diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts
index 501ab619316c2..26186efc286bf 100644
--- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts
+++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts
@@ -34,6 +34,7 @@ import { ServiceStatus, ServiceStatusLevels } from '../status';
interface MockedElasticSearchServiceSetup {
legacy: {
+ config$: BehaviorSubject;
createClient: jest.Mock;
client: jest.Mocked;
};
@@ -49,6 +50,7 @@ type MockedElasticSearchServiceStart = MockedElasticSearchServiceSetup & {
const createSetupContractMock = () => {
const setupContract: MockedElasticSearchServiceSetup = {
legacy: {
+ config$: new BehaviorSubject({} as ElasticsearchConfig),
createClient: jest.fn(),
client: legacyClientMock.createClusterClient(),
},
@@ -65,6 +67,7 @@ const createStartContractMock = () => {
client: elasticsearchClientMock.createClusterClient(),
createClient: jest.fn(),
legacy: {
+ config$: new BehaviorSubject({} as ElasticsearchConfig),
createClient: jest.fn(),
client: legacyClientMock.createClusterClient(),
},
@@ -99,7 +102,6 @@ const createInternalSetupContractMock = () => {
summary: 'Elasticsearch is available',
}),
legacy: {
- config$: new BehaviorSubject({} as ElasticsearchConfig),
...createSetupContractMock().legacy,
},
};
diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts
index 69bf593dd5862..2cc065aaaaeb1 100644
--- a/src/core/server/elasticsearch/elasticsearch_service.ts
+++ b/src/core/server/elasticsearch/elasticsearch_service.ts
@@ -123,6 +123,7 @@ export class ElasticsearchService
client: this.client!,
createClient,
legacy: {
+ config$: this.config$,
client: this.legacyClient,
createClient: this.createLegacyCustomClient,
},
diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts
index 88094af8047e7..55b5549a56a23 100644
--- a/src/core/server/elasticsearch/types.ts
+++ b/src/core/server/elasticsearch/types.ts
@@ -37,9 +37,14 @@ export interface ElasticsearchServiceSetup {
/**
* @deprecated
* Use {@link ElasticsearchServiceStart.legacy} instead.
- *
- * */
+ */
legacy: {
+ /**
+ * Provide direct access to the current elasticsearch configuration.
+ *
+ * @deprecated this will be removed in a later version.
+ */
+ readonly config$: Observable;
/**
* @deprecated
* Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead.
@@ -82,11 +87,7 @@ export interface ElasticsearchServiceSetup {
}
/** @internal */
-export interface InternalElasticsearchServiceSetup {
- // Required for the BWC with the legacy Kibana only.
- readonly legacy: ElasticsearchServiceSetup['legacy'] & {
- readonly config$: Observable;
- };
+export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup {
esNodesCompatibility$: Observable;
status$: Observable>;
}
@@ -132,6 +133,12 @@ export interface ElasticsearchServiceStart {
* Switch to the new elasticsearch client as soon as https://github.com/elastic/kibana/issues/35508 done.
* */
legacy: {
+ /**
+ * Provide direct access to the current elasticsearch configuration.
+ *
+ * @deprecated this will be removed in a later version.
+ */
+ readonly config$: Observable;
/**
* Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}.
*
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index 976d92e6fe7fb..0c1e8562a1deb 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -288,10 +288,7 @@ export class LegacyService implements CoreService {
capabilities: setupDeps.core.capabilities,
context: setupDeps.core.context,
elasticsearch: {
- legacy: {
- client: setupDeps.core.elasticsearch.legacy.client,
- createClient: setupDeps.core.elasticsearch.legacy.createClient,
- },
+ legacy: setupDeps.core.elasticsearch.legacy,
},
http: {
createCookieSessionStorageFactory: setupDeps.core.http.createCookieSessionStorageFactory,
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 21ef66230f698..60b01aa06d07f 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -716,6 +716,7 @@ export class ElasticsearchConfig {
export interface ElasticsearchServiceSetup {
// @deprecated (undocumented)
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
@@ -727,6 +728,7 @@ export interface ElasticsearchServiceStart {
readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient;
// @deprecated (undocumented)
legacy: {
+ readonly config$: Observable;
readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
};
diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts
index 6e8327e151543..b1669db00f227 100644
--- a/x-pack/plugins/licensing/server/plugin.test.ts
+++ b/x-pack/plugins/licensing/server/plugin.test.ts
@@ -37,7 +37,11 @@ function createCoreSetupWith(esClient: ILegacyClusterClient) {
...coreStart,
elasticsearch: {
...coreStart.elasticsearch,
- legacy: { client: esClient, createClient: jest.fn() },
+ legacy: {
+ ...coreStart.elasticsearch.legacy,
+ client: esClient,
+ createClient: jest.fn(),
+ },
},
},
{},
From df9b28a4ce39d55d76a0b1844a48e5cd1fb98245 Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Mon, 17 Aug 2020 20:25:22 +0100
Subject: [PATCH 008/177] skip flaky suite (#75124)
---
.../__jest__/client_integration/follower_indices_list.test.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js
index e96beda7243d6..91227299e92f9 100644
--- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js
+++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js
@@ -303,7 +303,8 @@ describe('', () => {
});
});
- describe('detail panel', () => {
+ // FLAKY: https://github.com/elastic/kibana/issues/75124
+ describe.skip('detail panel', () => {
test('should open a detail panel when clicking on a follower index', () => {
expect(exists('followerIndexDetail')).toBe(false);
From 4b4050c8db90a508745052eca9fadecf15632e47 Mon Sep 17 00:00:00 2001
From: Nathan L Smith
Date: Mon, 17 Aug 2020 14:33:00 -0500
Subject: [PATCH 009/177] APM and Observability storybook fixes (#75168)
In APM:
* Fix stories crashing with errors
* Hide some additional prop tables
* Fix node severites story to show correct node severites
* Fix Service Map Popover story
* Use knobs on sync badge story
In Observability:
* Remove an extra stray paren in decorators
There's additional refactoring and fixes that can be done but this just gets everything in working order.
---
.../ServiceMap/Popover/Popover.stories.tsx | 15 +-
.../__stories__/Cytoscape.stories.tsx | 80 ++++-----
.../index.stories.tsx | 5 +
.../Waterfall/SyncBadge.stories.tsx | 28 +---
.../WaterfallContainer.stories.tsx | 153 +++++++++---------
.../ErrorRateAlertTrigger/index.stories.tsx | 47 +++---
.../LicensePrompt/LicensePrompt.stories.tsx | 1 +
.../pages/overview/overview.stories.tsx | 24 +--
8 files changed, 187 insertions(+), 166 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
index 20f6f92f9995f..55a0bddcc7818 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
@@ -27,16 +27,18 @@ storiesOf('app/ServiceMap/Popover', module)
avgCpuUsage: 0.32809666568309237,
avgErrorRate: 0.556068173242986,
avgMemoryUsage: 0.5504868173242986,
- avgRequestsPerMinute: 164.47222031860858,
- avgTransactionDuration: 61634.38905590272,
+ transactionStats: {
+ avgRequestsPerMinute: 164.47222031860858,
+ avgTransactionDuration: 61634.38905590272,
+ },
}),
} as unknown) as HttpSetup;
createCallApmApi(httpMock);
- setImmediate(() => {
- cy.$('example service').select();
- });
+ setTimeout(() => {
+ cy.$id('example service').select();
+ }, 0);
return (
@@ -59,9 +61,10 @@ storiesOf('app/ServiceMap/Popover', module)
info: {
propTablesExclude: [
CytoscapeContext.Provider,
+ EuiThemeProvider,
MockApmPluginContextWrapper,
MockUrlParamsContextProvider,
- EuiThemeProvider,
+ Popover,
],
source: false,
},
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
index aee392b53298a..2a7d11bb57ca5 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
@@ -288,43 +288,51 @@ storiesOf('app/ServiceMap/Cytoscape', module).add(
}
);
-storiesOf('app/ServiceMap/Cytoscape', module).add(
- 'node severity',
- () => {
- const elements = [
- { data: { id: 'undefined', 'service.name': 'severity: undefined' } },
- {
- data: {
- id: 'warning',
- 'service.name': 'severity: warning',
- severity: 'warning',
+storiesOf('app/ServiceMap/Cytoscape', module)
+ .addDecorator((storyFn) => {storyFn()})
+ .add(
+ 'node severity',
+ () => {
+ const elements = [
+ {
+ data: {
+ id: 'undefined',
+ 'service.name': 'severity: undefined',
+ serviceAnomalyStats: { anomalyScore: undefined },
+ },
},
- },
- {
- data: {
- id: 'minor',
- 'service.name': 'severity: minor',
- severity: 'minor',
+ {
+ data: {
+ id: 'warning',
+ 'service.name': 'severity: warning',
+ serviceAnomalyStats: { anomalyScore: 0 },
+ },
},
- },
- {
- data: {
- id: 'major',
- 'service.name': 'severity: major',
- severity: 'major',
+ {
+ data: {
+ id: 'minor',
+ 'service.name': 'severity: minor',
+ serviceAnomalyStats: { anomalyScore: 40 },
+ },
},
- },
- {
- data: {
- id: 'critical',
- 'service.name': 'severity: critical',
- severity: 'critical',
+ {
+ data: {
+ id: 'major',
+ 'service.name': 'severity: major',
+ serviceAnomalyStats: { anomalyScore: 60 },
+ },
},
- },
- ];
- return ;
- },
- {
- info: { propTables: false, source: false },
- }
-);
+ {
+ data: {
+ id: 'critical',
+ 'service.name': 'severity: critical',
+ serviceAnomalyStats: { anomalyScore: 80 },
+ },
+ },
+ ];
+ return ;
+ },
+ {
+ info: { propTables: false, source: false },
+ }
+ );
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
index 920ef39e84ca3..db3f2c374a1ae 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
@@ -69,6 +69,11 @@ storiesOf(
},
{
info: {
+ propTablesExclude: [
+ AgentConfigurationCreateEdit,
+ ApmPluginContext.Provider,
+ EuiThemeProvider,
+ ],
source: false,
},
}
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx
index 31d0d5891fca4..a5393995f0864 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.stories.tsx
@@ -4,31 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { boolean, withKnobs } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { SyncBadge } from './SyncBadge';
storiesOf('app/TransactionDetails/SyncBadge', module)
+ .addDecorator(withKnobs)
.add(
- 'sync=true',
+ 'example',
() => {
- return ;
+ return ;
},
{
- info: {
- source: false,
- },
- }
- )
- .add(
- 'sync=false',
- () => {
- return ;
- },
- {
- info: {
- source: false,
- },
+ showPanel: true,
+ info: { source: false },
}
)
.add(
@@ -36,9 +26,5 @@ storiesOf('app/TransactionDetails/SyncBadge', module)
() => {
return ;
},
- {
- info: {
- source: false,
- },
- }
+ { info: { source: false } }
);
diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
index 69cb091e76880..8e3d0effb97a6 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx
@@ -18,79 +18,88 @@ import {
inferredSpans,
} from './waterfallContainer.stories.data';
import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';
+import { EuiThemeProvider } from '../../../../../../../observability/public';
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'simple',
- () => {
- const waterfall = getWaterfall(
- simpleTrace as TraceAPIResponse,
- '975c8d5bfd1dd20b'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()})
+ .add(
+ 'example',
+ () => {
+ const waterfall = getWaterfall(
+ simpleTrace as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'with errors',
- () => {
- const waterfall = getWaterfall(
- (traceWithErrors as unknown) as TraceAPIResponse,
- '975c8d5bfd1dd20b'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()})
+ .add(
+ 'with errors',
+ () => {
+ const waterfall = getWaterfall(
+ (traceWithErrors as unknown) as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'child starts before parent',
- () => {
- const waterfall = getWaterfall(
- traceChildStartBeforeParent as TraceAPIResponse,
- '975c8d5bfd1dd20b'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()})
+ .add(
+ 'child starts before parent',
+ () => {
+ const waterfall = getWaterfall(
+ traceChildStartBeforeParent as TraceAPIResponse,
+ '975c8d5bfd1dd20b'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
-storiesOf('app/TransactionDetails/Waterfall', module).add(
- 'inferred spans',
- () => {
- const waterfall = getWaterfall(
- inferredSpans as TraceAPIResponse,
- 'f2387d37260d00bd'
- );
- return (
-
- );
- },
- { info: { source: false } }
-);
+storiesOf('app/TransactionDetails/Waterfall', module)
+ .addDecorator((storyFn) => {storyFn()})
+ .add(
+ 'inferred spans',
+ () => {
+ const waterfall = getWaterfall(
+ inferredSpans as TraceAPIResponse,
+ 'f2387d37260d00bd'
+ );
+ return (
+
+ );
+ },
+ { info: { propTablesExclude: [EuiThemeProvider], source: false } }
+ );
diff --git a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
index ebcb1627984ad..632d53a9c63b6 100644
--- a/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.stories.tsx
@@ -13,23 +13,32 @@ import {
MockApmPluginContextWrapper,
} from '../../../context/ApmPluginContext/MockApmPluginContext';
-storiesOf('app/ErrorRateAlertTrigger', module).add('example', () => {
- const params = {
- threshold: 2,
- window: '5m',
- };
+storiesOf('app/ErrorRateAlertTrigger', module).add(
+ 'example',
+ () => {
+ const params = {
+ threshold: 2,
+ window: '5m',
+ };
- return (
-
-
- undefined}
- setAlertProperty={() => undefined}
- />
-
-
- );
-});
+ return (
+
+
+ undefined}
+ setAlertProperty={() => undefined}
+ />
+
+
+ );
+ },
+ {
+ info: {
+ propTablesExclude: [ErrorRateAlertTrigger, MockApmPluginContextWrapper],
+ source: false,
+ },
+ }
+);
diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
index 89f5bf28a4938..45fa3dd382266 100644
--- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx
@@ -27,6 +27,7 @@ storiesOf('app/LicensePrompt', module).add(
},
{
info: {
+ propTablesExclude: [ApmPluginContext.Provider, LicensePrompt],
source: false,
},
}
diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
index 79f3c63a98051..21c7b87568e09 100644
--- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
+++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx
@@ -122,7 +122,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -156,7 +156,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -180,7 +180,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -209,7 +209,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -238,7 +238,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -272,7 +272,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -311,7 +311,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -350,7 +350,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -389,7 +389,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -437,7 +437,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -484,7 +484,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
@@ -534,7 +534,7 @@ storiesOf('app/Overview', module)
.addDecorator((storyFn) => (
- {storyFn()})
+ {storyFn()}
))
From 83678e1388f059a7c400d045e707ecc68124063e Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Mon, 17 Aug 2020 13:24:39 -0700
Subject: [PATCH 010/177] [build] Use CentOS/UBI 8.2 as Docker base image
(#74656)
Signed-off-by: Tyler Smalley
---
.../docker_generator/bundle_dockerfiles.ts | 2 +-
.../tasks/os_packages/docker_generator/run.ts | 14 +-
.../docker_generator/template_context.ts | 4 +-
.../docker_generator/templates/Dockerfile | 122 ++++++++++++++++++
.../templates/build_docker_sh.template.ts | 6 +-
.../templates/dockerfile.template.ts | 118 ++---------------
6 files changed, 146 insertions(+), 120 deletions(-)
create mode 100644 src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts
index 7a8f7316913be..9c6ca78f7146a 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts
@@ -30,7 +30,7 @@ export async function bundleDockerFiles(config: Config, log: ToolingLog, scope:
`Generating kibana${scope.imageFlavor}${scope.ubiImageFlavor} docker build context bundle`
);
- const dockerFilesDirName = `kibana${scope.imageFlavor}${scope.ubiImageFlavor}-${scope.versionTag}-docker-build-context`;
+ const dockerFilesDirName = `kibana${scope.imageFlavor}${scope.ubiImageFlavor}-${scope.version}-docker-build-context`;
const dockerFilesBuildDir = resolve(scope.dockerBuildDir, dockerFilesDirName);
const dockerFilesOutputDir = config.resolveFromTarget(`${dockerFilesDirName}.tar.gz`);
diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts
index 0a26729f3502d..6cf4a7af70840 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/run.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts
@@ -40,16 +40,16 @@ export async function runDockerGenerator(
ubi: boolean = false
) {
// UBI var config
- const baseOSImage = ubi ? 'registry.access.redhat.com/ubi7/ubi-minimal:7.7' : 'centos:7';
- const ubiVersionTag = 'ubi7';
+ const baseOSImage = ubi ? 'registry.access.redhat.com/ubi8/ubi-minimal:latest' : 'centos:8';
+ const ubiVersionTag = 'ubi8';
const ubiImageFlavor = ubi ? `-${ubiVersionTag}` : '';
// General docker var config
const license = build.isOss() ? 'ASL 2.0' : 'Elastic License';
const imageFlavor = build.isOss() ? '-oss' : '';
const imageTag = 'docker.elastic.co/kibana/kibana';
- const versionTag = config.getBuildVersion();
- const artifactTarball = `kibana${imageFlavor}-${versionTag}-linux-x86_64.tar.gz`;
+ const version = config.getBuildVersion();
+ const artifactTarball = `kibana${imageFlavor}-${version}-linux-x86_64.tar.gz`;
const artifactsDir = config.resolveFromTarget('.');
const dockerBuildDate = new Date().toISOString();
// That would produce oss, default and default-ubi7
@@ -59,12 +59,12 @@ export async function runDockerGenerator(
build.isOss() ? `oss` : `default${ubiImageFlavor}`
);
const dockerOutputDir = config.resolveFromTarget(
- `kibana${imageFlavor}${ubiImageFlavor}-${versionTag}-docker.tar.gz`
+ `kibana${imageFlavor}${ubiImageFlavor}-${version}-docker.tar.gz`
);
const scope: TemplateContext = {
artifactTarball,
imageFlavor,
- versionTag,
+ version,
license,
artifactsDir,
imageTag,
@@ -73,6 +73,8 @@ export async function runDockerGenerator(
baseOSImage,
ubiImageFlavor,
dockerBuildDate,
+ ubi,
+ revision: config.getBuildSha(),
};
// Verify if we have the needed kibana target in order
diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts
index 115d4c6927c30..a7c40db44b87e 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts
@@ -20,7 +20,7 @@
export interface TemplateContext {
artifactTarball: string;
imageFlavor: string;
- versionTag: string;
+ version: string;
license: string;
artifactsDir: string;
imageTag: string;
@@ -30,4 +30,6 @@ export interface TemplateContext {
ubiImageFlavor: string;
dockerBuildDate: string;
usePublicArtifact?: boolean;
+ ubi: boolean;
+ revision: string;
}
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
new file mode 100644
index 0000000000000..c6f333beb060e
--- /dev/null
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
@@ -0,0 +1,122 @@
+################################################################################
+# This Dockerfile was generated from the template at:
+# src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
+#
+# Beginning of multi stage Dockerfile
+################################################################################
+
+################################################################################
+# Build stage 0 `builder`:
+# Extract Kibana artifact
+################################################################################
+FROM {{{baseOSImage}}} AS builder
+
+{{#ubi}}
+RUN {{packageManager}} install -y findutils tar gzip
+{{/ubi}}
+
+{{#usePublicArtifact}}
+RUN cd /opt && \
+ curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/{{artifactTarball}} && \
+ cd -
+{{/usePublicArtifact}}
+
+{{^usePublicArtifact}}
+COPY {{artifactTarball}} /opt
+{{/usePublicArtifact}}
+
+RUN mkdir /usr/share/kibana
+WORKDIR /usr/share/kibana
+RUN tar --strip-components=1 -zxf /opt/{{artifactTarball}}
+# Ensure that group permissions are the same as user permissions.
+# This will help when relying on GID-0 to run Kibana, rather than UID-1000.
+# OpenShift does this, for example.
+# REF: https://docs.openshift.org/latest/creating_images/guidelines.html
+RUN chmod -R g=u /usr/share/kibana
+RUN find /usr/share/kibana -type d -exec chmod g+s {} \;
+
+################################################################################
+# Build stage 1 (the actual Kibana image):
+#
+# Copy kibana from stage 0
+# Add entrypoint
+################################################################################
+FROM {{{baseOSImage}}}
+EXPOSE 5601
+
+RUN for iter in {1..10}; do \
+ {{packageManager}} update --setopt=tsflags=nodocs -y && \
+ {{packageManager}} install --setopt=tsflags=nodocs -y \
+ fontconfig freetype shadow-utils {{#ubi}}findutils{{/ubi}} && \
+ {{packageManager}} clean all && exit_code=0 && break || exit_code=$? && echo "{{packageManager}} error: retry $iter in 10s" && \
+ sleep 10; \
+ done; \
+ (exit $exit_code)
+
+# Add an init process, check the checksum to make sure it's a match
+RUN curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
+RUN echo "37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9 /usr/local/bin/dumb-init" | sha256sum -c -
+RUN chmod +x /usr/local/bin/dumb-init
+
+# Bring in Kibana from the initial stage.
+COPY --from=builder --chown=1000:0 /usr/share/kibana /usr/share/kibana
+WORKDIR /usr/share/kibana
+RUN ln -s /usr/share/kibana /opt/kibana
+
+ENV ELASTIC_CONTAINER true
+ENV PATH=/usr/share/kibana/bin:$PATH
+
+# Set some Kibana configuration defaults.
+COPY --chown=1000:0 config/kibana.yml /usr/share/kibana/config/kibana.yml
+
+# Add the launcher/wrapper script. It knows how to interpret environment
+# variables and translate them to Kibana CLI options.
+COPY --chown=1000:0 bin/kibana-docker /usr/local/bin/
+
+# Ensure gid 0 write permissions for OpenShift.
+RUN chmod g+ws /usr/share/kibana && \
+ find /usr/share/kibana -gid 0 -and -not -perm /g+w -exec chmod g+w {} \;
+
+# Remove the suid bit everywhere to mitigate "Stack Clash"
+RUN find / -xdev -perm -4000 -exec chmod u-s {} +
+
+# Provide a non-root user to run the process.
+RUN groupadd --gid 1000 kibana && \
+ useradd --uid 1000 --gid 1000 \
+ --home-dir /usr/share/kibana --no-create-home \
+ kibana
+USER kibana
+
+LABEL org.label-schema.build-date="{{dockerBuildDate}}" \
+ org.label-schema.license="{{license}}" \
+ org.label-schema.name="Kibana" \
+ org.label-schema.schema-version="1.0" \
+ org.label-schema.url="https://www.elastic.co/products/kibana" \
+ org.label-schema.usage="https://www.elastic.co/guide/en/kibana/reference/index.html" \
+ org.label-schema.vcs-ref="{{revision}}" \
+ org.label-schema.vcs-url="https://github.com/elastic/kibana" \
+ org.label-schema.vendor="Elastic" \
+ org.label-schema.version="{{version}}" \
+ org.opencontainers.image.created="{{dockerBuildDate}}" \
+ org.opencontainers.image.documentation="https://www.elastic.co/guide/en/kibana/reference/index.html" \
+ org.opencontainers.image.licenses="{{license}}" \
+ org.opencontainers.image.revision="{{revision}}" \
+ org.opencontainers.image.source="https://github.com/elastic/kibana" \
+ org.opencontainers.image.title="Kibana" \
+ org.opencontainers.image.url="https://www.elastic.co/products/kibana" \
+ org.opencontainers.image.vendor="Elastic" \
+ org.opencontainers.image.version="{{version}}"
+
+{{#ubi}}
+LABEL name="Kibana" \
+ maintainer="infra@elastic.co" \
+ vendor="Elastic" \
+ version="{{version}}" \
+ release="1" \
+ summary="Kibana" \
+ description="Your window into the Elastic Stack."
+{{/ubi}}
+
+ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
+
+CMD ["/usr/local/bin/kibana-docker"]
\ No newline at end of file
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts
index ff6fcf7548d9d..699bba758e1c9 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts
@@ -24,7 +24,7 @@ import { TemplateContext } from '../template_context';
function generator({
imageTag,
imageFlavor,
- versionTag,
+ version,
dockerOutputDir,
baseOSImage,
ubiImageFlavor,
@@ -39,9 +39,9 @@ function generator({
docker pull ${baseOSImage}
echo "Building: kibana${imageFlavor}${ubiImageFlavor}-docker"; \\
- docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${versionTag} -f Dockerfile . || exit 1;
+ docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} -f Dockerfile . || exit 1;
- docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${versionTag} | gzip -c > ${dockerOutputDir}
+ docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} | gzip -c > ${dockerOutputDir}
exit 0
`);
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
index ea2f881768c8f..9733021319aee 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts
@@ -17,118 +17,18 @@
* under the License.
*/
-import dedent from 'dedent';
+import { readFileSync } from 'fs';
+import { resolve } from 'path';
+import Mustache from 'mustache';
import { TemplateContext } from '../template_context';
-function generator({
- artifactTarball,
- versionTag,
- license,
- usePublicArtifact,
- baseOSImage,
- ubiImageFlavor,
- dockerBuildDate,
-}: TemplateContext) {
- const copyArtifactTarballInsideDockerOptFolder = () => {
- if (usePublicArtifact) {
- return `RUN cd /opt && curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/${artifactTarball} && cd -`;
- }
-
- return `COPY ${artifactTarball} /opt`;
- };
-
- const packageManager = () => {
- if (ubiImageFlavor) {
- return 'microdnf';
- }
-
- return 'yum';
- };
-
- return dedent(`
- #
- # ** THIS IS AN AUTO-GENERATED FILE **
- #
-
- ################################################################################
- # Build stage 0
- # Extract Kibana and make various file manipulations.
- ################################################################################
- FROM ${baseOSImage} AS prep_files
- # Add tar and gzip
- RUN ${packageManager()} update -y && ${packageManager()} install -y tar gzip && ${packageManager()} clean all
- ${copyArtifactTarballInsideDockerOptFolder()}
- RUN mkdir /usr/share/kibana
- WORKDIR /usr/share/kibana
- RUN tar --strip-components=1 -zxf /opt/${artifactTarball}
- # Ensure that group permissions are the same as user permissions.
- # This will help when relying on GID-0 to run Kibana, rather than UID-1000.
- # OpenShift does this, for example.
- # REF: https://docs.openshift.org/latest/creating_images/guidelines.html
- RUN chmod -R g=u /usr/share/kibana
- RUN find /usr/share/kibana -type d -exec chmod g+s {} \\;
-
- ################################################################################
- # Build stage 1
- # Copy prepared files from the previous stage and complete the image.
- ################################################################################
- FROM ${baseOSImage}
- EXPOSE 5601
-
- # Add Reporting dependencies.
- RUN ${packageManager()} update -y && ${packageManager()} install -y fontconfig freetype shadow-utils && ${packageManager()} clean all
-
- # Add an init process, check the checksum to make sure it's a match
- RUN curl -L -o /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
- RUN echo "37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9 /usr/local/bin/dumb-init" | sha256sum -c -
- RUN chmod +x /usr/local/bin/dumb-init
-
-
- # Bring in Kibana from the initial stage.
- COPY --from=prep_files --chown=1000:0 /usr/share/kibana /usr/share/kibana
- WORKDIR /usr/share/kibana
- RUN ln -s /usr/share/kibana /opt/kibana
-
- ENV ELASTIC_CONTAINER true
- ENV PATH=/usr/share/kibana/bin:$PATH
-
- # Set some Kibana configuration defaults.
- COPY --chown=1000:0 config/kibana.yml /usr/share/kibana/config/kibana.yml
-
- # Add the launcher/wrapper script. It knows how to interpret environment
- # variables and translate them to Kibana CLI options.
- COPY --chown=1000:0 bin/kibana-docker /usr/local/bin/
-
- # Ensure gid 0 write permissions for OpenShift.
- RUN chmod g+ws /usr/share/kibana && \\
- find /usr/share/kibana -gid 0 -and -not -perm /g+w -exec chmod g+w {} \\;
-
- # Remove the suid bit everywhere to mitigate "Stack Clash"
- RUN find / -xdev -perm -4000 -exec chmod u-s {} +
-
- # Provide a non-root user to run the process.
- RUN groupadd --gid 1000 kibana && \\
- useradd --uid 1000 --gid 1000 \\
- --home-dir /usr/share/kibana --no-create-home \\
- kibana
- USER kibana
-
- LABEL org.label-schema.schema-version="1.0" \\
- org.label-schema.vendor="Elastic" \\
- org.label-schema.name="kibana" \\
- org.label-schema.version="${versionTag}" \\
- org.label-schema.url="https://www.elastic.co/products/kibana" \\
- org.label-schema.vcs-url="https://github.com/elastic/kibana" \\
- org.label-schema.license="${license}" \\
- org.label-schema.usage="https://www.elastic.co/guide/en/kibana/index.html" \\
- org.label-schema.build-date="${dockerBuildDate}" \\
- license="${license}"
-
- ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
-
- CMD ["/usr/local/bin/kibana-docker"]
- `);
+function generator(options: TemplateContext) {
+ const template = readFileSync(resolve(__dirname, './Dockerfile'));
+ return Mustache.render(template.toString(), {
+ packageManager: options.ubiImageFlavor ? 'microdnf' : 'yum',
+ ...options,
+ });
}
export const dockerfileTemplate = {
From 24773f3a00c82fa21c2ff0733ba1990a06c4b427 Mon Sep 17 00:00:00 2001
From: Marshall Main <55718608+marshallmain@users.noreply.github.com>
Date: Mon, 17 Aug 2020 16:42:04 -0400
Subject: [PATCH 011/177] [Security solution][Exceptions] Add separate io-ts
types for endpoint exceptions (#74468)
* Add separate io-ts types for endpoint exception entries
* Fix text typos
* Fix test
* address review comments
* Add extra entry validation when adding items to endpoint_list
* fix test
* fix tests again
* really actually fix the tests
Co-authored-by: Elastic Machine
---
x-pack/plugins/lists/common/constants.mock.ts | 10 ++
.../lists/common/schemas/common/schemas.ts | 1 +
.../create_endpoint_list_item_schema.mock.ts | 4 +-
.../create_endpoint_list_item_schema.ts | 5 +-
.../schemas/types/endpoint/entries.mock.ts | 16 ++
.../schemas/types/endpoint/entries.test.ts | 111 ++++++++++++++
.../common/schemas/types/endpoint/entries.ts | 42 ++++++
.../types/endpoint/entry_match.mock.ts | 16 ++
.../types/endpoint/entry_match.test.ts | 102 +++++++++++++
.../schemas/types/endpoint/entry_match.ts | 20 +++
.../types/endpoint/entry_match_any.mock.ts | 16 ++
.../types/endpoint/entry_match_any.test.ts | 100 +++++++++++++
.../schemas/types/endpoint/entry_match_any.ts | 21 +++
.../types/endpoint/entry_nested.mock.ts | 17 +++
.../types/endpoint/entry_nested.test.ts | 137 ++++++++++++++++++
.../schemas/types/endpoint/entry_nested.ts | 20 +++
.../common/schemas/types/endpoint/index.ts | 7 +
.../non_empty_nested_entries_array.ts | 45 ++++++
.../create_exception_list_item_route.ts | 12 +-
.../plugins/lists/server/routes/validate.ts | 27 +++-
.../new/endpoint_list_item.json | 5 +-
.../plugins/lists/server/siem_server_deps.ts | 2 +
.../apis/lists/create_exception_list_item.ts | 8 +-
23 files changed, 726 insertions(+), 18 deletions(-)
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/index.ts
create mode 100644 x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts
diff --git a/x-pack/plugins/lists/common/constants.mock.ts b/x-pack/plugins/lists/common/constants.mock.ts
index 428cc90d2908b..46ed524ff33e3 100644
--- a/x-pack/plugins/lists/common/constants.mock.ts
+++ b/x-pack/plugins/lists/common/constants.mock.ts
@@ -6,6 +6,7 @@
import moment from 'moment';
import { EntriesArray } from './schemas/types';
+import { EndpointEntriesArray } from './schemas/types/endpoint';
export const DATE_NOW = '2020-04-20T15:25:31.830Z';
export const OLD_DATE_RELATIVE_TO_DATE_NOW = '2020-04-19T15:25:31.830Z';
export const USER = 'some user';
@@ -41,6 +42,7 @@ export const ITEM_ID = 'some-list-item-id';
export const ENDPOINT_TYPE = 'endpoint';
export const FIELD = 'host.name';
export const OPERATOR = 'included';
+export const OPERATOR_EXCLUDED = 'excluded';
export const ENTRY_VALUE = 'some host name';
export const MATCH = 'match';
export const MATCH_ANY = 'match_any';
@@ -57,6 +59,14 @@ export const ENTRIES: EntriesArray = [
},
{ field: 'some.not.nested.field', operator: 'included', type: 'match', value: 'some value' },
];
+export const ENDPOINT_ENTRIES: EndpointEntriesArray = [
+ {
+ entries: [{ field: 'nested.field', operator: 'included', type: 'match', value: 'some value' }],
+ field: 'some.parentField',
+ type: 'nested',
+ },
+ { field: 'some.not.nested.field', operator: 'included', type: 'match', value: 'some value' },
+];
export const ITEM_TYPE = 'simple';
export const _TAGS = [];
export const TAGS = [];
diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts
index 0d52b075ebf12..1556ef5a5dab9 100644
--- a/x-pack/plugins/lists/common/schemas/common/schemas.ts
+++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts
@@ -274,6 +274,7 @@ export type CursorOrUndefined = t.TypeOf;
export const namespace_type = DefaultNamespace;
+export const operatorIncluded = t.keyof({ included: null });
export const operator = t.keyof({ excluded: null, included: null });
export type Operator = t.TypeOf;
export enum OperatorEnum {
diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.mock.ts
index 5c4aff1fedcd3..529e173618f15 100644
--- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.mock.ts
+++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.mock.ts
@@ -7,7 +7,7 @@
import {
COMMENTS,
DESCRIPTION,
- ENTRIES,
+ ENDPOINT_ENTRIES,
ITEM_TYPE,
META,
NAME,
@@ -21,7 +21,7 @@ export const getCreateEndpointListItemSchemaMock = (): CreateEndpointListItemSch
_tags: _TAGS,
comments: COMMENTS,
description: DESCRIPTION,
- entries: ENTRIES,
+ entries: ENDPOINT_ENTRIES,
item_id: undefined,
meta: META,
name: NAME,
diff --git a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts
index dacd9d515de51..d1fc167f5a92b 100644
--- a/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts
+++ b/x-pack/plugins/lists/common/schemas/request/create_endpoint_list_item_schema.ts
@@ -18,7 +18,8 @@ import {
tags,
} from '../common/schemas';
import { RequiredKeepUndefined } from '../../types';
-import { CreateCommentsArray, DefaultCreateCommentsArray, nonEmptyEntriesArray } from '../types';
+import { CreateCommentsArray, DefaultCreateCommentsArray } from '../types';
+import { nonEmptyEndpointEntriesArray } from '../types/endpoint';
import { EntriesArray } from '../types/entries';
import { DefaultUuid } from '../../shared_imports';
@@ -26,7 +27,7 @@ export const createEndpointListItemSchema = t.intersection([
t.exact(
t.type({
description,
- entries: nonEmptyEntriesArray,
+ entries: nonEmptyEndpointEntriesArray,
name,
type: exceptionListItemType,
})
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts
new file mode 100644
index 0000000000000..c3bc88ab57a90
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.mock.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { EndpointEntriesArray } from './entries';
+import { getEndpointEntryMatchMock } from './entry_match.mock';
+import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock';
+import { getEndpointEntryNestedMock } from './entry_nested.mock';
+
+export const getEndpointEntriesArrayMock = (): EndpointEntriesArray => [
+ getEndpointEntryMatchMock(),
+ getEndpointEntryMatchAnyMock(),
+ getEndpointEntryNestedMock(),
+];
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts
new file mode 100644
index 0000000000000..ee52e6b7b6561
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.test.ts
@@ -0,0 +1,111 @@
+/*
+ * 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 { pipe } from 'fp-ts/lib/pipeable';
+import { left } from 'fp-ts/lib/Either';
+
+import { foldLeftRight, getPaths } from '../../../shared_imports';
+import { getEntryExistsMock } from '../entry_exists.mock';
+import { getEntryListMock } from '../entry_list.mock';
+
+import { getEndpointEntryMatchMock } from './entry_match.mock';
+import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock';
+import { getEndpointEntryNestedMock } from './entry_nested.mock';
+import { getEndpointEntriesArrayMock } from './entries.mock';
+import {
+ NonEmptyEndpointEntriesArray,
+ endpointEntriesArray,
+ nonEmptyEndpointEntriesArray,
+} from './entries';
+
+describe('Endpoint', () => {
+ describe('entriesArray', () => {
+ test('it should validate an array with match entry', () => {
+ const payload = [getEndpointEntryMatchMock()];
+ const decoded = endpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+
+ test('it should validate an array with match_any entry', () => {
+ const payload = [getEndpointEntryMatchAnyMock()];
+ const decoded = endpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+
+ test('it should NOT validate an empty array', () => {
+ const payload: NonEmptyEndpointEntriesArray = [];
+ const decoded = nonEmptyEndpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "[]" supplied to "NonEmptyEndpointEntriesArray"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('type guard for nonEmptyEndpointNestedEntries should allow array of endpoint entries', () => {
+ const payload: NonEmptyEndpointEntriesArray = [getEndpointEntryMatchAnyMock()];
+ const guarded = nonEmptyEndpointEntriesArray.is(payload);
+ expect(guarded).toBeTruthy();
+ });
+
+ test('type guard for nonEmptyEndpointNestedEntries should disallow empty arrays', () => {
+ const payload: NonEmptyEndpointEntriesArray = [];
+ const guarded = nonEmptyEndpointEntriesArray.is(payload);
+ expect(guarded).toBeFalsy();
+ });
+
+ test('it should NOT validate an array with exists entry', () => {
+ const payload = [getEntryExistsMock()];
+ const decoded = endpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "exists" supplied to "type"',
+ 'Invalid value "undefined" supplied to "value"',
+ 'Invalid value "undefined" supplied to "entries"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should NOT validate an array with list entry', () => {
+ const payload = [getEntryListMock()];
+ const decoded = endpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "list" supplied to "type"',
+ 'Invalid value "undefined" supplied to "value"',
+ 'Invalid value "undefined" supplied to "entries"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should validate an array with nested entry', () => {
+ const payload = [getEndpointEntryNestedMock()];
+ const decoded = endpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+
+ test('it should validate an array with all types of entries', () => {
+ const payload = getEndpointEntriesArrayMock();
+ const decoded = endpointEntriesArray.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+ });
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts
new file mode 100644
index 0000000000000..ebdac1a44293a
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entries.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 * as t from 'io-ts';
+import { Either } from 'fp-ts/lib/Either';
+
+import { endpointEntryMatchAny } from './entry_match_any';
+import { endpointEntryMatch } from './entry_match';
+import { endpointEntryNested } from './entry_nested';
+
+export const endpointEntriesArray = t.array(
+ t.union([endpointEntryMatch, endpointEntryMatchAny, endpointEntryNested])
+);
+export type EndpointEntriesArray = t.TypeOf;
+
+/**
+ * Types the nonEmptyEndpointEntriesArray as:
+ * - An array of entries of length 1 or greater
+ *
+ */
+export const nonEmptyEndpointEntriesArray = new t.Type<
+ EndpointEntriesArray,
+ EndpointEntriesArray,
+ unknown
+>(
+ 'NonEmptyEndpointEntriesArray',
+ (u: unknown): u is EndpointEntriesArray => endpointEntriesArray.is(u) && u.length > 0,
+ (input, context): Either => {
+ if (Array.isArray(input) && input.length === 0) {
+ return t.failure(input, context);
+ } else {
+ return endpointEntriesArray.validate(input, context);
+ }
+ },
+ t.identity
+);
+
+export type NonEmptyEndpointEntriesArray = t.OutputOf;
+export type NonEmptyEndpointEntriesArrayDecoded = t.TypeOf;
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts
new file mode 100644
index 0000000000000..35d10544c7fdb
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.mock.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { ENTRY_VALUE, FIELD, MATCH, OPERATOR } from '../../../constants.mock';
+
+import { EndpointEntryMatch } from './entry_match';
+
+export const getEndpointEntryMatchMock = (): EndpointEntryMatch => ({
+ field: FIELD,
+ operator: OPERATOR,
+ type: MATCH,
+ value: ENTRY_VALUE,
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts
new file mode 100644
index 0000000000000..48f128be0bb31
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.test.ts
@@ -0,0 +1,102 @@
+/*
+ * 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 { pipe } from 'fp-ts/lib/pipeable';
+import { left } from 'fp-ts/lib/Either';
+
+import { foldLeftRight, getPaths } from '../../../shared_imports';
+import { getEntryMatchMock } from '../entry_match.mock';
+
+import { getEndpointEntryMatchMock } from './entry_match.mock';
+import { EndpointEntryMatch, endpointEntryMatch } from './entry_match';
+
+describe('endpointEntryMatch', () => {
+ test('it should validate an entry', () => {
+ const payload = getEndpointEntryMatchMock();
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+
+ test('it should NOT validate when "operator" is "excluded"', () => {
+ // Use the generic entry mock so we can test operator: excluded
+ const payload = getEntryMatchMock();
+ payload.operator = 'excluded';
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "excluded" supplied to "operator"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "field" is empty string', () => {
+ const payload: Omit & { field: string } = {
+ ...getEndpointEntryMatchMock(),
+ field: '',
+ };
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "value" is not string', () => {
+ const payload: Omit & { value: string[] } = {
+ ...getEndpointEntryMatchMock(),
+ value: ['some value'],
+ };
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "["some value"]" supplied to "value"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "value" is empty string', () => {
+ const payload: Omit & { value: string } = {
+ ...getEndpointEntryMatchMock(),
+ value: '',
+ };
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "type" is not "match"', () => {
+ const payload: Omit & { type: string } = {
+ ...getEndpointEntryMatchMock(),
+ type: 'match_any',
+ };
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "match_any" supplied to "type"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should strip out extra keys', () => {
+ const payload: EndpointEntryMatch & {
+ extraKey?: string;
+ } = getEndpointEntryMatchMock();
+ payload.extraKey = 'some value';
+ const decoded = endpointEntryMatch.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(getEntryMatchMock());
+ });
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts
new file mode 100644
index 0000000000000..853e71cf68dd4
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 * as t from 'io-ts';
+
+import { NonEmptyString } from '../../../shared_imports';
+import { operatorIncluded } from '../../common/schemas';
+
+export const endpointEntryMatch = t.exact(
+ t.type({
+ field: NonEmptyString,
+ operator: operatorIncluded,
+ type: t.keyof({ match: null }),
+ value: NonEmptyString,
+ })
+);
+export type EndpointEntryMatch = t.TypeOf;
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts
new file mode 100644
index 0000000000000..75544e76dadab
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.mock.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { ENTRY_VALUE, FIELD, MATCH_ANY, OPERATOR } from '../../../constants.mock';
+
+import { EndpointEntryMatchAny } from './entry_match_any';
+
+export const getEndpointEntryMatchAnyMock = (): EndpointEntryMatchAny => ({
+ field: FIELD,
+ operator: OPERATOR,
+ type: MATCH_ANY,
+ value: [ENTRY_VALUE],
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts
new file mode 100644
index 0000000000000..6e52855bc25d4
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.test.ts
@@ -0,0 +1,100 @@
+/*
+ * 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 { pipe } from 'fp-ts/lib/pipeable';
+import { left } from 'fp-ts/lib/Either';
+
+import { foldLeftRight, getPaths } from '../../../shared_imports';
+import { getEntryMatchAnyMock } from '../entry_match_any.mock';
+
+import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock';
+import { EndpointEntryMatchAny, endpointEntryMatchAny } from './entry_match_any';
+
+describe('endpointEntryMatchAny', () => {
+ test('it should validate an entry', () => {
+ const payload = getEndpointEntryMatchAnyMock();
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+
+ test('it should NOT validate when operator is "excluded"', () => {
+ // Use the generic entry mock so we can test operator: excluded
+ const payload = getEntryMatchAnyMock();
+ payload.operator = 'excluded';
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "excluded" supplied to "operator"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when field is empty string', () => {
+ const payload: Omit & { field: string } = {
+ ...getEndpointEntryMatchAnyMock(),
+ field: '',
+ };
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when value is empty array', () => {
+ const payload: Omit & { value: string[] } = {
+ ...getEndpointEntryMatchAnyMock(),
+ value: [],
+ };
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "[]" supplied to "value"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when value is not string array', () => {
+ const payload: Omit & { value: string } = {
+ ...getEndpointEntryMatchAnyMock(),
+ value: 'some string',
+ };
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "some string" supplied to "value"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "type" is not "match_any"', () => {
+ const payload: Omit & { type: string } = {
+ ...getEndpointEntryMatchAnyMock(),
+ type: 'match',
+ };
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should strip out extra keys', () => {
+ const payload: EndpointEntryMatchAny & {
+ extraKey?: string;
+ } = getEndpointEntryMatchAnyMock();
+ payload.extraKey = 'some extra key';
+ const decoded = endpointEntryMatchAny.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(getEntryMatchAnyMock());
+ });
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts
new file mode 100644
index 0000000000000..8fda8357c7210
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_match_any.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 * as t from 'io-ts';
+
+import { NonEmptyString } from '../../../shared_imports';
+import { operatorIncluded } from '../../common/schemas';
+import { nonEmptyOrNullableStringArray } from '../non_empty_or_nullable_string_array';
+
+export const endpointEntryMatchAny = t.exact(
+ t.type({
+ field: NonEmptyString,
+ operator: operatorIncluded,
+ type: t.keyof({ match_any: null }),
+ value: nonEmptyOrNullableStringArray,
+ })
+);
+export type EndpointEntryMatchAny = t.TypeOf;
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts
new file mode 100644
index 0000000000000..5501631655c61
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.mock.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { FIELD, NESTED } from '../../../constants.mock';
+
+import { EndpointEntryNested } from './entry_nested';
+import { getEndpointEntryMatchMock } from './entry_match.mock';
+import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock';
+
+export const getEndpointEntryNestedMock = (): EndpointEntryNested => ({
+ entries: [getEndpointEntryMatchMock(), getEndpointEntryMatchAnyMock()],
+ field: FIELD,
+ type: NESTED,
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts
new file mode 100644
index 0000000000000..f19da71a5369c
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.test.ts
@@ -0,0 +1,137 @@
+/*
+ * 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 { pipe } from 'fp-ts/lib/pipeable';
+import { left } from 'fp-ts/lib/Either';
+
+import { foldLeftRight, getPaths } from '../../../shared_imports';
+import { getEntryExistsMock } from '../entry_exists.mock';
+
+import { getEndpointEntryNestedMock } from './entry_nested.mock';
+import { EndpointEntryNested, endpointEntryNested } from './entry_nested';
+import { getEndpointEntryMatchAnyMock } from './entry_match_any.mock';
+import {
+ NonEmptyEndpointNestedEntriesArray,
+ nonEmptyEndpointNestedEntriesArray,
+} from './non_empty_nested_entries_array';
+import { getEndpointEntryMatchMock } from './entry_match.mock';
+
+describe('endpointEntryNested', () => {
+ test('it should validate a nested entry', () => {
+ const payload = getEndpointEntryNestedMock();
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(payload);
+ });
+
+ test('it should FAIL validation when "type" is not "nested"', () => {
+ const payload: Omit & { type: 'match' } = {
+ ...getEndpointEntryNestedMock(),
+ type: 'match',
+ };
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "field" is empty string', () => {
+ const payload: Omit & {
+ field: string;
+ } = { ...getEndpointEntryNestedMock(), field: '' };
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "field" is not a string', () => {
+ const payload: Omit & {
+ field: number;
+ } = { ...getEndpointEntryNestedMock(), field: 1 };
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual(['Invalid value "1" supplied to "field"']);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should FAIL validation when "entries" is not an array', () => {
+ const payload: Omit & {
+ entries: string;
+ } = { ...getEndpointEntryNestedMock(), entries: 'im a string' };
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "im a string" supplied to "entries"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should validate when "entries" contains an entry item that is type "match"', () => {
+ const payload = { ...getEndpointEntryNestedMock(), entries: [getEndpointEntryMatchAnyMock()] };
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual({
+ entries: [
+ {
+ field: 'host.name',
+ operator: 'included',
+ type: 'match_any',
+ value: ['some host name'],
+ },
+ ],
+ field: 'host.name',
+ type: 'nested',
+ });
+ });
+
+ test('it should NOT validate when "entries" contains an entry item that is type "exists"', () => {
+ const payload = { ...getEndpointEntryNestedMock(), entries: [getEntryExistsMock()] };
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([
+ 'Invalid value "exists" supplied to "entries,type"',
+ 'Invalid value "undefined" supplied to "entries,value"',
+ ]);
+ expect(message.schema).toEqual({});
+ });
+
+ test('it should strip out extra keys', () => {
+ const payload: EndpointEntryNested & {
+ extraKey?: string;
+ } = getEndpointEntryNestedMock();
+ payload.extraKey = 'some extra key';
+ const decoded = endpointEntryNested.decode(payload);
+ const message = pipe(decoded, foldLeftRight);
+
+ expect(getPaths(left(message.errors))).toEqual([]);
+ expect(message.schema).toEqual(getEndpointEntryNestedMock());
+ });
+
+ test('type guard for nonEmptyEndpointNestedEntries should allow array of endpoint entries', () => {
+ const payload: NonEmptyEndpointNestedEntriesArray = [
+ getEndpointEntryMatchMock(),
+ getEndpointEntryMatchAnyMock(),
+ ];
+ const guarded = nonEmptyEndpointNestedEntriesArray.is(payload);
+ expect(guarded).toBeTruthy();
+ });
+
+ test('type guard for nonEmptyEndpointNestedEntries should disallow empty arrays', () => {
+ const payload: NonEmptyEndpointNestedEntriesArray = [];
+ const guarded = nonEmptyEndpointNestedEntriesArray.is(payload);
+ expect(guarded).toBeFalsy();
+ });
+});
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts
new file mode 100644
index 0000000000000..aad24674af08a
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/entry_nested.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 * as t from 'io-ts';
+
+import { NonEmptyString } from '../../../shared_imports';
+
+import { nonEmptyEndpointNestedEntriesArray } from './non_empty_nested_entries_array';
+
+export const endpointEntryNested = t.exact(
+ t.type({
+ entries: nonEmptyEndpointNestedEntriesArray,
+ field: NonEmptyString,
+ type: t.keyof({ nested: null }),
+ })
+);
+export type EndpointEntryNested = t.TypeOf;
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/index.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/index.ts
new file mode 100644
index 0000000000000..91554cd441db4
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export * from './entries';
diff --git a/x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts
new file mode 100644
index 0000000000000..f7d6e307da763
--- /dev/null
+++ b/x-pack/plugins/lists/common/schemas/types/endpoint/non_empty_nested_entries_array.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 * as t from 'io-ts';
+import { Either } from 'fp-ts/lib/Either';
+
+import { endpointEntryMatchAny } from './entry_match_any';
+import { endpointEntryMatch } from './entry_match';
+
+export const endpointNestedEntriesArray = t.array(
+ t.union([endpointEntryMatch, endpointEntryMatchAny])
+);
+export type EndpointNestedEntriesArray = t.TypeOf;
+
+/**
+ * Types the nonEmptyNestedEntriesArray as:
+ * - An array of entries of length 1 or greater
+ *
+ */
+export const nonEmptyEndpointNestedEntriesArray = new t.Type<
+ EndpointNestedEntriesArray,
+ EndpointNestedEntriesArray,
+ unknown
+>(
+ 'NonEmptyEndpointNestedEntriesArray',
+ (u: unknown): u is EndpointNestedEntriesArray => endpointNestedEntriesArray.is(u) && u.length > 0,
+ (input, context): Either => {
+ if (Array.isArray(input) && input.length === 0) {
+ return t.failure(input, context);
+ } else {
+ return endpointNestedEntriesArray.validate(input, context);
+ }
+ },
+ t.identity
+);
+
+export type NonEmptyEndpointNestedEntriesArray = t.OutputOf<
+ typeof nonEmptyEndpointNestedEntriesArray
+>;
+export type NonEmptyEndpointNestedEntriesArrayDecoded = t.TypeOf<
+ typeof nonEmptyEndpointNestedEntriesArray
+>;
diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts
index f092aec82a8f3..e51e113239f20 100644
--- a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts
+++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts
@@ -17,7 +17,7 @@ import {
import { getExceptionListClient } from './utils/get_exception_list_client';
import { endpointDisallowedFields } from './endpoint_disallowed_fields';
-import { validateExceptionListSize } from './validate';
+import { validateEndpointExceptionItemEntries, validateExceptionListSize } from './validate';
export const createExceptionListItemRoute = (router: IRouter): void => {
router.post(
@@ -73,13 +73,11 @@ export const createExceptionListItemRoute = (router: IRouter): void => {
});
} else {
if (exceptionList.type === 'endpoint') {
+ const error = validateEndpointExceptionItemEntries(request.body.entries);
+ if (error != null) {
+ return siemResponse.error(error);
+ }
for (const entry of entries) {
- if (entry.type === 'list') {
- return siemResponse.error({
- body: `cannot add exception item with entry of type "list" to endpoint exception list`,
- statusCode: 400,
- });
- }
if (endpointDisallowedFields.includes(entry.field)) {
return siemResponse.error({
body: `cannot add endpoint exception item on field ${entry.field}`,
diff --git a/x-pack/plugins/lists/server/routes/validate.ts b/x-pack/plugins/lists/server/routes/validate.ts
index a7f5c96e13d7b..703d0eee8c65a 100644
--- a/x-pack/plugins/lists/server/routes/validate.ts
+++ b/x-pack/plugins/lists/server/routes/validate.ts
@@ -4,11 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { pipe } from 'fp-ts/lib/pipeable';
+import * as t from 'io-ts';
+import { fold } from 'fp-ts/lib/Either';
+
import { ExceptionListClient } from '../services/exception_lists/exception_list_client';
import { MAX_EXCEPTION_LIST_SIZE } from '../../common/constants';
import { foundExceptionListItemSchema } from '../../common/schemas';
-import { NamespaceType } from '../../common/schemas/types';
-import { validate } from '../../common/shared_imports';
+import { NamespaceType, NonEmptyEntriesArray } from '../../common/schemas/types';
+import { nonEmptyEndpointEntriesArray } from '../../common/schemas/types/endpoint';
+import { exactCheck, validate } from '../../common/shared_imports';
+import { formatErrors } from '../siem_server_deps';
export const validateExceptionListSize = async (
exceptionLists: ExceptionListClient,
@@ -54,3 +60,20 @@ export const validateExceptionListSize = async (
}
return null;
};
+
+export const validateEndpointExceptionItemEntries = (
+ entries: NonEmptyEntriesArray
+): { body: string[]; statusCode: number } | null =>
+ pipe(
+ nonEmptyEndpointEntriesArray.decode(entries),
+ (decoded) => exactCheck(entries, decoded),
+ fold(
+ (errors: t.Errors) => {
+ return {
+ body: formatErrors(errors),
+ statusCode: 400,
+ };
+ },
+ () => null
+ )
+ );
diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json
index 8ccbe707f204c..6999441d21941 100644
--- a/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json
+++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/endpoint_list_item.json
@@ -8,8 +8,9 @@
"entries": [
{
"field": "actingProcess.file.signer",
- "operator": "excluded",
- "type": "exists"
+ "operator": "included",
+ "type": "match",
+ "value": "test"
},
{
"field": "host.name",
diff --git a/x-pack/plugins/lists/server/siem_server_deps.ts b/x-pack/plugins/lists/server/siem_server_deps.ts
index 324103b7fb50d..12543566db954 100644
--- a/x-pack/plugins/lists/server/siem_server_deps.ts
+++ b/x-pack/plugins/lists/server/siem_server_deps.ts
@@ -19,3 +19,5 @@ export {
buildRouteValidation,
readPrivileges,
} from '../../security_solution/server';
+
+export { formatErrors } from '../../security_solution/common';
diff --git a/x-pack/test/api_integration/apis/lists/create_exception_list_item.ts b/x-pack/test/api_integration/apis/lists/create_exception_list_item.ts
index bf35a6283aae5..0d65b0e66c54d 100644
--- a/x-pack/test/api_integration/apis/lists/create_exception_list_item.ts
+++ b/x-pack/test/api_integration/apis/lists/create_exception_list_item.ts
@@ -40,9 +40,11 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'xxx')
.send(badItem)
.expect(400);
- expect(body.message).to.eql(
- 'cannot add exception item with entry of type "list" to endpoint exception list'
- );
+ expect(body.message).to.eql([
+ 'Invalid value "list" supplied to "type"',
+ 'Invalid value "undefined" supplied to "value"',
+ 'Invalid value "undefined" supplied to "entries"',
+ ]);
});
it('should return a 400 if endpoint exception entry has disallowed field', async () => {
From 8a110a3c15373429e4230494f5f90bb2374f8c9b Mon Sep 17 00:00:00 2001
From: Joe Portner <5295965+jportner@users.noreply.github.com>
Date: Mon, 17 Aug 2020 16:56:32 -0400
Subject: [PATCH 012/177] lodash `4.17.19` -> `4.17.20` (#75205)
---
package.json | 8 +++++---
packages/kbn-pm/package.json | 2 +-
packages/kbn-test/package.json | 2 +-
x-pack/package.json | 2 +-
yarn.lock | 26 ++++++++------------------
5 files changed, 16 insertions(+), 24 deletions(-)
diff --git a/package.json b/package.json
index c79c2a2f3e33a..941cad4f0fd02 100644
--- a/package.json
+++ b/package.json
@@ -84,12 +84,14 @@
"**/@types/angular": "^1.6.56",
"**/@types/hoist-non-react-statics": "^3.3.1",
"**/@types/chai": "^4.2.11",
- "**/cypress/@types/lodash": "^4.14.155",
+ "**/cypress/@types/lodash": "^4.14.159",
+ "**/cypress/lodash": "^4.17.20",
"**/typescript": "3.9.5",
"**/graphql-toolkit/lodash": "^4.17.15",
"**/hoist-non-react-statics": "^3.3.2",
"**/isomorphic-git/**/base64-js": "^1.2.1",
"**/image-diff/gm/debug": "^2.6.9",
+ "**/load-grunt-config/lodash": "^4.17.20",
"**/react-dom": "^16.12.0",
"**/react": "^16.12.0",
"**/react-test-renderer": "^16.12.0",
@@ -205,7 +207,7 @@
"leaflet-vega": "^0.8.6",
"leaflet.heat": "0.2.0",
"less": "npm:@elastic/less@2.7.3-kibana",
- "lodash": "^4.17.15",
+ "lodash": "^4.17.20",
"lru-cache": "4.1.5",
"markdown-it": "^10.0.0",
"minimatch": "^3.0.4",
@@ -332,7 +334,7 @@
"@types/json5": "^0.0.30",
"@types/license-checker": "15.0.0",
"@types/listr": "^0.14.0",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/lru-cache": "^5.1.0",
"@types/markdown-it": "^0.0.7",
"@types/minimatch": "^2.0.29",
diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json
index 78fa48979c1b5..6b36f14df95e9 100644
--- a/packages/kbn-pm/package.json
+++ b/packages/kbn-pm/package.json
@@ -22,7 +22,7 @@
"@types/glob": "^5.0.35",
"@types/globby": "^6.1.0",
"@types/has-ansi": "^3.0.0",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/log-symbols": "^2.0.0",
"@types/ncp": "^2.0.1",
"@types/node": ">=10.17.17 <10.20.0",
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index f86bcfd2bb7b2..24655f8e57026 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -14,7 +14,7 @@
"@kbn/babel-preset": "1.0.0",
"@kbn/dev-utils": "1.0.0",
"@types/joi": "^13.4.2",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/parse-link-header": "^1.0.0",
"@types/strip-ansi": "^5.2.1",
"@types/xml2js": "^0.4.5",
diff --git a/x-pack/package.json b/x-pack/package.json
index 57a0b88f8c2a5..5333c67f6ac0f 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -83,7 +83,7 @@
"@types/jsdom": "^16.2.3",
"@types/json-stable-stringify": "^1.0.32",
"@types/jsonwebtoken": "^7.2.8",
- "@types/lodash": "^4.14.155",
+ "@types/lodash": "^4.14.159",
"@types/mapbox-gl": "^1.9.1",
"@types/memoize-one": "^4.1.0",
"@types/mime": "^2.0.1",
diff --git a/yarn.lock b/yarn.lock
index 81bb7338e615f..6dd6a39c1142e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4102,15 +4102,10 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.3.tgz#aaddec6a3c93bf03b402db3acf5d4c77bce8bdff"
integrity sha512-b9zScBKmB/RJqETbxu3YRya61vJOik89/lR+NdxjZAFMDcMSjwX6IhQoP4terJkhsa9TE1C+l6XwxCkhhsaZXg==
-"@types/lodash@^4.14.116":
- version "4.14.150"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.150.tgz#649fe44684c3f1fcb6164d943c5a61977e8cf0bd"
- integrity sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w==
-
-"@types/lodash@^4.14.155":
- version "4.14.156"
- resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1"
- integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ==
+"@types/lodash@^4.14.116", "@types/lodash@^4.14.159":
+ version "4.14.159"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065"
+ integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg==
"@types/log-symbols@^2.0.0":
version "2.0.0"
@@ -19329,15 +19324,10 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-lodash@4.17.11, lodash@4.17.19, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.16, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
- version "4.17.19"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
- integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
-
-lodash@4.17.15:
- version "4.17.15"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
- integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+lodash@4.17.11, lodash@4.17.15, lodash@4.17.19, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.16, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5:
+ version "4.17.20"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
+ integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
lodash@^3.10.1:
version "3.10.1"
From b8dc0a02a055a8299f481609ac1cb0f8e42def40 Mon Sep 17 00:00:00 2001
From: Brent Kimmel
Date: Mon, 17 Aug 2020 18:07:19 -0400
Subject: [PATCH 013/177] [Security Solution][Resolver] Enzyme tests for graph
layout (#75010)
* [Security Solution][Resolver] Enzyme tests for graph layout
Co-authored-by: Elastic Machine
Co-authored-by: Robert Austin
---
.../test_utilities/simulator/index.tsx | 9 +-
.../public/resolver/types.ts | 2 +-
.../resolver/view/clickthrough.test.tsx | 96 +++++++++++++++++++
.../public/resolver/view/edge_line.tsx | 1 +
4 files changed, 106 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx
index 43a03bb771501..47e92320c4c20 100644
--- a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx
@@ -221,7 +221,14 @@ export class Simulator {
}
/**
- * This manually runs the animation frames tied to a configurable timestamp in the future
+ * Lines that connect the nodes in the graph
+ */
+ public edgeLines(): ReactWrapper {
+ return this.domNodes('[data-test-subj="resolver:graph:edgeline"]');
+ }
+
+ /**
+ * This manually runs the animation frames tied to a configurable timestamp in the future.
*/
public runAnimationFramesTimeFromNow(time: number = 0) {
this.sideEffectSimulator.controls.time = time;
diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts
index 30634e722050f..97d97700b11ae 100644
--- a/x-pack/plugins/security_solution/public/resolver/types.ts
+++ b/x-pack/plugins/security_solution/public/resolver/types.ts
@@ -221,7 +221,7 @@ export type Vector2 = readonly [number, number];
*/
export interface AABB {
/**
- * Vector who's `x` component is the _left_ side of the `AABB` and who's `y` component is the _bottom_ side of the `AABB`.
+ * Vector whose `x` component represents the minimum side of the box and whose 'y' component represents the maximum side of the box.
**/
readonly minimum: Vector2;
/**
diff --git a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx
index 3265ee8bcfca0..7a2301b7bb515 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx
@@ -10,6 +10,7 @@ import { Simulator } from '../test_utilities/simulator';
import '../test_utilities/extend_jest';
import { noAncestorsTwoChildrenWithRelatedEventsOnOrigin } from '../data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin';
import { urlSearch } from '../test_utilities/url_search';
+import { Vector2, AABB } from '../types';
let simulator: Simulator;
let databaseDocumentID: string;
@@ -136,6 +137,11 @@ describe('Resolver, when analyzing a tree that has two related events for the or
});
describe('when it has loaded', () => {
+ let originBounds: AABB;
+ let firstChildBounds: AABB;
+ let secondChildBounds: AABB;
+ let edgeStartingCoordinates: Vector2[];
+ let edgeTerminalCoordinates: Vector2[];
beforeEach(async () => {
await expect(
simulator.map(() => ({
@@ -150,6 +156,31 @@ describe('Resolver, when analyzing a tree that has two related events for the or
graphErrorElements: 0,
originNodeButton: 1,
});
+
+ originBounds = computedNodeBoundaries(entityIDs.origin);
+ firstChildBounds = computedNodeBoundaries(entityIDs.firstChild);
+ secondChildBounds = computedNodeBoundaries(entityIDs.secondChild);
+ edgeStartingCoordinates = computedEdgeStartingCoordinates();
+ edgeTerminalCoordinates = computedEdgeTerminalCoordinates();
+ });
+
+ it('should have one and only one outgoing edge from the origin node', () => {
+ // This winnows edges to the one(s) that "start" under the origin node
+ const edgesThatStartUnderneathOrigin = edgeStartingCoordinates.filter(
+ coordinateBoundaryFilter(originBounds)
+ );
+ expect(edgesThatStartUnderneathOrigin).toHaveLength(1);
+ });
+ it('leaf nodes should each have one and only one incoming edge', () => {
+ const edgesThatTerminateUnderneathFirstChild = edgeTerminalCoordinates.filter(
+ coordinateBoundaryFilter(firstChildBounds)
+ );
+ expect(edgesThatTerminateUnderneathFirstChild).toHaveLength(1);
+
+ const edgesThatTerminateUnderneathSecondChild = edgeTerminalCoordinates.filter(
+ coordinateBoundaryFilter(secondChildBounds)
+ );
+ expect(edgesThatTerminateUnderneathSecondChild).toHaveLength(1);
});
it('should render a related events button', async () => {
@@ -195,3 +226,68 @@ describe('Resolver, when analyzing a tree that has two related events for the or
});
});
});
+
+/**
+ * Get the integer in a CSS px unit string
+ * @param px a string with `px` preceded by numbers
+ */
+function pxNum(px: string): number {
+ return parseInt(px.match(/\d+/)![0], 10);
+}
+
+/**
+ * Get computed boundaries for process node elements
+ */
+function computedNodeBoundaries(entityID: string): AABB {
+ const { left, top, width, height } = getComputedStyle(
+ simulator.processNodeElements({ entityID }).getDOMNode()
+ );
+ return {
+ minimum: [pxNum(left), pxNum(top)],
+ maximum: [pxNum(left) + pxNum(width), pxNum(top) + pxNum(height)],
+ };
+}
+
+/**
+ * Coordinates for where the edgelines "start"
+ */
+function computedEdgeStartingCoordinates(): Vector2[] {
+ return simulator.edgeLines().map((edge) => {
+ const { left, top } = getComputedStyle(edge.getDOMNode());
+ return [pxNum(left), pxNum(top)];
+ });
+}
+
+/**
+ * Coordinates for where edgelines "end" (after application of transform)
+ */
+function computedEdgeTerminalCoordinates(): Vector2[] {
+ return simulator.edgeLines().map((edge) => {
+ const { left, top, width, transform } = getComputedStyle(edge.getDOMNode());
+ /**
+ * Without the transform in the rotation, edgelines lay flat across the x-axis.
+ * Plotting the x/y of the line's terminal point here takes the rotation into account.
+ * This could cause tests to break if/when certain adjustments are made to the view that might
+ * regress the alignment of nodes and edges.
+ */
+ const edgeLineRotationInRadians = parseFloat(transform.match(/rotateZ\((-?\d+\.?\d+)/i)![1]);
+ const rotateDownTo = Math.sin(edgeLineRotationInRadians) * pxNum(width);
+ const rotateLeftTo = Math.cos(edgeLineRotationInRadians) * pxNum(width);
+ return [pxNum(left) + rotateLeftTo, pxNum(top) + rotateDownTo];
+ });
+}
+
+/**
+ *
+ * @param bounds Get a function that filters x/y of edges to those contained in a certain bounding box
+ */
+function coordinateBoundaryFilter(bounds: AABB) {
+ return (coords: Vector2) => {
+ return (
+ coords[0] >= bounds.minimum[0] &&
+ coords[0] <= bounds.maximum[0] &&
+ coords[1] >= bounds.minimum[1] &&
+ coords[1] <= bounds.maximum[1]
+ );
+ };
+}
diff --git a/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx b/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx
index 061dfce64b4e4..fcc363a1560d5 100644
--- a/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx
+++ b/x-pack/plugins/security_solution/public/resolver/view/edge_line.tsx
@@ -131,6 +131,7 @@ const EdgeLineComponent = React.memo(
style={style}
resolverEdgeColor={colorMap.resolverEdge}
magFactorX={magFactorX}
+ data-test-subj="resolver:graph:edgeline"
>
{elapsedTime && (
Date: Mon, 17 Aug 2020 19:29:38 -0700
Subject: [PATCH 014/177] Allow Watcher index actions to be configured without
specifying an index name. (#74684)
---
.../plugins/translations/translations/ja-JP.json | 2 --
.../plugins/translations/translations/zh-CN.json | 2 --
.../watch_create_threshold.test.tsx | 14 +++-----------
.../watcher/common/models/action/index_action.js | 15 ---------------
.../application/models/action/index_action.js | 7 -------
5 files changed, 3 insertions(+), 37 deletions(-)
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 8c92e7359b2f7..3daa936f5be58 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -19284,7 +19284,6 @@
"xpack.watcher.models.jiraAction.typeName": "Jira",
"xpack.watcher.models.jsonWatch.selectMessageText": "生 JSON のカスタムウォッチをセットアップします。",
"xpack.watcher.models.jsonWatch.typeName": "高度なウォッチ",
- "xpack.watcher.models.loggingAction.actionJsonIndexNamePropertyMissingBadRequestMessage": "json引数には{actionJsonIndexName}プロパティが含まれている必要があります",
"xpack.watcher.models.loggingAction.actionJsonLoggingPropertyMissingBadRequestMessage": "json 引数には {actionJsonLogging} プロパティが含まれている必要があります",
"xpack.watcher.models.loggingAction.actionJsonLoggingTextPropertyMissingBadRequestMessage": "json 引数には {actionJsonLoggingText} プロパティが含まれている必要があります",
"xpack.watcher.models.loggingAction.actionJsonWebhookHostPropertyMissingBadRequestMessage": "json引数には{actionJsonWebhookHost}プロパティが含まれている必要があります",
@@ -19550,7 +19549,6 @@
"xpack.watcher.timeUnits.minuteLabel": "{timeValue, plural, one {分} other {分}}",
"xpack.watcher.timeUnits.secondLabel": "{timeValue, plural, one {秒} other {秒}}",
"xpack.watcher.watchActions.email.emailRecipientIsRequiredValidationMessage": "送信先メールアドレスが必要です。",
- "xpack.watcher.watchActions.index.indexIsRequiredValidationMessage": "インデックス名が必要です。",
"xpack.watcher.watchActions.jira.issueTypeNameIsRequiredValidationMessage": "Jira問題タイプが必要です。",
"xpack.watcher.watchActions.jira.projectKeyIsRequiredValidationMessage": "Jiraプロジェクトキーが必要です。",
"xpack.watcher.watchActions.jira.summaryIsRequiredValidationMessage": "Jira概要が必要です。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 5ab70ff7a9d04..f396d950f7526 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -19292,7 +19292,6 @@
"xpack.watcher.models.jiraAction.typeName": "Jira",
"xpack.watcher.models.jsonWatch.selectMessageText": "以原始 JSON 格式设置定制监视。",
"xpack.watcher.models.jsonWatch.typeName": "高级监视",
- "xpack.watcher.models.loggingAction.actionJsonIndexNamePropertyMissingBadRequestMessage": "JSON 参数必须包含 {actionJsonIndexName} 属性",
"xpack.watcher.models.loggingAction.actionJsonLoggingPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonLogging} 属性",
"xpack.watcher.models.loggingAction.actionJsonLoggingTextPropertyMissingBadRequestMessage": "json 参数必须包含 {actionJsonLoggingText} 属性",
"xpack.watcher.models.loggingAction.actionJsonWebhookHostPropertyMissingBadRequestMessage": "JSON 参数必须包含 {actionJsonWebhookHost} 属性",
@@ -19558,7 +19557,6 @@
"xpack.watcher.timeUnits.minuteLabel": "{timeValue, plural, one {分钟} other {分钟}}",
"xpack.watcher.timeUnits.secondLabel": "{timeValue, plural, one {秒} other {秒}}",
"xpack.watcher.watchActions.email.emailRecipientIsRequiredValidationMessage": "“收件人”电子邮件地址必填。",
- "xpack.watcher.watchActions.index.indexIsRequiredValidationMessage": "索引名称必填。",
"xpack.watcher.watchActions.jira.issueTypeNameIsRequiredValidationMessage": "Jira 问题类型必填。",
"xpack.watcher.watchActions.jira.projectKeyIsRequiredValidationMessage": "Jira 项目键必填。",
"xpack.watcher.watchActions.jira.summaryIsRequiredValidationMessage": "Jira 摘要必填。",
diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx
index 3513606dbfe30..5c6cf22065d95 100644
--- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx
+++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx
@@ -290,24 +290,16 @@ describe(' create route', () => {
});
test('should simulate an index action', async () => {
- const { form, find, actions, exists } = testBed;
-
- const INDEX = 'my_index';
+ const { form, actions, exists } = testBed;
actions.clickAddActionButton();
actions.clickActionLink('index');
expect(exists('watchActionAccordion')).toBe(true);
- // First, provide invalid field and verify
+ // Verify an empty index is allowed
form.setInputValue('indexInput', '');
- expect(form.getErrorsMessages()).toContain('Index name is required.');
- expect(find('simulateActionButton').props().disabled).toEqual(true);
-
- // Next, provide valid field and verify
- form.setInputValue('indexInput', INDEX);
-
await act(async () => {
actions.clickSimulateButton();
await nextTick();
@@ -327,7 +319,7 @@ describe(' create route', () => {
id: 'index_1',
type: 'index',
index: {
- index: INDEX,
+ index: '',
},
},
],
diff --git a/x-pack/plugins/watcher/common/models/action/index_action.js b/x-pack/plugins/watcher/common/models/action/index_action.js
index 3db4e4f9b0dab..e21505ea98c8c 100644
--- a/x-pack/plugins/watcher/common/models/action/index_action.js
+++ b/x-pack/plugins/watcher/common/models/action/index_action.js
@@ -80,21 +80,6 @@ export class IndexAction extends BaseAction {
});
}
- if (json.index && !json.index.index) {
- errors.push({
- code: ERROR_CODES.ERR_PROP_MISSING,
- message: i18n.translate(
- 'xpack.watcher.models.loggingAction.actionJsonIndexNamePropertyMissingBadRequestMessage',
- {
- defaultMessage: 'JSON argument must contain an {actionJsonIndexName} property',
- values: {
- actionJsonIndexName: 'actionJson.index.index',
- },
- }
- ),
- });
- }
-
return { errors: errors.length ? errors : null };
}
}
diff --git a/x-pack/plugins/watcher/public/application/models/action/index_action.js b/x-pack/plugins/watcher/public/application/models/action/index_action.js
index 537a13fd855ca..6f9823e981e5b 100644
--- a/x-pack/plugins/watcher/public/application/models/action/index_action.js
+++ b/x-pack/plugins/watcher/public/application/models/action/index_action.js
@@ -19,13 +19,6 @@ export class IndexAction extends BaseAction {
const errors = {
index: [],
};
- if (!this.index) {
- errors.index.push(
- i18n.translate('xpack.watcher.watchActions.index.indexIsRequiredValidationMessage', {
- defaultMessage: 'Index name is required.',
- })
- );
- }
return errors;
}
From cf0e287c9eb87f9b9bb9045fc0e77c0f84d9eb4c Mon Sep 17 00:00:00 2001
From: Spencer
Date: Mon, 17 Aug 2020 20:48:22 -0700
Subject: [PATCH 015/177] [jenkins/security-cypress] run build before the tests
(#75203)
Co-authored-by: spalger
---
.ci/Jenkinsfile_security_cypress | 11 +++++++++--
vars/prChanges.groovy | 1 +
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/.ci/Jenkinsfile_security_cypress b/.ci/Jenkinsfile_security_cypress
index d0167cfd18099..d7f702a56563f 100644
--- a/.ci/Jenkinsfile_security_cypress
+++ b/.ci/Jenkinsfile_security_cypress
@@ -9,8 +9,15 @@ kibanaPipeline(timeoutMinutes: 180) {
channel: '#security-solution-slack-testing'
) {
catchError {
- workers.base(size: 's', ramDisk: false) {
- kibanaPipeline.bash('test/scripts/jenkins_security_solution_cypress.sh', 'Execute Security Solution Cypress Tests')
+ withEnv([
+ 'CI_PARALLEL_PROCESS_NUMBER=1'
+ ]) {
+ def job = 'xpack-securityCypress'
+
+ workers.ci(name: job, size: 'l', ramDisk: true) {
+ kibanaPipeline.bash('test/scripts/jenkins_xpack_build_kibana.sh', 'Build Default Distributable')
+ kibanaPipeline.functionalTestProcess(job, 'test/scripts/jenkins_security_solution_cypress.sh')()
+ }
}
}
}
diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy
index a7fe46e7bf014..5bdd62946cafc 100644
--- a/vars/prChanges.groovy
+++ b/vars/prChanges.groovy
@@ -11,6 +11,7 @@ def getSkippablePaths() {
/^.ci\/.+\.yml$/,
/^.ci\/es-snapshots\//,
/^.ci\/pipeline-library\//,
+ /^.ci\/Jenkinsfile_[^\/]+$/,
/^\.github\//,
/\.md$/,
]
From 843c2383ea10edca786e50e5a223d40a456fc96a Mon Sep 17 00:00:00 2001
From: Ben Skelker <54019610+benskelker@users.noreply.github.com>
Date: Tue, 18 Aug 2020 08:25:06 +0300
Subject: [PATCH 016/177] [Docs]Security docs 7.9 updates (#75156)
* security docs 7.9 updates
* terminology
* updates advanced settings
* terminology
* corrections
---
docs/management/advanced-options.asciidoc | 18 +++----
docs/siem/images/cases-ui.png | Bin 458554 -> 294911 bytes
docs/siem/images/detections-ui.png | Bin 458323 -> 237884 bytes
docs/siem/images/hosts-ui.png | Bin 470211 -> 233495 bytes
docs/siem/images/ml-ui.png | Bin 350691 -> 308937 bytes
docs/siem/images/network-ui.png | Bin 946866 -> 555282 bytes
docs/siem/images/overview-ui.png | Bin 678256 -> 467397 bytes
docs/siem/images/timeline-ui.png | Bin 768799 -> 412651 bytes
docs/siem/index.asciidoc | 30 ++++++-----
docs/siem/machine-learning.asciidoc | 14 +++--
docs/siem/siem-ui.asciidoc | 60 ++++++++++------------
11 files changed, 59 insertions(+), 63 deletions(-)
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 7dc360fd721f4..9f13c152b4cbe 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -222,19 +222,19 @@ might increase the search time. This setting is off by default. Users must opt-i
[float]
[[kibana-siem-settings]]
-==== SIEM
+==== Security Solution
[horizontal]
-`siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app.
-`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events.
-`siem:ipReputationLinks`:: A JSON array containing links for verifying the reputation of an IP address. The links are displayed on
-{security-guide}/siem-ui-overview.html#network-ui[IP detail] pages.
-`siem:enableNewsFeed`:: Enables the security news feed on the SIEM *Overview*
+`securitySolution:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the Security app.
+`securitySolution:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the Security app collects events.
+`securitySolution:ipReputationLinks`:: A JSON array containing links for verifying the reputation of an IP address. The links are displayed on
+{security-guide}/network-page-overview.html[IP detail] pages.
+`securitySolution:enableNewsFeed`:: Enables the security news feed on the Security *Overview*
page.
-`siem:newsFeedUrl`:: The URL from which the security news feed content is
+`securitySolution:newsFeedUrl`:: The URL from which the security news feed content is
retrieved.
-`siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds.
-`siem:timeDefaults`:: The default period of time in the SIEM time filter.
+`securitySolution:refreshIntervalDefaults`:: The default refresh interval for the Security time filter, in milliseconds.
+`securitySolution:timeDefaults`:: The default period of time in the Security time filter.
[float]
[[kibana-timelion-settings]]
diff --git a/docs/siem/images/cases-ui.png b/docs/siem/images/cases-ui.png
index d7b125b87a00470639279cf041e1ffe5a447bc79..cb6361581d19e9019113e6bdb4035c8bea08b8bc 100644
GIT binary patch
literal 294911
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