From a4fa73054aaa01d7d1417bb5f3076fc8cb870441 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 21 Aug 2020 13:53:41 -0700 Subject: [PATCH 01/16] [Search] Change logging level to debug (#74971) * [Search] Change logging level to debug * Fix tests --- .../data/server/search/es_search/es_search_strategy.test.ts | 2 +- .../data/server/search/es_search/es_search_strategy.ts | 2 +- src/plugins/data/server/search/search_service.test.ts | 2 +- src/plugins/data/server/search/search_service.ts | 4 ++-- .../data_enhanced/server/search/es_search_strategy.test.ts | 2 +- .../plugins/data_enhanced/server/search/es_search_strategy.ts | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index 11564bb336b085..c34c3a310814cb 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -23,7 +23,7 @@ import { esSearchStrategyProvider } from './es_search_strategy'; describe('ES search strategy', () => { const mockLogger: any = { - info: () => {}, + debug: () => {}, }; const mockApiCaller = jest.fn().mockResolvedValue({ body: { diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 7f9cb665b96b59..eabbf3e3e26002 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -31,7 +31,7 @@ export const esSearchStrategyProvider = ( ): ISearchStrategy => { return { search: async (context, request, options) => { - logger.info(`search ${request.params?.index}`); + logger.debug(`search ${request.params?.index}`); const config = await config$.pipe(first()).toPromise(); const defaultParams = getDefaultSearchParams(config); diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index 030f37d0f7c463..7057c9c7ca15cc 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -32,7 +32,7 @@ describe('Search service', () => { beforeEach(() => { const mockLogger: any = { - info: () => {}, + debug: () => {}, }; plugin = new SearchService(coreMock.createPluginInitializerContext({}), mockLogger); mockCoreSetup = coreMock.createSetup(); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index a8b1cdd608a845..5643406932552f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -126,12 +126,12 @@ export class SearchService implements Plugin { } private registerSearchStrategy = (name: string, strategy: ISearchStrategy) => { - this.logger.info(`Register strategy ${name}`); + this.logger.debug(`Register strategy ${name}`); this.searchStrategies[name] = strategy; }; private getSearchStrategy = (name: string): ISearchStrategy => { - this.logger.info(`Get strategy ${name}`); + this.logger.debug(`Get strategy ${name}`); const strategy = this.searchStrategies[name]; if (!strategy) { throw new Error(`Search strategy ${name} not found`); diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index 5cff7fea601d3f..054baa6ac81d1d 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -36,7 +36,7 @@ const mockRollupResponse = { describe('ES search strategy', () => { const mockApiCaller = jest.fn(); const mockLogger: any = { - info: () => {}, + debug: () => {}, }; const mockContext = { core: { diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index f6425a63cef678..46609af52d0722 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -39,7 +39,7 @@ export const enhancedEsSearchStrategyProvider = ( request: IEnhancedEsSearchRequest, options?: ISearchOptions ) => { - logger.info(`search ${JSON.stringify(request.params) || request.id}`); + logger.debug(`search ${JSON.stringify(request.params) || request.id}`); const config = await config$.pipe(first()).toPromise(); const client = context.core.elasticsearch.client.asCurrentUser; const defaultParams = getDefaultSearchParams(config); @@ -70,7 +70,7 @@ export const enhancedEsSearchStrategyProvider = ( }; const cancel = async (context: RequestHandlerContext, id: string) => { - logger.info(`cancel ${id}`); + logger.debug(`cancel ${id}`); await context.core.elasticsearch.client.asCurrentUser.transport.request({ method: 'DELETE', path: encodeURI(`/_async_search/${id}`), From 3df47dc66b6cedb8a76c770d0c78a825506c87e8 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Fri, 21 Aug 2020 15:40:07 -0600 Subject: [PATCH 02/16] [Maps] convert style classes to TS (#75374) * [Maps] convert style classes to TS * convert VectorStyle to TS * clean up * revert change to fix unit test * review feedback * review feedback Co-authored-by: Elastic Machine --- .../layer_descriptor_types.ts | 33 -- .../style_property_descriptor_types.ts | 41 +- .../public/actions/data_request_actions.ts | 9 +- .../maps/public/actions/layer_actions.ts | 10 +- .../maps/public/classes/joins/inner_join.d.ts | 2 + .../plugins/maps/public/classes/joins/join.ts | 2 + .../blended_vector_layer.ts | 1 + .../layers/vector_layer/vector_layer.js | 6 +- .../sources/es_agg_source/es_agg_source.d.ts | 2 + .../sources/vector_source/vector_source.d.ts | 1 + ...end_row.js => ranged_style_legend_row.tsx} | 27 +- .../{heatmap_legend.js => heatmap_legend.tsx} | 21 +- .../{heatmap_style.js => heatmap_style.tsx} | 55 ++- .../maps/public/classes/styles/style.ts | 45 +-- .../public/classes/styles/tile/tile_style.ts | 19 +- .../components/legend/ordinal_legend.tsx | 1 - .../components/legend/vector_style_legend.tsx | 4 +- .../vector/properties/__tests__/test_util.ts | 13 +- ...rty.js => dynamic_orientation_property.ts} | 6 +- .../classes/styles/vector/style_meta.ts | 8 +- .../classes/styles/vector/vector_style.d.ts | 34 -- .../{vector_style.js => vector_style.tsx} | 356 +++++++++++++----- .../toc_entry_actions_popover.test.tsx | 14 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 25 files changed, 433 insertions(+), 279 deletions(-) rename x-pack/plugins/maps/public/classes/styles/components/{ranged_style_legend_row.js => ranged_style_legend_row.tsx} (71%) rename x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/{heatmap_legend.js => heatmap_legend.tsx} (80%) rename x-pack/plugins/maps/public/classes/styles/heatmap/{heatmap_style.js => heatmap_style.tsx} (68%) rename x-pack/plugins/maps/public/classes/styles/vector/properties/{dynamic_orientation_property.js => dynamic_orientation_property.ts} (82%) delete mode 100644 x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts rename x-pack/plugins/maps/public/classes/styles/vector/{vector_style.js => vector_style.tsx} (65%) diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts index a04d0e1a978fd2..b67f05cb169fd5 100644 --- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts @@ -38,36 +38,3 @@ export type LayerDescriptor = { export type VectorLayerDescriptor = LayerDescriptor & { style: VectorStyleDescriptor; }; - -export type RangeFieldMeta = { - min: number; - max: number; - delta: number; - isMinOutsideStdRange?: boolean; - isMaxOutsideStdRange?: boolean; -}; - -export type Category = { - key: string; - count: number; -}; - -export type CategoryFieldMeta = { - categories: Category[]; -}; - -export type GeometryTypes = { - isPointsOnly: boolean; - isLinesOnly: boolean; - isPolygonsOnly: boolean; -}; - -export type StyleMetaDescriptor = { - geometryTypes?: GeometryTypes; - fieldMeta: { - [key: string]: { - range: RangeFieldMeta; - categories: CategoryFieldMeta; - }; - }; -}; diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts index ce6539c9c45205..5aba9b06a6ccf6 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts @@ -12,7 +12,6 @@ import { SYMBOLIZE_AS_TYPES, VECTOR_STYLES, STYLE_TYPE, - LAYER_STYLE_TYPE, } from '../constants'; // Non-static/dynamic options @@ -193,9 +192,47 @@ export type StyleDescriptor = { type: string; }; +export type RangeFieldMeta = { + min: number; + max: number; + delta: number; + isMinOutsideStdRange?: boolean; + isMaxOutsideStdRange?: boolean; +}; + +export type Category = { + key: string; + count: number; +}; + +export type CategoryFieldMeta = { + categories: Category[]; +}; + +export type GeometryTypes = { + isPointsOnly: boolean; + isLinesOnly: boolean; + isPolygonsOnly: boolean; +}; + +export type StyleMetaDescriptor = { + geometryTypes?: GeometryTypes; + fieldMeta: { + [key: string]: { + range?: RangeFieldMeta; + categories?: CategoryFieldMeta; + }; + }; +}; + export type VectorStyleDescriptor = StyleDescriptor & { - type: LAYER_STYLE_TYPE.VECTOR; properties: VectorStylePropertiesDescriptor; + isTimeAware: boolean; + __styleMeta?: StyleMetaDescriptor; +}; + +export type HeatmapStyleDescriptor = StyleDescriptor & { + colorRampName: string; }; export type StylePropertyOptions = diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts index 6f5ed680ac64fb..41d9f3fc13b5b8 100644 --- a/x-pack/plugins/maps/public/actions/data_request_actions.ts +++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts @@ -11,7 +11,7 @@ import uuid from 'uuid/v4'; import { multiPoint } from '@turf/helpers'; import { FeatureCollection } from 'geojson'; import { MapStoreState } from '../reducers/store'; -import { LAYER_TYPE, SOURCE_DATA_REQUEST_ID } from '../../common/constants'; +import { LAYER_STYLE_TYPE, LAYER_TYPE, SOURCE_DATA_REQUEST_ID } from '../../common/constants'; import { getDataFilters, getDataRequestDescriptor, @@ -42,6 +42,7 @@ import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types'; import { DataRequestAbortError } from '../classes/util/data_request'; import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_geo_utils'; +import { IVectorStyle } from '../classes/styles/vector/vector_style'; const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1; @@ -85,10 +86,12 @@ export function updateStyleMeta(layerId: string | null) { } const sourceDataRequest = layer.getSourceDataRequest(); const style = layer.getCurrentStyle(); - if (!style || !sourceDataRequest) { + if (!style || !sourceDataRequest || style.getType() !== LAYER_STYLE_TYPE.VECTOR) { return; } - const styleMeta = await style.pluckStyleMetaFromSourceDataRequest(sourceDataRequest); + const styleMeta = await (style as IVectorStyle).pluckStyleMetaFromSourceDataRequest( + sourceDataRequest + ); dispatch({ type: SET_LAYER_STYLE_META, layerId, diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 472e42129816b5..675bb14722889f 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -40,7 +40,8 @@ import { cleanTooltipStateForLayer } from './tooltip_actions'; import { JoinDescriptor, LayerDescriptor, StyleDescriptor } from '../../common/descriptor_types'; import { ILayer } from '../classes/layers/layer'; import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer'; -import { LAYER_TYPE } from '../../common/constants'; +import { LAYER_STYLE_TYPE, LAYER_TYPE } from '../../common/constants'; +import { IVectorStyle } from '../classes/styles/vector/vector_style'; export function trackCurrentLayerState(layerId: string) { return { @@ -381,12 +382,15 @@ export function clearMissingStyleProperties(layerId: string) { } const style = targetLayer!.getCurrentStyle(); - if (!style) { + if (!style || style.getType() !== LAYER_STYLE_TYPE.VECTOR) { return; } const nextFields = await (targetLayer as IVectorLayer).getFields(); // take into account all fields, since labels can be driven by any field (source or join) - const { hasChanges, nextStyleDescriptor } = style.getDescriptorWithMissingStylePropsRemoved( + const { + hasChanges, + nextStyleDescriptor, + } = (style as IVectorStyle).getDescriptorWithMissingStylePropsRemoved( nextFields, getMapColors(getState()) ); diff --git a/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts b/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts index 452a3fdc3a19f1..befff0965fb70c 100644 --- a/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts +++ b/x-pack/plugins/maps/public/classes/joins/inner_join.d.ts @@ -17,4 +17,6 @@ export class InnerJoin implements IJoin { toDescriptor(): JoinDescriptor; getSourceMetaDataRequestId(): string; + + getSourceFormattersDataRequestId(): string; } diff --git a/x-pack/plugins/maps/public/classes/joins/join.ts b/x-pack/plugins/maps/public/classes/joins/join.ts index 4a551c5bcc4857..5bcc4bfdec87e5 100644 --- a/x-pack/plugins/maps/public/classes/joins/join.ts +++ b/x-pack/plugins/maps/public/classes/joins/join.ts @@ -13,4 +13,6 @@ export interface IJoin { toDescriptor(): JoinDescriptor; getSourceMetaDataRequestId(): string; + + getSourceFormattersDataRequestId(): string; } diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 950d9890a3c650..90e8d25a77958d 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -102,6 +102,7 @@ function getClusterStyleDescriptor( }, }, }, + isTimeAware: true, }; documentStyle .getAllStyleProperties() diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js index f5f5071bab1586..2ba7f750e9b409 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js @@ -309,7 +309,7 @@ export class VectorLayer extends AbstractLayer { _getSearchFilters(dataFilters, source, style) { const fieldNames = [ ...source.getFieldNames(), - ...style.getSourceFieldNames(), + ...(style.getType() === LAYER_STYLE_TYPE.VECTOR ? style.getSourceFieldNames() : []), ...this.getValidJoins().map((join) => join.getLeftField().getName()), ]; @@ -415,7 +415,7 @@ export class VectorLayer extends AbstractLayer { } async _syncSourceStyleMeta(syncContext, source, style) { - if (this.getCurrentStyle().constructor.type !== LAYER_STYLE_TYPE.VECTOR) { + if (this.getCurrentStyle().getType() !== LAYER_STYLE_TYPE.VECTOR) { return; } @@ -511,7 +511,7 @@ export class VectorLayer extends AbstractLayer { } async _syncSourceFormatters(syncContext, source, style) { - if (style.constructor.type !== LAYER_STYLE_TYPE.VECTOR) { + if (style.getType() !== LAYER_STYLE_TYPE.VECTOR) { return; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.d.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.d.ts index 91ed0ab52b08e7..eb50cd7528c8b2 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.d.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.d.ts @@ -15,6 +15,7 @@ export interface IESAggSource extends IESSource { getAggLabel(aggType: AGG_TYPE, fieldName: string): string; getMetricFields(): IESAggField[]; hasMatchingMetricField(fieldName: string): boolean; + getMetricFieldForName(fieldName: string): IESAggField | null; } export class AbstractESAggSource extends AbstractESSource implements IESAggSource { @@ -24,4 +25,5 @@ export class AbstractESAggSource extends AbstractESSource implements IESAggSourc getAggLabel(aggType: AGG_TYPE, fieldName: string): string; getMetricFields(): IESAggField[]; hasMatchingMetricField(fieldName: string): boolean; + getMetricFieldForName(fieldName: string): IESAggField | null; } diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts index 62fc5a283e5f3e..271505010f36a8 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.d.ts @@ -53,6 +53,7 @@ export interface IVectorSource extends ISource { getApplyGlobalQuery(): boolean; createField({ fieldName }: { fieldName: string }): IField; canFormatFeatureProperties(): boolean; + getSupportedShapeTypes(): Promise; } export class AbstractVectorSource extends AbstractSource implements IVectorSource { diff --git a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx similarity index 71% rename from x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js rename to x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx index 4a43cc24e2c076..24812cb35b070e 100644 --- a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js +++ b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx @@ -4,12 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { ReactElement } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; -export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fieldLabel }) { +interface Props { + header: ReactElement; + minLabel: string | number; + maxLabel: string | number; + propertyLabel: string; + fieldLabel: string; +} + +export function RangedStyleLegendRow({ + header, + minLabel, + maxLabel, + propertyLabel, + fieldLabel, +}: Props) { return (
@@ -39,11 +52,3 @@ export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel
); } - -RangedStyleLegendRow.propTypes = { - header: PropTypes.node.isRequired, - minLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - maxLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - propertyLabel: PropTypes.string.isRequired, - fieldLabel: PropTypes.string.isRequired, -}; diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.js b/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx similarity index 80% rename from x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.js rename to x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx index 5c3600a149afe7..7024adc891b50d 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.js +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx @@ -4,18 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { ColorGradient } from './color_gradient'; import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; import { HEATMAP_COLOR_RAMP_LABEL } from '../heatmap_constants'; +import { IField } from '../../../../fields/field'; -export class HeatmapLegend extends React.Component { - constructor() { - super(); - this.state = { label: '' }; - } +interface Props { + colorRampName: string; + field: IField; +} + +interface State { + label: string; +} + +export class HeatmapLegend extends Component { + private _isMounted: boolean = false; + + state: State = { label: '' }; componentDidUpdate() { this._loadLabel(); diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx similarity index 68% rename from x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js rename to x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index 55bbbc9319dfb6..a3002251785269 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -5,43 +5,48 @@ */ import React from 'react'; -import { AbstractStyle } from '../style'; +import { Map as MbMap } from 'mapbox-gl'; +import { i18n } from '@kbn/i18n'; +import { EuiIcon } from '@elastic/eui'; +import { IStyle } from '../style'; import { HeatmapStyleEditor } from './components/heatmap_style_editor'; import { HeatmapLegend } from './components/legend/heatmap_legend'; import { DEFAULT_HEATMAP_COLOR_RAMP_NAME, getOrdinalMbColorRampStops } from '../color_palettes'; import { LAYER_STYLE_TYPE, GRID_RESOLUTION } from '../../../../common/constants'; +import { HeatmapStyleDescriptor, StyleDescriptor } from '../../../../common/descriptor_types'; +import { IField } from '../../fields/field'; -import { i18n } from '@kbn/i18n'; -import { EuiIcon } from '@elastic/eui'; - -//The heatmap range chosen hear runs from 0 to 1. It is arbitrary. -//Weighting is on the raw count/sum values. +// The heatmap range chosen hear runs from 0 to 1. It is arbitrary. +// Weighting is on the raw count/sum values. const MIN_RANGE = 0.1; // 0 to 0.1 is displayed as transparent color stop const MAX_RANGE = 1; -export class HeatmapStyle extends AbstractStyle { - static type = LAYER_STYLE_TYPE.HEATMAP; +export class HeatmapStyle implements IStyle { + readonly _descriptor: HeatmapStyleDescriptor; - constructor(descriptor = {}) { - super(); + constructor( + descriptor: { colorRampName: string } = { colorRampName: DEFAULT_HEATMAP_COLOR_RAMP_NAME } + ) { this._descriptor = HeatmapStyle.createDescriptor(descriptor.colorRampName); } - static createDescriptor(colorRampName) { + static createDescriptor(colorRampName: string) { return { - type: HeatmapStyle.type, + type: LAYER_STYLE_TYPE.HEATMAP, colorRampName: colorRampName ? colorRampName : DEFAULT_HEATMAP_COLOR_RAMP_NAME, }; } - static getDisplayName() { - return i18n.translate('xpack.maps.style.heatmap.displayNameLabel', { - defaultMessage: 'Heatmap style', - }); + getType() { + return LAYER_STYLE_TYPE.HEATMAP; } - renderEditor({ onStyleDescriptorChange }) { - const onHeatmapColorChange = ({ colorRampName }) => { + renderEditor({ + onStyleDescriptorChange, + }: { + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }) { + const onHeatmapColorChange = ({ colorRampName }: { colorRampName: string }) => { const styleDescriptor = HeatmapStyle.createDescriptor(colorRampName); onStyleDescriptorChange(styleDescriptor); }; @@ -54,7 +59,7 @@ export class HeatmapStyle extends AbstractStyle { ); } - renderLegendDetails(field) { + renderLegendDetails(field: IField) { return ; } @@ -62,7 +67,17 @@ export class HeatmapStyle extends AbstractStyle { return ; } - setMBPaintProperties({ mbMap, layerId, propertyName, resolution }) { + setMBPaintProperties({ + mbMap, + layerId, + propertyName, + resolution, + }: { + mbMap: MbMap; + layerId: string; + propertyName: string; + resolution: GRID_RESOLUTION; + }) { let radius; if (resolution === GRID_RESOLUTION.COARSE) { radius = 128; diff --git a/x-pack/plugins/maps/public/classes/styles/style.ts b/x-pack/plugins/maps/public/classes/styles/style.ts index 1859c7875ad11c..abaa6184b0ca44 100644 --- a/x-pack/plugins/maps/public/classes/styles/style.ts +++ b/x-pack/plugins/maps/public/classes/styles/style.ts @@ -5,18 +5,11 @@ */ import { ReactElement } from 'react'; -import { StyleDescriptor, StyleMetaDescriptor } from '../../../common/descriptor_types'; +import { StyleDescriptor } from '../../../common/descriptor_types'; import { ILayer } from '../layers/layer'; -import { IField } from '../fields/field'; -import { DataRequest } from '../util/data_request'; export interface IStyle { - getDescriptor(): StyleDescriptor | null; - getDescriptorWithMissingStylePropsRemoved( - nextFields: IField[], - mapColors: string[] - ): { hasChanges: boolean; nextStyleDescriptor?: StyleDescriptor }; - pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): StyleMetaDescriptor; + getType(): string; renderEditor({ layer, onStyleDescriptorChange, @@ -24,38 +17,4 @@ export interface IStyle { layer: ILayer; onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; }): ReactElement | null; - getSourceFieldNames(): string[]; -} - -export class AbstractStyle implements IStyle { - readonly _descriptor: StyleDescriptor | null; - - constructor(descriptor: StyleDescriptor | null) { - this._descriptor = descriptor; - } - - getDescriptorWithMissingStylePropsRemoved( - nextFields: IField[], - mapColors: string[] - ): { hasChanges: boolean; nextStyleDescriptor?: StyleDescriptor } { - return { - hasChanges: false, - }; - } - - pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): StyleMetaDescriptor { - return { fieldMeta: {} }; - } - - getDescriptor(): StyleDescriptor | null { - return this._descriptor; - } - - renderEditor(/* { layer, onStyleDescriptorChange } */) { - return null; - } - - getSourceFieldNames(): string[] { - return []; - } } diff --git a/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts b/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts index f658d0821edf2c..cac3913d3149d3 100644 --- a/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts +++ b/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts @@ -4,13 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AbstractStyle } from '../style'; +import { IStyle } from '../style'; +import { StyleDescriptor } from '../../../../common/descriptor_types'; import { LAYER_STYLE_TYPE } from '../../../../common/constants'; -export class TileStyle extends AbstractStyle { +export class TileStyle implements IStyle { + readonly _descriptor: StyleDescriptor; + constructor() { - super({ + this._descriptor = { type: LAYER_STYLE_TYPE.TILE, - }); + }; + } + + getType() { + return LAYER_STYLE_TYPE.TILE; + } + + renderEditor(/* { layer, onStyleDescriptorChange } */) { + return null; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx index a99548b6af7bf6..1851c14f7b952b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx @@ -7,7 +7,6 @@ import React, { Component, Fragment } from 'react'; import _ from 'lodash'; import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; -// @ts-expect-error import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; import { VECTOR_STYLES } from '../../../../../../common/constants'; import { CircleIcon } from './circle_icon'; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/vector_style_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/vector_style_legend.tsx index 4d50c632bfd6d4..fcf7a735d743a6 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/vector_style_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/vector_style_legend.tsx @@ -11,7 +11,7 @@ interface Props { isLinesOnly: boolean; isPointsOnly: boolean; styles: Array>; - symbolId: string; + symbolId?: string; } export function VectorStyleLegend({ isLinesOnly, isPointsOnly, styles, symbolId }: Props) { @@ -31,5 +31,5 @@ export function VectorStyleLegend({ isLinesOnly, isPointsOnly, styles, symbolId ); } - return legendRows; + return <>{legendRows}; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts index a8fba834d65abd..3f6edc81e30ef4 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts @@ -14,7 +14,7 @@ import { StyleMetaDescriptor, } from '../../../../../../common/descriptor_types'; import { AbstractField, IField } from '../../../../fields/field'; -import { IStyle, AbstractStyle } from '../../../style'; +import { IStyle } from '../../../style'; class MockField extends AbstractField { async getLabel(): Promise { @@ -30,16 +30,23 @@ export const mockField: IField = new MockField({ origin: FIELD_ORIGIN.SOURCE, }); -export class MockStyle extends AbstractStyle implements IStyle { +export class MockStyle implements IStyle { private readonly _min: number; private readonly _max: number; constructor({ min = 0, max = 100 } = {}) { - super(null); this._min = min; this._max = max; } + renderEditor() { + return null; + } + + getType() { + return 'mockStyle'; + } + getStyleMeta(): StyleMeta { const geomTypes: GeometryTypes = { isPointsOnly: false, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts similarity index 82% rename from x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js rename to x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts index 763eb81ad0f98a..dd976027a50f2f 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts @@ -4,12 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Map as MbMap } from 'mapbox-gl'; import { DynamicStyleProperty } from './dynamic_style_property'; import { getComputedFieldName } from '../style_util'; import { VECTOR_STYLES } from '../../../../../common/constants'; +import { OrientationDynamicOptions } from '../../../../../common/descriptor_types'; -export class DynamicOrientationProperty extends DynamicStyleProperty { - syncIconRotationWithMb(symbolLayerId, mbMap) { +export class DynamicOrientationProperty extends DynamicStyleProperty { + syncIconRotationWithMb(symbolLayerId: string, mbMap: MbMap) { if (this._field && this._field.isValid()) { const targetName = this._field.supportsAutoDomain() ? getComputedFieldName(VECTOR_STYLES.ICON_ORIENTATION, this.getFieldName()) diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_meta.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_meta.ts index 646b88d005af76..590a859c675584 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_meta.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_meta.ts @@ -17,14 +17,14 @@ export class StyleMeta { } getRangeFieldMetaDescriptor(fieldName: string): RangeFieldMeta | null { - return this._descriptor && this._descriptor.fieldMeta[fieldName] - ? this._descriptor.fieldMeta[fieldName].range + return this._descriptor.fieldMeta[fieldName] && this._descriptor.fieldMeta[fieldName].range + ? this._descriptor.fieldMeta[fieldName].range! : null; } getCategoryFieldMetaDescriptor(fieldName: string): CategoryFieldMeta | null { - return this._descriptor && this._descriptor.fieldMeta[fieldName] - ? this._descriptor.fieldMeta[fieldName].categories + return this._descriptor.fieldMeta[fieldName] && this._descriptor.fieldMeta[fieldName].categories + ? this._descriptor.fieldMeta[fieldName].categories! : null; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts deleted file mode 100644 index d48d075288a28e..00000000000000 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts +++ /dev/null @@ -1,34 +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 { IStyleProperty } from './properties/style_property'; -import { IDynamicStyleProperty } from './properties/dynamic_style_property'; -import { IVectorLayer } from '../../layers/vector_layer/vector_layer'; -import { IVectorSource } from '../../sources/vector_source'; -import { AbstractStyle, IStyle } from '../style'; -import { - DynamicStylePropertyOptions, - StylePropertyOptions, - VectorStyleDescriptor, - VectorStylePropertiesDescriptor, -} from '../../../../common/descriptor_types'; -import { StyleMeta } from './style_meta'; - -export interface IVectorStyle extends IStyle { - getAllStyleProperties(): Array>; - getDynamicPropertiesArray(): Array>; - getSourceFieldNames(): string[]; - getStyleMeta(): StyleMeta; -} - -export class VectorStyle extends AbstractStyle implements IVectorStyle { - static createDescriptor(properties: VectorStylePropertiesDescriptor): VectorStyleDescriptor; - static createDefaultStyleProperties(mapColors: string[]): VectorStylePropertiesDescriptor; - constructor(descriptor: VectorStyleDescriptor, source: IVectorSource, layer: IVectorLayer); - getSourceFieldNames(): string[]; - getAllStyleProperties(): Array>; - getDynamicPropertiesArray(): Array>; - getStyleMeta(): StyleMeta; -} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.js b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx similarity index 65% rename from x-pack/plugins/maps/public/classes/styles/vector/vector_style.js rename to x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 907e16a6a84272..956522524a2ebd 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -6,6 +6,9 @@ import _ from 'lodash'; import React from 'react'; +import { Map as MbMap, FeatureIdentifier } from 'mapbox-gl'; +import { FeatureCollection } from 'geojson'; +// @ts-expect-error import { VectorStyleEditor } from './components/vector_style_editor'; import { getDefaultProperties, @@ -13,7 +16,6 @@ import { LINE_STYLES, POLYGON_STYLES, } from './vector_style_defaults'; -import { AbstractStyle } from '../style'; import { GEO_JSON_TYPE, FIELD_ORIGIN, @@ -43,45 +45,109 @@ import { extractColorFromStyleProperty } from './components/legend/extract_color import { SymbolizeAsProperty } from './properties/symbolize_as_property'; import { StaticIconProperty } from './properties/static_icon_property'; import { DynamicIconProperty } from './properties/dynamic_icon_property'; +import { + ColorDynamicOptions, + ColorStaticOptions, + ColorStylePropertyDescriptor, + DynamicStylePropertyOptions, + IconDynamicOptions, + IconStaticOptions, + IconStylePropertyDescriptor, + LabelDynamicOptions, + LabelStaticOptions, + LabelStylePropertyDescriptor, + OrientationDynamicOptions, + OrientationStaticOptions, + OrientationStylePropertyDescriptor, + SizeDynamicOptions, + SizeStaticOptions, + SizeStylePropertyDescriptor, + StyleDescriptor, + StyleMetaDescriptor, + StylePropertyField, + StylePropertyOptions, + VectorStyleDescriptor, + VectorStylePropertiesDescriptor, +} from '../../../../common/descriptor_types'; +import { DataRequest } from '../../util/data_request'; +import { IStyle } from '../style'; +import { IStyleProperty } from './properties/style_property'; +import { IDynamicStyleProperty } from './properties/dynamic_style_property'; +import { IField } from '../../fields/field'; +import { IVectorLayer } from '../../layers/vector_layer/vector_layer'; +import { IVectorSource } from '../../sources/vector_source'; +import { ILayer } from '../../layers/layer'; const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON]; -function getNumericalMbFeatureStateValue(value) { +function getNumericalMbFeatureStateValue(value: string) { const valueAsFloat = parseFloat(value); return isNaN(valueAsFloat) ? null : valueAsFloat; } -export class VectorStyle extends AbstractStyle { - static type = LAYER_STYLE_TYPE.VECTOR; +export interface IVectorStyle extends IStyle { + getAllStyleProperties(): Array>; + getDynamicPropertiesArray(): Array>; + getSourceFieldNames(): string[]; + getStyleMeta(): StyleMeta; + getDescriptorWithMissingStylePropsRemoved( + nextFields: IField[], + mapColors: string[] + ): { hasChanges: boolean; nextStyleDescriptor?: VectorStyleDescriptor }; + pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): Promise; +} + +export class VectorStyle implements IVectorStyle { + private readonly _descriptor: VectorStyleDescriptor; + private readonly _layer: IVectorLayer; + private readonly _source: IVectorSource; + private readonly _styleMeta: StyleMeta; + + private readonly _symbolizeAsStyleProperty: SymbolizeAsProperty; + private readonly _lineColorStyleProperty: StaticColorProperty | DynamicColorProperty; + private readonly _fillColorStyleProperty: StaticColorProperty | DynamicColorProperty; + private readonly _lineWidthStyleProperty: StaticSizeProperty | DynamicSizeProperty; + private readonly _iconStyleProperty: StaticIconProperty | DynamicIconProperty; + private readonly _iconSizeStyleProperty: StaticSizeProperty | DynamicSizeProperty; + private readonly _iconOrientationProperty: StaticOrientationProperty | DynamicOrientationProperty; + private readonly _labelStyleProperty: StaticTextProperty | DynamicTextProperty; + private readonly _labelSizeStyleProperty: StaticSizeProperty | DynamicSizeProperty; + private readonly _labelColorStyleProperty: StaticColorProperty | DynamicColorProperty; + private readonly _labelBorderColorStyleProperty: StaticColorProperty | DynamicColorProperty; + private readonly _labelBorderSizeStyleProperty: LabelBorderSizeProperty; static createDescriptor(properties = {}, isTimeAware = true) { return { - type: VectorStyle.type, + type: LAYER_STYLE_TYPE.VECTOR, properties: { ...getDefaultProperties(), ...properties }, isTimeAware, }; } - static createDefaultStyleProperties(mapColors) { + static createDefaultStyleProperties(mapColors: string[]) { return getDefaultProperties(mapColors); } - constructor(descriptor = {}, source, layer) { - super(); - descriptor = descriptor === null ? {} : descriptor; + constructor( + descriptor: VectorStyleDescriptor | null, + source: IVectorSource, + layer: IVectorLayer + ) { this._source = source; this._layer = layer; - this._descriptor = { - ...descriptor, - ...VectorStyle.createDescriptor(descriptor.properties, descriptor.isTimeAware), - }; + this._descriptor = descriptor + ? { + ...descriptor, + ...VectorStyle.createDescriptor(descriptor.properties, descriptor.isTimeAware), + } + : VectorStyle.createDescriptor(); this._styleMeta = new StyleMeta(this._descriptor.__styleMeta); this._symbolizeAsStyleProperty = new SymbolizeAsProperty( - this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS].options, + this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS]!.options, VECTOR_STYLES.SYMBOLIZE_AS ); this._lineColorStyleProperty = this._makeColorProperty( @@ -94,7 +160,8 @@ export class VectorStyle extends AbstractStyle { ); this._lineWidthStyleProperty = this._makeSizeProperty( this._descriptor.properties[VECTOR_STYLES.LINE_WIDTH], - VECTOR_STYLES.LINE_WIDTH + VECTOR_STYLES.LINE_WIDTH, + this._symbolizeAsStyleProperty.isSymbolizedAsIcon() ); this._iconStyleProperty = this._makeIconProperty( this._descriptor.properties[VECTOR_STYLES.ICON] @@ -113,7 +180,8 @@ export class VectorStyle extends AbstractStyle { ); this._labelSizeStyleProperty = this._makeSizeProperty( this._descriptor.properties[VECTOR_STYLES.LABEL_SIZE], - VECTOR_STYLES.LABEL_SIZE + VECTOR_STYLES.LABEL_SIZE, + this._symbolizeAsStyleProperty.isSymbolizedAsIcon() ); this._labelColorStyleProperty = this._makeColorProperty( this._descriptor.properties[VECTOR_STYLES.LABEL_COLOR], @@ -124,12 +192,16 @@ export class VectorStyle extends AbstractStyle { VECTOR_STYLES.LABEL_BORDER_COLOR ); this._labelBorderSizeStyleProperty = new LabelBorderSizeProperty( - this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE].options, + this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE]!.options, VECTOR_STYLES.LABEL_BORDER_SIZE, this._labelSizeStyleProperty ); } + getType() { + return LAYER_STYLE_TYPE.VECTOR; + } + getAllStyleProperties() { return [ this._symbolizeAsStyleProperty, @@ -150,18 +222,24 @@ export class VectorStyle extends AbstractStyle { _hasBorder() { return this._lineWidthStyleProperty.isDynamic() ? this._lineWidthStyleProperty.isComplete() - : this._lineWidthStyleProperty.getOptions().size !== 0; + : (this._lineWidthStyleProperty as StaticSizeProperty).getOptions().size !== 0; } - renderEditor({ layer, onStyleDescriptorChange }) { + renderEditor({ + layer, + onStyleDescriptorChange, + }: { + layer: ILayer; + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }) { const rawProperties = this.getRawProperties(); - const handlePropertyChange = (propertyName, settings) => { - rawProperties[propertyName] = settings; //override single property, but preserve the rest + const handlePropertyChange = (propertyName: VECTOR_STYLES, settings: any) => { + rawProperties[propertyName] = settings; // override single property, but preserve the rest const vectorStyleDescriptor = VectorStyle.createDescriptor(rawProperties, this.isTimeAware()); onStyleDescriptorChange(vectorStyleDescriptor); }; - const onIsTimeAwareChange = (isTimeAware) => { + const onIsTimeAwareChange = (isTimeAware: boolean) => { const vectorStyleDescriptor = VectorStyle.createDescriptor(rawProperties, isTimeAware); onStyleDescriptorChange(vectorStyleDescriptor); }; @@ -170,8 +248,9 @@ export class VectorStyle extends AbstractStyle { return dynamicStyleProp.isFieldMetaEnabled(); }); - const styleProperties = {}; + const styleProperties: VectorStylePropertiesDescriptor = {}; this.getAllStyleProperties().forEach((styleProperty) => { + // @ts-expect-error styleProperties[styleProperty.getStyleName()] = styleProperty; }); @@ -201,26 +280,40 @@ export class VectorStyle extends AbstractStyle { * This method does not update its descriptor. It just returns a new descriptor that the caller * can then use to update store state via dispatch. */ - getDescriptorWithMissingStylePropsRemoved(nextFields, mapColors) { + getDescriptorWithMissingStylePropsRemoved(nextFields: IField[], mapColors: string[]) { const originalProperties = this.getRawProperties(); - const updatedProperties = {}; + const updatedProperties = {} as VectorStylePropertiesDescriptor; - const dynamicProperties = Object.keys(originalProperties).filter((key) => { - const { type, options } = originalProperties[key] || {}; - return type === STYLE_TYPE.DYNAMIC && options.field && options.field.name; + const dynamicProperties = (Object.keys(originalProperties) as VECTOR_STYLES[]).filter((key) => { + if (!originalProperties[key]) { + return false; + } + const propertyDescriptor = originalProperties[key]; + if ( + !propertyDescriptor || + !('type' in propertyDescriptor) || + propertyDescriptor.type !== STYLE_TYPE.DYNAMIC || + !propertyDescriptor.options + ) { + return false; + } + const dynamicOptions = propertyDescriptor.options as DynamicStylePropertyOptions; + return dynamicOptions.field && dynamicOptions.field.name; }); - dynamicProperties.forEach((key) => { + dynamicProperties.forEach((key: VECTOR_STYLES) => { // Convert dynamic styling to static stying when there are no nextFields if (nextFields.length === 0) { const staticProperties = getDefaultStaticProperties(mapColors); - updatedProperties[key] = staticProperties[key]; + updatedProperties[key] = staticProperties[key] as any; return; } const dynamicProperty = originalProperties[key]; - const fieldName = - dynamicProperty && dynamicProperty.options.field && dynamicProperty.options.field.name; + if (!dynamicProperty || !dynamicProperty.options) { + return; + } + const fieldName = (dynamicProperty.options as DynamicStylePropertyOptions).field!.name; if (!fieldName) { return; } @@ -236,9 +329,10 @@ export class VectorStyle extends AbstractStyle { updatedProperties[key] = { type: DynamicStyleProperty.type, options: { - ...originalProperties[key].options, + ...originalProperties[key]!.options, }, - }; + } as any; + // @ts-expect-error delete updatedProperties[key].options.field; }); @@ -261,7 +355,7 @@ export class VectorStyle extends AbstractStyle { }; } - async pluckStyleMetaFromSourceDataRequest(sourceDataRequest) { + async pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest) { const features = _.get(sourceDataRequest.getData(), 'features', []); const supportedFeatures = await this._source.getSupportedShapeTypes(); @@ -307,7 +401,7 @@ export class VectorStyle extends AbstractStyle { ), }, fieldMeta: {}, - }; + } as StyleMetaDescriptor; const dynamicProperties = this.getDynamicPropertiesArray(); if (dynamicProperties.length === 0 || features.length === 0) { @@ -318,7 +412,7 @@ export class VectorStyle extends AbstractStyle { dynamicProperties.forEach((dynamicProperty) => { const categoricalStyleMeta = dynamicProperty.pluckCategoricalStyleMetaFromFeatures(features); const ordinalStyleMeta = dynamicProperty.pluckOrdinalStyleMetaFromFeatures(features); - const name = dynamicProperty.getField().getName(); + const name = dynamicProperty.getFieldName(); if (!styleMeta.fieldMeta[name]) { styleMeta.fieldMeta[name] = {}; } @@ -335,10 +429,10 @@ export class VectorStyle extends AbstractStyle { } getSourceFieldNames() { - const fieldNames = []; + const fieldNames: string[] = []; this.getDynamicPropertiesArray().forEach((styleProperty) => { if (styleProperty.getFieldOrigin() === FIELD_ORIGIN.SOURCE) { - fieldNames.push(styleProperty.getField().getName()); + fieldNames.push(styleProperty.getFieldName()); } }); return fieldNames; @@ -356,7 +450,7 @@ export class VectorStyle extends AbstractStyle { const styleProperties = this.getAllStyleProperties(); return styleProperties.filter( (styleProperty) => styleProperty.isDynamic() && styleProperty.isComplete() - ); + ) as Array>; } _getIsPointsOnly = () => { @@ -371,10 +465,10 @@ export class VectorStyle extends AbstractStyle { return this._styleMeta.isPolygonsOnly(); }; - _getDynamicPropertyByFieldName(fieldName) { + _getDynamicPropertyByFieldName(fieldName: string) { const dynamicProps = this.getDynamicPropertiesArray(); return dynamicProps.find((dynamicProp) => { - return fieldName === dynamicProp.getField().getName(); + return fieldName === dynamicProp.getFieldName(); }); } @@ -382,7 +476,7 @@ export class VectorStyle extends AbstractStyle { return this._styleMeta; } - _getFieldFormatter = (fieldName) => { + _getFieldFormatter = (fieldName: string) => { const dynamicProp = this._getDynamicPropertyByFieldName(fieldName); if (!dynamicProp) { return null; @@ -392,11 +486,11 @@ export class VectorStyle extends AbstractStyle { if (dynamicProp.getFieldOrigin() === FIELD_ORIGIN.SOURCE) { dataRequestId = SOURCE_FORMATTERS_DATA_REQUEST_ID; } else { - const join = this._layer.getValidJoins().find((join) => { + const targetJoin = this._layer.getValidJoins().find((join) => { return join.getRightJoinSource().hasMatchingMetricField(fieldName); }); - if (join) { - dataRequestId = join.getSourceFormattersDataRequestId(); + if (targetJoin) { + dataRequestId = targetJoin.getSourceFormattersDataRequestId(); } } @@ -410,13 +504,14 @@ export class VectorStyle extends AbstractStyle { } const formatters = formattersDataRequest.getData(); - return formatters[fieldName]; + // @ts-expect-error + return formatters ? formatters[fieldName] : null; }; _getSymbolId() { - return this.arePointsSymbolizedAsCircles() + return this.arePointsSymbolizedAsCircles() || this._iconStyleProperty.isDynamic() ? undefined - : this._iconStyleProperty.getOptions().value; + : (this._iconStyleProperty as StaticIconProperty).getOptions().value; } getIcon = () => { @@ -434,7 +529,7 @@ export class VectorStyle extends AbstractStyle { ); } const fillColor = isLinesOnly - ? null + ? undefined : extractColorFromStyleProperty( this._descriptor.properties[VECTOR_STYLES.FILL_COLOR], 'grey' @@ -485,10 +580,10 @@ export class VectorStyle extends AbstractStyle { ); } - clearFeatureState(featureCollection, mbMap, sourceId) { - const tmpFeatureIdentifier = { - source: null, - id: null, + clearFeatureState(featureCollection: FeatureCollection, mbMap: MbMap, sourceId: string) { + const tmpFeatureIdentifier: FeatureIdentifier = { + source: '', + id: undefined, }; for (let i = 0; i < featureCollection.features.length; i++) { const feature = featureCollection.features[i]; @@ -498,7 +593,11 @@ export class VectorStyle extends AbstractStyle { } } - setFeatureStateAndStyleProps(featureCollection, mbMap, mbSourceId) { + setFeatureStateAndStyleProps( + featureCollection: FeatureCollection, + mbMap: MbMap, + mbSourceId: string + ) { if (!featureCollection) { return; } @@ -508,24 +607,24 @@ export class VectorStyle extends AbstractStyle { return; } - const tmpFeatureIdentifier = { - source: null, - id: null, + const tmpFeatureIdentifier: FeatureIdentifier = { + source: '', + id: undefined, }; - const tmpFeatureState = {}; + const tmpFeatureState: any = {}; for (let i = 0; i < featureCollection.features.length; i++) { const feature = featureCollection.features[i]; for (let j = 0; j < dynamicStyleProps.length; j++) { const dynamicStyleProp = dynamicStyleProps[j]; - const name = dynamicStyleProp.getField().getName(); + const name = dynamicStyleProp.getFieldName(); const computedName = getComputedFieldName(dynamicStyleProp.getStyleName(), name); - const rawValue = feature.properties[name]; + const rawValue = feature.properties ? feature.properties[name] : undefined; if (dynamicStyleProp.supportsMbFeatureState()) { - tmpFeatureState[name] = getNumericalMbFeatureStateValue(rawValue); //the same value will be potentially overridden multiple times, if the name remains identical + tmpFeatureState[name] = getNumericalMbFeatureStateValue(rawValue); // the same value will be potentially overridden multiple times, if the name remains identical } else { - //in practice, a new system property will only be created for: + // in practice, a new system property will only be created for: // - label text: this requires the value to be formatted first. // - icon orientation: this is a lay-out property which do not support feature-state (but we're still coercing to a number) @@ -533,7 +632,7 @@ export class VectorStyle extends AbstractStyle { ? getNumericalMbFeatureStateValue(rawValue) : dynamicStyleProp.formatField(rawValue); - feature.properties[computedName] = formattedValue; + if (feature.properties) feature.properties[computedName] = formattedValue; } } tmpFeatureIdentifier.source = mbSourceId; @@ -541,10 +640,10 @@ export class VectorStyle extends AbstractStyle { mbMap.setFeatureState(tmpFeatureIdentifier, tmpFeatureState); } - //returns boolean indicating if styles do not support feature-state and some values are stored in geojson properties - //this return-value is used in an optimization for style-updates with mapbox-gl. - //`true` indicates the entire data needs to reset on the source (otherwise the style-rules will not be reapplied) - //`false` indicates the data does not need to be reset on the store, because styles are re-evaluated if they use featureState + // returns boolean indicating if styles do not support feature-state and some values are stored in geojson properties + // this return-value is used in an optimization for style-updates with mapbox-gl. + // `true` indicates the entire data needs to reset on the source (otherwise the style-rules will not be reapplied) + // `false` indicates the data does not need to be reset on the store, because styles are re-evaluated if they use featureState return dynamicStyleProps.some((dynamicStyleProp) => !dynamicStyleProp.supportsMbFeatureState()); } @@ -552,23 +651,49 @@ export class VectorStyle extends AbstractStyle { return !this._symbolizeAsStyleProperty.isSymbolizedAsIcon(); } - setMBPaintProperties({ alpha, mbMap, fillLayerId, lineLayerId }) { + setMBPaintProperties({ + alpha, + mbMap, + fillLayerId, + lineLayerId, + }: { + alpha: number; + mbMap: MbMap; + fillLayerId: string; + lineLayerId: string; + }) { this._fillColorStyleProperty.syncFillColorWithMb(fillLayerId, mbMap, alpha); this._lineColorStyleProperty.syncLineColorWithMb(lineLayerId, mbMap, alpha); this._lineWidthStyleProperty.syncLineWidthWithMb(lineLayerId, mbMap); } - setMBPaintPropertiesForPoints({ alpha, mbMap, pointLayerId }) { + setMBPaintPropertiesForPoints({ + alpha, + mbMap, + pointLayerId, + }: { + alpha: number; + mbMap: MbMap; + pointLayerId: string; + }) { this._fillColorStyleProperty.syncCircleColorWithMb(pointLayerId, mbMap, alpha); this._lineColorStyleProperty.syncCircleStrokeWithMb(pointLayerId, mbMap, alpha); const hasNoRadius = !this._iconSizeStyleProperty.isDynamic() && - this._iconSizeStyleProperty.getOptions().size === 0; + (this._iconSizeStyleProperty as StaticSizeProperty).getOptions().size === 0; this._lineWidthStyleProperty.syncCircleStrokeWidthWithMb(pointLayerId, mbMap, hasNoRadius); - this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap, hasNoRadius); + this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap); } - setMBPropertiesForLabelText({ alpha, mbMap, textLayerId }) { + setMBPropertiesForLabelText({ + alpha, + mbMap, + textLayerId, + }: { + alpha: number; + mbMap: MbMap; + textLayerId: string; + }) { mbMap.setLayoutProperty(textLayerId, 'icon-allow-overlap', true); mbMap.setLayoutProperty(textLayerId, 'text-allow-overlap', true); this._labelStyleProperty.syncTextFieldWithMb(textLayerId, mbMap); @@ -578,7 +703,15 @@ export class VectorStyle extends AbstractStyle { this._labelBorderColorStyleProperty.syncLabelBorderColorWithMb(textLayerId, mbMap); } - setMBSymbolPropertiesForPoints({ mbMap, symbolLayerId, alpha }) { + setMBSymbolPropertiesForPoints({ + mbMap, + symbolLayerId, + alpha, + }: { + alpha: number; + mbMap: MbMap; + symbolLayerId: string; + }) { mbMap.setLayoutProperty(symbolLayerId, 'icon-ignore-placement', true); mbMap.setPaintProperty(symbolLayerId, 'icon-opacity', alpha); @@ -595,34 +728,41 @@ export class VectorStyle extends AbstractStyle { this._iconOrientationProperty.syncIconRotationWithMb(symbolLayerId, mbMap); } - _makeField(fieldDescriptor) { + _makeField(fieldDescriptor?: StylePropertyField) { if (!fieldDescriptor || !fieldDescriptor.name) { return null; } - //fieldDescriptor.label is ignored. This is essentially cruft duplicating label-info from the metric-selection - //Ignore this custom label + // fieldDescriptor.label is ignored. This is essentially cruft duplicating label-info from the metric-selection + // Ignore this custom label if (fieldDescriptor.origin === FIELD_ORIGIN.SOURCE) { return this._source.getFieldByName(fieldDescriptor.name); } else if (fieldDescriptor.origin === FIELD_ORIGIN.JOIN) { - const join = this._layer.getValidJoins().find((join) => { + const targetJoin = this._layer.getValidJoins().find((join) => { return join.getRightJoinSource().hasMatchingMetricField(fieldDescriptor.name); }); - return join ? join.getRightJoinSource().getMetricFieldForName(fieldDescriptor.name) : null; + return targetJoin + ? targetJoin.getRightJoinSource().getMetricFieldForName(fieldDescriptor.name) + : null; } else { throw new Error(`Unknown origin-type ${fieldDescriptor.origin}`); } } - _makeSizeProperty(descriptor, styleName, isSymbolizedAsIcon) { + _makeSizeProperty( + descriptor: SizeStylePropertyDescriptor | undefined, + styleName: VECTOR_STYLES, + isSymbolizedAsIcon: boolean + ) { if (!descriptor || !descriptor.options) { return new StaticSizeProperty({ size: 0 }, styleName); } else if (descriptor.type === StaticStyleProperty.type) { - return new StaticSizeProperty(descriptor.options, styleName); + return new StaticSizeProperty(descriptor.options as SizeStaticOptions, styleName); } else if (descriptor.type === DynamicStyleProperty.type) { - const field = this._makeField(descriptor.options.field); + const options = descriptor.options as SizeDynamicOptions; + const field = this._makeField(options.field); return new DynamicSizeProperty( - descriptor.options, + options, styleName, field, this._layer, @@ -634,15 +774,19 @@ export class VectorStyle extends AbstractStyle { } } - _makeColorProperty(descriptor, styleName) { + _makeColorProperty( + descriptor: ColorStylePropertyDescriptor | undefined, + styleName: VECTOR_STYLES + ) { if (!descriptor || !descriptor.options) { - return new StaticColorProperty({ color: null }, styleName); + return new StaticColorProperty({ color: '' }, styleName); } else if (descriptor.type === StaticStyleProperty.type) { - return new StaticColorProperty(descriptor.options, styleName); + return new StaticColorProperty(descriptor.options as ColorStaticOptions, styleName); } else if (descriptor.type === DynamicStyleProperty.type) { - const field = this._makeField(descriptor.options.field); + const options = descriptor.options as ColorDynamicOptions; + const field = this._makeField(options.field); return new DynamicColorProperty( - descriptor.options, + options, styleName, field, this._layer, @@ -653,15 +797,22 @@ export class VectorStyle extends AbstractStyle { } } - _makeOrientationProperty(descriptor, styleName) { + _makeOrientationProperty( + descriptor: OrientationStylePropertyDescriptor | undefined, + styleName: VECTOR_STYLES + ) { if (!descriptor || !descriptor.options) { return new StaticOrientationProperty({ orientation: 0 }, styleName); } else if (descriptor.type === StaticStyleProperty.type) { - return new StaticOrientationProperty(descriptor.options, styleName); + return new StaticOrientationProperty( + descriptor.options as OrientationStaticOptions, + styleName + ); } else if (descriptor.type === DynamicStyleProperty.type) { - const field = this._makeField(descriptor.options.field); + const options = descriptor.options as OrientationDynamicOptions; + const field = this._makeField(options.field); return new DynamicOrientationProperty( - descriptor.options, + options, styleName, field, this._layer, @@ -672,15 +823,19 @@ export class VectorStyle extends AbstractStyle { } } - _makeLabelProperty(descriptor) { + _makeLabelProperty(descriptor?: LabelStylePropertyDescriptor) { if (!descriptor || !descriptor.options) { return new StaticTextProperty({ value: '' }, VECTOR_STYLES.LABEL_TEXT); } else if (descriptor.type === StaticStyleProperty.type) { - return new StaticTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT); + return new StaticTextProperty( + descriptor.options as LabelStaticOptions, + VECTOR_STYLES.LABEL_TEXT + ); } else if (descriptor.type === DynamicStyleProperty.type) { - const field = this._makeField(descriptor.options.field); + const options = descriptor.options as LabelDynamicOptions; + const field = this._makeField(options.field); return new DynamicTextProperty( - descriptor.options, + options, VECTOR_STYLES.LABEL_TEXT, field, this._layer, @@ -691,15 +846,16 @@ export class VectorStyle extends AbstractStyle { } } - _makeIconProperty(descriptor) { + _makeIconProperty(descriptor?: IconStylePropertyDescriptor) { if (!descriptor || !descriptor.options) { return new StaticIconProperty({ value: DEFAULT_ICON }, VECTOR_STYLES.ICON); } else if (descriptor.type === StaticStyleProperty.type) { - return new StaticIconProperty(descriptor.options, VECTOR_STYLES.ICON); + return new StaticIconProperty(descriptor.options as IconStaticOptions, VECTOR_STYLES.ICON); } else if (descriptor.type === DynamicStyleProperty.type) { - const field = this._makeField(descriptor.options.field); + const options = descriptor.options as IconDynamicOptions; + const field = this._makeField(options.field); return new DynamicIconProperty( - descriptor.options, + options, VECTOR_STYLES.ICON, field, this._layer, diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx index 95f13574105b7c..6c6cb6ba143cda 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { AbstractLayer, ILayer } from '../../../../../../classes/layers/layer'; import { AbstractSource, ISource } from '../../../../../../classes/sources/source'; -import { AbstractStyle, IStyle } from '../../../../../../classes/styles/style'; +import { IStyle } from '../../../../../../classes/styles/style'; import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; @@ -17,7 +17,15 @@ let supportsFitToBounds: boolean; class MockSource extends AbstractSource implements ISource {} -class MockStyle extends AbstractStyle implements IStyle {} +class MockStyle implements IStyle { + renderEditor() { + return null; + } + + getType() { + return 'mockStyle'; + } +} class LayerMock extends AbstractLayer implements ILayer { constructor() { @@ -25,7 +33,7 @@ class LayerMock extends AbstractLayer implements ILayer { type: 'mySourceType', }; const source = new MockSource(sourceDescriptor); - const style = new MockStyle({ type: 'myStyleType' }); + const style = new MockStyle(); const layerDescriptor = { id: 'testLayer', sourceDescriptor, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b1e24958d53651..8287f8f42abdc4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10609,7 +10609,6 @@ "xpack.maps.style.customColorPaletteLabel": "カスタムカラーパレット", "xpack.maps.style.customColorRampLabel": "カスタマカラーランプ", "xpack.maps.style.fieldSelect.OriginLabel": "{fieldOrigin} からのフィールド", - "xpack.maps.style.heatmap.displayNameLabel": "ヒートマップスタイル", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "解像度パラメーターが認識されません: {resolution}", "xpack.maps.styles.categorical.otherCategoryLabel": "その他", "xpack.maps.styles.color.staticDynamicSelect.staticLabel": "塗りつぶし", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 82abb924611fb9..aff78ad79ae487 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10612,7 +10612,6 @@ "xpack.maps.style.customColorPaletteLabel": "定制调色板", "xpack.maps.style.customColorRampLabel": "定制颜色渐变", "xpack.maps.style.fieldSelect.OriginLabel": "来自 {fieldOrigin} 的字段", - "xpack.maps.style.heatmap.displayNameLabel": "热图样式", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "无法识别分辨率参数:{resolution}", "xpack.maps.styles.categorical.otherCategoryLabel": "其他", "xpack.maps.styles.color.staticDynamicSelect.staticLabel": "纯色", From 6c02dc8e209cecf05643efd022cd5ecbd780916f Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 21 Aug 2020 17:58:11 -0400 Subject: [PATCH 03/16] skip suite blocking es snapshot promotion (#75707) --- x-pack/test/api_integration/apis/security/basic_login.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/security/basic_login.js b/x-pack/test/api_integration/apis/security/basic_login.js index 70eddc9aee4d8e..db58f17582c60d 100644 --- a/x-pack/test/api_integration/apis/security/basic_login.js +++ b/x-pack/test/api_integration/apis/security/basic_login.js @@ -15,7 +15,8 @@ export default function ({ getService }) { const validUsername = kibanaServerConfig.username; const validPassword = kibanaServerConfig.password; - describe('Basic authentication', () => { + // Failing: See https://github.com/elastic/kibana/issues/75707 + describe.skip('Basic authentication', () => { it('should redirect non-AJAX requests to the login page if not authenticated', async () => { const response = await supertest.get('/abc/xyz').expect(302); From 19c5ba838a309a9fb8519105c44858c7fa76e0ab Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 21 Aug 2020 16:49:36 -0700 Subject: [PATCH 04/16] skip suite blocking es snapshot promotion (#75707) --- .../kerberos_api_integration/apis/security/kerberos_login.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 38a8697e05252b..3c211dca2a783c 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -37,7 +37,8 @@ export default function ({ getService }: FtrProviderContext) { expect(cookie.maxAge).to.be(0); } - describe('Kerberos authentication', () => { + // FAILING: https://github.com/elastic/kibana/issues/75707 + describe.skip('Kerberos authentication', () => { before(async () => { await getService('esSupertest') .post('/_security/role_mapping/krb5') From 50499a589c554b49da3c8130fa8f83fb9aa14aaf Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 21 Aug 2020 17:53:03 -0700 Subject: [PATCH 05/16] Surface data stream stats, index template, and ILM policy in the UI (#75107) * Add Index Management README and quick testing steps for data streams. * Surface data stream health in Data Streams tab (table and detail panel). - Extract out DataHealth component for use in both Data Streams and Indices tabs. - Refactor detail panel to use data structure & algo to build component. - Refactor detail panel to use i18n.translate instead of FormattedMessage. * Render index template name and index lifecycle policy name in the detail panel. * Render storage size and max timestamp information in table and detail panel. - Add 'Include stats' switch. - Add humanizeTimeStamp service, localized to data streams. --- .../architecture/code-exploration.asciidoc | 4 +- x-pack/plugins/index_management/README.md | 22 ++ .../helpers/test_subjects.ts | 7 +- .../home/data_streams_tab.helpers.ts | 11 + .../home/data_streams_tab.test.ts | 44 ++- .../common/lib/data_stream_serialization.ts | 19 +- .../common/types/data_streams.ts | 14 + .../index_management/common/types/index.ts | 2 +- .../data_health.tsx} | 14 +- .../public/application/components/index.ts | 1 + .../data_stream_detail_panel.tsx | 265 +++++++++++------- .../data_stream_list/data_stream_list.tsx | 93 ++++-- .../data_stream_table/data_stream_table.tsx | 161 +++++++---- .../data_stream_list/humanize_time_stamp.ts | 10 + .../detail_panel/summary/summary.js | 5 +- .../index_list/index_table/index_table.js | 6 +- .../public/application/services/api.ts | 5 +- .../public/application/services/index.ts | 1 - .../api/data_streams/register_get_route.ts | 63 ++++- .../index_management/data_streams.ts | 46 ++- 20 files changed, 568 insertions(+), 225 deletions(-) create mode 100644 x-pack/plugins/index_management/README.md rename x-pack/plugins/index_management/public/application/{services/health_to_color.ts => components/data_health.tsx} (53%) create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/humanize_time_stamp.ts diff --git a/docs/developer/architecture/code-exploration.asciidoc b/docs/developer/architecture/code-exploration.asciidoc index 6e814921d3f320..d65456f2ad928f 100644 --- a/docs/developer/architecture/code-exploration.asciidoc +++ b/docs/developer/architecture/code-exploration.asciidoc @@ -426,9 +426,9 @@ You can test that the Frozen badge, phase filtering, and lifecycle information i Index Management by running this series of requests in Console: -- {kib-repo}blob/{branch}/x-pack/plugins/index_management[indexManagement] +- {kib-repo}blob/{branch}/x-pack/plugins/index_management/README.md[indexManagement] -WARNING: Missing README. +Create a data stream using Console and you'll be able to view it in the UI: - {kib-repo}blob/{branch}/x-pack/plugins/infra/README.md[infra] diff --git a/x-pack/plugins/index_management/README.md b/x-pack/plugins/index_management/README.md new file mode 100644 index 00000000000000..07c5b9317b5cbc --- /dev/null +++ b/x-pack/plugins/index_management/README.md @@ -0,0 +1,22 @@ +# Index Management UI + +## Data streams tab + +### Quick steps for testing + +Create a data stream using Console and you'll be able to view it in the UI: + +``` +# Configure template for creating a data stream +PUT _index_template/ds +{ + "index_patterns": ["ds"], + "data_stream": {} +} + +# Add a document to the data stream +POST ds/_doc +{ + "@timestamp": "2020-01-27" +} +``` \ No newline at end of file diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index ecedf819e61851..8a610a04f8bb17 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -20,6 +20,7 @@ export type TestSubjects = | 'documentationLink' | 'emptyPrompt' | 'filterList.filterItem' + | 'includeStatsSwitch' | 'indexTable' | 'indexTableIncludeHiddenIndicesToggle' | 'indexTableIndexNameLink' @@ -28,16 +29,17 @@ export type TestSubjects = | 'legacyTemplateTable' | 'manageTemplateButton' | 'mappingsTabContent' - | 'previewTabContent' | 'noAliasesCallout' | 'noMappingsCallout' | 'noSettingsCallout' + | 'previewTabContent' | 'reloadButton' | 'reloadIndicesButton' | 'row' | 'sectionError' | 'sectionLoading' | 'settingsTabContent' + | 'simulateTemplatePreview' | 'summaryTab' | 'summaryTitle' | 'systemTemplatesSwitch' @@ -49,5 +51,4 @@ export type TestSubjects = | 'templateList' | 'templatesTab' | 'templateTable' - | 'viewButton' - | 'simulateTemplatePreview'; + | 'viewButton'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index db7541c93f9ac2..2fcf2a822cb2d1 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -22,6 +22,7 @@ export interface DataStreamsTabTestBed extends TestBed { actions: { goToDataStreamsList: () => void; clickEmptyPromptIndexTemplateLink: () => void; + clickIncludeStatsSwitch: () => void; clickReloadButton: () => void; clickNameAt: (index: number) => void; clickIndicesAt: (index: number) => void; @@ -74,6 +75,11 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find } = testBed; + find('includeStatsSwitch').simulate('click'); + }; + const clickReloadButton = () => { const { find } = testBed; find('reloadButton').simulate('click'); @@ -149,6 +155,7 @@ export const setup = async (overridingDependencies: any = {}): Promise ({ }, ], generation: 1, + health: 'green', + indexTemplateName: 'indexTemplate', + storageSize: '1b', + maxTimeStamp: 420, }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 89a95135bb07a1..ade4a62ceb1989 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -78,7 +78,13 @@ describe('Data Streams tab', () => { describe('when there are data streams', () => { beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndicesResponse([ + const { + setLoadIndicesResponse, + setLoadDataStreamsResponse, + setLoadDataStreamResponse, + } = httpRequestsMockHelpers; + + setLoadIndicesResponse([ { health: '', status: '', @@ -105,20 +111,16 @@ describe('Data Streams tab', () => { ]); const dataStreamForDetailPanel = createDataStreamPayload('dataStream1'); - - httpRequestsMockHelpers.setLoadDataStreamsResponse([ + setLoadDataStreamsResponse([ dataStreamForDetailPanel, createDataStreamPayload('dataStream2'), ]); - - httpRequestsMockHelpers.setLoadDataStreamResponse(dataStreamForDetailPanel); + setLoadDataStreamResponse(dataStreamForDetailPanel); testBed = await setup(); - await act(async () => { testBed.actions.goToDataStreamsList(); }); - testBed.component.update(); }); @@ -127,8 +129,8 @@ describe('Data Streams tab', () => { const { tableCellsValues } = table.getMetaData('dataStreamTable'); expect(tableCellsValues).toEqual([ - ['', 'dataStream1', '1', 'Delete'], - ['', 'dataStream2', '1', 'Delete'], + ['', 'dataStream1', 'green', '1', 'Delete'], + ['', 'dataStream2', 'green', '1', 'Delete'], ]); }); @@ -146,6 +148,30 @@ describe('Data Streams tab', () => { expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/data_streams`); }); + test('has a switch that will reload the data streams with additional stats when clicked', async () => { + const { exists, actions, table, component } = testBed; + const totalRequests = server.requests.length; + + expect(exists('includeStatsSwitch')).toBe(true); + + // Changing the switch will automatically reload the data streams. + await act(async () => { + actions.clickIncludeStatsSwitch(); + }); + component.update(); + + // A request is sent, but sinon isn't capturing the query parameters for some reason. + expect(server.requests.length).toBe(totalRequests + 1); + expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/data_streams`); + + // The table renders with the stats columns though. + const { tableCellsValues } = table.getMetaData('dataStreamTable'); + expect(tableCellsValues).toEqual([ + ['', 'dataStream1', 'green', 'December 31st, 1969 7:00:00 PM', '1b', '1', 'Delete'], + ['', 'dataStream2', 'green', 'December 31st, 1969 7:00:00 PM', '1b', '1', 'Delete'], + ]); + }); + test('clicking the indices count navigates to the backing indices', async () => { const { table, actions } = testBed; await actions.clickIndicesAt(0); diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index 7832662aea4949..69004eaa020eb1 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -4,10 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DataStream, DataStreamFromEs } from '../types'; +import { DataStream, DataStreamFromEs, Health } from '../types'; export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataStream { - const { name, timestamp_field: timeStampField, indices, generation } = dataStreamFromEs; + const { + name, + timestamp_field: timeStampField, + indices, + generation, + status, + template, + ilm_policy: ilmPolicyName, + store_size: storageSize, + maximum_timestamp: maxTimeStamp, + } = dataStreamFromEs; return { name, @@ -20,6 +30,11 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS }) ), generation, + health: status.toLowerCase() as Health, // ES typically returns status in all-caps + indexTemplateName: template, + ilmPolicyName, + storageSize, + maxTimeStamp, }; } diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index d1936c4426b49e..7c348f9a8085df 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -10,11 +10,18 @@ interface TimestampFieldFromEs { type TimestampField = TimestampFieldFromEs; +export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED'; + export interface DataStreamFromEs { name: string; timestamp_field: TimestampFieldFromEs; indices: DataStreamIndexFromEs[]; generation: number; + status: HealthFromEs; + template: string; + ilm_policy?: string; + store_size?: string; + maximum_timestamp?: number; } export interface DataStreamIndexFromEs { @@ -22,11 +29,18 @@ export interface DataStreamIndexFromEs { index_uuid: string; } +export type Health = 'green' | 'yellow' | 'red'; + export interface DataStream { name: string; timeStampField: TimestampField; indices: DataStreamIndex[]; generation: number; + health: Health; + indexTemplateName: string; + ilmPolicyName?: string; + storageSize?: string; + maxTimeStamp?: number; } export interface DataStreamIndex { diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index c4ba60573d430c..0a6f18be2e030d 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -12,6 +12,6 @@ export * from './mappings'; export * from './templates'; -export { DataStreamFromEs, DataStream, DataStreamIndex } from './data_streams'; +export { DataStreamFromEs, Health, DataStream, DataStreamIndex } from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/public/application/services/health_to_color.ts b/x-pack/plugins/index_management/public/application/components/data_health.tsx similarity index 53% rename from x-pack/plugins/index_management/public/application/services/health_to_color.ts rename to x-pack/plugins/index_management/public/application/components/data_health.tsx index ab3d042529aa50..21df8a16947d56 100644 --- a/x-pack/plugins/index_management/public/application/services/health_to_color.ts +++ b/x-pack/plugins/index_management/public/application/components/data_health.tsx @@ -4,7 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -export const healthToColor = (health: 'green' | 'yellow' | 'red') => { +import React from 'react'; +import { EuiHealth } from '@elastic/eui'; +import { Health } from '../../../common/types'; + +interface Props { + health: Health; +} + +const healthToColor = (health: Health) => { switch (health) { case 'green': return 'success'; @@ -14,3 +22,7 @@ export const healthToColor = (health: 'green' | 'yellow' | 'red') => { return 'danger'; } }; + +export const DataHealth: React.FunctionComponent = ({ health }) => ( + {health} +); diff --git a/x-pack/plugins/index_management/public/application/components/index.ts b/x-pack/plugins/index_management/public/application/components/index.ts index b6a325c0d56adb..4b0bf4680c49af 100644 --- a/x-pack/plugins/index_management/public/application/components/index.ts +++ b/x-pack/plugins/index_management/public/application/components/index.ts @@ -10,6 +10,7 @@ export { NoMatch } from './no_match'; export { PageErrorForbidden } from './page_error'; export { TemplateDeleteModal } from './template_delete_modal'; export { TemplateForm } from './template_form'; +export { DataHealth } from './data_health'; export * from './mappings_editor'; export * from './component_templates'; export * from './index_templates'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index a0381557db21e9..0af22b497a4c0e 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -5,7 +5,7 @@ */ import React, { useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiButtonEmpty, @@ -24,9 +24,51 @@ import { } from '@elastic/eui'; import { reactRouterNavigate } from '../../../../../shared_imports'; -import { SectionLoading, SectionError, Error } from '../../../../components'; +import { SectionLoading, SectionError, Error, DataHealth } from '../../../../components'; import { useLoadDataStream } from '../../../../services/api'; import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal'; +import { humanizeTimeStamp } from '../humanize_time_stamp'; + +interface DetailsListProps { + details: Array<{ + name: string; + toolTip: string; + content: any; + }>; +} + +const DetailsList: React.FunctionComponent = ({ details }) => { + const groups: any[] = []; + let items: any[]; + + details.forEach((detail, index) => { + const { name, toolTip, content } = detail; + + if (index % 2 === 0) { + items = []; + + groups.push({items}); + } + + items.push( + + + + {name} + + + {toolTip && } + + + + + {content} + + ); + }); + + return {groups}; +}; interface Props { dataStreamName: string; @@ -48,116 +90,123 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ if (isLoading) { content = ( - + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.loadingDataStreamDescription', { + defaultMessage: 'Loading data stream', + })} ); } else if (error) { content = ( - } + title={i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.loadingDataStreamErrorMessage', { + defaultMessage: 'Error loading data stream', + })} error={error as Error} data-test-subj="sectionError" /> ); } else if (dataStream) { - const { indices, timeStampField, generation } = dataStream; + const { + health, + indices, + timeStampField, + generation, + indexTemplateName, + ilmPolicyName, + storageSize, + maxTimeStamp, + } = dataStream; + const details = [ + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthTitle', { + defaultMessage: 'Health', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.healthToolTip', { + defaultMessage: `The health of the data stream's current backing indices`, + }), + content: , + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampTitle', { + defaultMessage: 'Last updated', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampToolTip', { + defaultMessage: 'The most recent document to be added to the data stream', + }), + content: maxTimeStamp ? ( + humanizeTimeStamp(maxTimeStamp) + ) : ( + + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.maxTimeStampNoneMessage', { + defaultMessage: `Never`, + })} + + ), + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeTitle', { + defaultMessage: 'Storage size', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.storageSizeToolTip', { + defaultMessage: `Total size of all shards in the data stream’s backing indices`, + }), + content: storageSize, + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesTitle', { + defaultMessage: 'Indices', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indicesToolTip', { + defaultMessage: `The data stream's current backing indices`, + }), + content: {indices.length}, + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle', { + defaultMessage: 'Timestamp field', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip', { + defaultMessage: 'Timestamp field shared by all documents in the data stream', + }), + content: timeStampField.name, + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationTitle', { + defaultMessage: 'Generation', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.generationToolTip', { + defaultMessage: 'Cumulative count of backing indices created for the data stream', + }), + content: generation, + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateTitle', { + defaultMessage: 'Index template', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.indexTemplateToolTip', { + defaultMessage: + 'The index template that configured the data stream and configures its backing indices', + }), + content: indexTemplateName, + }, + { + name: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyTitle', { + defaultMessage: 'Index lifecycle policy', + }), + toolTip: i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyToolTip', { + defaultMessage: `The index lifecycle policy that manages the data stream's data`, + }), + content: ilmPolicyName ?? ( + + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.ilmPolicyContentNoneMessage', { + defaultMessage: `None`, + })} + + ), + }, + ]; - content = ( - - - - - - - - - - - - } - position="top" - /> - - - - - - {indices.length} - - - - - - - - - - - } - position="top" - /> - - - - - {timeStampField.name} - - - - - - - - - - - - - - } - position="top" - /> - - - - - {generation} - - - - ); + content = ; } return ( @@ -201,10 +250,9 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ onClick={() => onClose()} data-test-subj="closeDetailsButton" > - + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.closeButtonLabel', { + defaultMessage: 'Close', + })} @@ -216,10 +264,9 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ onClick={() => setIsDeleting(true)} data-test-subj="deleteDataStreamButton" > - + {i18n.translate('xpack.idxMgmt.dataStreamDetailPanel.deleteButtonLabel', { + defaultMessage: 'Delete data stream', + })} ) : null} diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 239b119051c06a..d37576f18e849d 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -4,11 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiText, EuiSpacer, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSwitch, + EuiText, + EuiIconTip, + EuiSpacer, + EuiEmptyPrompt, + EuiLink, +} from '@elastic/eui'; import { ScopedHistory } from 'kibana/public'; import { reactRouterNavigate, extractQueryParams } from '../../../../shared_imports'; @@ -39,7 +48,10 @@ export const DataStreamList: React.FunctionComponent 0) { content = ( <> - {/* TODO: Add a switch for toggling on data streams created by Ingest Manager */} - - - {i18n.translate('xpack.idxMgmt.dataStreamListDescription.learnMoreLinkText', { - defaultMessage: 'Learn more.', - })} - - ), - }} - /> - + + + {/* TODO: Add a switch for toggling on data streams created by Ingest Manager */} + + + {i18n.translate('xpack.idxMgmt.dataStreamListDescription.learnMoreLinkText', { + defaultMessage: 'Learn more.', + })} + + ), + }} + /> + + + + + + + setIsIncludeStatsChecked(e.target.checked)} + data-test-subj="includeStatsSwitch" + /> + + + + + + + + @@ -166,6 +212,7 @@ export const DataStreamList: React.FunctionComponent ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index d1e093f1ffc832..a586e9495cf71a 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -13,13 +13,16 @@ import { ScopedHistory } from 'kibana/public'; import { DataStream } from '../../../../../../common/types'; import { reactRouterNavigate } from '../../../../../shared_imports'; import { encodePathForReactRouter } from '../../../../services/routing'; +import { DataHealth } from '../../../../components'; import { Section } from '../../../home'; import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal'; +import { humanizeTimeStamp } from '../humanize_time_stamp'; interface Props { dataStreams?: DataStream[]; reload: () => {}; history: ScopedHistory; + includeStats: boolean; filters?: string; } @@ -28,76 +31,118 @@ export const DataStreamTable: React.FunctionComponent = ({ reload, history, filters, + includeStats, }) => { const [selection, setSelection] = useState([]); const [dataStreamsToDelete, setDataStreamsToDelete] = useState([]); - const columns: Array> = [ - { - field: 'name', - name: i18n.translate('xpack.idxMgmt.dataStreamList.table.nameColumnTitle', { - defaultMessage: 'Name', - }), - truncateText: true, - sortable: true, - render: (name: DataStream['name'], item: DataStream) => { - return ( - - {name} - - ); - }, - }, - { - field: 'indices', - name: i18n.translate('xpack.idxMgmt.dataStreamList.table.indicesColumnTitle', { - defaultMessage: 'Indices', - }), - truncateText: true, - sortable: true, - render: (indices: DataStream['indices'], dataStream) => ( + const columns: Array> = []; + + columns.push({ + field: 'name', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.nameColumnTitle', { + defaultMessage: 'Name', + }), + truncateText: true, + sortable: true, + render: (name: DataStream['name'], item: DataStream) => { + return ( - {indices.length} + {name} - ), + ); }, - { - name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', { - defaultMessage: 'Actions', + }); + + columns.push({ + field: 'health', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.healthColumnTitle', { + defaultMessage: 'Health', + }), + truncateText: true, + sortable: true, + render: (health: DataStream['health']) => { + return ; + }, + }); + + if (includeStats) { + columns.push({ + field: 'maxTimeStamp', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnTitle', { + defaultMessage: 'Last updated', }), - actions: [ - { - name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteText', { - defaultMessage: 'Delete', - }), - description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDecription', { - defaultMessage: 'Delete this data stream', - }), - icon: 'trash', - color: 'danger', - type: 'icon', - onClick: ({ name }: DataStream) => { - setDataStreamsToDelete([name]); - }, - isPrimary: true, - 'data-test-subj': 'deleteDataStream', + width: '300px', + truncateText: true, + sortable: true, + render: (maxTimeStamp: DataStream['maxTimeStamp']) => + maxTimeStamp + ? humanizeTimeStamp(maxTimeStamp) + : i18n.translate('xpack.idxMgmt.dataStreamList.table.maxTimeStampColumnNoneMessage', { + defaultMessage: 'Never', + }), + }); + + columns.push({ + field: 'storageSize', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.storageSizeColumnTitle', { + defaultMessage: 'Storage size', + }), + truncateText: true, + sortable: true, + }); + } + + columns.push({ + field: 'indices', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.indicesColumnTitle', { + defaultMessage: 'Indices', + }), + truncateText: true, + sortable: true, + render: (indices: DataStream['indices'], dataStream) => ( + + {indices.length} + + ), + }); + + columns.push({ + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteText', { + defaultMessage: 'Delete', + }), + description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDecription', { + defaultMessage: 'Delete this data stream', + }), + icon: 'trash', + color: 'danger', + type: 'icon', + onClick: ({ name }: DataStream) => { + setDataStreamsToDelete([name]); }, - ], - }, - ]; + isPrimary: true, + 'data-test-subj': 'deleteDataStream', + }, + ], + }); const pagination = { initialPageSize: 20, diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/humanize_time_stamp.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/humanize_time_stamp.ts new file mode 100644 index 00000000000000..b346f828901793 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/humanize_time_stamp.ts @@ -0,0 +1,10 @@ +/* + * 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 moment from 'moment'; + +export const humanizeTimeStamp = (timeStamp: number): string => + moment(timeStamp).format('MMMM Do, YYYY h:mm:ss A'); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js index 2fda71035fb580..6b3c83daa28a88 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, - EuiHealth, EuiDescriptionList, EuiHorizontalRule, EuiDescriptionListTitle, @@ -18,7 +17,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { healthToColor } from '../../../../../services'; +import { DataHealth } from '../../../../../components'; import { AppContextConsumer } from '../../../../../app_context'; const getHeaders = () => { @@ -78,7 +77,7 @@ export class Summary extends React.PureComponent { const value = index[fieldName]; let content = value; if (fieldName === 'health') { - content = {value}; + content = ; } if (Array.isArray(content)) { content = content.join(', '); diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 85a8571c1e7251..b4e003b667074b 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -13,7 +13,6 @@ import qs from 'query-string'; import { EuiButton, EuiCallOut, - EuiHealth, EuiLink, EuiCheckbox, EuiFlexGroup, @@ -38,12 +37,11 @@ import { import { UIM_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; import { reactRouterNavigate } from '../../../../../shared_imports'; import { REFRESH_RATE_INDEX_LIST } from '../../../../constants'; -import { healthToColor } from '../../../../services'; import { encodePathForReactRouter } from '../../../../services/routing'; import { documentationService } from '../../../../services/documentation'; import { AppContextConsumer } from '../../../../app_context'; import { renderBadges } from '../../../../lib/render_badges'; -import { NoMatch, PageErrorForbidden } from '../../../../components'; +import { NoMatch, PageErrorForbidden, DataHealth } from '../../../../components'; import { IndexActionsContextMenu } from '../index_actions_context_menu'; const HEADERS = { @@ -260,7 +258,7 @@ export class IndexTable extends Component { const { openDetailPanel, filterChanged, history } = this.props; if (fieldName === 'health') { - return {value}; + return ; } else if (fieldName === 'name') { return ( diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 546a0115ee4a9a..35ded3ea73d916 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -47,10 +47,13 @@ export const setUiMetricService = (_uiMetricService: UiMetricService({ path: `${API_BASE_PATH}/data_streams`, method: 'get', + query: { + includeStats, + }, }); } diff --git a/x-pack/plugins/index_management/public/application/services/index.ts b/x-pack/plugins/index_management/public/application/services/index.ts index a78e0bac14ae1d..86c11b4a9c1eb5 100644 --- a/x-pack/plugins/index_management/public/application/services/index.ts +++ b/x-pack/plugins/index_management/public/application/services/index.ts @@ -24,7 +24,6 @@ export { useLoadIndexTemplates, simulateIndexTemplate, } from './api'; -export { healthToColor } from './health_to_color'; export { sortTable } from './sort_table'; export { UiMetricService } from './ui_metric'; diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts index b91c7b4650180d..34edcb6fb7477d 100644 --- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -10,19 +10,52 @@ import { deserializeDataStream, deserializeDataStreamList } from '../../../../co import { RouteDependencies } from '../../../types'; import { addBasePath } from '../index'; +const querySchema = schema.object({ + includeStats: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])), +}); + export function registerGetAllRoute({ router, license, lib: { isEsError } }: RouteDependencies) { router.get( - { path: addBasePath('/data_streams'), validate: false }, + { path: addBasePath('/data_streams'), validate: { query: querySchema } }, license.guardApiRoute(async (ctx, req, res) => { const { callAsCurrentUser } = ctx.dataManagement!.client; + const includeStats = (req.query as TypeOf).includeStats === 'true'; + try { const { data_streams: dataStreams } = await callAsCurrentUser( 'dataManagement.getDataStreams' ); - const body = deserializeDataStreamList(dataStreams); - return res.ok({ body }); + if (includeStats) { + const { + data_streams: dataStreamsStats, + } = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', { + path: '/_data_stream/*/_stats', + method: 'GET', + query: { + human: true, + }, + }); + + // Merge stats into data streams. + for (let i = 0; i < dataStreams.length; i++) { + const dataStream = dataStreams[i]; + + // eslint-disable-next-line @typescript-eslint/naming-convention + const { store_size, maximum_timestamp } = dataStreamsStats.find( + ({ data_stream: statsName }: { data_stream: string }) => statsName === dataStream.name + ); + + dataStreams[i] = { + ...dataStream, + store_size, + maximum_timestamp, + }; + } + } + + return res.ok({ body: deserializeDataStreamList(dataStreams) }); } catch (error) { if (isEsError(error)) { return res.customError({ @@ -52,12 +85,28 @@ export function registerGetOneRoute({ router, license, lib: { isEsError } }: Rou const { callAsCurrentUser } = ctx.dataManagement!.client; try { - const { data_streams: dataStream } = await callAsCurrentUser( - 'dataManagement.getDataStream', - { name } - ); + const [ + { data_streams: dataStream }, + { data_streams: dataStreamsStats }, + ] = await Promise.all([ + callAsCurrentUser('dataManagement.getDataStream', { name }), + ctx.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', { + path: `/_data_stream/${name}/_stats`, + method: 'GET', + query: { + human: true, + }, + }), + ]); if (dataStream[0]) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { store_size, maximum_timestamp } = dataStreamsStats[0]; + dataStream[0] = { + ...dataStream[0], + store_size, + maximum_timestamp, + }; const body = deserializeDataStream(dataStream[0]); return res.ok({ body }); } diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index f8ddca374209bd..f4b947336e044f 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -51,6 +51,14 @@ export default function ({ getService }: FtrProviderContext) { await deleteComposableIndexTemplate(name); }; + const assertDataStreamStorageSizeExists = (storageSize: string) => { + // Storage size of a document doesn't like it would be deterministic (could vary depending + // on how ES, Lucene, and the file system interact), so we'll just assert its presence and + // type. + expect(storageSize).to.be.ok(); + expect(typeof storageSize).to.be('string'); + }; + describe('Data streams', function () { describe('Get', () => { const testDataStreamName = 'test-data-stream'; @@ -77,10 +85,40 @@ export default function ({ getService }: FtrProviderContext) { }, ], generation: 1, + health: 'yellow', + indexTemplateName: testDataStreamName, }, ]); }); + it('includes stats when provided the includeStats query parameter', async () => { + const { body: dataStreams } = await supertest + .get(`${API_BASE_PATH}/data_streams?includeStats=true`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + // ES determines these values so we'll just echo them back. + const { name: indexName, uuid } = dataStreams[0].indices[0]; + const { storageSize, ...dataStreamWithoutStorageSize } = dataStreams[0]; + assertDataStreamStorageSizeExists(storageSize); + + expect(dataStreams.length).to.be(1); + expect(dataStreamWithoutStorageSize).to.eql({ + name: testDataStreamName, + timeStampField: { name: '@timestamp' }, + indices: [ + { + name: indexName, + uuid, + }, + ], + generation: 1, + health: 'yellow', + indexTemplateName: testDataStreamName, + maxTimeStamp: 0, + }); + }); + it('returns a single data stream by ID', async () => { const { body: dataStream } = await supertest .get(`${API_BASE_PATH}/data_streams/${testDataStreamName}`) @@ -89,7 +127,10 @@ export default function ({ getService }: FtrProviderContext) { // ES determines these values so we'll just echo them back. const { name: indexName, uuid } = dataStream.indices[0]; - expect(dataStream).to.eql({ + const { storageSize, ...dataStreamWithoutStorageSize } = dataStream; + assertDataStreamStorageSizeExists(storageSize); + + expect(dataStreamWithoutStorageSize).to.eql({ name: testDataStreamName, timeStampField: { name: '@timestamp' }, indices: [ @@ -99,6 +140,9 @@ export default function ({ getService }: FtrProviderContext) { }, ], generation: 1, + health: 'yellow', + indexTemplateName: testDataStreamName, + maxTimeStamp: 0, }); }); }); From f7cfceae1c5ccc0e026e1c7ba5b675ab09222f7b Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Fri, 21 Aug 2020 19:27:34 -0600 Subject: [PATCH 06/16] [@elastic/datemath] Remove build step from datemath package. (#75505) * Remove build step from datemath. * Remove outdated docs reference to datemath. Co-authored-by: Elastic Machine --- .../contributing/development-tests.asciidoc | 2 +- packages/elastic-datemath/.babelrc | 4 ---- packages/elastic-datemath/.npmignore | 4 ---- packages/elastic-datemath/__tests__/index.js | 8 ++++---- packages/elastic-datemath/{src => }/index.d.ts | 0 packages/elastic-datemath/{src => }/index.js | 4 ++-- packages/elastic-datemath/package.json | 18 ++---------------- packages/elastic-datemath/tsconfig.json | 8 ++------ packages/elastic-datemath/yarn.lock | 1 - 9 files changed, 11 insertions(+), 38 deletions(-) delete mode 100644 packages/elastic-datemath/.babelrc rename packages/elastic-datemath/{src => }/index.d.ts (100%) rename packages/elastic-datemath/{src => }/index.js (98%) delete mode 120000 packages/elastic-datemath/yarn.lock diff --git a/docs/developer/contributing/development-tests.asciidoc b/docs/developer/contributing/development-tests.asciidoc index 2e40f664faba9f..e4bd49e12101bb 100644 --- a/docs/developer/contributing/development-tests.asciidoc +++ b/docs/developer/contributing/development-tests.asciidoc @@ -20,7 +20,7 @@ root) |`yarn test:jest_integration -t regexp [test path]` |Mocha -|`src/**/__tests__/**/*.js` `!src/**/public/__tests__/*.js``packages/kbn-datemath/test/**/*.js` `packages/kbn-dev-utils/src/**/__tests__/**/*.js` `tasks/**/__tests__/**/*.js` +|`src/**/__tests__/**/*.js` `!src/**/public/__tests__/*.js` `packages/kbn-dev-utils/src/**/__tests__/**/*.js` `tasks/**/__tests__/**/*.js` |`node scripts/mocha --grep=regexp [test path]` |Functional diff --git a/packages/elastic-datemath/.babelrc b/packages/elastic-datemath/.babelrc deleted file mode 100644 index 64daee413aeb36..00000000000000 --- a/packages/elastic-datemath/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["@babel/preset-env"], - "plugins": ["add-module-exports"] -} diff --git a/packages/elastic-datemath/.npmignore b/packages/elastic-datemath/.npmignore index a56a2f3ff793e4..591be7afd16696 100644 --- a/packages/elastic-datemath/.npmignore +++ b/packages/elastic-datemath/.npmignore @@ -1,6 +1,2 @@ -/src -/test /tsconfig.json -/.babelrc -/yarn.lock /__tests__ diff --git a/packages/elastic-datemath/__tests__/index.js b/packages/elastic-datemath/__tests__/index.js index 8f06ff0ab4aadd..1a61021b48a6e0 100644 --- a/packages/elastic-datemath/__tests__/index.js +++ b/packages/elastic-datemath/__tests__/index.js @@ -17,10 +17,10 @@ * under the License. */ -import dateMath from '../src'; -import moment from 'moment'; -import sinon from 'sinon'; -import expect from '@kbn/expect'; +const dateMath = require('../index'); +const moment = require('moment'); +const sinon = require('sinon'); +const expect = require('@kbn/expect'); /** * Require a new instance of the moment library, bypassing the require cache. diff --git a/packages/elastic-datemath/src/index.d.ts b/packages/elastic-datemath/index.d.ts similarity index 100% rename from packages/elastic-datemath/src/index.d.ts rename to packages/elastic-datemath/index.d.ts diff --git a/packages/elastic-datemath/src/index.js b/packages/elastic-datemath/index.js similarity index 98% rename from packages/elastic-datemath/src/index.js rename to packages/elastic-datemath/index.js index 52ce12ddf70279..8a69d251d057dd 100644 --- a/packages/elastic-datemath/src/index.js +++ b/packages/elastic-datemath/index.js @@ -17,7 +17,7 @@ * under the License. */ -import moment from 'moment'; +const moment = require('moment'); const unitsMap = { ms: { weight: 1, type: 'fixed', base: 1 }, @@ -151,7 +151,7 @@ function parseDateMath(mathString, time, roundUp) { return dateTime; } -export default { +module.exports = { parse: parse, unitsMap: Object.freeze(unitsMap), units: Object.freeze(units), diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index ad4190f9814397..04bb96206e47ed 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -3,22 +3,8 @@ "version": "5.0.3", "description": "elasticsearch datemath parser, used in kibana", "license": "Apache-2.0", - "main": "target/index.js", - "typings": "target/index.d.ts", - "scripts": { - "build": "babel src --out-dir target --copy-files", - "kbn:bootstrap": "yarn build --quiet", - "kbn:watch": "yarn build --watch" - }, - "devDependencies": { - "@babel/cli": "^7.10.5", - "@babel/preset-env": "^7.11.0", - "babel-plugin-add-module-exports": "^1.0.2", - "moment": "^2.24.0" - }, - "dependencies": { - "tslib": "^2.0.0" - }, + "main": "index.js", + "typings": "index.d.ts", "peerDependencies": { "moment": "^2.24.0" } diff --git a/packages/elastic-datemath/tsconfig.json b/packages/elastic-datemath/tsconfig.json index c23b6635a5c19e..3604f1004cf6c4 100644 --- a/packages/elastic-datemath/tsconfig.json +++ b/packages/elastic-datemath/tsconfig.json @@ -1,10 +1,6 @@ { "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": true, - "outDir": "./target" - }, "include": [ - "./src/**/*.ts" - ] + "index.d.ts" + ], } diff --git a/packages/elastic-datemath/yarn.lock b/packages/elastic-datemath/yarn.lock deleted file mode 120000 index 3f82ebc9cdbae3..00000000000000 --- a/packages/elastic-datemath/yarn.lock +++ /dev/null @@ -1 +0,0 @@ -../../yarn.lock \ No newline at end of file From 6dbc4be8f7f418315a96532562fad683c7a493d4 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Sat, 22 Aug 2020 08:59:14 -0400 Subject: [PATCH 07/16] [SECURITY SOLUTION] Add our first search strategy for all host query (#75439) * add security solution search strategy on server side * get security solution search strategy in the public app for all host * fix types * fix Check core API changes * thank you cypress test * Remove any by the right type IESearchRequest Co-authored-by: Lukas Olson * add translation and filter error when we abort the query * pr review * fix translation * review II * fix merge issue Co-authored-by: Elastic Machine Co-authored-by: Lukas Olson --- ...kibana-plugin-core-public.doclinksstart.md | 2 +- ...n-plugins-data-public.iessearchresponse.md | 4 +- ...ta-public.iessearchresponse.rawresponse.md | 2 +- ...ugin-plugins-data-public.isearchgeneric.md | 2 +- ...n-plugins-data-server.iessearchresponse.md | 4 +- ...ta-server.iessearchresponse.rawresponse.md | 2 +- ...plugin-plugins-data-server.isearchsetup.md | 2 +- ...ver.isearchsetup.registersearchstrategy.md | 2 +- ...a-server.isearchstart.getsearchstrategy.md | 2 +- ...plugin-plugins-data-server.isearchstart.md | 4 +- ...gin-plugins-data-server.isearchstrategy.md | 4 +- ...gins-data-server.isearchstrategy.search.md | 2 +- ...plugin-plugins-data-server.plugin.start.md | 4 +- .../data/common/search/es_search/types.ts | 4 +- src/plugins/data/public/public.api.md | 6 +- .../data/public/search/search_interceptor.ts | 2 +- .../data/public/search/search_service.ts | 4 +- src/plugins/data/public/search/types.ts | 9 +- src/plugins/data/server/search/routes.test.ts | 4 +- src/plugins/data/server/search/routes.ts | 12 +- .../data/server/search/search_service.ts | 19 +- src/plugins/data/server/search/types.ts | 26 +- src/plugins/data/server/server.api.md | 16 +- .../public/search/search_interceptor.ts | 2 +- .../common/ecs/auditd/index.ts | 45 +++ .../common/ecs/cloud/index.ts | 20 ++ .../common/ecs/destination/index.ts | 21 ++ .../security_solution/common/ecs/dns/index.ts | 19 + .../common/ecs/endgame/index.ts | 33 ++ .../common/ecs/event/index.ts | 45 +++ .../common/ecs/file/index.ts | 37 ++ .../security_solution/common/ecs/geo/index.ts | 27 ++ .../common/ecs/host/index.ts | 35 ++ .../common/ecs/http/index.ts | 37 ++ .../security_solution/common/ecs/index.ts | 78 +++++ .../common/ecs/network/index.ts | 19 + .../common/ecs/process/index.ts | 39 +++ .../common/ecs/rule/index.ts | 69 ++++ .../common/ecs/signal/index.ts | 13 + .../common/ecs/source/index.ts | 21 ++ .../common/ecs/suricata/index.ts | 23 ++ .../common/ecs/system/index.ts | 39 +++ .../security_solution/common/ecs/tls/index.ts | 33 ++ .../security_solution/common/ecs/url/index.ts | 15 + .../common/ecs/user/index.ts | 21 ++ .../common/ecs/winlog/index.ts | 9 + .../common/ecs/zeek/index.ts | 133 +++++++ .../security_solution/hosts/index.ts | 84 +++++ .../security_solution/index.ts | 109 ++++++ .../public/hosts/containers/hosts/index.tsx | 324 ++++++++++-------- .../hosts/containers/hosts/translations.ts | 21 ++ .../pages/navigation/hosts_query_tab_body.tsx | 56 ++- .../security_solution/server/plugin.ts | 13 + .../hosts/dsl/query.detail_host.dsl.ts | 49 +++ .../factory/hosts/dsl/query.hosts.dsl.ts | 89 +++++ .../dsl/query.last_first_seen_host.dsl.ts | 35 ++ .../factory/hosts/helpers.ts | 115 +++++++ .../security_solution/factory/hosts/index.ts | 92 +++++ .../security_solution/factory/index.ts | 17 + .../security_solution/factory/types.ts | 23 ++ .../security_solution/index.ts | 38 ++ .../server/utils/build_query/filters.ts | 2 +- .../server/utils/build_query/index.ts | 2 +- 63 files changed, 1802 insertions(+), 238 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/ecs/auditd/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/cloud/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/destination/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/dns/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/endgame/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/event/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/file/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/geo/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/host/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/http/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/network/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/process/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/rule/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/signal/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/source/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/suricata/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/system/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/tls/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/url/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/user/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/winlog/index.ts create mode 100644 x-pack/plugins/security_solution/common/ecs/zeek/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts create mode 100644 x-pack/plugins/security_solution/public/hosts/containers/hosts/translations.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts create mode 100644 x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts 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 fa2d9090e3159c..4644dc432bc9a9 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 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>;
} | | +| [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>;
readonly visualize: Record<string, string>;
} | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md index 041d79de3282ec..7c9a6aa7024634 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface IEsSearchResponse extends IKibanaSearchResponse +export interface IEsSearchResponse extends IKibanaSearchResponse ``` ## Properties @@ -16,5 +16,5 @@ export interface IEsSearchResponse extends IKibanaSearchResponse | --- | --- | --- | | [isPartial](./kibana-plugin-plugins-data-public.iessearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | | [isRunning](./kibana-plugin-plugins-data-public.iessearchresponse.isrunning.md) | boolean | Indicates whether async search is still in flight | -| [rawResponse](./kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md) | SearchResponse<any> | | +| [rawResponse](./kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md) | SearchResponse<Source> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md index d7912f377ca9f0..f4648143ebc2e0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iessearchresponse.rawresponse.md @@ -7,5 +7,5 @@ Signature: ```typescript -rawResponse: SearchResponse; +rawResponse: SearchResponse; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md index 3bd6a398c8df54..861b59e73ef044 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchgeneric.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type ISearchGeneric = (request: IEsSearchRequest, options?: ISearchOptions) => Observable; +export declare type ISearchGeneric = (request: SearchStrategyRequest, options?: ISearchOptions) => Observable; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md index 0407dce5fe4181..55c0399e90e2f1 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface IEsSearchResponse extends IKibanaSearchResponse +export interface IEsSearchResponse extends IKibanaSearchResponse ``` ## Properties @@ -16,5 +16,5 @@ export interface IEsSearchResponse extends IKibanaSearchResponse | --- | --- | --- | | [isPartial](./kibana-plugin-plugins-data-server.iessearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | | [isRunning](./kibana-plugin-plugins-data-server.iessearchresponse.isrunning.md) | boolean | Indicates whether async search is still in flight | -| [rawResponse](./kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md) | SearchResponse<any> | | +| [rawResponse](./kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md) | SearchResponse<Source> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md index 0ee1691d0f697c..9987debfa551cf 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iessearchresponse.rawresponse.md @@ -7,5 +7,5 @@ Signature: ```typescript -rawResponse: SearchResponse; +rawResponse: SearchResponse; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md index e5b11a0b997ea6..ac2ae13372f7ad 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md @@ -15,6 +15,6 @@ export interface ISearchSetup | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-server.isearchsetup.aggs.md) | AggsSetup | | -| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | (name: string, strategy: ISearchStrategy) => void | Extension point exposed for other plugins to register their own search strategies. | +| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse>(name: string, strategy: ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse>) => void | Extension point exposed for other plugins to register their own search strategies. | | [usage](./kibana-plugin-plugins-data-server.isearchsetup.usage.md) | SearchUsage | Used internally for telemetry | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md index 73c575e7095ed2..f20c6f49110629 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md @@ -9,5 +9,5 @@ Extension point exposed for other plugins to register their own search strategie Signature: ```typescript -registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; +registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md index 970b2811a574b0..398ea21641942c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md @@ -9,5 +9,5 @@ Get other registered search strategies. For example, if a new strategy needs to Signature: ```typescript -getSearchStrategy: (name: string) => ISearchStrategy; +getSearchStrategy: (name: string) => ISearchStrategy; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md index 3762da963d4d9b..62d954cb80eb7f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface ISearchStart +export interface ISearchStart ``` ## Properties @@ -15,6 +15,6 @@ export interface ISearchStart | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-server.isearchstart.aggs.md) | AggsStart | | -| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | +| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | | [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | (context: RequestHandlerContext, request: IKibanaSearchRequest, options: ISearchOptions) => Promise<IKibanaSearchResponse> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md index d54e027c4b847e..dc076455ab2728 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md @@ -9,7 +9,7 @@ Search strategy interface contains a search method that takes in a request and r Signature: ```typescript -export interface ISearchStrategy +export interface ISearchStrategy ``` ## Properties @@ -17,5 +17,5 @@ export interface ISearchStrategy | Property | Type | Description | | --- | --- | --- | | [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | (context: RequestHandlerContext, id: string) => Promise<void> | | -| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise<IEsSearchResponse> | | +| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (context: RequestHandlerContext, request: SearchStrategyRequest, options?: ISearchOptions) => Promise<SearchStrategyResponse> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md index 1a225d0c9aeabf..45f43648ab603b 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md @@ -7,5 +7,5 @@ Signature: ```typescript -search: (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise; +search: (context: RequestHandlerContext, request: SearchStrategyRequest, options?: ISearchOptions) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 74bffc516725ff..2d9104ef894bc8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -8,7 +8,7 @@ ```typescript start(core: CoreStart): { - search: ISearchStart; + search: ISearchStart>; fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; @@ -27,7 +27,7 @@ start(core: CoreStart): { Returns: `{ - search: ISearchStart; + search: ISearchStart>; fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 8853e40dd0ad2e..654ed3899656d1 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -31,7 +31,7 @@ export interface IEsSearchRequest extends IKibanaSearchRequest { indexType?: string; } -export interface IEsSearchResponse extends IKibanaSearchResponse { +export interface IEsSearchResponse extends IKibanaSearchResponse { /** * Indicates whether async search is still in flight */ @@ -40,5 +40,5 @@ export interface IEsSearchResponse extends IKibanaSearchResponse { * Indicates whether the results returned are complete or partial */ isPartial?: boolean; - rawResponse: SearchResponse; + rawResponse: SearchResponse; } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 7defddb8f570a1..35ee1789b2fdff 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -791,11 +791,11 @@ export interface IEsSearchRequest extends IKibanaSearchRequest { // Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchResponse extends IKibanaSearchResponse { +export interface IEsSearchResponse extends IKibanaSearchResponse { isPartial?: boolean; isRunning?: boolean; // (undocumented) - rawResponse: SearchResponse_2; + rawResponse: SearchResponse_2; } // Warning: (ae-missing-release-tag) "IFieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1240,7 +1240,7 @@ export type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) // Warning: (ae-missing-release-tag) "ISearchGeneric" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ISearchGeneric = (request: IEsSearchRequest, options?: ISearchOptions) => Observable; +export type ISearchGeneric = (request: SearchStrategyRequest, options?: ISearchOptions) => Observable; // Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 99fccda7fddf35..30e509edd4987d 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -106,7 +106,7 @@ export class SearchInterceptor { ): Observable { const { id, ...searchRequest } = request; const path = trimEnd(`/internal/search/${strategy || ES_SEARCH_STRATEGY}/${id || ''}`, '/'); - const body = JSON.stringify(id != null ? {} : searchRequest); + const body = JSON.stringify(searchRequest); return from( this.deps.http.fetch({ method: 'POST', diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index a65b2b4b71f200..9a30a15936fe53 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -103,9 +103,9 @@ export class SearchService implements Plugin { { application, http, injectedMetadata, notifications, uiSettings }: CoreStart, { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { - const search: ISearchGeneric = (request, options) => { + const search = ((request, options) => { return this.searchInterceptor.search(request, options); - }; + }) as ISearchGeneric; const legacySearch = { esClient: this.esClient!, diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index d1a44379434023..55726e40f5a775 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -43,10 +43,13 @@ export type ISearch = ( options?: ISearchOptions ) => Observable; -export type ISearchGeneric = ( - request: IEsSearchRequest, +export type ISearchGeneric = < + SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse +>( + request: SearchStrategyRequest, options?: ISearchOptions -) => Observable; +) => Observable; export interface ISearchStartLegacy { esClient: LegacyApiCaller; diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index 167bd5af5d51d6..d91aeee1fe8186 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -36,7 +36,7 @@ describe('Search service', () => { const response = { id: 'yay' }; mockDataStart.search.search.mockResolvedValue(response); const mockContext = {}; - const mockBody = { params: {} }; + const mockBody = { id: undefined, params: {} }; const mockParams = { strategy: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ body: mockBody, @@ -67,7 +67,7 @@ describe('Search service', () => { }); const mockContext = {}; - const mockBody = { params: {} }; + const mockBody = { id: undefined, params: {} }; const mockParams = { strategy: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ body: mockBody, diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts index 32d8f8c1b09e06..3d813f745305f2 100644 --- a/src/plugins/data/server/search/routes.ts +++ b/src/plugins/data/server/search/routes.ts @@ -47,10 +47,14 @@ export function registerSearchRoute(core: CoreSetup): v const [, , selfStart] = await core.getStartServices(); try { - const response = await selfStart.search.search(context, id ? { id } : searchRequest, { - signal, - strategy, - }); + const response = await selfStart.search.search( + context, + { ...searchRequest, id }, + { + signal, + strategy, + } + ); return res.ok({ body: response }); } catch (err) { return res.customError({ diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 5643406932552f..cc23c455bed267 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -37,11 +37,12 @@ import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; import { searchTelemetry } from '../saved_objects'; -import { IEsSearchRequest } from '../../common'; +import { IEsSearchRequest, IEsSearchResponse } from '../../common'; -interface StrategyMap { - [name: string]: ISearchStrategy; -} +type StrategyMap< + SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse +> = Record>; /** @internal */ export interface SearchServiceSetupDependencies { @@ -56,7 +57,7 @@ export interface SearchServiceStartDependencies { export class SearchService implements Plugin { private readonly aggsService = new AggsService(); - private searchStrategies: StrategyMap = {}; + private searchStrategies: StrategyMap = {}; constructor( private initializerContext: PluginInitializerContext, @@ -125,7 +126,13 @@ export class SearchService implements Plugin { this.aggsService.stop(); } - private registerSearchStrategy = (name: string, strategy: ISearchStrategy) => { + private registerSearchStrategy = < + SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + >( + name: string, + strategy: ISearchStrategy + ) => { this.logger.debug(`Register strategy ${name}`); this.searchStrategies[name] = strategy; }; diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index fe54975d766244..56f803512aa196 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -37,7 +37,13 @@ export interface ISearchSetup { * Extension point exposed for other plugins to register their own search * strategies. */ - registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; + registerSearchStrategy: < + SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse + >( + name: string, + strategy: ISearchStrategy + ) => void; /** * Used internally for telemetry @@ -45,13 +51,18 @@ export interface ISearchSetup { usage?: SearchUsage; } -export interface ISearchStart { +export interface ISearchStart< + SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse +> { aggs: AggsStart; /** * Get other registered search strategies. For example, if a new strategy needs to use the * already-registered ES search strategy, it can use this function to accomplish that. */ - getSearchStrategy: (name: string) => ISearchStrategy; + getSearchStrategy: ( + name: string + ) => ISearchStrategy; search: ( context: RequestHandlerContext, request: IKibanaSearchRequest, @@ -63,11 +74,14 @@ export interface ISearchStart { * Search strategy interface contains a search method that takes in a request and returns a promise * that resolves to a response. */ -export interface ISearchStrategy { +export interface ISearchStrategy< + SearchStrategyRequest extends IEsSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IEsSearchResponse = IEsSearchResponse +> { search: ( context: RequestHandlerContext, - request: IEsSearchRequest, + request: SearchStrategyRequest, options?: ISearchOptions - ) => Promise; + ) => Promise; cancel?: (context: RequestHandlerContext, id: string) => Promise; } diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index f0a0d2763ff23f..f870030ae9562c 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -498,11 +498,11 @@ export interface IEsSearchRequest extends IKibanaSearchRequest { // Warning: (ae-missing-release-tag) "IEsSearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IEsSearchResponse extends IKibanaSearchResponse { +export interface IEsSearchResponse extends IKibanaSearchResponse { isPartial?: boolean; isRunning?: boolean; // (undocumented) - rawResponse: SearchResponse; + rawResponse: SearchResponse; } // Warning: (ae-missing-release-tag) "IFieldFormatsRegistry" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -689,19 +689,19 @@ export interface ISearchSetup { // // (undocumented) aggs: AggsSetup; - registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; + registerSearchStrategy: (name: string, strategy: ISearchStrategy) => void; usage?: SearchUsage; } // Warning: (ae-missing-release-tag) "ISearchStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ISearchStart { +export interface ISearchStart { // Warning: (ae-forgotten-export) The symbol "AggsStart" needs to be exported by the entry point index.d.ts // // (undocumented) aggs: AggsStart; - getSearchStrategy: (name: string) => ISearchStrategy; + getSearchStrategy: (name: string) => ISearchStrategy; // Warning: (ae-forgotten-export) The symbol "RequestHandlerContext" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -711,11 +711,11 @@ export interface ISearchStart { // Warning: (ae-missing-release-tag) "ISearchStrategy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export interface ISearchStrategy { +export interface ISearchStrategy { // (undocumented) cancel?: (context: RequestHandlerContext, id: string) => Promise; // (undocumented) - search: (context: RequestHandlerContext, request: IEsSearchRequest, options?: ISearchOptions) => Promise; + search: (context: RequestHandlerContext, request: SearchStrategyRequest, options?: ISearchOptions) => Promise; } // @public (undocumented) @@ -862,7 +862,7 @@ export class Plugin implements Plugin_2>; fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index ae6dddf33536f0..47099e32fcc726 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -96,7 +96,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { return timer(pollInterval).pipe( // Send future requests using just the ID from the response mergeMap(() => { - return this.runSearch({ id }, combinedSignal, options?.strategy); + return this.runSearch({ ...request, id }, combinedSignal, options?.strategy); }) ); }), diff --git a/x-pack/plugins/security_solution/common/ecs/auditd/index.ts b/x-pack/plugins/security_solution/common/ecs/auditd/index.ts new file mode 100644 index 00000000000000..4b170eec98c027 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/auditd/index.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. + */ + +export interface AuditdEcs { + result?: string[]; + + session?: string[]; + + data?: AuditdDataEcs; + + summary?: SummaryEcs; + + sequence?: string[]; +} + +export interface AuditdDataEcs { + acct?: string[]; + + terminal?: string[]; + + op?: string[]; +} + +export interface SummaryEcs { + actor?: PrimarySecondaryEcs; + + object?: PrimarySecondaryEcs; + + how?: string[]; + + message_type?: string[]; + + sequence?: string[]; +} + +export interface PrimarySecondaryEcs { + primary?: string[]; + + secondary?: string[]; + + type?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/cloud/index.ts b/x-pack/plugins/security_solution/common/ecs/cloud/index.ts new file mode 100644 index 00000000000000..812b30bcc13f14 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/cloud/index.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. + */ + +export interface CloudEcs { + instance?: CloudInstanceEcs; + machine?: CloudMachineEcs; + provider?: string[]; + region?: string[]; +} + +export interface CloudMachineEcs { + type?: string[]; +} + +export interface CloudInstanceEcs { + id?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/destination/index.ts b/x-pack/plugins/security_solution/common/ecs/destination/index.ts new file mode 100644 index 00000000000000..9b4038205350ef --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/destination/index.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 { GeoEcs } from '../geo'; + +export interface DestinationEcs { + bytes?: number[]; + + ip?: string[]; + + port?: number[]; + + domain?: string[]; + + geo?: GeoEcs; + + packets?: number[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/dns/index.ts b/x-pack/plugins/security_solution/common/ecs/dns/index.ts new file mode 100644 index 00000000000000..6844cd517acebb --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/dns/index.ts @@ -0,0 +1,19 @@ +/* + * 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 interface DnsEcs { + question?: DnsQuestionEcs; + + resolved_ip?: string[]; + + response_code?: string[]; +} + +export interface DnsQuestionEcs { + name?: string[]; + + type?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/endgame/index.ts b/x-pack/plugins/security_solution/common/ecs/endgame/index.ts new file mode 100644 index 00000000000000..f435db4f478104 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/endgame/index.ts @@ -0,0 +1,33 @@ +/* + * 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 interface EndgameEcs { + exit_code?: number; + + file_name?: string; + + file_path?: string; + + logon_type?: number; + + parent_process_name?: string; + + pid?: number; + + process_name?: string; + + subject_domain_name?: string; + + subject_logon_id?: string; + + subject_user_name?: string; + + target_domain_name?: string; + + target_logon_id?: string; + + target_user_name?: string; +} diff --git a/x-pack/plugins/security_solution/common/ecs/event/index.ts b/x-pack/plugins/security_solution/common/ecs/event/index.ts new file mode 100644 index 00000000000000..cb18a8c5881e86 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/event/index.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. + */ + +export interface EventEcs { + action?: string[]; + + category?: string[]; + + code?: string[]; + + created?: string[]; + + dataset?: string[]; + + duration?: number[]; + + end?: string[]; + + hash?: string[]; + + id?: string[]; + + kind?: string[]; + + module?: string[]; + + original?: string[]; + + outcome?: string[]; + + risk_score?: number[]; + + risk_score_norm?: number[]; + + severity?: number[]; + + start?: string[]; + + timezone?: string[]; + + type?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/file/index.ts b/x-pack/plugins/security_solution/common/ecs/file/index.ts new file mode 100644 index 00000000000000..808e9eaa3c854e --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/file/index.ts @@ -0,0 +1,37 @@ +/* + * 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 interface FileEcs { + name?: string[]; + + path?: string[]; + + target_path?: string[]; + + extension?: string[]; + + type?: string[]; + + device?: string[]; + + inode?: string[]; + + uid?: string[]; + + owner?: string[]; + + gid?: string[]; + + group?: string[]; + + mode?: string[]; + + size?: number[]; + + mtime?: string[]; + + ctime?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/geo/index.ts b/x-pack/plugins/security_solution/common/ecs/geo/index.ts new file mode 100644 index 00000000000000..409b5bbdc17a40 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/geo/index.ts @@ -0,0 +1,27 @@ +/* + * 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 interface GeoEcs { + city_name?: string[]; + + continent_name?: string[]; + + country_iso_code?: string[]; + + country_name?: string[]; + + location?: Location; + + region_iso_code?: string[]; + + region_name?: string[]; +} + +export interface Location { + lon?: number[]; + + lat?: number[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/host/index.ts b/x-pack/plugins/security_solution/common/ecs/host/index.ts new file mode 100644 index 00000000000000..056291a70b62f4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/host/index.ts @@ -0,0 +1,35 @@ +/* + * 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 interface HostEcs { + architecture?: string[]; + + id?: string[]; + + ip?: string[]; + + mac?: string[]; + + name?: string[]; + + os?: OsEcs; + + type?: string[]; +} + +export interface OsEcs { + platform?: string[]; + + name?: string[]; + + full?: string[]; + + family?: string[]; + + version?: string[]; + + kernel?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/http/index.ts b/x-pack/plugins/security_solution/common/ecs/http/index.ts new file mode 100644 index 00000000000000..ff56d15e70bb38 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/http/index.ts @@ -0,0 +1,37 @@ +/* + * 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 interface HttpEcs { + version?: string[]; + + request?: HttpRequestData; + + response?: HttpResponseData; +} + +export interface HttpRequestData { + method?: string[]; + + body?: HttpBodyData; + + referrer?: string[]; + + bytes?: number[]; +} + +export interface HttpBodyData { + content?: string[]; + + bytes?: number[]; +} + +export interface HttpResponseData { + status_code?: number[]; + + body?: HttpBodyData; + + bytes?: number[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts new file mode 100644 index 00000000000000..ff21ebc5ef9739 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/index.ts @@ -0,0 +1,78 @@ +/* + * 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 { AuditdEcs } from './auditd'; +import { DestinationEcs } from './destination'; +import { DnsEcs } from './dns'; +import { EndgameEcs } from './endgame'; +import { EventEcs } from './event'; +import { GeoEcs } from './geo'; +import { HostEcs } from './host'; +import { NetworkEcs } from './network'; +import { RuleEcs } from './rule'; +import { SignalEcs } from './signal'; +import { SourceEcs } from './source'; +import { SuricataEcs } from './suricata'; +import { TlsEcs } from './tls'; +import { ZeekEcs } from './zeek'; +import { HttpEcs } from './http'; +import { UrlEcs } from './url'; +import { UserEcs } from './user'; +import { WinlogEcs } from './winlog'; +import { ProcessEcs } from './process'; +import { SystemEcs } from './system'; + +export interface Ecs { + _id: string; + + _index?: string; + + auditd?: AuditdEcs; + + destination?: DestinationEcs; + + dns?: DnsEcs; + + endgame?: EndgameEcs; + + event?: EventEcs; + + geo?: GeoEcs; + + host?: HostEcs; + + network?: NetworkEcs; + + rule?: RuleEcs; + + signal?: SignalEcs; + + source?: SourceEcs; + + suricata?: SuricataEcs; + + tls?: TlsEcs; + + zeek?: ZeekEcs; + + http?: HttpEcs; + + url?: UrlEcs; + + timestamp?: string; + + message?: string[]; + + user?: UserEcs; + + winlog?: WinlogEcs; + + process?: ProcessEcs; + + file?: File; + + system?: SystemEcs; +} diff --git a/x-pack/plugins/security_solution/common/ecs/network/index.ts b/x-pack/plugins/security_solution/common/ecs/network/index.ts new file mode 100644 index 00000000000000..c2fc3cb4b9f487 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/network/index.ts @@ -0,0 +1,19 @@ +/* + * 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 interface NetworkEcs { + bytes?: number[]; + + community_id?: string[]; + + direction?: string[]; + + packets?: number[]; + + protocol?: string[]; + + transport?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/process/index.ts b/x-pack/plugins/security_solution/common/ecs/process/index.ts new file mode 100644 index 00000000000000..0584d95c8059d8 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/process/index.ts @@ -0,0 +1,39 @@ +/* + * 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 interface ProcessEcs { + hash?: ProcessHashData; + + pid?: number[]; + + name?: string[]; + + ppid?: number[]; + + args?: string[]; + + executable?: string[]; + + title?: string[]; + + thread?: Thread; + + working_directory?: string[]; +} + +export interface ProcessHashData { + md5?: string[]; + + sha1?: string[]; + + sha256?: string[]; +} + +export interface Thread { + id?: number[]; + + start?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/rule/index.ts b/x-pack/plugins/security_solution/common/ecs/rule/index.ts new file mode 100644 index 00000000000000..c1ef1ee17ca0c8 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/rule/index.ts @@ -0,0 +1,69 @@ +/* + * 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 interface RuleEcs { + id?: string[]; + + rule_id?: string[]; + + false_positives: string[]; + + saved_id?: string[]; + + timeline_id?: string[]; + + timeline_title?: string[]; + + max_signals?: number[]; + + risk_score?: string[]; + + output_index?: string[]; + + description?: string[]; + + from?: string[]; + + immutable?: boolean[]; + + index?: string[]; + + interval?: string[]; + + language?: string[]; + + query?: string[]; + + references?: string[]; + + severity?: string[]; + + tags?: string[]; + + threat?: unknown; + + type?: string[]; + + size?: string[]; + + to?: string[]; + + enabled?: boolean[]; + + filters?: unknown; + + created_at?: string[]; + + updated_at?: string[]; + + created_by?: string[]; + + updated_by?: string[]; + + version?: string[]; + + note?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/signal/index.ts b/x-pack/plugins/security_solution/common/ecs/signal/index.ts new file mode 100644 index 00000000000000..66e35e26af341f --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/signal/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { RuleEcs } from '../rule'; + +export interface SignalEcs { + rule?: RuleEcs; + + original_time?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/source/index.ts b/x-pack/plugins/security_solution/common/ecs/source/index.ts new file mode 100644 index 00000000000000..9e6b6563cec680 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/source/index.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 { GeoEcs } from '../geo'; + +export interface SourceEcs { + bytes?: number[]; + + ip?: string[]; + + port?: number[]; + + domain?: string[]; + + geo?: GeoEcs; + + packets?: number[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/suricata/index.ts b/x-pack/plugins/security_solution/common/ecs/suricata/index.ts new file mode 100644 index 00000000000000..53c193edddaf24 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/suricata/index.ts @@ -0,0 +1,23 @@ +/* + * 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 interface SuricataEcs { + eve?: SuricataEveData; +} + +export interface SuricataEveData { + alert?: SuricataAlertData; + + flow_id?: number[]; + + proto?: string[]; +} + +export interface SuricataAlertData { + signature?: string[]; + + signature_id?: number[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/system/index.ts b/x-pack/plugins/security_solution/common/ecs/system/index.ts new file mode 100644 index 00000000000000..803d8197080ffa --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/system/index.ts @@ -0,0 +1,39 @@ +/* + * 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 interface SystemEcs { + audit?: AuditEcs; + + auth?: AuthEcs; +} + +export interface AuditEcs { + package?: PackageEcs; +} + +export interface PackageEcs { + arch?: string[]; + + entity_id?: string[]; + + name?: string[]; + + size?: number[]; + + summary?: string[]; + + version?: string[]; +} + +export interface AuthEcs { + ssh?: SshEcs; +} + +export interface SshEcs { + method?: string[]; + + signature?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/tls/index.ts b/x-pack/plugins/security_solution/common/ecs/tls/index.ts new file mode 100644 index 00000000000000..86a2a1a9459a2a --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/tls/index.ts @@ -0,0 +1,33 @@ +/* + * 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 interface TlsEcs { + client_certificate?: TlsClientCertificateData; + + fingerprints?: TlsFingerprintsData; + + server_certificate?: TlsServerCertificateData; +} + +export interface TlsClientCertificateData { + fingerprint?: FingerprintData; +} + +export interface FingerprintData { + sha1?: string[]; +} + +export interface TlsFingerprintsData { + ja3?: TlsJa3Data; +} + +export interface TlsJa3Data { + hash?: string[]; +} + +export interface TlsServerCertificateData { + fingerprint?: FingerprintData; +} diff --git a/x-pack/plugins/security_solution/common/ecs/url/index.ts b/x-pack/plugins/security_solution/common/ecs/url/index.ts new file mode 100644 index 00000000000000..66033ea9f07255 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/url/index.ts @@ -0,0 +1,15 @@ +/* + * 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 interface UrlEcs { + domain?: string[]; + + original?: string[]; + + username?: string[]; + + password?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/user/index.ts b/x-pack/plugins/security_solution/common/ecs/user/index.ts new file mode 100644 index 00000000000000..d72362d5f5cf96 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/user/index.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. + */ + +export interface UserEcs { + domain?: string[]; + + id?: string[]; + + name?: string[]; + + full_name?: string[]; + + email?: string[]; + + hash?: string[]; + + group?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/winlog/index.ts b/x-pack/plugins/security_solution/common/ecs/winlog/index.ts new file mode 100644 index 00000000000000..a449fb9130e6f2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/winlog/index.ts @@ -0,0 +1,9 @@ +/* + * 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 interface WinlogEcs { + event_id?: number[]; +} diff --git a/x-pack/plugins/security_solution/common/ecs/zeek/index.ts b/x-pack/plugins/security_solution/common/ecs/zeek/index.ts new file mode 100644 index 00000000000000..289390a87db121 --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/zeek/index.ts @@ -0,0 +1,133 @@ +/* + * 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 interface ZeekEcs { + session_id?: string[]; + + connection?: ZeekConnectionData; + + notice?: ZeekNoticeData; + + dns?: ZeekDnsData; + + http?: ZeekHttpData; + + files?: ZeekFileData; + + ssl?: ZeekSslData; +} + +export interface ZeekConnectionData { + local_resp?: boolean[]; + + local_orig?: boolean[]; + + missed_bytes?: number[]; + + state?: string[]; + + history?: string[]; +} + +export interface ZeekNoticeData { + suppress_for?: number[]; + + msg?: string[]; + + note?: string[]; + + sub?: string[]; + + dst?: string[]; + + dropped?: boolean[]; + + peer_descr?: string[]; +} + +export interface ZeekDnsData { + AA?: boolean[]; + + qclass_name?: string[]; + + RD?: boolean[]; + + qtype_name?: string[]; + + rejected?: boolean[]; + + qtype?: string[]; + + query?: string[]; + + trans_id?: number[]; + + qclass?: string[]; + + RA?: boolean[]; + + TC?: boolean[]; +} + +export interface ZeekHttpData { + resp_mime_types?: string[]; + + trans_depth?: string[]; + + status_msg?: string[]; + + resp_fuids?: string[]; + + tags?: string[]; +} + +export interface ZeekFileData { + session_ids?: string[]; + + timedout?: boolean[]; + + local_orig?: boolean[]; + + tx_host?: string[]; + + source?: string[]; + + is_orig?: boolean[]; + + overflow_bytes?: number[]; + + sha1?: string[]; + + duration?: number[]; + + depth?: number[]; + + analyzers?: string[]; + + mime_type?: string[]; + + rx_host?: string[]; + + total_bytes?: number[]; + + fuid?: string[]; + + seen_bytes?: number[]; + + missing_bytes?: number[]; + + md5?: string[]; +} + +export interface ZeekSslData { + cipher?: string[]; + + established?: boolean[]; + + resumed?: boolean[]; + + version?: string[]; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts new file mode 100644 index 00000000000000..3a0942d2decb82 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -0,0 +1,84 @@ +/* + * 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 { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; +import { CloudEcs } from '../../../ecs/cloud'; +import { HostEcs } from '../../../ecs/host'; + +import { + CursorType, + Inspect, + Maybe, + PageInfoPaginated, + RequestOptionsPaginated, + SortField, + TimerangeInput, +} from '..'; + +export enum HostsQueries { + hosts = 'hosts', + hostOverview = 'hostOverview', +} + +export enum HostPolicyResponseActionStatus { + success = 'success', + failure = 'failure', + warning = 'warning', +} + +export interface EndpointFields { + endpointPolicy?: Maybe; + + sensorVersion?: Maybe; + + policyStatus?: Maybe; +} + +export interface HostItem { + _id?: Maybe; + + cloud?: Maybe; + + endpoint?: Maybe; + + host?: Maybe; + + lastSeen?: Maybe; +} + +export interface HostsEdges { + node: HostItem; + + cursor: CursorType; +} + +export interface HostsStrategyResponse extends IEsSearchResponse { + edges: HostsEdges[]; + + totalCount: number; + + pageInfo: PageInfoPaginated; + + inspect?: Maybe; +} + +export interface HostOverviewStrategyResponse extends IEsSearchResponse, HostItem { + inspect?: Maybe; +} + +export interface HostsRequestOptions extends RequestOptionsPaginated { + sort: SortField; + defaultIndex: string[]; +} + +export interface HostLastFirstSeenRequestOptions extends Partial { + hostName: string; +} + +export interface HostOverviewRequestOptions extends HostLastFirstSeenRequestOptions { + fields: string[]; + timerange: TimerangeInput; +} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts new file mode 100644 index 00000000000000..edb5dda2ca6da7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -0,0 +1,109 @@ +/* + * 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 { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; +import { ESQuery } from '../../typed_json'; +import { + HostOverviewStrategyResponse, + HostOverviewRequestOptions, + HostsQueries, + HostsRequestOptions, + HostsStrategyResponse, +} from './hosts'; +export * from './hosts'; +export type Maybe = T | null; + +export type FactoryQueryTypes = HostsQueries; + +export interface Inspect { + dsl: string[]; + response: string[]; +} + +export interface PageInfoPaginated { + activePage: number; + fakeTotalCount: number; + showMorePagesIndicator: boolean; +} + +export interface CursorType { + value?: Maybe; + tiebreaker?: Maybe; +} + +export enum Direction { + asc = 'asc', + desc = 'desc', +} + +export interface SortField { + field: string; + direction: Direction; +} + +export interface TimerangeInput { + /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */ + interval: string; + /** The end of the timerange */ + to: string; + /** The beginning of the timerange */ + from: string; +} + +export interface PaginationInput { + /** The limit parameter allows you to configure the maximum amount of items to be returned */ + limit: number; + /** The cursor parameter defines the next result you want to fetch */ + cursor?: Maybe; + /** The tiebreaker parameter allow to be more precise to fetch the next item */ + tiebreaker?: Maybe; +} + +export interface PaginationInputPaginated { + /** The activePage parameter defines the page of results you want to fetch */ + activePage: number; + /** The cursorStart parameter defines the start of the results to be displayed */ + cursorStart: number; + /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */ + fakePossibleCount: number; + /** The querySize parameter is the number of items to be returned */ + querySize: number; +} + +export interface DocValueFields { + field: string; + format: string; +} + +export interface RequestBasicOptions extends IEsSearchRequest { + timerange: TimerangeInput; + filterQuery: ESQuery | string | undefined; + defaultIndex: string[]; + docValueFields?: DocValueFields[]; + factoryQueryType?: FactoryQueryTypes; +} + +export interface RequestOptions extends RequestBasicOptions { + pagination: PaginationInput; + sortField?: SortField; +} + +export interface RequestOptionsPaginated extends RequestBasicOptions { + pagination: PaginationInputPaginated; + sortField?: SortField; +} + +export type StrategyResponseType = T extends HostsQueries.hosts + ? HostsStrategyResponse + : T extends HostsQueries.hostOverview + ? HostOverviewStrategyResponse + : never; + +export type StrategyRequestType = T extends HostsQueries.hosts + ? HostsRequestOptions + : T extends HostsQueries.hostOverview + ? HostOverviewRequestOptions + : never; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 8af24e6e6abc1f..346de9f87313f3 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -4,185 +4,211 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, getOr } from 'lodash/fp'; -import memoizeOne from 'memoize-one'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; +import deepEqual from 'fast-deep-equal'; +import { noop } from 'lodash/fp'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; -import { - Direction, - GetHostsTableQuery, - HostsEdges, - HostsFields, - PageInfoPaginated, -} from '../../../graphql/types'; -import { inputsModel, State, inputsSelectors } from '../../../common/store'; -import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers'; -import { - QueryTemplatePaginated, - QueryTemplatePaginatedProps, -} from '../../../common/containers/query_template_paginated'; -import { withKibana, WithKibanaProps } from '../../../common/lib/kibana'; +import { HostsEdges, PageInfoPaginated } from '../../../graphql/types'; +import { inputsModel, State } from '../../../common/store'; +import { createFilter } from '../../../common/containers/helpers'; +import { useKibana } from '../../../common/lib/kibana'; import { hostsModel, hostsSelectors } from '../../store'; -import { HostsTableQuery } from './hosts_table.gql_query'; import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers'; +import { + DocValueFields, + HostsQueries, + HostsRequestOptions, + HostsStrategyResponse, +} from '../../../../common/search_strategy/security_solution'; +import { ESTermQuery } from '../../../../common/typed_json'; + +import * as i18n from './translations'; +import { AbortError } from '../../../../../../../src/plugins/data/common'; const ID = 'hostsQuery'; +type LoadPage = (newActivePage: number) => void; export interface HostsArgs { endDate: string; hosts: HostsEdges[]; id: string; inspect: inputsModel.InspectQuery; isInspected: boolean; - loading: boolean; - loadPage: (newActivePage: number) => void; + loadPage: LoadPage; pageInfo: PageInfoPaginated; refetch: inputsModel.Refetch; startDate: string; totalCount: number; } -export interface OwnProps extends QueryTemplatePaginatedProps { - children: (args: HostsArgs) => React.ReactNode; - type: hostsModel.HostsType; - startDate: string; +interface UseAllHost { + docValueFields?: DocValueFields[]; + filterQuery?: ESTermQuery | string; endDate: string; + startDate: string; + type: hostsModel.HostsType; } -export interface HostsComponentReduxProps { - activePage: number; - isInspected: boolean; - limit: number; - sortField: HostsFields; - direction: Direction; -} - -type HostsProps = OwnProps & HostsComponentReduxProps & WithKibanaProps; +export const useAllHost = ({ + docValueFields, + filterQuery, + endDate, + startDate, + type, +}: UseAllHost): [boolean, HostsArgs] => { + const getHostsSelector = hostsSelectors.hostsSelector(); + const { activePage, direction, limit, sortField } = useSelector((state: State) => + getHostsSelector(state, type) + ); + const { data, notifications, uiSettings } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY); + const [loading, setLoading] = useState(false); + const [hostsRequest, setHostRequest] = useState({ + defaultIndex, + docValueFields: docValueFields ?? [], + factoryQueryType: HostsQueries.hosts, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + sort: { + direction, + field: sortField, + }, + // inspect: isInspected, + }); -class HostsComponentQuery extends QueryTemplatePaginated< - HostsProps, - GetHostsTableQuery.Query, - GetHostsTableQuery.Variables -> { - private memoizedHosts: ( - variables: string, - data: GetHostsTableQuery.Source | undefined - ) => HostsEdges[]; + const wrappedLoadMore = useCallback( + (newActivePage: number) => { + setHostRequest((prevRequest) => { + return { + ...prevRequest, + pagination: generateTablePaginationOptions(newActivePage, limit), + }; + }); + }, + [limit] + ); - constructor(props: HostsProps) { - super(props); - this.memoizedHosts = memoizeOne(this.getHosts); - } + const [hostsResponse, setHostsResponse] = useState({ + endDate, + hosts: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + loadPage: wrappedLoadMore, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + refetch: refetch.current, + startDate, + totalCount: -1, + }); - public render() { - const { - activePage, - docValueFields, - id = ID, - isInspected, - children, - direction, - filterQuery, - endDate, - kibana, - limit, - startDate, - skip, - sourceId, - sortField, - } = this.props; - const defaultIndex = kibana.services.uiSettings.get(DEFAULT_INDEX_KEY); + const hostsSearch = useCallback( + (request: HostsRequestOptions) => { + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); - const variables: GetHostsTableQuery.Variables = { - sourceId, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, - sort: { - direction, - field: sortField, - }, - pagination: generateTablePaginationOptions(activePage, limit), - filterQuery: createFilter(filterQuery), - defaultIndex, - docValueFields: docValueFields ?? [], - inspect: isInspected, - }; - return ( - - query={HostsTableQuery} - fetchPolicy={getDefaultFetchPolicy()} - notifyOnNetworkStatusChange - variables={variables} - skip={skip} - > - {({ data, loading, fetchMore, networkStatus, refetch }) => { - this.setFetchMore(fetchMore); - this.setFetchMoreOptions((newActivePage: number) => ({ - variables: { - pagination: generateTablePaginationOptions(newActivePage, limit), + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionSearchStrategy', + signal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (!response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + setHostsResponse((prevResponse) => ({ + ...prevResponse, + hosts: response.edges, + inspect: response.inspect ?? prevResponse.inspect, + pageInfo: response.pageInfo, + refetch: refetch.current, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (response.isPartial && !response.isRunning) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_ALL_HOST); + searchSubscription$.unsubscribe(); + } }, - updateQuery: (prev, { fetchMoreResult }) => { - if (!fetchMoreResult) { - return prev; + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_ALL_HOST, text: msg.message }); } - return { - ...fetchMoreResult, - source: { - ...fetchMoreResult.source, - Hosts: { - ...fetchMoreResult.source.Hosts, - edges: [...fetchMoreResult.source.Hosts.edges], - }, - }, - }; }, - })); - const isLoading = this.isItAValidLoading(loading, variables, networkStatus); - return children({ - endDate, - hosts: this.memoizedHosts(JSON.stringify(variables), get('source', data)), - id, - inspect: getOr(null, 'source.Hosts.inspect', data), - isInspected, - loading: isLoading, - loadPage: this.wrappedLoadMore, - pageInfo: getOr({}, 'source.Hosts.pageInfo', data), - refetch: this.memoizedRefetchQuery(variables, limit, refetch), - startDate, - totalCount: getOr(-1, 'source.Hosts.totalCount', data), }); - }} - - ); - } + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts] + ); - private getHosts = ( - variables: string, - source: GetHostsTableQuery.Source | undefined - ): HostsEdges[] => getOr([], 'Hosts.edges', source); -} + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...prevRequest, + defaultIndex, + docValueFields: docValueFields ?? [], + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + timerange: { + interval: '12h', + from: startDate, + to: endDate, + }, + sort: { + direction, + field: sortField, + }, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [ + activePage, + defaultIndex, + direction, + docValueFields, + endDate, + filterQuery, + limit, + startDate, + sortField, + ]); -const makeMapStateToProps = () => { - const getHostsSelector = hostsSelectors.hostsSelector(); - const getQuery = inputsSelectors.globalQueryByIdSelector(); - const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => { - const { isInspected } = getQuery(state, id); - return { - ...getHostsSelector(state, type), - isInspected, - }; - }; - return mapStateToProps; -}; + useEffect(() => { + hostsSearch(hostsRequest); + }, [hostsRequest, hostsSearch]); -export const HostsQuery = compose>( - connect(makeMapStateToProps), - withKibana -)(HostsComponentQuery); + return [loading, hostsResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/translations.ts b/x-pack/plugins/security_solution/public/hosts/containers/hosts/translations.ts new file mode 100644 index 00000000000000..ada713d135c227 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/translations.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 { i18n } from '@kbn/i18n'; + +export const ERROR_ALL_HOST = i18n.translate( + 'xpack.securitySolution.allHost.errorSearchDescription', + { + defaultMessage: `An error has occurred on all hosts search`, + } +); + +export const FAIL_ALL_HOST = i18n.translate( + 'xpack.securitySolution.allHost.failSearchDescription', + { + defaultMessage: `Failed to run search on all hosts`, + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx index 80cf62bc49f78a..5232dcfd88189d 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx @@ -6,7 +6,7 @@ import { getOr } from 'lodash/fp'; import React from 'react'; -import { HostsQuery } from '../../containers/hosts'; +import { useAllHost } from '../../containers/hosts'; import { HostsComponentsQueryProps } from './types'; import { HostsTable } from '../../components/hosts_table'; import { manageQuery } from '../../../common/components/page/manage_query'; @@ -23,35 +23,29 @@ export const HostsQueryTabBody = ({ setQuery, startDate, type, -}: HostsComponentsQueryProps) => ( - - {({ hosts, totalCount, loading, pageInfo, loadPage, id, inspect, isInspected, refetch }) => ( - - )} - -); +}: HostsComponentsQueryProps) => { + const [ + loading, + { hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch }, + ] = useAllHost({ docValueFields, endDate, filterQuery, startDate, type }); + return ( + + ); +}; HostsQueryTabBody.displayName = 'HostsQueryTabBody'; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 85764eaaee32d6..25ca89ce9186e0 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -17,6 +17,8 @@ import { PluginInitializerContext, SavedObjectsClient, } from '../../../../src/core/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { DataPluginSetup, DataPluginStart } from '../../../../src/plugins/data/server/plugin'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { PluginSetupContract as AlertingSetup } from '../../alerts/server'; import { SecurityPluginSetup as SecuritySetup } from '../../security/server'; @@ -58,9 +60,11 @@ import { EndpointAppContext } from './endpoint/types'; import { registerDownloadExceptionListRoute } from './endpoint/routes/artifacts'; import { initUsageCollectors } from './usage'; import { AppRequestContext } from './types'; +import { securitySolutionSearchStrategyProvider } from './search_strategy/security_solution'; export interface SetupPlugins { alerts: AlertingSetup; + data: DataPluginSetup; encryptedSavedObjects?: EncryptedSavedObjectsSetup; features: FeaturesSetup; licensing: LicensingPluginSetup; @@ -73,6 +77,7 @@ export interface SetupPlugins { } export interface StartPlugins { + data: DataPluginStart; ingestManager?: IngestManagerStartContract; taskManager?: TaskManagerStartContract; } @@ -263,6 +268,14 @@ export class Plugin implements IPlugin { + const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider(depsStart.data); + plugins.data.search.registerSearchStrategy( + 'securitySolutionSearchStrategy', + securitySolutionSearchStrategy + ); + }); + return {}; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts new file mode 100644 index 00000000000000..5c5dec92a51001 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.detail_host.dsl.ts @@ -0,0 +1,49 @@ +/* + * 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 { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { HostOverviewRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import { cloudFieldsMap, hostFieldsMap } from '../../../../../lib/ecs_fields'; +import { buildFieldsTermAggregation } from '../../../../../lib/hosts/helpers'; +import { reduceFields } from '../../../../../utils/build_query/reduce_fields'; + +export const buildHostOverviewQuery = ({ + fields, + hostName, + defaultIndex, + timerange: { from, to }, +}: HostOverviewRequestOptions): ISearchRequestParams => { + const esFields = reduceFields(fields, { ...hostFieldsMap, ...cloudFieldsMap }); + + const filter = [ + { term: { 'host.name': hostName } }, + { + range: { + '@timestamp': { + format: 'strict_date_optional_time', + gte: from, + lte: to, + }, + }, + }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggregations: { + ...buildFieldsTermAggregation(esFields.filter((field) => !['@timestamp'].includes(field))), + }, + query: { bool: { filter } }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts new file mode 100644 index 00000000000000..3d72f98f35355c --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.hosts.dsl.ts @@ -0,0 +1,89 @@ +/* + * 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 { isEmpty, isString } from 'lodash/fp'; +import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { + Direction, + HostsRequestOptions, + SortField, +} from '../../../../../../common/search_strategy/security_solution'; +import { assertUnreachable, createQueryFilterClauses } from '../../../../../utils/build_query'; + +export const buildHostsQuery = ({ + defaultIndex, + docValueFields, + filterQuery, + pagination: { querySize }, + sort, + timerange: { from, to }, +}: HostsRequestOptions): ISearchRequestParams => { + const filter = [ + ...createQueryFilterClauses(isString(filterQuery) ? JSON.parse(filterQuery) : filterQuery), + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ]; + + const agg = { host_count: { cardinality: { field: 'host.name' } } }; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + ...(isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), + aggregations: { + ...agg, + host_data: { + terms: { size: querySize, field: 'host.name', order: getQueryOrder(sort) }, + aggs: { + lastSeen: { max: { field: '@timestamp' } }, + os: { + top_hits: { + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + _source: { + includes: ['host.os.*'], + }, + }, + }, + }, + }, + }, + query: { bool: { filter } }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; + +type QueryOrder = { lastSeen: Direction } | { _key: Direction }; + +const getQueryOrder = (sort: SortField): QueryOrder => { + switch (sort.field) { + case 'lastSeen': + return { lastSeen: sort.direction }; + case 'hostName': + return { _key: sort.direction }; + default: + return assertUnreachable(sort.field); + } +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts new file mode 100644 index 00000000000000..b57bbd2960e4fa --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/dsl/query.last_first_seen_host.dsl.ts @@ -0,0 +1,35 @@ +/* + * 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 { isEmpty } from 'lodash/fp'; +import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { HostLastFirstSeenRequestOptions } from '../../../../../../common/search_strategy/security_solution'; + +export const buildLastFirstSeenHostQuery = ({ + hostName, + defaultIndex, + docValueFields, +}: HostLastFirstSeenRequestOptions): ISearchRequestParams => { + const filter = [{ term: { 'host.name': hostName } }]; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + ...(isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), + aggregations: { + firstSeen: { min: { field: '@timestamp' } }, + lastSeen: { max: { field: '@timestamp' } }, + }, + query: { bool: { filter } }, + size: 0, + track_total_hits: false, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts new file mode 100644 index 00000000000000..a7ec822839d21a --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/helpers.ts @@ -0,0 +1,115 @@ +/* + * 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 { set } from '@elastic/safer-lodash-set/fp'; +import { get, has, head } from 'lodash/fp'; +import { + HostsEdges, + HostItem, +} from '../../../../../common/search_strategy/security_solution/hosts'; +import { hostFieldsMap } from '../../../../lib/ecs_fields'; + +import { HostAggEsItem, HostBuckets, HostValue } from '../../../../lib/hosts/types'; + +const hostsFields = ['_id', 'lastSeen', 'host.id', 'host.name', 'host.os.name', 'host.os.version']; + +export const formatHostEdgesData = (bucket: HostAggEsItem): HostsEdges => + hostsFields.reduce( + (flattenedFields, fieldName) => { + const hostId = get('key', bucket); + flattenedFields.node._id = hostId || null; + flattenedFields.cursor.value = hostId || ''; + const fieldValue = getHostFieldValue(fieldName, bucket); + if (fieldValue != null) { + return set( + `node.${fieldName}`, + Array.isArray(fieldValue) ? fieldValue : [fieldValue], + flattenedFields + ); + } + return flattenedFields; + }, + { + node: {}, + cursor: { + value: '', + tiebreaker: null, + }, + } as HostsEdges + ); + +const hostFields = [ + '_id', + 'host.architecture', + 'host.id', + 'host.ip', + 'host.id', + 'host.mac', + 'host.name', + 'host.os.family', + 'host.os.name', + 'host.os.platform', + 'host.os.version', + 'host.type', + 'cloud.instance.id', + 'cloud.machine.type', + 'cloud.provider', + 'cloud.region', + 'endpoint.endpointPolicy', + 'endpoint.policyStatus', + 'endpoint.sensorVersion', +]; + +export const formatHostItem = (bucket: HostAggEsItem): HostItem => + hostFields.reduce((flattenedFields, fieldName) => { + const fieldValue = getHostFieldValue(fieldName, bucket); + if (fieldValue != null) { + return set(fieldName, fieldValue, flattenedFields); + } + return flattenedFields; + }, {}); + +const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | string[] | null => { + const aggField = hostFieldsMap[fieldName] + ? hostFieldsMap[fieldName].replace(/\./g, '_') + : fieldName.replace(/\./g, '_'); + if ( + [ + 'host.ip', + 'host.mac', + 'cloud.instance.id', + 'cloud.machine.type', + 'cloud.provider', + 'cloud.region', + ].includes(fieldName) && + has(aggField, bucket) + ) { + const data: HostBuckets = get(aggField, bucket); + return data.buckets.map((obj) => obj.key); + } else if (has(`${aggField}.buckets`, bucket)) { + return getFirstItem(get(`${aggField}`, bucket)); + } else if (has(aggField, bucket)) { + const valueObj: HostValue = get(aggField, bucket); + return valueObj.value_as_string; + } else if (['host.name', 'host.os.name', 'host.os.version'].includes(fieldName)) { + switch (fieldName) { + case 'host.name': + return get('key', bucket) || null; + case 'host.os.name': + return get('os.hits.hits[0]._source.host.os.name', bucket) || null; + case 'host.os.version': + return get('os.hits.hits[0]._source.host.os.version', bucket) || null; + } + } + return null; +}; + +const getFirstItem = (data: HostBuckets): string | null => { + const firstItem = head(data.buckets); + if (firstItem == null) { + return null; + } + return firstItem.key; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts new file mode 100644 index 00000000000000..443e524d71ca3b --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -0,0 +1,92 @@ +/* + * 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 { get, getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; + +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants'; +import { FactoryQueryTypes } from '../../../../../common/search_strategy/security_solution'; +import { + HostsStrategyResponse, + HostOverviewStrategyResponse, + HostsQueries, + HostsRequestOptions, + HostOverviewRequestOptions, +} from '../../../../../common/search_strategy/security_solution/hosts'; + +// TO DO need to move all this types in common +import { HostAggEsData, HostAggEsItem } from '../../../../lib/hosts/types'; + +import { inspectStringifyObject } from '../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../types'; +import { buildHostOverviewQuery } from './dsl/query.detail_host.dsl'; +import { buildHostsQuery } from './dsl/query.hosts.dsl'; +import { formatHostEdgesData, formatHostItem } from './helpers'; + +export const allHosts: SecuritySolutionFactory = { + buildDsl: (options: HostsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildHostsQuery(options); + }, + parse: async ( + options: HostsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; + const totalCount = getOr(0, 'aggregations.host_count.value', response.rawResponse); + const buckets: HostAggEsItem[] = getOr( + [], + 'aggregations.host_data.buckets', + response.rawResponse + ); + const hostsEdges = buckets.map((bucket) => formatHostEdgesData(bucket)); + const fakeTotalCount = fakePossibleCount <= totalCount ? fakePossibleCount : totalCount; + const edges = hostsEdges.splice(cursorStart, querySize - cursorStart); + const inspect = { + dsl: [inspectStringifyObject(buildHostsQuery(options))], + response: [inspectStringifyObject(response)], + }; + const showMorePagesIndicator = totalCount > fakeTotalCount; + + return { + ...response, + inspect, + edges, + totalCount, + pageInfo: { + activePage: activePage ? activePage : 0, + fakeTotalCount, + showMorePagesIndicator, + }, + }; + }, +}; + +export const overviewHost: SecuritySolutionFactory = { + buildDsl: (options: HostOverviewRequestOptions) => { + return buildHostOverviewQuery(options); + }, + parse: async ( + options: HostOverviewRequestOptions, + response: IEsSearchResponse + ): Promise => { + const aggregations: HostAggEsItem = get('aggregations', response.rawResponse) || {}; + const inspect = { + dsl: [inspectStringifyObject(buildHostOverviewQuery(options))], + response: [inspectStringifyObject(response)], + }; + const formattedHostItem = formatHostItem(aggregations); + return { ...response, inspect, _id: options.hostName, ...formattedHostItem }; + }, +}; + +export const hostsFactory: Record> = { + [HostsQueries.hosts]: allHosts, + [HostsQueries.hostOverview]: overviewHost, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts new file mode 100644 index 00000000000000..53433dfc208cbc --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.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 { FactoryQueryTypes } from '../../../../common/search_strategy/security_solution'; + +import { hostsFactory } from './hosts'; +import { SecuritySolutionFactory } from './types'; + +export const securitySolutionFactory: Record< + FactoryQueryTypes, + SecuritySolutionFactory +> = { + ...hostsFactory, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts new file mode 100644 index 00000000000000..cb9e3a3d7628a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts @@ -0,0 +1,23 @@ +/* + * 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 { + IEsSearchResponse, + ISearchRequestParams, +} from '../../../../../../../src/plugins/data/common'; +import { + FactoryQueryTypes, + StrategyRequestType, + StrategyResponseType, +} from '../../../../common/search_strategy/security_solution'; + +export interface SecuritySolutionFactory { + buildDsl: (options: StrategyRequestType) => ISearchRequestParams; + parse: ( + options: StrategyRequestType, + response: IEsSearchResponse + ) => Promise>; +} diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts new file mode 100644 index 00000000000000..d94a32174cd7a2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.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. + */ + +import { ISearchStrategy, PluginStart } from '../../../../../../src/plugins/data/server'; +import { + FactoryQueryTypes, + StrategyResponseType, + StrategyRequestType, +} from '../../../common/search_strategy/security_solution'; +import { securitySolutionFactory } from './factory'; +import { SecuritySolutionFactory } from './factory/types'; + +export const securitySolutionSearchStrategyProvider = ( + data: PluginStart +): ISearchStrategy, StrategyResponseType> => { + const es = data.search.getSearchStrategy('es'); + + return { + search: async (context, request, options) => { + if (request.factoryQueryType == null) { + throw new Error('factoryQueryType is required'); + } + const queryFactory: SecuritySolutionFactory = + securitySolutionFactory[request.factoryQueryType]; + const dsl = queryFactory.buildDsl(request); + const esSearchRes = await es.search(context, { ...request, params: dsl }, options); + return queryFactory.parse(request, esSearchRes); + }, + cancel: async (context, id) => { + if (es.cancel) { + es.cancel(context, id); + } + }, + }; +}; diff --git a/x-pack/plugins/security_solution/server/utils/build_query/filters.ts b/x-pack/plugins/security_solution/server/utils/build_query/filters.ts index 95c9a975454f23..ac736e8cb51ee9 100644 --- a/x-pack/plugins/security_solution/server/utils/build_query/filters.ts +++ b/x-pack/plugins/security_solution/server/utils/build_query/filters.ts @@ -8,5 +8,5 @@ import { isEmpty } from 'lodash/fp'; import { ESQuery } from '../../../common/typed_json'; -export const createQueryFilterClauses = (filterQuery: ESQuery | undefined) => +export const createQueryFilterClauses = (filterQuery: ESQuery | string | undefined) => !isEmpty(filterQuery) ? [filterQuery] : []; diff --git a/x-pack/plugins/security_solution/server/utils/build_query/index.ts b/x-pack/plugins/security_solution/server/utils/build_query/index.ts index c97e78aad2b697..233ba70968fa18 100644 --- a/x-pack/plugins/security_solution/server/utils/build_query/index.ts +++ b/x-pack/plugins/security_solution/server/utils/build_query/index.ts @@ -10,7 +10,7 @@ export * from './merge_fields_with_hits'; export * from './calculate_timeseries_interval'; export const assertUnreachable = ( - x: never, + x: unknown, message: string = 'Unknown Field in switch statement' ): never => { throw new Error(`${message} ${x}`); From bdb73b55efdf071ccd56a814581d9c2722471baf Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Sun, 23 Aug 2020 15:34:40 +0300 Subject: [PATCH 08/16] [Search] Cleanup SearchRequest and SearchResponse types (#75471) * improve test stability * Replace SearchRequest = any with Record * Remove SearchResponse = any from data plugin * docs * logs * Revert "Replace SearchRequest = any with Record" This reverts commit 9914ab5a01642fe037a8f5af5fc111de828ce859. * code review * list control * null check * null null null * Jest fix --- .../kibana-plugin-plugins-data-public.md | 2 - ...lugin-plugins-data-public.searchrequest.md | 11 ----- ...ugin-plugins-data-public.searchresponse.md | 11 ----- .../data/common/search/es_search/types.ts | 4 +- src/plugins/data/public/index.ts | 1 - src/plugins/data/public/public.api.md | 46 ++++++++----------- .../search/fetch/handle_response.test.ts | 12 +++-- .../public/search/fetch/handle_response.tsx | 13 ++---- .../data/public/search/fetch/request_error.ts | 7 +-- src/plugins/data/public/search/fetch/types.ts | 10 +++- src/plugins/data/public/search/index.ts | 8 +--- .../data/public/search/legacy/call_client.ts | 5 +- .../public/search/legacy/es_client/types.ts | 5 +- .../public/search/legacy/fetch_soon.test.ts | 20 ++++++-- .../data/public/search/legacy/fetch_soon.ts | 12 +++-- .../data/public/search/legacy/types.ts | 7 +-- .../search/search_source/search_source.ts | 17 +++++-- src/plugins/data/public/ui/index.ts | 6 +-- .../__mocks__/shard_failure_response.ts | 7 +-- .../public/ui/shard_failure_modal/index.ts | 2 +- .../shard_failure_description.test.tsx | 3 +- .../shard_failure_modal.tsx | 15 ++++-- .../shard_failure_open_modal_button.tsx | 5 +- .../shard_failure_table.test.tsx | 2 +- .../shard_failure_types.ts | 10 ---- .../api/utils/fetch_hits_in_interval.ts | 5 +- .../public/control/list_control_factory.ts | 4 +- .../public/utils/logs_overview_fetchers.ts | 8 ++-- 28 files changed, 125 insertions(+), 133 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchrequest.md delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchresponse.md 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 dc83cfb930d7d8..a5453c7c51d5b1 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 @@ -156,8 +156,6 @@ | [RangeFilterMeta](./kibana-plugin-plugins-data-public.rangefiltermeta.md) | | | [SavedQueryTimeFilter](./kibana-plugin-plugins-data-public.savedquerytimefilter.md) | | | [SearchBarProps](./kibana-plugin-plugins-data-public.searchbarprops.md) | | -| [SearchRequest](./kibana-plugin-plugins-data-public.searchrequest.md) | | -| [SearchResponse](./kibana-plugin-plugins-data-public.searchresponse.md) | | | [StatefulSearchBarProps](./kibana-plugin-plugins-data-public.statefulsearchbarprops.md) | | | [TabbedAggRow](./kibana-plugin-plugins-data-public.tabbedaggrow.md) | \* | | [TimefilterContract](./kibana-plugin-plugins-data-public.timefiltercontract.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchrequest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchrequest.md deleted file mode 100644 index dbb465839e52cf..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchrequest.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchRequest](./kibana-plugin-plugins-data-public.searchrequest.md) - -## SearchRequest type - -Signature: - -```typescript -export declare type SearchRequest = any; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchresponse.md deleted file mode 100644 index 6da31c8bced7e8..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchresponse.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchResponse](./kibana-plugin-plugins-data-public.searchresponse.md) - -## SearchResponse type - -Signature: - -```typescript -export declare type SearchResponse = any; -``` diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index 654ed3899656d1..3184fbe3417055 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -22,9 +22,9 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types'; export const ES_SEARCH_STRATEGY = 'es'; -export type ISearchRequestParams = { +export type ISearchRequestParams> = { trackTotalHits?: boolean; -} & Search; +} & Search; export interface IEsSearchRequest extends IKibanaSearchRequest { params?: ISearchRequestParams; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ecf076aa517fbf..eb5703f1c63c1d 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -360,7 +360,6 @@ export { SearchInterceptor, SearchInterceptorDeps, SearchRequest, - SearchResponse, SearchSourceFields, SortDirection, // expression functions and types diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 35ee1789b2fdff..f8a108a5a4c58f 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -61,7 +61,7 @@ import { SavedObject } from 'src/core/server'; import { SavedObject as SavedObject_3 } from 'src/core/public'; import { SavedObjectsClientContract } from 'src/core/public'; import { Search } from '@elastic/elasticsearch/api/requestParams'; -import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; +import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; import { Subscription } from 'rxjs'; import { Toast } from 'kibana/public'; @@ -727,6 +727,7 @@ export function getEsPreference(uiSettings: IUiSettingsClient_2, sessionId?: str export const getKbnTypeNames: () => string[]; // Warning: (ae-forgotten-export) The symbol "ISearchRequestParams" needs to be exported by the entry point index.d.ts +// Warning: (ae-incompatible-release-tags) The symbol "getSearchParamsFromRequest" is marked as @public, but its signature references "SearchRequest" which is marked as @internal // // @public (undocumented) export function getSearchParamsFromRequest(searchRequest: SearchRequest, dependencies: { @@ -795,7 +796,7 @@ export interface IEsSearchResponse extends IKibanaSearchResponse { isPartial?: boolean; isRunning?: boolean; // (undocumented) - rawResponse: SearchResponse_2; + rawResponse: SearchResponse; } // Warning: (ae-missing-release-tag) "IFieldFormat" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1783,15 +1784,8 @@ export interface SearchInterceptorDeps { usageCollector?: SearchUsageCollector; } -// Warning: (ae-missing-release-tag) "SearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type SearchRequest = any; - -// Warning: (ae-missing-release-tag) "SearchResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type SearchResponse = any; +// @internal +export type SearchRequest = Record; // Warning: (ae-missing-release-tag) "SearchSourceFields" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1991,21 +1985,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: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/index.ts:371:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:371:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:373:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:374:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:383:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:384:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398: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/fetch/handle_response.test.ts b/src/plugins/data/public/search/fetch/handle_response.test.ts index 10e6eda3de3d02..9a9d806bc9cf8f 100644 --- a/src/plugins/data/public/search/fetch/handle_response.test.ts +++ b/src/plugins/data/public/search/fetch/handle_response.test.ts @@ -23,6 +23,7 @@ import { handleResponse } from './handle_response'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { notificationServiceMock } from '../../../../../core/public/notifications/notifications_service.mock'; import { setNotifications } from '../../services'; +import { SearchResponse } from 'elasticsearch'; jest.mock('@kbn/i18n', () => { return { @@ -44,7 +45,7 @@ describe('handleResponse', () => { const request = { body: {} }; const response = { timed_out: true, - }; + } as SearchResponse; const result = handleResponse(request, response); expect(result).toBe(response); expect(notifications.toasts.addWarning).toBeCalled(); @@ -57,9 +58,12 @@ describe('handleResponse', () => { const request = { body: {} }; const response = { _shards: { - failed: true, + failed: 1, + total: 2, + successful: 1, + skipped: 1, }, - }; + } as SearchResponse; const result = handleResponse(request, response); expect(result).toBe(response); expect(notifications.toasts.addWarning).toBeCalled(); @@ -70,7 +74,7 @@ describe('handleResponse', () => { test('returns the response', () => { const request = {}; - const response = {}; + const response = {} as SearchResponse; const result = handleResponse(request, response); expect(result).toBe(response); }); diff --git a/src/plugins/data/public/search/fetch/handle_response.tsx b/src/plugins/data/public/search/fetch/handle_response.tsx index 7905468f91c5f5..14e9b59f49bfb8 100644 --- a/src/plugins/data/public/search/fetch/handle_response.tsx +++ b/src/plugins/data/public/search/fetch/handle_response.tsx @@ -20,12 +20,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; -import { ShardFailureOpenModalButton, ShardFailureRequest, ShardFailureResponse } from '../../ui'; +import { SearchResponse } from 'elasticsearch'; +import { ShardFailureOpenModalButton } from '../../ui'; import { toMountPoint } from '../../../../kibana_react/public'; import { getNotifications } from '../../services'; -import { SearchRequest, SearchResponse } from '..'; +import { SearchRequest } from '..'; -export function handleResponse(request: SearchRequest, response: SearchResponse) { +export function handleResponse(request: SearchRequest, response: SearchResponse) { if (response.timed_out) { getNotifications().toasts.addWarning({ title: i18n.translate('data.search.searchSource.fetch.requestTimedOutNotificationMessage', { @@ -53,11 +54,7 @@ export function handleResponse(request: SearchRequest, response: SearchResponse) <> {description} - + ); diff --git a/src/plugins/data/public/search/fetch/request_error.ts b/src/plugins/data/public/search/fetch/request_error.ts index 5e42a6fcf5b655..efaaafadf404e3 100644 --- a/src/plugins/data/public/search/fetch/request_error.ts +++ b/src/plugins/data/public/search/fetch/request_error.ts @@ -17,8 +17,9 @@ * under the License. */ +import { SearchResponse } from 'elasticsearch'; import { KbnError } from '../../../../kibana_utils/common'; -import { SearchError, SearchResponse } from './types'; +import { SearchError } from './types'; /** * Request Failure - When an entire multi request fails @@ -26,8 +27,8 @@ import { SearchError, SearchResponse } from './types'; * @param {Object} resp - optional HTTP response */ export class RequestFailure extends KbnError { - public resp: SearchResponse; - constructor(err: SearchError | null = null, resp?: SearchResponse) { + public resp?: SearchResponse; + constructor(err: SearchError | null = null, resp?: SearchResponse) { super(`Request to Elasticsearch failed: ${JSON.stringify(resp || err?.message)}`); this.resp = resp; diff --git a/src/plugins/data/public/search/fetch/types.ts b/src/plugins/data/public/search/fetch/types.ts index 18d277204815ba..670c4f731971ac 100644 --- a/src/plugins/data/public/search/fetch/types.ts +++ b/src/plugins/data/public/search/fetch/types.ts @@ -20,8 +20,14 @@ import { GetConfigFn } from '../../../common'; import { ISearchStartLegacy } from '../types'; -export type SearchRequest = any; -export type SearchResponse = any; +/** + * @internal + * + * This type is used when flattenning a SearchSource and passing it down to legacy search. + * Once legacy search is removed, this type should become internal to `SearchSource`, + * where `ISearchRequestParams` is used externally instead. + */ +export type SearchRequest = Record; export interface FetchOptions { abortSignal?: AbortSignal; diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 32bcd8a279036b..14eff13b378ee1 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -34,13 +34,7 @@ export { getEsPreference } from './es_search'; export { IKibanaSearchResponse, IKibanaSearchRequest } from '../../common/search'; -export { - SearchError, - FetchOptions, - SearchRequest, - SearchResponse, - getSearchParamsFromRequest, -} from './fetch'; +export { SearchError, FetchOptions, getSearchParamsFromRequest, SearchRequest } from './fetch'; export { ISearchSource, diff --git a/src/plugins/data/public/search/legacy/call_client.ts b/src/plugins/data/public/search/legacy/call_client.ts index 4b12f517daf781..3dcf11f72a7424 100644 --- a/src/plugins/data/public/search/legacy/call_client.ts +++ b/src/plugins/data/public/search/legacy/call_client.ts @@ -17,6 +17,7 @@ * under the License. */ +import { SearchResponse } from 'elasticsearch'; import { FetchOptions, FetchHandlers, handleResponse } from '../fetch'; import { defaultSearchStrategy } from './default_search_strategy'; import { SearchRequest } from '../index'; @@ -32,7 +33,7 @@ export function callClient( FetchOptions ]> = searchRequests.map((request, i) => [request, requestsOptions[i]]); const requestOptionsMap = new Map(requestOptionEntries); - const requestResponseMap = new Map(); + const requestResponseMap = new Map>>(); const { searching, abort } = defaultSearchStrategy.search({ searchRequests, @@ -45,5 +46,5 @@ export function callClient( if (abortSignal) abortSignal.addEventListener('abort', abort); requestResponseMap.set(request, response); }); - return searchRequests.map((request) => requestResponseMap.get(request)); + return searchRequests.map((request) => requestResponseMap.get(request)!); } diff --git a/src/plugins/data/public/search/legacy/es_client/types.ts b/src/plugins/data/public/search/legacy/es_client/types.ts index 7a56b9b0cb00a8..2d35188322a4e9 100644 --- a/src/plugins/data/public/search/legacy/es_client/types.ts +++ b/src/plugins/data/public/search/legacy/es_client/types.ts @@ -17,13 +17,14 @@ * under the License. */ -import { SearchRequest, SearchResponse } from '../../fetch'; +import { SearchResponse } from 'elasticsearch'; +import { SearchRequest } from '../../fetch'; export interface LegacyApiCaller { search: (searchRequest: SearchRequest) => LegacyApiCallerResponse; msearch: (searchRequest: SearchRequest) => LegacyApiCallerResponse; } -interface LegacyApiCallerResponse extends Promise { +interface LegacyApiCallerResponse extends Promise> { abort: () => void; } diff --git a/src/plugins/data/public/search/legacy/fetch_soon.test.ts b/src/plugins/data/public/search/legacy/fetch_soon.test.ts index d375398af1adda..d7a85e65b475de 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.test.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.test.ts @@ -20,17 +20,27 @@ import { fetchSoon } from './fetch_soon'; import { callClient } from './call_client'; import { FetchHandlers, FetchOptions } from '../fetch/types'; -import { SearchRequest, SearchResponse } from '../index'; +import { SearchRequest } from '../index'; +import { SearchResponse } from 'elasticsearch'; import { GetConfigFn, UI_SETTINGS } from '../../../common'; function getConfigStub(config: any = {}): GetConfigFn { return (key) => config[key]; } -const mockResponses: Record = { - foo: {}, - bar: {}, - baz: {}, +const mockResponses: Record> = { + foo: { + took: 1, + timed_out: false, + } as SearchResponse, + bar: { + took: 2, + timed_out: false, + } as SearchResponse, + baz: { + took: 3, + timed_out: false, + } as SearchResponse, }; jest.useFakeTimers(); diff --git a/src/plugins/data/public/search/legacy/fetch_soon.ts b/src/plugins/data/public/search/legacy/fetch_soon.ts index 252682c7c8e58d..16920a8a4dd979 100644 --- a/src/plugins/data/public/search/legacy/fetch_soon.ts +++ b/src/plugins/data/public/search/legacy/fetch_soon.ts @@ -17,9 +17,10 @@ * under the License. */ +import { SearchResponse } from 'elasticsearch'; import { callClient } from './call_client'; import { FetchHandlers, FetchOptions } from '../fetch/types'; -import { SearchRequest, SearchResponse } from '../index'; +import { SearchRequest } from '../index'; import { UI_SETTINGS } from '../../../common'; /** @@ -53,7 +54,7 @@ let requestsToFetch: SearchRequest[] = []; let requestOptions: FetchOptions[] = []; // The in-progress fetch (if there is one) -let fetchInProgress: Promise | null = null; +let fetchInProgress: any = null; /** * Delay fetching for a given amount of time, while batching up the requests to be fetched. @@ -67,7 +68,7 @@ async function delayedFetch( options: FetchOptions, fetchHandlers: FetchHandlers, ms: number -) { +): Promise> { if (ms === 0) { return callClient([request], [options], fetchHandlers)[0]; } @@ -75,7 +76,10 @@ async function delayedFetch( const i = requestsToFetch.length; requestsToFetch = [...requestsToFetch, request]; requestOptions = [...requestOptions, options]; - const responses: SearchResponse[] = await (fetchInProgress = + + // Note: the typescript here only worked because `SearchResponse` was `any` + // Since this code is legacy, I'm leaving the any here. + const responses: any[] = await (fetchInProgress = fetchInProgress || delay(() => { const response = callClient(requestsToFetch, requestOptions, fetchHandlers); diff --git a/src/plugins/data/public/search/legacy/types.ts b/src/plugins/data/public/search/legacy/types.ts index 3812cec7a2aa20..ed17db464feffa 100644 --- a/src/plugins/data/public/search/legacy/types.ts +++ b/src/plugins/data/public/search/legacy/types.ts @@ -17,8 +17,9 @@ * under the License. */ +import { SearchResponse } from 'elasticsearch'; import { FetchHandlers } from '../fetch'; -import { SearchRequest, SearchResponse } from '..'; +import { SearchRequest } from '..'; export interface SearchStrategySearchParams extends FetchHandlers { searchRequests: SearchRequest[]; @@ -30,7 +31,7 @@ export interface SearchStrategyProvider { search: (params: SearchStrategySearchParams) => SearchStrategyResponse; } -export interface SearchStrategyResponse { - searching: Promise; +export interface SearchStrategyResponse { + searching: Promise>>; abort: () => void; } diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 06ad13bfcfdf5a..d2e33707620590 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -75,9 +75,15 @@ import { map } from 'rxjs/operators'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; -import { IIndexPattern, ISearchGeneric, SearchRequest } from '../..'; +import { IIndexPattern, ISearchGeneric } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; -import { FetchOptions, RequestFailure, handleResponse, getSearchParamsFromRequest } from '../fetch'; +import { + FetchOptions, + RequestFailure, + handleResponse, + getSearchParamsFromRequest, + SearchRequest, +} from '../fetch'; import { getEsQueryConfig, buildEsQuery, Filter, UI_SETTINGS } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; @@ -268,10 +274,11 @@ export class SearchSource { if (getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)) { response = await this.legacyFetch(searchRequest, options); } else { - response = this.fetch$(searchRequest, options.abortSignal).toPromise(); + response = await this.fetch$(searchRequest, options.abortSignal).toPromise(); } - if (response.error) { + // TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved + if ((response as any).error) { throw new RequestFailure(null, response); } @@ -403,7 +410,7 @@ export class SearchSource { return searchRequest; } - private getIndexType(index: IIndexPattern) { + private getIndexType(index?: IIndexPattern) { if (this.searchStrategyId) { return this.searchStrategyId === 'default' ? undefined : this.searchStrategyId; } else { diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index cb46a838a8c308..35b1bc50ddb1ef 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -24,11 +24,7 @@ export { QueryStringInput } from './query_string_input/query_string_input'; export { SearchBar, SearchBarProps, StatefulSearchBarProps } from './search_bar'; // @internal -export { - ShardFailureOpenModalButton, - ShardFailureRequest, - ShardFailureResponse, -} from './shard_failure_modal'; +export { ShardFailureOpenModalButton, ShardFailureRequest } from './shard_failure_modal'; // @internal export { SavedQueryManagementComponent } from './saved_query_management'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/__mocks__/shard_failure_response.ts b/src/plugins/data/public/ui/shard_failure_modal/__mocks__/shard_failure_response.ts index 573aeefcdf4696..6178fcf92a7903 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/__mocks__/shard_failure_response.ts +++ b/src/plugins/data/public/ui/shard_failure_modal/__mocks__/shard_failure_response.ts @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { ShardFailureResponse } from '../shard_failure_types'; -export const shardFailureResponse = { +import { SearchResponse } from 'elasticsearch'; + +export const shardFailureResponse: SearchResponse = { _shards: { total: 2, successful: 1, @@ -43,4 +44,4 @@ export const shardFailureResponse = { }, ], }, -} as ShardFailureResponse; +} as any; diff --git a/src/plugins/data/public/ui/shard_failure_modal/index.ts b/src/plugins/data/public/ui/shard_failure_modal/index.ts index f4c2e26a756e37..e5af9633e73b72 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/index.ts +++ b/src/plugins/data/public/ui/shard_failure_modal/index.ts @@ -17,5 +17,5 @@ * under the License. */ -export { ShardFailureRequest, ShardFailureResponse } from './shard_failure_types'; +export { ShardFailureRequest } from './shard_failure_types'; export { ShardFailureOpenModalButton } from './shard_failure_open_modal_button'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx index 49983c99263814..d40770fb74ef12 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx @@ -24,7 +24,8 @@ import { ShardFailure } from './shard_failure_types'; describe('ShardFailureDescription', () => { it('renders matching snapshot given valid properties', () => { - const failure = shardFailureResponse._shards.failures[0] as ShardFailure; + // TODO: remove cast once https://github.com/elastic/elasticsearch-js/issues/1286 is resolved + const failure = (shardFailureResponse._shards as any).failures[0] as ShardFailure; const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx index 535f63161966d9..52f3cf702c2fb2 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.tsx @@ -32,18 +32,24 @@ import { EuiButtonEmpty, EuiCallOut, } from '@elastic/eui'; +import { SearchResponse } from 'elasticsearch'; import { ShardFailureTable } from './shard_failure_table'; -import { ShardFailureResponse, ShardFailureRequest } from './shard_failure_types'; +import { ShardFailureRequest } from './shard_failure_types'; export interface Props { onClose: () => void; request: ShardFailureRequest; - response: ShardFailureResponse; + response: SearchResponse; title: string; } export function ShardFailureModal({ request, response, title, onClose }: Props) { - if (!response || !response._shards || !Array.isArray(response._shards.failures) || !request) { + if ( + !response || + !response._shards || + !Array.isArray((response._shards as any).failures) || + !request + ) { // this should never ever happen, but just in case return ( @@ -51,10 +57,9 @@ export function ShardFailureModal({ request, response, title, onClose }: Props) ); } - + const failures = (response._shards as any).failures; const requestJSON = JSON.stringify(request, null, 2); const responseJSON = JSON.stringify(response, null, 2); - const failures = response._shards.failures; const tabs = [ { diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx index fa42745da2e485..9d89dc4cb1a299 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.tsx @@ -20,14 +20,15 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiTextAlign } from '@elastic/eui'; +import { SearchResponse } from 'elasticsearch'; import { getOverlays } from '../../services'; import { toMountPoint } from '../../../../kibana_react/public'; import { ShardFailureModal } from './shard_failure_modal'; -import { ShardFailureResponse, ShardFailureRequest } from './shard_failure_types'; +import { ShardFailureRequest } from './shard_failure_types'; interface Props { request: ShardFailureRequest; - response: ShardFailureResponse; + response: SearchResponse; title: string; } diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx index 9d00233d37f8c0..22478ebd573931 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx @@ -24,7 +24,7 @@ import { ShardFailure } from './shard_failure_types'; describe('ShardFailureTable', () => { it('renders matching snapshot given valid properties', () => { - const failures = shardFailureResponse._shards.failures as ShardFailure[]; + const failures = (shardFailureResponse._shards as any).failures as ShardFailure[]; const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_types.ts b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_types.ts index b1ce3f30c42789..a7a56d2de9621d 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_types.ts +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_types.ts @@ -25,16 +25,6 @@ export interface ShardFailureRequest { stored_fields: string[]; } -export interface ShardFailureResponse { - _shards: { - failed: number; - failures: ShardFailure[]; - skipped: number; - successful: number; - total: number; - }; -} - export interface ShardFailure { index: string; node: string; diff --git a/src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts b/src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts index 437898201863f2..9a199ea4a62fc6 100644 --- a/src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts @@ -18,7 +18,7 @@ */ import { ISearchSource, EsQuerySortValue, SortDirection } from '../../../../../../../data/public'; import { convertTimeValueToIso } from './date_conversion'; -import { EsHitRecordList } from '../context'; +import { EsHitRecordList, EsHitRecord } from '../context'; import { IntervalValue } from './generate_intervals'; import { EsQuerySearchAfter } from './get_es_query_search_after'; @@ -76,5 +76,6 @@ export async function fetchHitsInInterval( .setField('version', true) .fetch(); - return response.hits ? response.hits.hits : []; + // TODO: There's a difference in the definition of SearchResponse and EsHitRecord + return ((response.hits?.hits as unknown) as EsHitRecord[]) || []; } diff --git a/src/plugins/input_control_vis/public/control/list_control_factory.ts b/src/plugins/input_control_vis/public/control/list_control_factory.ts index acbbf08c7d004f..325eb471d510b1 100644 --- a/src/plugins/input_control_vis/public/control/list_control_factory.ts +++ b/src/plugins/input_control_vis/public/control/list_control_factory.ts @@ -190,7 +190,9 @@ export class ListControl extends Control { return; } - this.partialResults = resp.terminated_early || resp.timed_out; + // TODO: terminated_early is missing from response definition. + // https://github.com/elastic/elasticsearch-js/issues/1289 + this.partialResults = (resp as any).terminated_early || resp.timed_out; this.selectOptions = selectOptions; this.enable = true; this.disabledReason = ''; diff --git a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts index 02d5a415eec2e4..9ca6db40a30549 100644 --- a/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/utils/logs_overview_fetchers.ts @@ -5,7 +5,7 @@ */ import { encode } from 'rison-node'; -import { SearchResponse } from 'src/plugins/data/public'; +import { SearchResponse } from 'elasticsearch'; import { FetchData, FetchDataParams, @@ -87,7 +87,7 @@ async function fetchLogsOverview( dataPlugin: InfraClientStartDeps['data'] ): Promise { return new Promise((resolve, reject) => { - let esResponse: SearchResponse = {}; + let esResponse: SearchResponse | undefined; dataPlugin.search .search({ @@ -104,8 +104,8 @@ async function fetchLogsOverview( (response) => (esResponse = response.rawResponse), (error) => reject(error), () => { - if (esResponse.aggregations) { - resolve(processLogsOverviewAggregations(esResponse.aggregations)); + if (esResponse?.aggregations) { + resolve(processLogsOverviewAggregations(esResponse!.aggregations)); } else { resolve({ stats: {}, series: {} }); } From 0f42b1aeb5ae5eda0f1ff7021b8c72afa93124a0 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Mon, 24 Aug 2020 11:09:02 +0100 Subject: [PATCH 09/16] [ML] Add date picker back onto index based data visualizer page (#75658) Co-authored-by: Elastic Machine --- .../datavisualizer/index_based/page.tsx | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index 3c332d305d7e99..26ed3152058dd5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -31,6 +31,7 @@ import { } from '../../../../../../../src/plugins/data/public'; import { SavedSearchSavedObject } from '../../../../common/types/kibana'; import { NavigationMenu } from '../../components/navigation_menu'; +import { DatePickerWrapper } from '../../components/navigation_menu/date_picker_wrapper'; import { ML_JOB_FIELD_TYPES } from '../../../../common/constants/field_types'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; import { isFullLicense } from '../../license'; @@ -655,20 +656,24 @@ export const Page: FC = () => {

{currentIndexPattern.title}

- {currentIndexPattern.timeFieldName !== undefined && ( - - - - )} + + + {currentIndexPattern.timeFieldName !== undefined && ( + + + + )} + + + + + - {showActionsPanel === true && ( - - )} From f3799c37f65895e0fb22b2c4d19fe39492a141e4 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 24 Aug 2020 11:43:44 +0100 Subject: [PATCH 10/16] [ML] Fixing new population job wizard with saved search (#75731) --- .../server/models/job_service/new_job/population_chart.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index a9a2ce57f966cc..ab75787a0069f0 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -159,6 +159,14 @@ function getPopulationSearchJsonFromConfig( }, }; + if (query.bool === undefined) { + query.bool = { + must: [], + }; + } else if (query.bool.must === undefined) { + query.bool.must = []; + } + query.bool.must.push({ range: { [timeField]: { From c6e86cf773fd412abc7d7da00a2a58d5b1930b4f Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 24 Aug 2020 14:06:57 +0200 Subject: [PATCH 11/16] In-chart "Explore underlying data" action telemetry (#74516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add telemetry for in-chart "Explore underlying data" * refactor: 💡 use shared Config type from /common folder * feat: 🎸 register usage collector * chore: 🤖 upate telemetry schema * fix: 🐛 use relative import for usage_collector * fix: 🐛 use relative imports for core * fix: 🐛 use relative import Co-authored-by: Elastic Machine --- src/plugins/telemetry/schema/oss_plugins.json | 13 +++++ .../discover_enhanced/common/config.ts | 9 +++ .../plugins/discover_enhanced/common/index.ts | 7 +++ x-pack/plugins/discover_enhanced/kibana.json | 2 +- .../discover_enhanced/public/plugin.ts | 5 +- .../plugins/discover_enhanced/server/index.ts | 8 +-- .../discover_enhanced/server/plugin.ts | 58 +++++++++++++++++++ .../schema/xpack_plugins.json | 7 +++ 8 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/discover_enhanced/common/config.ts create mode 100644 x-pack/plugins/discover_enhanced/common/index.ts create mode 100644 x-pack/plugins/discover_enhanced/server/plugin.ts diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index a5172c01b1dad2..49e431324a49ea 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -13,6 +13,19 @@ } } }, + "search": { + "properties": { + "successCount": { + "type": "number" + }, + "errorCount": { + "type": "number" + }, + "averageDuration": { + "type": "long" + } + } + }, "sample-data": { "properties": { "installed": { diff --git a/x-pack/plugins/discover_enhanced/common/config.ts b/x-pack/plugins/discover_enhanced/common/config.ts new file mode 100644 index 00000000000000..1ee329ea8d1158 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/common/config.ts @@ -0,0 +1,9 @@ +/* + * 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 interface Config { + actions: { exploreDataInChart: { enabled: boolean } }; +} diff --git a/x-pack/plugins/discover_enhanced/common/index.ts b/x-pack/plugins/discover_enhanced/common/index.ts new file mode 100644 index 00000000000000..876fca26dc6f43 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/common/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 './config'; diff --git a/x-pack/plugins/discover_enhanced/kibana.json b/x-pack/plugins/discover_enhanced/kibana.json index 531a84cd4c0e00..da95a0f21a0204 100644 --- a/x-pack/plugins/discover_enhanced/kibana.json +++ b/x-pack/plugins/discover_enhanced/kibana.json @@ -5,7 +5,7 @@ "server": true, "ui": true, "requiredPlugins": ["uiActions", "embeddable", "discover"], - "optionalPlugins": ["share", "kibanaLegacy"], + "optionalPlugins": ["share", "kibanaLegacy", "usageCollection"], "configPath": ["xpack", "discoverEnhanced"], "requiredBundles": ["kibanaUtils", "data"] } diff --git a/x-pack/plugins/discover_enhanced/public/plugin.ts b/x-pack/plugins/discover_enhanced/public/plugin.ts index 9e66925132a7d0..f1273ab00bdd40 100644 --- a/x-pack/plugins/discover_enhanced/public/plugin.ts +++ b/x-pack/plugins/discover_enhanced/public/plugin.ts @@ -28,6 +28,7 @@ import { ACTION_EXPLORE_DATA_CHART, ExploreDataChartActionContext, } from './actions'; +import { Config } from '../common'; declare module '../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -55,10 +56,10 @@ export interface DiscoverEnhancedStartDependencies { export class DiscoverEnhancedPlugin implements Plugin { - public readonly config: { actions: { exploreDataInChart: { enabled: boolean } } }; + public readonly config: Config; constructor(protected readonly context: PluginInitializerContext) { - this.config = context.config.get(); + this.config = context.config.get(); } setup( diff --git a/x-pack/plugins/discover_enhanced/server/index.ts b/x-pack/plugins/discover_enhanced/server/index.ts index e361b9fb075eda..461a2616efdb3f 100644 --- a/x-pack/plugins/discover_enhanced/server/index.ts +++ b/x-pack/plugins/discover_enhanced/server/index.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PluginInitializerContext } from '../../../../src/core/server'; +import { DiscoverEnhancedPlugin } from './plugin'; + export { config } from './config'; -export const plugin = () => ({ - setup() {}, - start() {}, -}); +export const plugin = (context: PluginInitializerContext) => new DiscoverEnhancedPlugin(context); diff --git a/x-pack/plugins/discover_enhanced/server/plugin.ts b/x-pack/plugins/discover_enhanced/server/plugin.ts new file mode 100644 index 00000000000000..9d80a6dc7dcd1c --- /dev/null +++ b/x-pack/plugins/discover_enhanced/server/plugin.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, +} from '../../../../src/core/server'; +import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; +import { Config } from '../common'; + +interface SetupDependencies { + usageCollection?: UsageCollectionSetup; +} + +interface StartDependencies { + usageCollection?: unknown; +} + +export class DiscoverEnhancedPlugin + implements Plugin { + private config$: Observable; + + constructor(protected readonly context: PluginInitializerContext) { + this.config$ = context.config.create(); + } + + public setup(core: CoreSetup, { usageCollection }: SetupDependencies) { + if (!!usageCollection) { + const collector = usageCollection.makeUsageCollector<{ + exploreDataInChartActionEnabled: boolean; + }>({ + type: 'discoverEnhanced', + schema: { + exploreDataInChartActionEnabled: { + type: 'boolean', + }, + }, + isReady: () => true, + fetch: async () => { + const config = await this.config$.pipe(take(1)).toPromise(); + return { + exploreDataInChartActionEnabled: config.actions.exploreDataInChart.enabled, + }; + }, + }); + usageCollection.registerCollector(collector); + } + } + + public start(core: CoreStart) {} +} diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index fd21b70660bb61..5280f094e3a5d2 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -7,6 +7,13 @@ } } }, + "discoverEnhanced": { + "properties": { + "exploreDataInChartActionEnabled": { + "type": "boolean" + } + } + }, "app_search": { "properties": { "ui_viewed": { From 3768aab743ff677e987778fd1d517dfafcc13fa5 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 24 Aug 2020 14:41:40 +0200 Subject: [PATCH 12/16] [SIEM] Fixes 'sets and reads the url state for timeline by id' timeline Cypress test (#75125) * fixes 'sets and reads the url state for timeline by id' timeline ttest * makes test more reliable Co-authored-by: Elastic Machine --- .../cypress/integration/url_state.spec.ts | 27 +++++++++++-------- .../cypress/screens/timeline.ts | 2 ++ .../cypress/tasks/timeline.ts | 10 ++++--- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index cdcdde252d6d61..6d605e1d577a9f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -36,6 +36,7 @@ import { addNameToTimeline, closeTimeline, executeTimelineKQL, + waitForTimelineChanges, } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; @@ -217,7 +218,7 @@ describe('url state', () => { cy.get(KQL_INPUT).invoke('text').should('eq', 'source.ip: "10.142.0.9"'); }); - it.skip('sets and reads the url state for timeline by id', () => { + it('sets and reads the url state for timeline by id', () => { loginAndWaitForPage(HOSTS_URL); openTimeline(); executeTimelineKQL('host.name: *'); @@ -229,20 +230,24 @@ describe('url state', () => { cy.wrap(intCount).should('be.above', 0); }); + cy.server(); + cy.route('PATCH', '**/api/timeline').as('timeline'); + const timelineName = 'Security'; + const timelineDescription = 'This is the best timeline of the world'; addNameToTimeline(timelineName); - addDescriptionToTimeline('This is the best timeline of the world'); - cy.wait(5000); - - cy.url({ timeout: 30000 }).should('match', /\w*-\w*-\w*-\w*-\w*/); - cy.url().then((url) => { - const matched = url.match(/\w*-\w*-\w*-\w*-\w*/); - const newTimelineId = matched && matched.length > 0 ? matched[0] : 'null'; - expect(matched).to.have.lengthOf(1); + waitForTimelineChanges(); + addDescriptionToTimeline(timelineDescription); + waitForTimelineChanges(); + + cy.wait('@timeline').then((response) => { closeTimeline(); + cy.wrap(response.status).should('eql', 200); + const JsonResponse = JSON.parse(response.xhr.responseText); + const timelineId = JsonResponse.data.persistTimeline.timeline.savedObjectId; cy.visit('/app/home'); - cy.visit(`/app/security/timelines?timeline=(id:%27${newTimelineId}%27,isOpen:!t)`); - cy.contains('a', 'Security'); + cy.visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`); + cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('exist'); cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).invoke('text').should('not.equal', 'Updating'); cy.get(TIMELINE_TITLE).should('be.visible'); cy.get(TIMELINE_TITLE).should('have.attr', 'value', timelineName); diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 135dea35ca0d8c..26203a8ca3b839 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -39,6 +39,8 @@ export const TIMELINE = (id: string) => { return `[data-test-subj="title-${id}"]`; }; +export const TIMELINE_CHANGES_IN_PROGRESS = '[data-test-subj="timeline"] .euiProgress'; + export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]'; export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 9eeb9fc8bdf8af..08624df06e096d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DATE_PICKER_APPLY_BUTTON_TIMELINE } from '../screens/date_picker'; - import { CLOSE_TIMELINE_BTN, CREATE_NEW_TIMELINE, @@ -16,6 +14,7 @@ import { PIN_EVENT, SEARCH_OR_FILTER_CONTAINER, SERVER_SIDE_EVENT_COUNT, + TIMELINE_CHANGES_IN_PROGRESS, TIMELINE_DESCRIPTION, TIMELINE_FIELDS_BUTTON, TIMELINE_INSPECT_BUTTON, @@ -33,7 +32,7 @@ export const hostExistsQuery = 'host.name: *'; export const addDescriptionToTimeline = (description: string) => { cy.get(TIMELINE_DESCRIPTION).type(`${description}{enter}`); - cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).click().invoke('text').should('not.equal', 'Updating'); + cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', description); }; export const addNameToTimeline = (name: string) => { @@ -122,3 +121,8 @@ export const removeColumn = (column: number) => { export const resetFields = () => { cy.get(RESET_FIELDS).click({ force: true }); }; + +export const waitForTimelineChanges = () => { + cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('exist'); + cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('not.exist'); +}; From 65c724808f2bbe5c53557954e2b80b3ef50be484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Mon, 24 Aug 2020 15:25:51 +0200 Subject: [PATCH 13/16] [Security Solution] Fix setting the initial Kibana filters (#75215) * [Security Solution] Fix setting the initial Kibana filters * add unit test * keep appState in kibana storage * fix logic Co-authored-by: Elastic Machine --- src/plugins/data/public/mocks.ts | 2 +- .../components/search_bar/index.test.tsx | 44 +++++++++++ .../common/components/search_bar/index.tsx | 76 ++++++++++++------- 3 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 78e40cfedd9060..0eb0e3b6580458 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -61,7 +61,7 @@ const createStartContract = (): Start => { query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), - SearchBar: jest.fn(), + SearchBar: jest.fn().mockReturnValue(null), }, indexPatterns: ({ createField: jest.fn(() => {}), diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx new file mode 100644 index 00000000000000..356456c7777913 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.test.tsx @@ -0,0 +1,44 @@ +/* + * 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 { mount } from 'enzyme'; + +import { InputsModelId } from '../../store/inputs/constants'; +import { SearchBarComponent } from '.'; +import { TestProviders } from '../../mock'; + +jest.mock('../../lib/kibana'); + +describe('SearchBarComponent', () => { + const props = { + id: 'global' as InputsModelId, + indexPattern: { + fields: [], + title: '', + }, + updateSearch: jest.fn(), + setSavedQuery: jest.fn(), + setSearchBarFilter: jest.fn(), + end: '', + start: '', + toStr: '', + fromStr: '', + isLoading: false, + filterQuery: { + query: '', + language: '', + }, + queries: [], + savedQuery: undefined, + }; + + it('calls setSearchBarFilter on mount', () => { + mount(, { wrappingComponent: TestProviders }); + + expect(props.setSearchBarFilter).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx index de60bca73cedf6..2dc44fd48e66dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/search_bar/index.tsx @@ -33,7 +33,6 @@ import { filterQuerySelector, fromStrSelector, isLoadingSelector, - kindSelector, queriesSelector, savedQuerySelector, startSelector, @@ -44,6 +43,8 @@ import { networkActions } from '../../../network/store'; import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; +const APP_STATE_STORAGE_KEY = 'securitySolution.searchBar.appState'; + interface SiemSearchBarProps { id: InputsModelId; indexPattern: IIndexPattern; @@ -57,7 +58,7 @@ const SearchBarContainer = styled.div` } `; -const SearchBarComponent = memo( +export const SearchBarComponent = memo( ({ end, filterQuery, @@ -74,20 +75,27 @@ const SearchBarComponent = memo( updateSearch, dataTestSubj, }) => { - const { data } = useKibana().services; const { - timefilter: { timefilter }, - filterManager, - } = data.query; - - if (fromStr != null && toStr != null) { - timefilter.setTime({ from: fromStr, to: toStr }); - } else if (start != null && end != null) { - timefilter.setTime({ - from: new Date(start).toISOString(), - to: new Date(end).toISOString(), - }); - } + data: { + query: { + timefilter: { timefilter }, + filterManager, + }, + ui: { SearchBar }, + }, + storage, + } = useKibana().services; + + useEffect(() => { + if (fromStr != null && toStr != null) { + timefilter.setTime({ from: fromStr, to: toStr }); + } else if (start != null && end != null) { + timefilter.setTime({ + from: new Date(start).toISOString(), + to: new Date(end).toISOString(), + }); + } + }, [end, fromStr, start, timefilter, toStr]); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -135,8 +143,7 @@ const SearchBarComponent = memo( window.setTimeout(() => updateSearch(updateSearchBar), 0); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, end, filterQuery, fromStr, queries, start, toStr] + [id, toStr, end, fromStr, start, filterManager, filterQuery, queries, updateSearch] ); const onRefresh = useCallback( @@ -155,16 +162,14 @@ const SearchBarComponent = memo( queries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)()); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, queries, filterManager] + [updateSearch, id, filterManager, queries] ); const onSaved = useCallback( (newSavedQuery: SavedQuery) => { setSavedQuery({ id, savedQuery: newSavedQuery }); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id] + [id, setSavedQuery] ); const onSavedQueryUpdated = useCallback( @@ -200,8 +205,7 @@ const SearchBarComponent = memo( updateSearch(updateSearchBar); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [id, end, fromStr, start, toStr] + [id, toStr, end, fromStr, start, filterManager, updateSearch] ); const onClearSavedQuery = useCallback(() => { @@ -223,8 +227,16 @@ const SearchBarComponent = memo( filterManager, }); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, end, filterManager, fromStr, start, toStr, savedQuery]); + }, [savedQuery, updateSearch, id, toStr, end, fromStr, start, filterManager]); + + const saveAppStateToStorage = useCallback( + (filters: Filter[]) => storage.set(APP_STATE_STORAGE_KEY, filters), + [storage] + ); + + const getAppStateFromStorage = useCallback(() => storage.get(APP_STATE_STORAGE_KEY) ?? [], [ + storage, + ]); useEffect(() => { let isSubscribed = true; @@ -234,6 +246,7 @@ const SearchBarComponent = memo( filterManager.getUpdates$().subscribe({ next: () => { if (isSubscribed) { + saveAppStateToStorage(filterManager.getAppFilters()); setSearchBarFilter({ id, filters: filterManager.getFilters(), @@ -243,16 +256,25 @@ const SearchBarComponent = memo( }) ); + // for the initial state + filterManager.setAppFilters(getAppStateFromStorage()); + setSearchBarFilter({ + id, + filters: filterManager.getFilters(), + }); + return () => { isSubscribed = false; subscriptions.unsubscribe(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const indexPatterns = useMemo(() => [indexPattern], [indexPattern]); + return ( - { const getEndSelector = endSelector(); const getFromStrSelector = fromStrSelector(); const getIsLoadingSelector = isLoadingSelector(); - const getKindSelector = kindSelector(); const getQueriesSelector = queriesSelector(); const getStartSelector = startSelector(); const getToStrSelector = toStrSelector(); @@ -292,7 +313,6 @@ const makeMapStateToProps = () => { fromStr: getFromStrSelector(inputsRange), filterQuery: getFilterQuerySelector(inputsRange), isLoading: getIsLoadingSelector(inputsRange), - kind: getKindSelector(inputsRange), queries: getQueriesSelector(inputsRange), savedQuery: getSavedQuerySelector(inputsRange), start: getStartSelector(inputsRange), From 7eb02d11aa1cc8383adf0a36fed4d3c781a32d73 Mon Sep 17 00:00:00 2001 From: Marius Dragomir Date: Mon, 24 Aug 2020 15:28:25 +0200 Subject: [PATCH 14/16] Handle change in saml VM name (#73808) * Handle change in saml VM name * fix lint problem * Update login_page.ts * Fix tslint Co-authored-by: Elastic Machine --- test/functional/page_objects/login_page.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/functional/page_objects/login_page.ts b/test/functional/page_objects/login_page.ts index 350ab8be1a2742..6cab2d39f3a9e9 100644 --- a/test/functional/page_objects/login_page.ts +++ b/test/functional/page_objects/login_page.ts @@ -48,10 +48,8 @@ export function LoginPageProvider({ getService }: FtrProviderContext) { class LoginPage { async login(user: string, pwd: string) { - if ( - process.env.VM === 'ubuntu18_deb_oidc' || - process.env.VM === 'ubuntu16_deb_desktop_saml' - ) { + const loginType = process.env.VM || ''; + if (loginType.includes('oidc') || loginType.includes('saml')) { await samlLogin(user, pwd); return; } From fdc93af824d79d5c8c61a7393355e9e95d1ee346 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 24 Aug 2020 14:34:33 +0100 Subject: [PATCH 15/16] skip flaky suite (#75721) --- .../public/management/pages/endpoint_hosts/view/index.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 5ab9df79ee14b8..6e373679304668 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -246,7 +246,8 @@ describe('when on the list page', () => { }); }); - describe('when polling on Endpoint List', () => { + // FLAKY: https://github.com/elastic/kibana/issues/75721 + describe.skip('when polling on Endpoint List', () => { beforeEach(async () => { await reactTestingLibrary.act(() => { const hostListData = mockEndpointResultList({ total: 4 }).hosts; From ed53ca6b46769740a8bd595e3cea623cea8ae30e Mon Sep 17 00:00:00 2001 From: Sandra Gonzales Date: Mon, 24 Aug 2020 09:42:39 -0400 Subject: [PATCH 16/16] [Ingest Manager] check for packages stuck installing and reinstall (#75226) * check for packages stuck installing and reinstall * update mock endpoint package * diferentiate between reinstall and reupdate type of install, remove isUpdate, add integration test * create new EpmPackageInstallStatus type instead of using InstallStatus * fix merge conflict * change EpmPackageInstallStatus to a union type * change time to install to 1 minute * used saved object find Co-authored-by: Elastic Machine --- .../ingest_manager/common/constants/epm.ts | 1 + .../ingest_manager/common/types/models/epm.ts | 5 + .../ingest_manager/server/constants/index.ts | 1 + .../server/saved_objects/index.ts | 3 + .../epm/elasticsearch/template/install.ts | 1 - .../services/epm/kibana/assets/install.ts | 1 - .../server/services/epm/packages/get.ts | 8 +- .../server/services/epm/packages/install.ts | 64 +++++-- .../ingest_manager/server/services/setup.ts | 6 +- .../ingest_manager/server/types/index.tsx | 1 + .../common/endpoint/generate_data.ts | 3 + .../apis/epm/index.js | 1 + .../apis/epm/install_remove_assets.ts | 3 + .../apis/epm/install_update.ts | 28 +++ .../apis/epm/package_install_complete.ts | 167 ++++++++++++++++++ .../apis/epm/update_assets.ts | 3 + 16 files changed, 279 insertions(+), 17 deletions(-) create mode 100644 x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts diff --git a/x-pack/plugins/ingest_manager/common/constants/epm.ts b/x-pack/plugins/ingest_manager/common/constants/epm.ts index 73cd8463bb6aab..571580e81258f3 100644 --- a/x-pack/plugins/ingest_manager/common/constants/epm.ts +++ b/x-pack/plugins/ingest_manager/common/constants/epm.ts @@ -7,3 +7,4 @@ export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages'; export const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; export const INDEX_PATTERN_PLACEHOLDER_SUFFIX = '-index_pattern_placeholder'; +export const MAX_TIME_COMPLETE_INSTALL = 60000; diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index 6ec5b73eaa43ee..140a76ac85e61b 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -19,6 +19,8 @@ export enum InstallStatus { uninstalling = 'uninstalling', } +export type EpmPackageInstallStatus = 'installed' | 'installing'; + export type DetailViewPanelName = 'overview' | 'usages' | 'settings'; export type ServiceName = 'kibana' | 'elasticsearch'; export type AssetType = KibanaAssetType | ElasticsearchAssetType | AgentAssetType; @@ -234,6 +236,9 @@ export interface Installation extends SavedObjectAttributes { es_index_patterns: Record; name: string; version: string; + install_status: EpmPackageInstallStatus; + install_version: string; + install_started_at: string; } export type Installable = Installed | NotInstalled; diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts index 54d2d876c75b12..d677b79bb46f8d 100644 --- a/x-pack/plugins/ingest_manager/server/constants/index.ts +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -14,6 +14,7 @@ export { AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS, AGENT_UPDATE_ACTIONS_INTERVAL_MS, INDEX_PATTERN_PLACEHOLDER_SUFFIX, + MAX_TIME_COMPLETE_INSTALL, // Routes LIMITED_CONCURRENCY_ROUTE_TAG, PLUGIN_ID, diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts index 50957c48f70e61..1bbe3b71bf9191 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts @@ -285,6 +285,9 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = { type: { type: 'keyword' }, }, }, + install_started_at: { type: 'date' }, + install_version: { type: 'keyword' }, + install_status: { type: 'keyword' }, }, }, }, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts index 2a3120f064904a..f4e8c3bfd99d3f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts @@ -22,7 +22,6 @@ import { removeAssetsFromInstalledEsByType, saveInstalledEsRefs } from '../../pa export const installTemplates = async ( registryPackage: RegistryPackage, - isUpdate: boolean, callCluster: CallESAsCurrentUser, paths: string[], savedObjectsClient: SavedObjectsClientContract diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts index 84892d2027847d..ff1a91b00d84f6 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/assets/install.ts @@ -48,7 +48,6 @@ export async function installKibanaAssets(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; kibanaAssets: ArchiveAsset[]; - isUpdate: boolean; }): Promise { const { savedObjectsClient, kibanaAssets } = options; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index 1688900fc1758f..c4232247cc4bd5 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsClientContract, SavedObjectsFindOptions } from 'src/core/server'; import { isPackageLimited } from '../../../../common'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import { Installation, InstallationStatus, PackageInfo, KibanaAssetType } from '../../../types'; @@ -72,8 +72,12 @@ export async function getLimitedPackages(options: { return installedPackagesInfo.filter(isPackageLimited).map((pkgInfo) => pkgInfo.name); } -export async function getPackageSavedObjects(savedObjectsClient: SavedObjectsClientContract) { +export async function getPackageSavedObjects( + savedObjectsClient: SavedObjectsClientContract, + options?: Omit +) { return savedObjectsClient.find({ + ...(options || {}), type: PACKAGES_SAVED_OBJECT_TYPE, }); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 6bc461845f1244..e49dbe8f0b5d4e 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -6,7 +6,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import semver from 'semver'; -import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; +import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants'; import { AssetReference, Installation, @@ -33,6 +33,7 @@ import { import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; import { deleteKibanaSavedObjectsAssets } from './remove'; import { PackageOutdatedError } from '../../../errors'; +import { getPackageSavedObjects } from './get'; export async function installLatestPackage(options: { savedObjectsClient: SavedObjectsClientContract; @@ -107,22 +108,24 @@ export async function installPackage({ // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge // and be replaced by getPackageInfo after adjusting for it to not group/use archive assets const latestPackage = await Registry.fetchFindLatestPackage(pkgName); - if (semver.lt(pkgVersion, latestPackage.version) && !force) - throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); + // get the currently installed package + const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); + const reinstall = pkgVersion === installedPkg?.attributes.version; + const reupdate = pkgVersion === installedPkg?.attributes.install_version; + // let the user install if using the force flag or this is a reinstall or reupdate due to intallation interruption + if (semver.lt(pkgVersion, latestPackage.version) && !force && !reinstall && !reupdate) { + throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); + } const paths = await Registry.getArchiveInfo(pkgName, pkgVersion); const registryPackageInfo = await Registry.fetchInfo(pkgName, pkgVersion); - // get the currently installed package - const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); - const isUpdate = installedPkg && installedPkg.attributes.version < pkgVersion ? true : false; - - const reinstall = pkgVersion === installedPkg?.attributes.version; const removable = !isRequiredPackage(pkgName); const { internal = false } = registryPackageInfo; const toSaveESIndexPatterns = generateESIndexPatterns(registryPackageInfo.datasets); - // add the package installation to the saved object + // add the package installation to the saved object. + // if some installation already exists, just update install info if (!installedPkg) { await createInstallation({ savedObjectsClient, @@ -134,6 +137,12 @@ export async function installPackage({ installed_es: [], toSaveESIndexPatterns, }); + } else { + await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + install_version: pkgVersion, + install_status: 'installing', + install_started_at: new Date().toISOString(), + }); } const installIndexPatternPromise = installIndexPatterns(savedObjectsClient, pkgName, pkgVersion); const kibanaAssets = await getKibanaAssets(paths); @@ -152,7 +161,6 @@ export async function installPackage({ savedObjectsClient, pkgName, kibanaAssets, - isUpdate, }); // the rest of the installation must happen in sequential order @@ -172,7 +180,6 @@ export async function installPackage({ // install or update the templates referencing the newly installed pipelines const installedTemplates = await installTemplates( registryPackageInfo, - isUpdate, callCluster, paths, savedObjectsClient @@ -197,9 +204,14 @@ export async function installPackage({ })); await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]); // update to newly installed version when all assets are successfully installed - if (isUpdate) await updateVersion(savedObjectsClient, pkgName, pkgVersion); + if (installedPkg) await updateVersion(savedObjectsClient, pkgName, pkgVersion); + await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + install_version: pkgVersion, + install_status: 'installed', + }); return [...installedKibanaAssetsRefs, ...installedPipelines, ...installedTemplateRefs]; } + const updateVersion = async ( savedObjectsClient: SavedObjectsClientContract, pkgName: string, @@ -239,6 +251,9 @@ export async function createInstallation(options: { version: pkgVersion, internal, removable, + install_version: pkgVersion, + install_status: 'installing', + install_started_at: new Date().toISOString(), }, { id: pkgName, overwrite: true } ); @@ -286,3 +301,28 @@ export const removeAssetsFromInstalledEsByType = async ( installed_es: installedAssetsToSave, }); }; + +export async function ensurePackagesCompletedInstall( + savedObjectsClient: SavedObjectsClientContract, + callCluster: CallESAsCurrentUser +) { + const installingPackages = await getPackageSavedObjects(savedObjectsClient, { + searchFields: ['install_status'], + search: 'installing', + }); + const installingPromises = installingPackages.saved_objects.reduce< + Array> + >((acc, pkg) => { + const startDate = pkg.attributes.install_started_at; + const nowDate = new Date().toISOString(); + const elapsedTime = Date.parse(nowDate) - Date.parse(startDate); + const pkgkey = `${pkg.attributes.name}-${pkg.attributes.install_version}`; + // reinstall package + if (elapsedTime > MAX_TIME_COMPLETE_INSTALL) { + acc.push(installPackage({ savedObjectsClient, pkgkey, callCluster })); + } + return acc; + }, []); + await Promise.all(installingPromises); + return installingPackages; +} diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index fb4430f8cf727d..fd5d94a71d672c 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -10,7 +10,10 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { CallESAsCurrentUser } from '../types'; import { agentPolicyService } from './agent_policy'; import { outputService } from './output'; -import { ensureInstalledDefaultPackages } from './epm/packages/install'; +import { + ensureInstalledDefaultPackages, + ensurePackagesCompletedInstall, +} from './epm/packages/install'; import { ensureDefaultIndices } from './epm/kibana/index_pattern/install'; import { packageToPackagePolicy, @@ -51,6 +54,7 @@ async function createSetupSideEffects( ensureInstalledDefaultPackages(soClient, callCluster), outputService.ensureDefaultOutput(soClient), agentPolicyService.ensureDefaultAgentPolicy(soClient), + ensurePackagesCompletedInstall(soClient, callCluster), ensureDefaultIndices(callCluster), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 8e3219a8c08e03..aabe4bd3e35975 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -37,6 +37,7 @@ export { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes, Installation, + EpmPackageInstallStatus, InstallationStatus, PackageInfo, RegistryVarsEntry, diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 0a6473b8338633..7340b1c021eba4 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -1158,6 +1158,9 @@ export class EndpointDocGenerator { version: '0.5.0', internal: false, removable: false, + install_version: '0.5.0', + install_status: 'installed', + install_started_at: '2020-06-24T14:41:23.098Z', }, references: [], updated_at: '2020-06-24T14:41:23.098Z', diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/index.js b/x-pack/test/ingest_manager_api_integration/apis/epm/index.js index 0a259cb96bf590..85558142459099 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/index.js +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/index.js @@ -17,5 +17,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./install_update')); loadTestFile(require.resolve('./update_assets')); loadTestFile(require.resolve('./data_stream')); + loadTestFile(require.resolve('./package_install_complete')); }); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts index e575d7b6803011..c7cfee565b2e92 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_remove_assets.ts @@ -170,6 +170,9 @@ export default function (providerContext: FtrProviderContext) { version: '0.1.0', internal: false, removable: true, + install_version: '0.1.0', + install_status: 'installed', + install_started_at: res.attributes.install_started_at, }); }); }); diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts index 9de6cd9118fe41..bdcd202d8c46d6 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/install_update.ts @@ -7,6 +7,10 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; +import { + PACKAGES_SAVED_OBJECT_TYPE, + MAX_TIME_COMPLETE_INSTALL, +} from '../../../../plugins/ingest_manager/common'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; @@ -62,6 +66,12 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }) .expect(200); }); + it('should return 200 if trying to reinstall an out-of-date package', async function () { + await supertest + .post(`/api/ingest_manager/epm/packages/multiple_versions-0.1.0`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + }); it('should return 400 if trying to update to an out-of-date package', async function () { await supertest .post(`/api/ingest_manager/epm/packages/multiple_versions-0.2.0`) @@ -75,6 +85,24 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }) .expect(200); }); + it('should return 200 if trying to reupdate an out-of-date package', async function () { + const previousInstallDate = new Date(Date.now() - MAX_TIME_COMPLETE_INSTALL).toISOString(); + // mock package to be stuck installing an update + await kibanaServer.savedObjects.update({ + id: 'multiple_versions', + type: PACKAGES_SAVED_OBJECT_TYPE, + attributes: { + install_status: 'installing', + install_started_at: previousInstallDate, + install_version: '0.2.0', + version: '0.1.0', + }, + }); + await supertest + .post(`/api/ingest_manager/epm/packages/multiple_versions-0.2.0`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + }); it('should return 200 if trying to update to the latest package', async function () { await supertest .post(`/api/ingest_manager/epm/packages/multiple_versions-0.3.0`) diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts new file mode 100644 index 00000000000000..dc311c9db1913a --- /dev/null +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/package_install_complete.ts @@ -0,0 +1,167 @@ +/* + * 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 expect from '@kbn/expect'; +import { + PACKAGES_SAVED_OBJECT_TYPE, + MAX_TIME_COMPLETE_INSTALL, +} from '../../../../plugins/ingest_manager/common'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + const pkgName = 'multiple_versions'; + const pkgVersion = '0.1.0'; + const pkgUpdateVersion = '0.2.0'; + describe('setup checks packages completed install', async () => { + describe('package install', async () => { + before(async () => { + await supertest + .post(`/api/ingest_manager/epm/packages/${pkgName}-0.1.0`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }); + it('should have not reinstalled if package install completed', async function () { + const packageBeforeSetup = await kibanaServer.savedObjects.get({ + type: 'epm-packages', + id: pkgName, + }); + const installStartedAtBeforeSetup = packageBeforeSetup.attributes.install_started_at; + await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxx').send(); + const packageAfterSetup = await kibanaServer.savedObjects.get({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: pkgName, + }); + const installStartedAfterSetup = packageAfterSetup.attributes.install_started_at; + expect(installStartedAtBeforeSetup).equal(installStartedAfterSetup); + }); + it('should have reinstalled if package installing did not complete in elapsed time', async function () { + // change the saved object to installing to mock kibana crashing and not finishing the install + const previousInstallDate = new Date(Date.now() - MAX_TIME_COMPLETE_INSTALL).toISOString(); + await kibanaServer.savedObjects.update({ + id: pkgName, + type: PACKAGES_SAVED_OBJECT_TYPE, + attributes: { + install_status: 'installing', + install_started_at: previousInstallDate, + }, + }); + await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxx').send(); + const packageAfterSetup = await kibanaServer.savedObjects.get({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: pkgName, + }); + const installStartedAfterSetup = packageAfterSetup.attributes.install_started_at; + expect(Date.parse(installStartedAfterSetup)).greaterThan(Date.parse(previousInstallDate)); + expect(packageAfterSetup.attributes.install_status).equal('installed'); + }); + it('should have not reinstalled if package installing did not surpass elapsed time', async function () { + // change the saved object to installing to mock package still installing, but a time less than the max time allowable + const previousInstallDate = new Date(Date.now()).toISOString(); + await kibanaServer.savedObjects.update({ + id: pkgName, + type: PACKAGES_SAVED_OBJECT_TYPE, + attributes: { + install_status: 'installing', + install_started_at: previousInstallDate, + }, + }); + await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxx').send(); + const packageAfterSetup = await kibanaServer.savedObjects.get({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: pkgName, + }); + const installStartedAfterSetup = packageAfterSetup.attributes.install_started_at; + expect(Date.parse(installStartedAfterSetup)).equal(Date.parse(previousInstallDate)); + expect(packageAfterSetup.attributes.install_status).equal('installing'); + }); + after(async () => { + await supertest + .delete(`/api/ingest_manager/epm/packages/multiple_versions-0.1.0`) + .set('kbn-xsrf', 'xxxx'); + }); + }); + describe('package update', async () => { + before(async () => { + await supertest + .post(`/api/ingest_manager/epm/packages/${pkgName}-0.1.0`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + await supertest + .post(`/api/ingest_manager/epm/packages/${pkgName}-0.2.0`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }); + it('should have not reinstalled if package update completed', async function () { + const packageBeforeSetup = await kibanaServer.savedObjects.get({ + type: 'epm-packages', + id: pkgName, + }); + const installStartedAtBeforeSetup = packageBeforeSetup.attributes.install_started_at; + await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxx').send(); + const packageAfterSetup = await kibanaServer.savedObjects.get({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: pkgName, + }); + const installStartedAfterSetup = packageAfterSetup.attributes.install_started_at; + expect(installStartedAtBeforeSetup).equal(installStartedAfterSetup); + }); + it('should have reinstalled if package updating did not complete in elapsed time', async function () { + // change the saved object to installing to mock kibana crashing and not finishing the update + const previousInstallDate = new Date(Date.now() - MAX_TIME_COMPLETE_INSTALL).toISOString(); + await kibanaServer.savedObjects.update({ + id: pkgName, + type: PACKAGES_SAVED_OBJECT_TYPE, + attributes: { + version: pkgVersion, + install_status: 'installing', + install_started_at: previousInstallDate, + install_version: pkgUpdateVersion, // set version back + }, + }); + await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxx').send(); + const packageAfterSetup = await kibanaServer.savedObjects.get({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: pkgName, + }); + const installStartedAfterSetup = packageAfterSetup.attributes.install_started_at; + expect(Date.parse(installStartedAfterSetup)).greaterThan(Date.parse(previousInstallDate)); + expect(packageAfterSetup.attributes.install_status).equal('installed'); + expect(packageAfterSetup.attributes.version).equal(pkgUpdateVersion); + expect(packageAfterSetup.attributes.install_version).equal(pkgUpdateVersion); + }); + it('should have not reinstalled if package updating did not surpass elapsed time', async function () { + // change the saved object to installing to mock package still installing, but a time less than the max time allowable + const previousInstallDate = new Date(Date.now()).toISOString(); + await kibanaServer.savedObjects.update({ + id: pkgName, + type: PACKAGES_SAVED_OBJECT_TYPE, + attributes: { + install_status: 'installing', + install_started_at: previousInstallDate, + version: pkgVersion, // set version back + }, + }); + await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxx').send(); + const packageAfterSetup = await kibanaServer.savedObjects.get({ + type: PACKAGES_SAVED_OBJECT_TYPE, + id: pkgName, + }); + const installStartedAfterSetup = packageAfterSetup.attributes.install_started_at; + expect(Date.parse(installStartedAfterSetup)).equal(Date.parse(previousInstallDate)); + expect(packageAfterSetup.attributes.install_status).equal('installing'); + expect(packageAfterSetup.attributes.version).equal(pkgVersion); + }); + after(async () => { + await supertest + .delete(`/api/ingest_manager/epm/packages/multiple_versions-0.1.0`) + .set('kbn-xsrf', 'xxxx'); + }); + }); + }); +} diff --git a/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts b/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts index 8ad6fe12dcd43f..9af27f5f985582 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/epm/update_assets.ts @@ -322,6 +322,9 @@ export default function (providerContext: FtrProviderContext) { version: '0.2.0', internal: false, removable: true, + install_version: '0.2.0', + install_status: 'installed', + install_started_at: res.attributes.install_started_at, }); }); });