;
canFormatFeatureProperties(): boolean;
getApplyGlobalQuery(): boolean;
getFieldNames(): string[];
diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js
index ccf6c7963c9b4e..ecb13bb875721d 100644
--- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js
+++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.js
@@ -9,7 +9,7 @@ import { AbstractSource } from './../source';
import * as topojson from 'topojson-client';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
-import { VECTOR_SHAPE_TYPES } from './../vector_feature_types';
+import { VECTOR_SHAPE_TYPE } from '../../../../common/constants';
export class AbstractVectorSource extends AbstractSource {
static async getGeoJson({ format, featureCollectionPath, fetchUrl }) {
@@ -127,7 +127,7 @@ export class AbstractVectorSource extends AbstractSource {
}
async getSupportedShapeTypes() {
- return [VECTOR_SHAPE_TYPES.POINT, VECTOR_SHAPE_TYPES.LINE, VECTOR_SHAPE_TYPES.POLYGON];
+ return [VECTOR_SHAPE_TYPE.POINT, VECTOR_SHAPE_TYPE.LINE, VECTOR_SHAPE_TYPE.POLYGON];
}
getSourceTooltipContent(/* sourceDataRequest */) {
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js
index 3424a972fed062..7856a4ddaff395 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js
@@ -16,7 +16,6 @@ import { VectorStyleLabelBorderSizeEditor } from './label/vector_style_label_bor
import { OrientationEditor } from './orientation/orientation_editor';
import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults';
import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils';
-import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui';
@@ -26,6 +25,7 @@ import {
LABEL_BORDER_SIZES,
VECTOR_STYLES,
STYLE_TYPE,
+ VECTOR_SHAPE_TYPE,
} from '../../../../../common/constants';
export class VectorStyleEditor extends Component {
@@ -96,11 +96,11 @@ export class VectorStyleEditor extends Component {
}
if (this.state.selectedFeature === null) {
- let selectedFeature = VECTOR_SHAPE_TYPES.POLYGON;
+ let selectedFeature = VECTOR_SHAPE_TYPE.POLYGON;
if (this.props.isPointsOnly) {
- selectedFeature = VECTOR_SHAPE_TYPES.POINT;
+ selectedFeature = VECTOR_SHAPE_TYPE.POINT;
} else if (this.props.isLinesOnly) {
- selectedFeature = VECTOR_SHAPE_TYPES.LINE;
+ selectedFeature = VECTOR_SHAPE_TYPE.LINE;
}
this.setState({
selectedFeature: selectedFeature,
@@ -414,30 +414,30 @@ export class VectorStyleEditor extends Component {
if (supportedFeatures.length === 1) {
switch (supportedFeatures[0]) {
- case VECTOR_SHAPE_TYPES.POINT:
+ case VECTOR_SHAPE_TYPE.POINT:
return this._renderPointProperties();
- case VECTOR_SHAPE_TYPES.LINE:
+ case VECTOR_SHAPE_TYPE.LINE:
return this._renderLineProperties();
- case VECTOR_SHAPE_TYPES.POLYGON:
+ case VECTOR_SHAPE_TYPE.POLYGON:
return this._renderPolygonProperties();
}
}
const featureButtons = [
{
- id: VECTOR_SHAPE_TYPES.POINT,
+ id: VECTOR_SHAPE_TYPE.POINT,
label: i18n.translate('xpack.maps.vectorStyleEditor.pointLabel', {
defaultMessage: 'Points',
}),
},
{
- id: VECTOR_SHAPE_TYPES.LINE,
+ id: VECTOR_SHAPE_TYPE.LINE,
label: i18n.translate('xpack.maps.vectorStyleEditor.lineLabel', {
defaultMessage: 'Lines',
}),
},
{
- id: VECTOR_SHAPE_TYPES.POLYGON,
+ id: VECTOR_SHAPE_TYPE.POLYGON,
label: i18n.translate('xpack.maps.vectorStyleEditor.polygonLabel', {
defaultMessage: 'Polygons',
}),
@@ -445,9 +445,9 @@ export class VectorStyleEditor extends Component {
];
let styleProperties = this._renderPolygonProperties();
- if (selectedFeature === VECTOR_SHAPE_TYPES.LINE) {
+ if (selectedFeature === VECTOR_SHAPE_TYPE.LINE) {
styleProperties = this._renderLineProperties();
- } else if (selectedFeature === VECTOR_SHAPE_TYPES.POINT) {
+ } else if (selectedFeature === VECTOR_SHAPE_TYPE.POINT) {
styleProperties = this._renderPointProperties();
}
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap
index b4843324a0def0..631a6117a111db 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Should render categorical legend with breaks 1`] = `
+exports[`renderLegendDetailRow Should render categorical legend with breaks 1`] = `
+
+
+ Other
+
+ }
+ styleName="icon"
+ symbolId="square"
+ />
+
`;
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js
index 4c02dee762e9da..556bb2b79e836e 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js
@@ -13,7 +13,7 @@ import {
GRADIENT_INTERVALS,
} from '../../color_utils';
import React from 'react';
-import { COLOR_MAP_TYPE } from '../../../../../common/constants';
+import { COLOR_MAP_TYPE, MB_LOOKUP_FUNCTION } from '../../../../../common/constants';
import {
isCategoricalStopsInvalid,
getOtherCategoryLabel,
@@ -152,7 +152,7 @@ export class DynamicColorProperty extends DynamicStyleProperty {
makeMbClampedNumberExpression({
minValue: rangeFieldMeta.min,
maxValue: rangeFieldMeta.max,
- lookupFunction: 'feature-state',
+ lookupFunction: MB_LOOKUP_FUNCTION.FEATURE_STATE,
fallback: lessThanFirstStopValue,
fieldName: targetName,
}),
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js
index c7620512710dc5..665317569e5e8a 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js
@@ -23,7 +23,7 @@ export class DynamicIconProperty extends DynamicStyleProperty {
getNumberOfCategories() {
const palette = getIconPalette(this._options.iconPaletteId);
- return palette ? palette.length : 0;
+ return palette.length;
}
syncIconWithMb(symbolLayerId, mbMap, iconPixelSize) {
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx
index 505c08ac35ba7e..132c0b3f276034 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx
@@ -34,8 +34,8 @@ const makeProperty = (options: Partial, field: IField = mock
);
};
-describe('DynamicIconProperty', () => {
- it('should derive category number from palettes', async () => {
+describe('getNumberOfCategories', () => {
+ test('should derive category number from palettes', async () => {
const filled = makeProperty({
iconPaletteId: 'filledShapes',
});
@@ -47,15 +47,53 @@ describe('DynamicIconProperty', () => {
});
});
-test('Should render categorical legend with breaks', async () => {
- const iconStyle = makeProperty({
- iconPaletteId: 'filledShapes',
+describe('renderLegendDetailRow', () => {
+ test('Should render categorical legend with breaks', async () => {
+ const iconStyle = makeProperty({
+ iconPaletteId: 'filledShapes',
+ });
+
+ const legendRow = iconStyle.renderLegendDetailRow({ isPointsOnly: true, isLinesOnly: false });
+ const component = shallow(legendRow);
+ await new Promise((resolve) => process.nextTick(resolve));
+ component.update();
+
+ expect(component).toMatchSnapshot();
});
+});
- const legendRow = iconStyle.renderLegendDetailRow({ isPointsOnly: true, isLinesOnly: false });
- const component = shallow(legendRow);
- await new Promise((resolve) => process.nextTick(resolve));
- component.update();
+describe('get mapbox icon-image expression (via internal _getMbIconImageExpression)', () => {
+ describe('categorical icon palette', () => {
+ test('should return mapbox expression for pre-defined icon palette', async () => {
+ const iconStyle = makeProperty({
+ iconPaletteId: 'filledShapes',
+ });
+ expect(iconStyle._getMbIconImageExpression(15)).toEqual([
+ 'match',
+ ['to-string', ['get', 'foobar']],
+ 'US',
+ 'circle-15',
+ 'CN',
+ 'marker-15',
+ 'square-15',
+ ]);
+ });
- expect(component).toMatchSnapshot();
+ test('should return mapbox expression for custom icon palette', async () => {
+ const iconStyle = makeProperty({
+ useCustomIconMap: true,
+ customIconStops: [
+ { stop: null, icon: 'circle' },
+ { stop: 'MX', icon: 'marker' },
+ ],
+ });
+ expect(iconStyle._getMbIconImageExpression(15)).toEqual([
+ 'match',
+ ['to-string', ['get', 'foobar']],
+ 'MX',
+ 'marker-15',
+ 'circle-15',
+ ]);
+ });
+ });
});
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js
index a0af2fbb939d85..662d1ccf33b95d 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js
@@ -12,7 +12,7 @@ import {
LARGE_MAKI_ICON_SIZE,
SMALL_MAKI_ICON_SIZE,
} from '../symbol_utils';
-import { VECTOR_STYLES } from '../../../../../common/constants';
+import { MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants';
import _ from 'lodash';
import React from 'react';
@@ -60,7 +60,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty {
minValue: rangeFieldMeta.min,
maxValue: rangeFieldMeta.max,
fallback: 0,
- lookupFunction: 'get',
+ lookupFunction: MB_LOOKUP_FUNCTION.GET,
fieldName: targetName,
}),
rangeFieldMeta.min,
@@ -109,7 +109,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty {
}
_getMbDataDrivenSize({ targetName, minSize, maxSize, minValue, maxValue }) {
- const lookup = this.supportsMbFeatureState() ? 'feature-state' : 'get';
+ const lookup = this.supportsMbFeatureState()
+ ? MB_LOOKUP_FUNCTION.FEATURE_STATE
+ : MB_LOOKUP_FUNCTION.GET;
const stops =
minValue === maxValue ? [maxValue, maxSize] : [minValue, minSize, maxValue, maxSize];
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js b/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.ts
similarity index 60%
rename from x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js
rename to x-pack/plugins/maps/public/classes/styles/vector/style_util.test.ts
index eb4c6708fb2dd9..6c1f060383d052 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.ts
@@ -5,58 +5,67 @@
*/
import { isOnlySingleFeatureType, assignCategoriesToPalette, dynamicRound } from './style_util';
-import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types';
+import { VECTOR_SHAPE_TYPE } from '../../../../common/constants';
describe('isOnlySingleFeatureType', () => {
describe('source supports single feature type', () => {
- const supportedFeatures = [VECTOR_SHAPE_TYPES.POINT];
+ const supportedFeatures = [VECTOR_SHAPE_TYPE.POINT];
+ const hasFeatureType = {
+ [VECTOR_SHAPE_TYPE.POINT]: false,
+ [VECTOR_SHAPE_TYPE.LINE]: false,
+ [VECTOR_SHAPE_TYPE.POLYGON]: false,
+ };
test('Is only single feature type when only supported feature type is target feature type', () => {
- expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures)).toBe(true);
+ expect(
+ isOnlySingleFeatureType(VECTOR_SHAPE_TYPE.POINT, supportedFeatures, hasFeatureType)
+ ).toBe(true);
});
test('Is not single feature type when only supported feature type is not target feature type', () => {
- expect(isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures)).toBe(false);
+ expect(
+ isOnlySingleFeatureType(VECTOR_SHAPE_TYPE.LINE, supportedFeatures, hasFeatureType)
+ ).toBe(false);
});
});
describe('source supports multiple feature types', () => {
const supportedFeatures = [
- VECTOR_SHAPE_TYPES.POINT,
- VECTOR_SHAPE_TYPES.LINE,
- VECTOR_SHAPE_TYPES.POLYGON,
+ VECTOR_SHAPE_TYPE.POINT,
+ VECTOR_SHAPE_TYPE.LINE,
+ VECTOR_SHAPE_TYPE.POLYGON,
];
test('Is only single feature type when data only has target feature type', () => {
const hasFeatureType = {
- [VECTOR_SHAPE_TYPES.POINT]: true,
- [VECTOR_SHAPE_TYPES.LINE]: false,
- [VECTOR_SHAPE_TYPES.POLYGON]: false,
+ [VECTOR_SHAPE_TYPE.POINT]: true,
+ [VECTOR_SHAPE_TYPE.LINE]: false,
+ [VECTOR_SHAPE_TYPE.POLYGON]: false,
};
expect(
- isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)
+ isOnlySingleFeatureType(VECTOR_SHAPE_TYPE.POINT, supportedFeatures, hasFeatureType)
).toBe(true);
});
test('Is not single feature type when data has multiple feature types', () => {
const hasFeatureType = {
- [VECTOR_SHAPE_TYPES.POINT]: true,
- [VECTOR_SHAPE_TYPES.LINE]: true,
- [VECTOR_SHAPE_TYPES.POLYGON]: true,
+ [VECTOR_SHAPE_TYPE.POINT]: true,
+ [VECTOR_SHAPE_TYPE.LINE]: true,
+ [VECTOR_SHAPE_TYPE.POLYGON]: true,
};
expect(
- isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE, supportedFeatures, hasFeatureType)
+ isOnlySingleFeatureType(VECTOR_SHAPE_TYPE.LINE, supportedFeatures, hasFeatureType)
).toBe(false);
});
test('Is not single feature type when data does not have target feature types', () => {
const hasFeatureType = {
- [VECTOR_SHAPE_TYPES.POINT]: false,
- [VECTOR_SHAPE_TYPES.LINE]: true,
- [VECTOR_SHAPE_TYPES.POLYGON]: false,
+ [VECTOR_SHAPE_TYPE.POINT]: false,
+ [VECTOR_SHAPE_TYPE.LINE]: true,
+ [VECTOR_SHAPE_TYPE.POLYGON]: false,
};
expect(
- isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT, supportedFeatures, hasFeatureType)
+ isOnlySingleFeatureType(VECTOR_SHAPE_TYPE.POINT, supportedFeatures, hasFeatureType)
).toBe(false);
});
});
@@ -64,7 +73,12 @@ describe('isOnlySingleFeatureType', () => {
describe('assignCategoriesToPalette', () => {
test('Categories and palette values have same length', () => {
- const categories = [{ key: 'alpah' }, { key: 'bravo' }, { key: 'charlie' }, { key: 'delta' }];
+ const categories = [
+ { key: 'alpah', count: 1 },
+ { key: 'bravo', count: 1 },
+ { key: 'charlie', count: 1 },
+ { key: 'delta', count: 1 },
+ ];
const paletteValues = ['red', 'orange', 'yellow', 'green'];
expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({
stops: [
@@ -72,31 +86,39 @@ describe('assignCategoriesToPalette', () => {
{ stop: 'bravo', style: 'orange' },
{ stop: 'charlie', style: 'yellow' },
],
- fallback: 'green',
+ fallbackSymbolId: 'green',
});
});
test('Should More categories than palette values', () => {
- const categories = [{ key: 'alpah' }, { key: 'bravo' }, { key: 'charlie' }, { key: 'delta' }];
+ const categories = [
+ { key: 'alpah', count: 1 },
+ { key: 'bravo', count: 1 },
+ { key: 'charlie', count: 1 },
+ { key: 'delta', count: 1 },
+ ];
const paletteValues = ['red', 'orange', 'yellow'];
expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({
stops: [
{ stop: 'alpah', style: 'red' },
{ stop: 'bravo', style: 'orange' },
],
- fallback: 'yellow',
+ fallbackSymbolId: 'yellow',
});
});
test('Less categories than palette values', () => {
- const categories = [{ key: 'alpah' }, { key: 'bravo' }];
+ const categories = [
+ { key: 'alpah', count: 1 },
+ { key: 'bravo', count: 1 },
+ ];
const paletteValues = ['red', 'orange', 'yellow', 'green', 'blue'];
expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({
stops: [
{ stop: 'alpah', style: 'red' },
{ stop: 'bravo', style: 'orange' },
],
- fallback: 'yellow',
+ fallbackSymbolId: 'yellow',
});
});
});
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.js b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts
similarity index 57%
rename from x-pack/plugins/maps/public/classes/styles/vector/style_util.js
rename to x-pack/plugins/maps/public/classes/styles/vector/style_util.ts
index 3b62dcb27dced6..d190a62e6f300a 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts
@@ -5,6 +5,8 @@
*/
import { i18n } from '@kbn/i18n';
+import { MB_LOOKUP_FUNCTION, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
+import { Category } from '../../../../common/descriptor_types';
export function getOtherCategoryLabel() {
return i18n.translate('xpack.maps.styles.categorical.otherCategoryLabel', {
@@ -12,29 +14,32 @@ export function getOtherCategoryLabel() {
});
}
-export function getComputedFieldName(styleName, fieldName) {
+export function getComputedFieldName(styleName: string, fieldName: string) {
return `${getComputedFieldNamePrefix(fieldName)}__${styleName}`;
}
-export function getComputedFieldNamePrefix(fieldName) {
+export function getComputedFieldNamePrefix(fieldName: string) {
return `__kbn__dynamic__${fieldName}`;
}
-export function isOnlySingleFeatureType(featureType, supportedFeatures, hasFeatureType) {
+export function isOnlySingleFeatureType(
+ featureType: VECTOR_SHAPE_TYPE,
+ supportedFeatures: VECTOR_SHAPE_TYPE[],
+ hasFeatureType: { [key in keyof typeof VECTOR_SHAPE_TYPE]: boolean }
+): boolean {
if (supportedFeatures.length === 1) {
return supportedFeatures[0] === featureType;
}
const featureTypes = Object.keys(hasFeatureType);
- return featureTypes.reduce((isOnlyTargetFeatureType, featureTypeKey) => {
+ // @ts-expect-error
+ return featureTypes.reduce((accumulator: boolean, featureTypeKey: VECTOR_SHAPE_TYPE) => {
const hasFeature = hasFeatureType[featureTypeKey];
- return featureTypeKey === featureType
- ? isOnlyTargetFeatureType && hasFeature
- : isOnlyTargetFeatureType && !hasFeature;
+ return featureTypeKey === featureType ? accumulator && hasFeature : accumulator && !hasFeature;
}, true);
}
-export function dynamicRound(value) {
+export function dynamicRound(value: number | string) {
if (typeof value !== 'number') {
return value;
}
@@ -49,13 +54,19 @@ export function dynamicRound(value) {
return precision === 0 ? Math.round(value) : parseFloat(value.toFixed(precision + 1));
}
-export function assignCategoriesToPalette({ categories, paletteValues }) {
+export function assignCategoriesToPalette({
+ categories,
+ paletteValues,
+}: {
+ categories: Category[];
+ paletteValues: string[];
+}) {
const stops = [];
- let fallback = null;
+ let fallbackSymbolId = null;
- if (categories && categories.length && paletteValues && paletteValues.length) {
+ if (categories.length && paletteValues.length) {
const maxLength = Math.min(paletteValues.length, categories.length + 1);
- fallback = paletteValues[maxLength - 1];
+ fallbackSymbolId = paletteValues[maxLength - 1];
for (let i = 0; i < maxLength - 1; i++) {
stops.push({
stop: categories[i].key,
@@ -66,7 +77,7 @@ export function assignCategoriesToPalette({ categories, paletteValues }) {
return {
stops,
- fallback,
+ fallbackSymbolId,
};
}
@@ -76,6 +87,12 @@ export function makeMbClampedNumberExpression({
minValue,
maxValue,
fallback,
+}: {
+ lookupFunction: MB_LOOKUP_FUNCTION;
+ fieldName: string;
+ minValue: number;
+ maxValue: number;
+ fallback: number;
}) {
const clamp = ['max', ['min', ['to-number', [lookupFunction, fieldName]], maxValue], minValue];
return [
@@ -83,7 +100,7 @@ export function makeMbClampedNumberExpression({
[
'case',
['==', [lookupFunction, fieldName], null],
- minValue - 1, //== does a JS-y like check where returns true for null and undefined
+ minValue - 1, // == does a JS-y like check where returns true for null and undefined
clamp,
],
fallback,
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js b/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js
index 1672af8eccff8a..04df9d73d75cd8 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/symbol_utils.js
@@ -140,5 +140,5 @@ export function getIconPaletteOptions(isDarkMode) {
export function getIconPalette(paletteId) {
const palette = ICON_PALETTES.find(({ id }) => id === paletteId);
- return palette ? [...palette.icons] : null;
+ return palette ? [...palette.icons] : [];
}
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.js
index 989ac268c05521..04a5381fa25927 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.js
@@ -16,12 +16,12 @@ import {
SOURCE_FORMATTERS_DATA_REQUEST_ID,
LAYER_STYLE_TYPE,
DEFAULT_ICON,
+ VECTOR_SHAPE_TYPE,
VECTOR_STYLES,
} from '../../../../common/constants';
import { StyleMeta } from './style_meta';
import { VectorIcon } from './components/legend/vector_icon';
import { VectorStyleLegend } from './components/legend/vector_style_legend';
-import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types';
import { getComputedFieldName, isOnlySingleFeatureType } from './style_util';
import { StaticStyleProperty } from './properties/static_style_property';
import { DynamicStyleProperty } from './properties/dynamic_style_property';
@@ -249,24 +249,24 @@ export class VectorStyle extends AbstractStyle {
const supportedFeatures = await this._source.getSupportedShapeTypes();
const hasFeatureType = {
- [VECTOR_SHAPE_TYPES.POINT]: false,
- [VECTOR_SHAPE_TYPES.LINE]: false,
- [VECTOR_SHAPE_TYPES.POLYGON]: false,
+ [VECTOR_SHAPE_TYPE.POINT]: false,
+ [VECTOR_SHAPE_TYPE.LINE]: false,
+ [VECTOR_SHAPE_TYPE.POLYGON]: false,
};
if (supportedFeatures.length > 1) {
for (let i = 0; i < features.length; i++) {
const feature = features[i];
- if (!hasFeatureType[VECTOR_SHAPE_TYPES.POINT] && POINTS.includes(feature.geometry.type)) {
- hasFeatureType[VECTOR_SHAPE_TYPES.POINT] = true;
+ if (!hasFeatureType[VECTOR_SHAPE_TYPE.POINT] && POINTS.includes(feature.geometry.type)) {
+ hasFeatureType[VECTOR_SHAPE_TYPE.POINT] = true;
}
- if (!hasFeatureType[VECTOR_SHAPE_TYPES.LINE] && LINES.includes(feature.geometry.type)) {
- hasFeatureType[VECTOR_SHAPE_TYPES.LINE] = true;
+ if (!hasFeatureType[VECTOR_SHAPE_TYPE.LINE] && LINES.includes(feature.geometry.type)) {
+ hasFeatureType[VECTOR_SHAPE_TYPE.LINE] = true;
}
if (
- !hasFeatureType[VECTOR_SHAPE_TYPES.POLYGON] &&
+ !hasFeatureType[VECTOR_SHAPE_TYPE.POLYGON] &&
POLYGONS.includes(feature.geometry.type)
) {
- hasFeatureType[VECTOR_SHAPE_TYPES.POLYGON] = true;
+ hasFeatureType[VECTOR_SHAPE_TYPE.POLYGON] = true;
}
}
}
@@ -274,17 +274,17 @@ export class VectorStyle extends AbstractStyle {
const styleMeta = {
geometryTypes: {
isPointsOnly: isOnlySingleFeatureType(
- VECTOR_SHAPE_TYPES.POINT,
+ VECTOR_SHAPE_TYPE.POINT,
supportedFeatures,
hasFeatureType
),
isLinesOnly: isOnlySingleFeatureType(
- VECTOR_SHAPE_TYPES.LINE,
+ VECTOR_SHAPE_TYPE.LINE,
supportedFeatures,
hasFeatureType
),
isPolygonsOnly: isOnlySingleFeatureType(
- VECTOR_SHAPE_TYPES.POLYGON,
+ VECTOR_SHAPE_TYPE.POLYGON,
supportedFeatures,
hasFeatureType
),
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js
index 426f1d6afa952d..a0dc07b8e545bb 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js
+++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.test.js
@@ -6,8 +6,7 @@
import { VectorStyle } from './vector_style';
import { DataRequest } from '../../util/data_request';
-import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types';
-import { FIELD_ORIGIN, STYLE_TYPE } from '../../../../common/constants';
+import { FIELD_ORIGIN, STYLE_TYPE, VECTOR_SHAPE_TYPE } from '../../../../common/constants';
jest.mock('../../../kibana_services');
jest.mock('ui/new_platform');
@@ -28,7 +27,7 @@ class MockField {
class MockSource {
constructor({ supportedShapeTypes } = {}) {
- this._supportedShapeTypes = supportedShapeTypes || Object.values(VECTOR_SHAPE_TYPES);
+ this._supportedShapeTypes = supportedShapeTypes || Object.values(VECTOR_SHAPE_TYPE);
}
getSupportedShapeTypes() {
return this._supportedShapeTypes;
diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts
index c73225fc4285b3..8fb0ecb50b28bb 100644
--- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts
+++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts
@@ -12,7 +12,7 @@ import {
IContainer,
} from '../../../../../src/plugins/embeddable/public';
import '../index.scss';
-import { createMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants';
+import { getExistingMapPath, MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants';
import { LayerDescriptor } from '../../common/descriptor_types';
import { MapEmbeddableInput } from './types';
import { lazyLoadMapModules } from '../lazy_load_bundle';
@@ -113,7 +113,7 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition {
{
layerList,
title: savedMap.title,
- editUrl: getHttp().basePath.prepend(createMapPath(savedObjectId)),
+ editUrl: getHttp().basePath.prepend(getExistingMapPath(savedObjectId)),
indexPatterns,
editable: await this.isEditable(),
settings,
diff --git a/x-pack/plugins/maps/public/maps_vis_type_alias.js b/x-pack/plugins/maps/public/maps_vis_type_alias.js
index cb7b3db17eab58..d90674f0f7725b 100644
--- a/x-pack/plugins/maps/public/maps_vis_type_alias.js
+++ b/x-pack/plugins/maps/public/maps_vis_type_alias.js
@@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { APP_ID, APP_ICON, MAP_SAVED_OBJECT_TYPE } from '../common/constants';
+import { APP_ID, APP_ICON, MAP_PATH } from '../common/constants';
import { getShowMapVisualizationTypes, getVisualizations } from './kibana_services';
export function getMapsVisTypeAlias() {
@@ -28,7 +28,7 @@ The Maps app offers more functionality and is easier to use.`,
return {
aliasApp: APP_ID,
- aliasPath: `/${MAP_SAVED_OBJECT_TYPE}`,
+ aliasPath: `/${MAP_PATH}`,
name: APP_ID,
title: i18n.translate('xpack.maps.visTypeAlias.title', {
defaultMessage: 'Maps',
diff --git a/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js b/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js
index f24c7be65afa35..f8c783f673bab9 100644
--- a/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js
+++ b/x-pack/plugins/maps/public/routing/bootstrap/services/saved_gis_map.js
@@ -19,7 +19,7 @@ import {
import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors';
import { copyPersistentState } from '../../../reducers/util';
import { extractReferences, injectReferences } from '../../../../common/migrations/references';
-import { MAP_BASE_URL, MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
+import { getExistingMapPath, MAP_SAVED_OBJECT_TYPE } from '../../../../common/constants';
import { getStore } from '../../store_operations';
export function createSavedGisMapClass(services) {
@@ -76,7 +76,7 @@ export function createSavedGisMapClass(services) {
}
getFullPath() {
- return `${MAP_BASE_URL}/${this.id}`;
+ return getExistingMapPath(this.id);
}
getLayerList() {
diff --git a/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js b/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js
index 36a355719d945c..de2ee42b491715 100644
--- a/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js
+++ b/x-pack/plugins/maps/public/routing/page_elements/breadcrumbs.js
@@ -6,7 +6,7 @@
import { i18n } from '@kbn/i18n';
import { getCoreChrome } from '../../kibana_services';
-import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants';
+import { MAP_PATH } from '../../../common/constants';
import _ from 'lodash';
import { getLayerListRaw } from '../../selectors/map_selectors';
import { copyPersistentState } from '../../reducers/util';
@@ -31,7 +31,7 @@ function hasUnsavedChanges(savedMap, initialLayerListConfig) {
}
export const updateBreadcrumbs = (savedMap, initialLayerListConfig, currentPath = '') => {
- const isOnMapNow = currentPath.startsWith(`/${MAP_SAVED_OBJECT_TYPE}`);
+ const isOnMapNow = currentPath.startsWith(`/${MAP_PATH}`);
const breadCrumbs = isOnMapNow
? [
{
diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts
index f2331b9a1a9600..dbcce50ac2b9af 100644
--- a/x-pack/plugins/maps/server/plugin.ts
+++ b/x-pack/plugins/maps/server/plugin.ts
@@ -14,7 +14,7 @@ import { getFlightsSavedObjects } from './sample_data/flights_saved_objects.js';
// @ts-ignore
import { getWebLogsSavedObjects } from './sample_data/web_logs_saved_objects.js';
import { registerMapsUsageCollector } from './maps_telemetry/collectors/register';
-import { APP_ID, APP_ICON, MAP_SAVED_OBJECT_TYPE, createMapPath } from '../common/constants';
+import { APP_ID, APP_ICON, MAP_SAVED_OBJECT_TYPE, getExistingMapPath } from '../common/constants';
import { mapSavedObjects, mapsTelemetrySavedObjects } from './saved_objects';
import { MapsXPackConfig } from '../config';
// @ts-ignore
@@ -58,7 +58,7 @@ export class MapsPlugin implements Plugin {
home.sampleData.addAppLinksToSampleDataset('ecommerce', [
{
- path: createMapPath('2c9c1f60-1909-11e9-919b-ffe5949a18d2'),
+ path: getExistingMapPath('2c9c1f60-1909-11e9-919b-ffe5949a18d2'),
label: sampleDataLinkLabel,
icon: APP_ICON,
},
@@ -80,7 +80,7 @@ export class MapsPlugin implements Plugin {
home.sampleData.addAppLinksToSampleDataset('flights', [
{
- path: createMapPath('5dd88580-1906-11e9-919b-ffe5949a18d2'),
+ path: getExistingMapPath('5dd88580-1906-11e9-919b-ffe5949a18d2'),
label: sampleDataLinkLabel,
icon: APP_ICON,
},
@@ -101,7 +101,7 @@ export class MapsPlugin implements Plugin {
home.sampleData.addSavedObjectsToSampleDataset('logs', getWebLogsSavedObjects());
home.sampleData.addAppLinksToSampleDataset('logs', [
{
- path: createMapPath('de71f4f0-1902-11e9-919b-ffe5949a18d2'),
+ path: getExistingMapPath('de71f4f0-1902-11e9-919b-ffe5949a18d2'),
label: sampleDataLinkLabel,
icon: APP_ICON,
},
diff --git a/x-pack/plugins/maps/server/saved_objects/map.ts b/x-pack/plugins/maps/server/saved_objects/map.ts
index 0fcadc5a972033..ce9d5791378647 100644
--- a/x-pack/plugins/maps/server/saved_objects/map.ts
+++ b/x-pack/plugins/maps/server/saved_objects/map.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SavedObjectsType } from 'src/core/server';
-import { APP_ICON, createMapPath } from '../../common/constants';
+import { APP_ICON, getExistingMapPath } from '../../common/constants';
// @ts-ignore
import { migrations } from './migrations';
@@ -31,7 +31,7 @@ export const mapSavedObjects: SavedObjectsType = {
},
getInAppUrl(obj) {
return {
- path: createMapPath(obj.id),
+ path: getExistingMapPath(obj.id),
uiCapabilitiesPath: 'maps.show',
};
},
diff --git a/x-pack/plugins/maps/server/tutorials/ems/index.ts b/x-pack/plugins/maps/server/tutorials/ems/index.ts
index e96af89e526851..be15120cb19e14 100644
--- a/x-pack/plugins/maps/server/tutorials/ems/index.ts
+++ b/x-pack/plugins/maps/server/tutorials/ems/index.ts
@@ -6,7 +6,7 @@
import { i18n } from '@kbn/i18n';
import { TutorialsCategory } from '../../../../../../src/plugins/home/server';
-import { MAP_BASE_URL } from '../../../common/constants';
+import { getNewMapPath } from '../../../common/constants';
export function emsBoundariesSpecProvider({
emsLandingPageUrl,
@@ -64,7 +64,7 @@ Indexing EMS administrative boundaries in Elasticsearch allows for search on bou
2. Click `Add layer`, then select `Upload GeoJSON`.\n\
3. Upload the GeoJSON file and click `Import file`.',
values: {
- newMapUrl: prependBasePath(MAP_BASE_URL),
+ newMapUrl: prependBasePath(getNewMapPath()),
},
}),
},
diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts
index 9acd359fa0db4e..eccd6c7db16989 100644
--- a/x-pack/plugins/reporting/server/core.ts
+++ b/x-pack/plugins/reporting/server/core.ts
@@ -24,6 +24,7 @@ import { screenshotsObservableFactory } from './export_types/common/lib/screensh
import { checkLicense, getExportTypesRegistry } from './lib';
import { ESQueueInstance } from './lib/create_queue';
import { EnqueueJobFn } from './lib/enqueue_job';
+import { ReportingStore } from './lib/store';
export interface ReportingInternalSetup {
elasticsearch: ElasticsearchServiceSetup;
@@ -37,6 +38,7 @@ export interface ReportingInternalStart {
browserDriverFactory: HeadlessChromiumDriverFactory;
enqueueJob: EnqueueJobFn;
esqueue: ESQueueInstance;
+ store: ReportingStore;
savedObjects: SavedObjectsServiceStart;
uiSettings: UiSettingsServiceStart;
}
diff --git a/x-pack/plugins/reporting/server/lib/create_queue.ts b/x-pack/plugins/reporting/server/lib/create_queue.ts
index 5d09af312a41b4..a8dcb92c55b2de 100644
--- a/x-pack/plugins/reporting/server/lib/create_queue.ts
+++ b/x-pack/plugins/reporting/server/lib/create_queue.ts
@@ -8,17 +8,16 @@ import { ReportingCore } from '../core';
import { JobSource, TaskRunResult } from '../types';
import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed
import { createWorkerFactory } from './create_worker';
-import { Job } from './enqueue_job';
// @ts-ignore
import { Esqueue } from './esqueue';
import { LevelLogger } from './level_logger';
+import { ReportingStore } from './store';
interface ESQueueWorker {
on: (event: string, handler: any) => void;
}
export interface ESQueueInstance {
- addJob: (type: string, payload: unknown, options: object) => Job;
registerWorker: (
pluginId: string,
workerFn: GenericWorkerFn,
@@ -37,26 +36,25 @@ type GenericWorkerFn = (
...workerRestArgs: any[]
) => void | Promise;
-export async function createQueueFactory(
+export async function createQueueFactory(
reporting: ReportingCore,
+ store: ReportingStore,
logger: LevelLogger
): Promise {
const config = reporting.getConfig();
- const queueIndexInterval = config.get('queue', 'indexInterval');
+
+ // esqueue-related
const queueTimeout = config.get('queue', 'timeout');
- const queueIndex = config.get('index');
const isPollingEnabled = config.get('queue', 'pollEnabled');
- const elasticsearch = await reporting.getElasticsearchService();
+ const elasticsearch = reporting.getElasticsearchService();
const queueOptions = {
- interval: queueIndexInterval,
timeout: queueTimeout,
- dateSeparator: '.',
client: elasticsearch.legacy.client,
logger: createTaggedLogger(logger, ['esqueue', 'queue-worker']),
};
- const queue: ESQueueInstance = new Esqueue(queueIndex, queueOptions);
+ const queue: ESQueueInstance = new Esqueue(store, queueOptions);
if (isPollingEnabled) {
// create workers to poll the index for idle jobs waiting to be claimed and executed
diff --git a/x-pack/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/plugins/reporting/server/lib/enqueue_job.ts
index 625da90f3b4f23..d1554a03b9389a 100644
--- a/x-pack/plugins/reporting/server/lib/enqueue_job.ts
+++ b/x-pack/plugins/reporting/server/lib/enqueue_job.ts
@@ -4,39 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EventEmitter } from 'events';
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
import { AuthenticatedUser } from '../../../security/server';
import { ESQueueCreateJobFn } from '../../server/types';
import { ReportingCore } from '../core';
-// @ts-ignore
-import { events as esqueueEvents } from './esqueue';
-import { LevelLogger } from './level_logger';
+import { LevelLogger } from './';
+import { ReportingStore, Report } from './store';
-interface ConfirmedJob {
- id: string;
- index: string;
- _seq_no: number;
- _primary_term: number;
-}
-
-export type Job = EventEmitter & {
- id: string;
- toJSON: () => {
- id: string;
- };
-};
-
-export type EnqueueJobFn = (
+export type EnqueueJobFn = (
exportTypeId: string,
- jobParams: JobParamsType,
+ jobParams: unknown,
user: AuthenticatedUser | null,
context: RequestHandlerContext,
request: KibanaRequest
-) => Promise;
+) => Promise;
export function enqueueJobFactory(
reporting: ReportingCore,
+ store: ReportingStore,
parentLogger: LevelLogger
): EnqueueJobFn {
const config = reporting.getConfig();
@@ -45,16 +30,16 @@ export function enqueueJobFactory(
const maxAttempts = config.get('capture', 'maxAttempts');
const logger = parentLogger.clone(['queue-job']);
- return async function enqueueJob(
+ return async function enqueueJob(
exportTypeId: string,
- jobParams: JobParamsType,
+ jobParams: unknown,
user: AuthenticatedUser | null,
context: RequestHandlerContext,
request: KibanaRequest
- ): Promise {
- type ScheduleTaskFnType = ESQueueCreateJobFn;
+ ) {
+ type ScheduleTaskFnType = ESQueueCreateJobFn;
+
const username = user ? user.username : false;
- const esqueue = await reporting.getEsqueue();
const exportType = reporting.getExportTypesRegistry().getById(exportTypeId);
if (exportType == null) {
@@ -71,16 +56,6 @@ export function enqueueJobFactory(
max_attempts: maxAttempts,
};
- return new Promise((resolve, reject) => {
- const job = esqueue.addJob(exportType.jobType, payload, options);
-
- job.on(esqueueEvents.EVENT_JOB_CREATED, (createdJob: ConfirmedJob) => {
- if (createdJob.id === job.id) {
- logger.info(`Successfully queued job: ${createdJob.id}`);
- resolve(job);
- }
- });
- job.on(esqueueEvents.EVENT_JOB_CREATE_ERROR, reject);
- });
+ return await store.addReport(exportType.jobType, payload, options);
};
}
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js
deleted file mode 100644
index 691bd4f618a1c8..00000000000000
--- a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/create_index.js
+++ /dev/null
@@ -1,100 +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 expect from '@kbn/expect';
-import sinon from 'sinon';
-import { createIndex } from '../../helpers/create_index';
-import { ClientMock } from '../fixtures/legacy_elasticsearch';
-import { constants } from '../../constants';
-
-describe('Create Index', function () {
- describe('Does not exist', function () {
- let client;
- let createSpy;
-
- beforeEach(function () {
- client = new ClientMock();
- createSpy = sinon.spy(client, 'callAsInternalUser').withArgs('indices.create');
- });
-
- it('should return true', function () {
- const indexName = 'test-index';
- const result = createIndex(client, indexName);
-
- return result.then((exists) => expect(exists).to.be(true));
- });
-
- it('should create the index with mappings and default settings', function () {
- const indexName = 'test-index';
- const settings = constants.DEFAULT_SETTING_INDEX_SETTINGS;
- const result = createIndex(client, indexName);
-
- return result.then(function () {
- const payload = createSpy.getCall(0).args[1];
- sinon.assert.callCount(createSpy, 1);
- expect(payload).to.have.property('index', indexName);
- expect(payload).to.have.property('body');
- expect(payload.body).to.have.property('settings');
- expect(payload.body.settings).to.eql(settings);
- expect(payload.body).to.have.property('mappings');
- expect(payload.body.mappings).to.have.property('properties');
- });
- });
-
- it('should create the index with custom settings', function () {
- const indexName = 'test-index';
- const settings = {
- ...constants.DEFAULT_SETTING_INDEX_SETTINGS,
- auto_expand_replicas: false,
- number_of_shards: 3000,
- number_of_replicas: 1,
- format: '3000',
- };
- const result = createIndex(client, indexName, settings);
-
- return result.then(function () {
- const payload = createSpy.getCall(0).args[1];
- sinon.assert.callCount(createSpy, 1);
- expect(payload).to.have.property('index', indexName);
- expect(payload).to.have.property('body');
- expect(payload.body).to.have.property('settings');
- expect(payload.body.settings).to.eql(settings);
- expect(payload.body).to.have.property('mappings');
- expect(payload.body.mappings).to.have.property('properties');
- });
- });
- });
-
- describe('Does exist', function () {
- let client;
- let createSpy;
-
- beforeEach(function () {
- client = new ClientMock();
- sinon
- .stub(client, 'callAsInternalUser')
- .withArgs('indices.exists')
- .callsFake(() => Promise.resolve(true));
- createSpy = client.callAsInternalUser.withArgs('indices.create');
- });
-
- it('should return true', function () {
- const indexName = 'test-index';
- const result = createIndex(client, indexName);
-
- return result.then((exists) => expect(exists).to.be(true));
- });
-
- it('should not create the index', function () {
- const indexName = 'test-index';
- const result = createIndex(client, indexName);
-
- return result.then(function () {
- sinon.assert.callCount(createSpy, 0);
- });
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js
deleted file mode 100644
index 71dc8a363e4298..00000000000000
--- a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/helpers/index_timestamp.js
+++ /dev/null
@@ -1,93 +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 expect from '@kbn/expect';
-import sinon from 'sinon';
-import moment from 'moment';
-import { constants } from '../../constants';
-import { indexTimestamp } from '../../helpers/index_timestamp';
-
-const anchor = '2016-04-02T01:02:03.456'; // saturday
-
-describe('Index timestamp interval', function () {
- describe('construction', function () {
- it('should throw given an invalid interval', function () {
- const init = () => indexTimestamp('bananas');
- expect(init).to.throwException(/invalid.+interval/i);
- });
- });
-
- describe('timestamps', function () {
- let clock;
- let separator;
-
- beforeEach(function () {
- separator = constants.DEFAULT_SETTING_DATE_SEPARATOR;
- clock = sinon.useFakeTimers(moment(anchor).valueOf());
- });
-
- afterEach(function () {
- clock.restore();
- });
-
- describe('formats', function () {
- it('should return the year', function () {
- const timestamp = indexTimestamp('year');
- const str = `2016`;
- expect(timestamp).to.equal(str);
- });
-
- it('should return the year and month', function () {
- const timestamp = indexTimestamp('month');
- const str = `2016${separator}04`;
- expect(timestamp).to.equal(str);
- });
-
- it('should return the year, month, and first day of the week', function () {
- const timestamp = indexTimestamp('week');
- const str = `2016${separator}03${separator}27`;
- expect(timestamp).to.equal(str);
- });
-
- it('should return the year, month, and day of the week', function () {
- const timestamp = indexTimestamp('day');
- const str = `2016${separator}04${separator}02`;
- expect(timestamp).to.equal(str);
- });
-
- it('should return the year, month, day and hour', function () {
- const timestamp = indexTimestamp('hour');
- const str = `2016${separator}04${separator}02${separator}01`;
- expect(timestamp).to.equal(str);
- });
-
- it('should return the year, month, day, hour and minute', function () {
- const timestamp = indexTimestamp('minute');
- const str = `2016${separator}04${separator}02${separator}01${separator}02`;
- expect(timestamp).to.equal(str);
- });
- });
-
- describe('date separator', function () {
- it('should be customizable', function () {
- const separators = ['-', '.', '_'];
- separators.forEach((customSep) => {
- const str = `2016${customSep}04${customSep}02${customSep}01${customSep}02`;
- const timestamp = indexTimestamp('minute', customSep);
- expect(timestamp).to.equal(str);
- });
- });
-
- it('should throw if a letter is used', function () {
- const separators = ['a', 'B', 'YYYY'];
- separators.forEach((customSep) => {
- const fn = () => indexTimestamp('minute', customSep);
- expect(fn).to.throwException();
- });
- });
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/job.js b/x-pack/plugins/reporting/server/lib/esqueue/__tests__/job.js
deleted file mode 100644
index 955eed8d657226..00000000000000
--- a/x-pack/plugins/reporting/server/lib/esqueue/__tests__/job.js
+++ /dev/null
@@ -1,420 +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 events from 'events';
-import expect from '@kbn/expect';
-import sinon from 'sinon';
-import proxyquire from 'proxyquire';
-import { QueueMock } from './fixtures/queue';
-import { ClientMock } from './fixtures/legacy_elasticsearch';
-import { constants } from '../constants';
-
-const createIndexMock = sinon.stub();
-const { Job } = proxyquire.noPreserveCache()('../job', {
- './helpers/create_index': { createIndex: createIndexMock },
-});
-
-const maxPriority = 20;
-const minPriority = -20;
-const defaultPriority = 10;
-const defaultCreatedBy = false;
-
-function validateDoc(spy) {
- sinon.assert.callCount(spy, 1);
- const spyCall = spy.getCall(0);
- return spyCall.args[1];
-}
-
-describe('Job Class', function () {
- let mockQueue;
- let client;
- let index;
-
- let type;
- let payload;
- let options;
-
- beforeEach(function () {
- createIndexMock.resetHistory();
- createIndexMock.returns(Promise.resolve('mock'));
- index = 'test';
-
- client = new ClientMock();
- mockQueue = new QueueMock();
- mockQueue.setClient(client);
- });
-
- it('should be an event emitter', function () {
- const job = new Job(mockQueue, index, 'test', {});
- expect(job).to.be.an(events.EventEmitter);
- });
-
- describe('invalid construction', function () {
- it('should throw with a missing type', function () {
- const init = () => new Job(mockQueue, index);
- expect(init).to.throwException(/type.+string/i);
- });
-
- it('should throw with an invalid type', function () {
- const init = () => new Job(mockQueue, index, { 'not a string': true });
- expect(init).to.throwException(/type.+string/i);
- });
-
- it('should throw with an invalid payload', function () {
- const init = () => new Job(mockQueue, index, 'type1', [1, 2, 3]);
- expect(init).to.throwException(/plain.+object/i);
- });
-
- it(`should throw error if invalid maxAttempts`, function () {
- const init = () => new Job(mockQueue, index, 'type1', { id: '123' }, { max_attempts: -1 });
- expect(init).to.throwException(/invalid.+max_attempts/i);
- });
- });
-
- describe('construction', function () {
- let indexSpy;
- beforeEach(function () {
- type = 'type1';
- payload = { id: '123' };
- indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index');
- });
-
- it('should create the target index', function () {
- const job = new Job(mockQueue, index, type, payload, options);
- return job.ready.then(() => {
- sinon.assert.calledOnce(createIndexMock);
- const args = createIndexMock.getCall(0).args;
- expect(args[0]).to.equal(client);
- expect(args[1]).to.equal(index);
- });
- });
-
- it('should index the payload', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs).to.have.property('index', index);
- expect(indexArgs).to.have.property('body');
- expect(indexArgs.body).to.have.property('payload', payload);
- });
- });
-
- it('should index the job type', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs).to.have.property('index', index);
- expect(indexArgs).to.have.property('body');
- expect(indexArgs.body).to.have.property('jobtype', type);
- });
- });
-
- it('should set event creation time', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('created_at');
- });
- });
-
- it('should refresh the index', function () {
- const refreshSpy = client.callAsInternalUser.withArgs('indices.refresh');
-
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- sinon.assert.calledOnce(refreshSpy);
- const spyCall = refreshSpy.getCall(0);
- expect(spyCall.args[1]).to.have.property('index', index);
- });
- });
-
- it('should emit the job information on success', function (done) {
- const job = new Job(mockQueue, index, type, payload);
- job.once(constants.EVENT_JOB_CREATED, (jobDoc) => {
- try {
- expect(jobDoc).to.have.property('id');
- expect(jobDoc).to.have.property('index');
- expect(jobDoc).to.have.property('_seq_no');
- expect(jobDoc).to.have.property('_primary_term');
- done();
- } catch (e) {
- done(e);
- }
- });
- });
-
- it('should emit error on index creation failure', function (done) {
- const errMsg = 'test index creation failure';
-
- createIndexMock.returns(Promise.reject(new Error(errMsg)));
- const job = new Job(mockQueue, index, type, payload);
-
- job.once(constants.EVENT_JOB_CREATE_ERROR, (err) => {
- try {
- expect(err.message).to.equal(errMsg);
- done();
- } catch (e) {
- done(e);
- }
- });
- });
-
- it('should emit error on client index failure', function (done) {
- const errMsg = 'test document index failure';
-
- client.callAsInternalUser.restore();
- sinon
- .stub(client, 'callAsInternalUser')
- .withArgs('index')
- .callsFake(() => Promise.reject(new Error(errMsg)));
- const job = new Job(mockQueue, index, type, payload);
-
- job.once(constants.EVENT_JOB_CREATE_ERROR, (err) => {
- try {
- expect(err.message).to.equal(errMsg);
- done();
- } catch (e) {
- done(e);
- }
- });
- });
- });
-
- describe('event emitting', function () {
- it('should trigger events on the queue instance', function (done) {
- const eventName = 'test event';
- const payload1 = {
- test: true,
- deep: { object: 'ok' },
- };
- const payload2 = 'two';
- const payload3 = new Error('test error');
-
- const job = new Job(mockQueue, index, type, payload, options);
-
- mockQueue.on(eventName, (...args) => {
- try {
- expect(args[0]).to.equal(payload1);
- expect(args[1]).to.equal(payload2);
- expect(args[2]).to.equal(payload3);
- done();
- } catch (e) {
- done(e);
- }
- });
-
- job.emit(eventName, payload1, payload2, payload3);
- });
- });
-
- describe('default values', function () {
- let indexSpy;
- beforeEach(function () {
- type = 'type1';
- payload = { id: '123' };
- indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index');
- });
-
- it('should set attempt count to 0', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('attempts', 0);
- });
- });
-
- it('should index default created_by value', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('created_by', defaultCreatedBy);
- });
- });
-
- it('should set an expired process_expiration time', function () {
- const now = new Date().getTime();
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('process_expiration');
- expect(indexArgs.body.process_expiration.getTime()).to.be.lessThan(now);
- });
- });
-
- it('should set status as pending', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('status', constants.JOB_STATUS_PENDING);
- });
- });
-
- it('should have a default priority of 10', function () {
- const job = new Job(mockQueue, index, type, payload, options);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('priority', defaultPriority);
- });
- });
-
- it('should set a browser type', function () {
- const job = new Job(mockQueue, index, type, payload);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('browser_type');
- });
- });
- });
-
- describe('option passing', function () {
- let indexSpy;
- beforeEach(function () {
- type = 'type1';
- payload = { id: '123' };
- options = {
- timeout: 4567,
- max_attempts: 9,
- headers: {
- authorization: 'Basic cXdlcnR5',
- },
- };
- indexSpy = sinon.spy(client, 'callAsInternalUser').withArgs('index');
- });
-
- it('should index the created_by value', function () {
- const createdBy = 'user_identifier';
- const job = new Job(mockQueue, index, type, payload, {
- created_by: createdBy,
- ...options,
- });
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('created_by', createdBy);
- });
- });
-
- it('should index timeout value from options', function () {
- const job = new Job(mockQueue, index, type, payload, options);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('timeout', options.timeout);
- });
- });
-
- it('should set max attempt count', function () {
- const job = new Job(mockQueue, index, type, payload, options);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('max_attempts', options.max_attempts);
- });
- });
-
- it('should add headers to the request params', function () {
- const job = new Job(mockQueue, index, type, payload, options);
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs).to.have.property('headers', options.headers);
- });
- });
-
- it(`should use upper priority of ${maxPriority}`, function () {
- const job = new Job(mockQueue, index, type, payload, { priority: maxPriority * 2 });
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('priority', maxPriority);
- });
- });
-
- it(`should use lower priority of ${minPriority}`, function () {
- const job = new Job(mockQueue, index, type, payload, { priority: minPriority * 2 });
- return job.ready.then(() => {
- const indexArgs = validateDoc(indexSpy);
- expect(indexArgs.body).to.have.property('priority', minPriority);
- });
- });
- });
-
- describe('get method', function () {
- beforeEach(function () {
- type = 'type2';
- payload = { id: '123' };
- });
-
- it('should return the job document', function () {
- const job = new Job(mockQueue, index, type, payload);
-
- return job.get().then((doc) => {
- const jobDoc = job.document; // document should be resolved
- expect(doc).to.have.property('index', index);
- expect(doc).to.have.property('id', jobDoc.id);
- expect(doc).to.have.property('_seq_no', jobDoc._seq_no);
- expect(doc).to.have.property('_primary_term', jobDoc._primary_term);
- expect(doc).to.have.property('created_by', defaultCreatedBy);
-
- expect(doc).to.have.property('payload');
- expect(doc).to.have.property('jobtype');
- expect(doc).to.have.property('priority');
- expect(doc).to.have.property('timeout');
- });
- });
-
- it('should contain optional data', function () {
- const optionals = {
- created_by: 'some_ident',
- };
-
- const job = new Job(mockQueue, index, type, payload, optionals);
- return Promise.resolve(client.callAsInternalUser('get', {}, optionals))
- .then((doc) => {
- sinon.stub(client, 'callAsInternalUser').withArgs('get').returns(Promise.resolve(doc));
- })
- .then(() => {
- return job.get().then((doc) => {
- expect(doc).to.have.property('created_by', optionals.created_by);
- });
- });
- });
- });
-
- describe('toJSON method', function () {
- beforeEach(function () {
- type = 'type2';
- payload = { id: '123' };
- options = {
- timeout: 4567,
- max_attempts: 9,
- priority: 8,
- };
- });
-
- it('should return the static information about the job', function () {
- const job = new Job(mockQueue, index, type, payload, options);
-
- // toJSON is sync, should work before doc is written to elasticsearch
- expect(job.document).to.be(undefined);
-
- const doc = job.toJSON();
- expect(doc).to.have.property('index', index);
- expect(doc).to.have.property('jobtype', type);
- expect(doc).to.have.property('created_by', defaultCreatedBy);
- expect(doc).to.have.property('timeout', options.timeout);
- expect(doc).to.have.property('max_attempts', options.max_attempts);
- expect(doc).to.have.property('priority', options.priority);
- expect(doc).to.have.property('id');
- expect(doc).to.not.have.property('version');
- });
-
- it('should contain optional data', function () {
- const optionals = {
- created_by: 'some_ident',
- };
-
- const job = new Job(mockQueue, index, type, payload, optionals);
- const doc = job.toJSON();
- expect(doc).to.have.property('created_by', optionals.created_by);
- });
- });
-});
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/index.js b/x-pack/plugins/reporting/server/lib/esqueue/index.js
index 735d19f8f6c473..0fbcb54c673dd3 100644
--- a/x-pack/plugins/reporting/server/lib/esqueue/index.js
+++ b/x-pack/plugins/reporting/server/lib/esqueue/index.js
@@ -5,20 +5,17 @@
*/
import { EventEmitter } from 'events';
-import { Job } from './job';
import { Worker } from './worker';
import { constants } from './constants';
-import { indexTimestamp } from './helpers/index_timestamp';
import { omit } from 'lodash';
export { events } from './constants/events';
export class Esqueue extends EventEmitter {
- constructor(index, options = {}) {
- if (!index) throw new Error('Must specify an index to write to');
-
+ constructor(store, options = {}) {
super();
- this.index = index;
+ this.store = store; // for updating jobs in ES
+ this.index = this.store.indexPrefix; // for polling for pending jobs
this.settings = {
interval: constants.DEFAULT_SETTING_INTERVAL,
timeout: constants.DEFAULT_SETTING_TIMEOUT,
@@ -40,21 +37,6 @@ export class Esqueue extends EventEmitter {
});
}
- addJob(jobtype, payload, opts = {}) {
- const timestamp = indexTimestamp(this.settings.interval, this.settings.dateSeparator);
- const index = `${this.index}-${timestamp}`;
- const defaults = {
- timeout: this.settings.timeout,
- };
-
- const options = Object.assign(defaults, opts, {
- indexSettings: this.settings.indexSettings,
- logger: this._logger,
- });
-
- return new Job(this, index, jobtype, payload, options);
- }
-
registerWorker(type, workerFn, opts) {
const worker = new Worker(this, type, workerFn, { ...opts, logger: this._logger });
this._workers.push(worker);
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/job.js b/x-pack/plugins/reporting/server/lib/esqueue/job.js
deleted file mode 100644
index 6ab78eeb1b86ba..00000000000000
--- a/x-pack/plugins/reporting/server/lib/esqueue/job.js
+++ /dev/null
@@ -1,142 +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 events from 'events';
-import Puid from 'puid';
-import { constants } from './constants';
-import { createIndex } from './helpers/create_index';
-import { isPlainObject } from 'lodash';
-
-const puid = new Puid();
-
-export class Job extends events.EventEmitter {
- constructor(queue, index, jobtype, payload, options = {}) {
- if (typeof jobtype !== 'string') throw new Error('Jobtype must be a string');
- if (!isPlainObject(payload)) throw new Error('Payload must be a plain object');
-
- super();
-
- this.queue = queue;
- this._client = this.queue.client;
- this.id = puid.generate();
- this.index = index;
- this.jobtype = jobtype;
- this.payload = payload;
- this.created_by = options.created_by || false;
- this.timeout = options.timeout || 10000;
- this.maxAttempts = options.max_attempts || 3;
- this.priority = Math.max(Math.min(options.priority || 10, 20), -20);
- this.indexSettings = options.indexSettings || {};
- this.browser_type = options.browser_type;
-
- if (typeof this.maxAttempts !== 'number' || this.maxAttempts < 1) {
- throw new Error(`Invalid max_attempts: ${this.maxAttempts}`);
- }
-
- this.debug = (msg, err) => {
- const logger = options.logger || function () {};
- const message = `${this.id} - ${msg}`;
- const tags = ['debug'];
-
- if (err) {
- logger(`${message}: ${err}`, tags);
- return;
- }
-
- logger(message, tags);
- };
-
- const indexParams = {
- index: this.index,
- id: this.id,
- body: {
- jobtype: this.jobtype,
- meta: {
- // We are copying these values out of payload because these fields are indexed and can be aggregated on
- // for tracking stats, while payload contents are not.
- objectType: payload.objectType,
- layout: payload.layout ? payload.layout.id : 'none',
- },
- payload: this.payload,
- priority: this.priority,
- created_by: this.created_by,
- timeout: this.timeout,
- process_expiration: new Date(0), // use epoch so the job query works
- created_at: new Date(),
- attempts: 0,
- max_attempts: this.maxAttempts,
- status: constants.JOB_STATUS_PENDING,
- browser_type: this.browser_type,
- },
- };
-
- if (options.headers) {
- indexParams.headers = options.headers;
- }
-
- this.ready = createIndex(this._client, this.index, this.indexSettings)
- .then(() => this._client.callAsInternalUser('index', indexParams))
- .then((doc) => {
- this.document = {
- id: doc._id,
- index: doc._index,
- _seq_no: doc._seq_no,
- _primary_term: doc._primary_term,
- };
- this.debug(`Job created in index ${this.index}`);
-
- return this._client
- .callAsInternalUser('indices.refresh', {
- index: this.index,
- })
- .then(() => {
- this.debug(`Job index refreshed ${this.index}`);
- this.emit(constants.EVENT_JOB_CREATED, this.document);
- });
- })
- .catch((err) => {
- this.debug('Job creation failed', err);
- this.emit(constants.EVENT_JOB_CREATE_ERROR, err);
- });
- }
-
- emit(name, ...args) {
- super.emit(name, ...args);
- this.queue.emit(name, ...args);
- }
-
- get() {
- return this.ready
- .then(() => {
- return this._client.callAsInternalUser('get', {
- index: this.index,
- id: this.id,
- });
- })
- .then((doc) => {
- return Object.assign(doc._source, {
- index: doc._index,
- id: doc._id,
- _seq_no: doc._seq_no,
- _primary_term: doc._primary_term,
- });
- });
- }
-
- toJSON() {
- return {
- id: this.id,
- index: this.index,
- jobtype: this.jobtype,
- created_by: this.created_by,
- payload: this.payload,
- timeout: this.timeout,
- max_attempts: this.maxAttempts,
- priority: this.priority,
- browser_type: this.browser_type,
- };
- }
-}
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/plugins/reporting/server/lib/esqueue/worker.js
index b26ed731c68315..469bafd6946122 100644
--- a/x-pack/plugins/reporting/server/lib/esqueue/worker.js
+++ b/x-pack/plugins/reporting/server/lib/esqueue/worker.js
@@ -158,8 +158,8 @@ export class Worker extends events.EventEmitter {
kibana_name: this.kibanaName,
};
- return this._client
- .callAsInternalUser('update', {
+ return this.queue.store
+ .updateReport({
index: job._index,
id: job._id,
if_seq_no: job._seq_no,
@@ -197,8 +197,8 @@ export class Worker extends events.EventEmitter {
output: docOutput,
});
- return this._client
- .callAsInternalUser('update', {
+ return this.queue.store
+ .updateReport({
index: job._index,
id: job._id,
if_seq_no: job._seq_no,
@@ -294,8 +294,8 @@ export class Worker extends events.EventEmitter {
output: docOutput,
};
- return this._client
- .callAsInternalUser('update', {
+ return this.queue.store
+ .updateReport({
index: job._index,
id: job._id,
if_seq_no: job._seq_no,
diff --git a/x-pack/plugins/reporting/server/lib/index.ts b/x-pack/plugins/reporting/server/lib/index.ts
index 0e9c49b1708877..f5a50fca28b7a6 100644
--- a/x-pack/plugins/reporting/server/lib/index.ts
+++ b/x-pack/plugins/reporting/server/lib/index.ts
@@ -12,3 +12,4 @@ export { enqueueJobFactory } from './enqueue_job';
export { getExportTypesRegistry } from './export_types_registry';
export { runValidations } from './validate';
export { startTrace } from './trace';
+export { ReportingStore } from './store';
diff --git a/x-pack/plugins/maps/public/classes/sources/vector_feature_types.ts b/x-pack/plugins/reporting/server/lib/store/index.ts
similarity index 72%
rename from x-pack/plugins/maps/public/classes/sources/vector_feature_types.ts
rename to x-pack/plugins/reporting/server/lib/store/index.ts
index 9f03357e17dad5..a88d36d3fdf9ae 100644
--- a/x-pack/plugins/maps/public/classes/sources/vector_feature_types.ts
+++ b/x-pack/plugins/reporting/server/lib/store/index.ts
@@ -4,8 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export enum VECTOR_SHAPE_TYPES {
- POINT = 'POINT',
- LINE = 'LINE',
- POLYGON = 'POLYGON',
-}
+export { Report } from './report';
+export { ReportingStore } from './store';
diff --git a/x-pack/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js b/x-pack/plugins/reporting/server/lib/store/index_timestamp.ts
similarity index 80%
rename from x-pack/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js
rename to x-pack/plugins/reporting/server/lib/store/index_timestamp.ts
index ceb4ef43b2d9df..71ce0b1e572f84 100644
--- a/x-pack/plugins/reporting/server/lib/esqueue/helpers/index_timestamp.js
+++ b/x-pack/plugins/reporting/server/lib/store/index_timestamp.ts
@@ -4,19 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import moment from 'moment';
+import moment, { unitOfTime } from 'moment';
export const intervals = ['year', 'month', 'week', 'day', 'hour', 'minute'];
// TODO: This helper function can be removed by using `schema.duration` objects in the reporting config schema
-export function indexTimestamp(intervalStr, separator = '-') {
+export function indexTimestamp(intervalStr: string, separator = '-') {
+ const startOf = intervalStr as unitOfTime.StartOf;
if (separator.match(/[a-z]/i)) throw new Error('Interval separator can not be a letter');
const index = intervals.indexOf(intervalStr);
- if (index === -1) throw new Error('Invalid index interval: ', intervalStr);
+ if (index === -1) throw new Error('Invalid index interval: ' + intervalStr);
const m = moment();
- m.startOf(intervalStr);
+ m.startOf(startOf);
let dateString;
switch (intervalStr) {
diff --git a/x-pack/plugins/reporting/server/lib/store/mapping.ts b/x-pack/plugins/reporting/server/lib/store/mapping.ts
new file mode 100644
index 00000000000000..a819923e2f1054
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/store/mapping.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export const mapping = {
+ meta: {
+ // We are indexing these properties with both text and keyword fields because that's what will be auto generated
+ // when an index already exists. This schema is only used when a reporting index doesn't exist. This way existing
+ // reporting indexes and new reporting indexes will look the same and the data can be queried in the same
+ // manner.
+ properties: {
+ /**
+ * Type of object that is triggering this report. Should be either search, visualization or dashboard.
+ * Used for job listing and telemetry stats only.
+ */
+ objectType: {
+ type: 'text',
+ fields: {
+ keyword: {
+ type: 'keyword',
+ ignore_above: 256,
+ },
+ },
+ },
+ /**
+ * Can be either preserve_layout, print or none (in the case of csv export).
+ * Used for phone home stats only.
+ */
+ layout: {
+ type: 'text',
+ fields: {
+ keyword: {
+ type: 'keyword',
+ ignore_above: 256,
+ },
+ },
+ },
+ },
+ },
+ browser_type: { type: 'keyword' },
+ jobtype: { type: 'keyword' },
+ payload: { type: 'object', enabled: false },
+ priority: { type: 'byte' },
+ timeout: { type: 'long' },
+ process_expiration: { type: 'date' },
+ created_by: { type: 'keyword' },
+ created_at: { type: 'date' },
+ started_at: { type: 'date' },
+ completed_at: { type: 'date' },
+ attempts: { type: 'short' },
+ max_attempts: { type: 'short' },
+ kibana_name: { type: 'keyword' },
+ kibana_id: { type: 'keyword' },
+ status: { type: 'keyword' },
+ output: {
+ type: 'object',
+ properties: {
+ content_type: { type: 'keyword' },
+ size: { type: 'long' },
+ content: { type: 'object', enabled: false },
+ },
+ },
+};
diff --git a/x-pack/plugins/reporting/server/lib/store/report.test.ts b/x-pack/plugins/reporting/server/lib/store/report.test.ts
new file mode 100644
index 00000000000000..83444494e61d33
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/store/report.test.ts
@@ -0,0 +1,77 @@
+/*
+ * 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 { Report } from './report';
+
+describe('Class Report', () => {
+ it('constructs Report instance', () => {
+ const opts = {
+ index: '.reporting-test-index-12345',
+ jobtype: 'test-report',
+ created_by: 'created_by_test_string',
+ browser_type: 'browser_type_test_string',
+ max_attempts: 50,
+ payload: { payload_test_field: 1 },
+ timeout: 30000,
+ priority: 1,
+ };
+ const report = new Report(opts);
+ expect(report.toJSON()).toMatchObject({
+ _primary_term: undefined,
+ _seq_no: undefined,
+ browser_type: 'browser_type_test_string',
+ created_by: 'created_by_test_string',
+ jobtype: 'test-report',
+ max_attempts: 50,
+ payload: {
+ payload_test_field: 1,
+ },
+ priority: 1,
+ timeout: 30000,
+ });
+
+ expect(report.id).toBeDefined();
+ });
+
+ it('updateWithDoc method syncs takes fields to sync ES metadata', () => {
+ const opts = {
+ index: '.reporting-test-index-12345',
+ jobtype: 'test-report',
+ created_by: 'created_by_test_string',
+ browser_type: 'browser_type_test_string',
+ max_attempts: 50,
+ payload: { payload_test_field: 1 },
+ timeout: 30000,
+ priority: 1,
+ };
+ const report = new Report(opts);
+
+ const metadata = {
+ _index: '.reporting-test-update',
+ _id: '12342p9o387549o2345',
+ _primary_term: 77,
+ _seq_no: 99,
+ };
+ report.updateWithDoc(metadata);
+
+ expect(report.toJSON()).toMatchObject({
+ index: '.reporting-test-update',
+ _primary_term: 77,
+ _seq_no: 99,
+ browser_type: 'browser_type_test_string',
+ created_by: 'created_by_test_string',
+ jobtype: 'test-report',
+ max_attempts: 50,
+ payload: {
+ payload_test_field: 1,
+ },
+ priority: 1,
+ timeout: 30000,
+ });
+
+ expect(report._id).toBe('12342p9o387549o2345');
+ });
+});
diff --git a/x-pack/plugins/reporting/server/lib/store/report.ts b/x-pack/plugins/reporting/server/lib/store/report.ts
new file mode 100644
index 00000000000000..cc9967e64b6ebc
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/store/report.ts
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+// @ts-ignore no module definition
+import Puid from 'puid';
+
+interface Payload {
+ id?: string;
+ index: string;
+ jobtype: string;
+ created_by: string | boolean;
+ payload: unknown;
+ browser_type: string;
+ priority: number;
+ max_attempts: number;
+ timeout: number;
+}
+
+const puid = new Puid();
+
+export class Report {
+ public readonly jobtype: string;
+ public readonly created_by: string | boolean;
+ public readonly payload: unknown;
+ public readonly browser_type: string;
+ public readonly id: string;
+
+ public readonly priority: number;
+ // queue stuff, to be removed with Task Manager integration
+ public readonly max_attempts: number;
+ public readonly timeout: number;
+
+ public _index: string;
+ public _id?: string; // set by ES
+ public _primary_term?: unknown; // set by ES
+ public _seq_no: unknown; // set by ES
+
+ /*
+ * Create an unsaved report
+ */
+ constructor(opts: Payload) {
+ this.jobtype = opts.jobtype;
+ this.created_by = opts.created_by;
+ this.payload = opts.payload;
+ this.browser_type = opts.browser_type;
+ this.priority = opts.priority;
+ this.max_attempts = opts.max_attempts;
+ this.timeout = opts.timeout;
+ this.id = puid.generate();
+
+ this._index = opts.index;
+ }
+
+ /*
+ * Update the report with "live" storage metadata
+ */
+ updateWithDoc(doc: Partial) {
+ if (doc._index) {
+ this._index = doc._index; // can not be undefined
+ }
+
+ this._id = doc._id;
+ this._primary_term = doc._primary_term;
+ this._seq_no = doc._seq_no;
+ }
+
+ toJSON() {
+ return {
+ id: this.id,
+ index: this._index,
+ _seq_no: this._seq_no,
+ _primary_term: this._primary_term,
+ jobtype: this.jobtype,
+ created_by: this.created_by,
+ payload: this.payload,
+ timeout: this.timeout,
+ max_attempts: this.max_attempts,
+ priority: this.priority,
+ browser_type: this.browser_type,
+ };
+ }
+}
diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts
new file mode 100644
index 00000000000000..4868a1dfdd8f3a
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts
@@ -0,0 +1,166 @@
+/*
+ * 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 sinon from 'sinon';
+import { ReportingConfig, ReportingCore } from '../..';
+import { createMockReportingCore } from '../../test_helpers';
+import { createMockLevelLogger } from '../../test_helpers/create_mock_levellogger';
+import { ReportingStore } from './store';
+import { ElasticsearchServiceSetup } from 'src/core/server';
+
+const getMockConfig = (mockConfigGet: sinon.SinonStub) => ({
+ get: mockConfigGet,
+ kbnConfig: { get: mockConfigGet },
+});
+
+describe('ReportingStore', () => {
+ const mockLogger = createMockLevelLogger();
+ let mockConfig: ReportingConfig;
+ let mockCore: ReportingCore;
+
+ const callClusterStub = sinon.stub();
+ const mockElasticsearch = { legacy: { client: { callAsInternalUser: callClusterStub } } };
+
+ beforeEach(async () => {
+ const mockConfigGet = sinon.stub();
+ mockConfigGet.withArgs('index').returns('.reporting-test');
+ mockConfigGet.withArgs('queue', 'indexInterval').returns('week');
+ mockConfig = getMockConfig(mockConfigGet);
+ mockCore = await createMockReportingCore(mockConfig);
+
+ callClusterStub.withArgs('indices.exists').resolves({});
+ callClusterStub.withArgs('indices.create').resolves({});
+ callClusterStub.withArgs('index').resolves({});
+ callClusterStub.withArgs('indices.refresh').resolves({});
+ callClusterStub.withArgs('update').resolves({});
+
+ mockCore.getElasticsearchService = () =>
+ (mockElasticsearch as unknown) as ElasticsearchServiceSetup;
+ });
+
+ describe('addReport', () => {
+ it('returns Report object', async () => {
+ const store = new ReportingStore(mockCore, mockLogger);
+ const reportType = 'unknowntype';
+ const reportPayload = {};
+ const reportOptions = {
+ timeout: 10000,
+ created_by: 'created_by_string',
+ browser_type: 'browser_type_string',
+ max_attempts: 1,
+ };
+ await expect(
+ store.addReport(reportType, reportPayload, reportOptions)
+ ).resolves.toMatchObject({
+ _primary_term: undefined,
+ _seq_no: undefined,
+ browser_type: 'browser_type_string',
+ created_by: 'created_by_string',
+ jobtype: 'unknowntype',
+ max_attempts: 1,
+ payload: {},
+ priority: 10,
+ timeout: 10000,
+ });
+ });
+
+ it('throws if options has invalid indexInterval', async () => {
+ const mockConfigGet = sinon.stub();
+ mockConfigGet.withArgs('index').returns('.reporting-test');
+ mockConfigGet.withArgs('queue', 'indexInterval').returns('centurially');
+ mockConfig = getMockConfig(mockConfigGet);
+ mockCore = await createMockReportingCore(mockConfig);
+
+ const store = new ReportingStore(mockCore, mockLogger);
+ const reportType = 'unknowntype';
+ const reportPayload = {};
+ const reportOptions = {
+ timeout: 10000,
+ created_by: 'created_by_string',
+ browser_type: 'browser_type_string',
+ max_attempts: 1,
+ };
+ expect(
+ store.addReport(reportType, reportPayload, reportOptions)
+ ).rejects.toMatchInlineSnapshot(`[Error: Invalid index interval: centurially]`);
+ });
+
+ it('handles error creating the index', async () => {
+ // setup
+ callClusterStub.withArgs('indices.exists').resolves(false);
+ callClusterStub.withArgs('indices.create').rejects(new Error('error'));
+
+ const store = new ReportingStore(mockCore, mockLogger);
+ const reportType = 'unknowntype';
+ const reportPayload = {};
+ const reportOptions = {
+ timeout: 10000,
+ created_by: 'created_by_string',
+ browser_type: 'browser_type_string',
+ max_attempts: 1,
+ };
+ await expect(
+ store.addReport(reportType, reportPayload, reportOptions)
+ ).rejects.toMatchInlineSnapshot(`[Error: error]`);
+ });
+
+ /* Creating the index will fail, if there were multiple jobs staged in
+ * parallel and creation completed from another Kibana instance. Only the
+ * first request in line can successfully create it.
+ * In spite of that race condition, adding the new job in Elasticsearch is
+ * fine.
+ */
+ it('ignores index creation error if the index already exists and continues adding the report', async () => {
+ // setup
+ callClusterStub.withArgs('indices.exists').resolves(false);
+ callClusterStub.withArgs('indices.create').rejects(new Error('error'));
+
+ const store = new ReportingStore(mockCore, mockLogger);
+ const reportType = 'unknowntype';
+ const reportPayload = {};
+ const reportOptions = {
+ timeout: 10000,
+ created_by: 'created_by_string',
+ browser_type: 'browser_type_string',
+ max_attempts: 1,
+ };
+ await expect(
+ store.addReport(reportType, reportPayload, reportOptions)
+ ).rejects.toMatchInlineSnapshot(`[Error: error]`);
+ });
+
+ it('skips creating the index if already exists', async () => {
+ // setup
+ callClusterStub.withArgs('indices.exists').resolves(false);
+ callClusterStub
+ .withArgs('indices.create')
+ .rejects(new Error('resource_already_exists_exception')); // will be triggered but ignored
+
+ const store = new ReportingStore(mockCore, mockLogger);
+ const reportType = 'unknowntype';
+ const reportPayload = {};
+ const reportOptions = {
+ timeout: 10000,
+ created_by: 'created_by_string',
+ browser_type: 'browser_type_string',
+ max_attempts: 1,
+ };
+ await expect(
+ store.addReport(reportType, reportPayload, reportOptions)
+ ).resolves.toMatchObject({
+ _primary_term: undefined,
+ _seq_no: undefined,
+ browser_type: 'browser_type_string',
+ created_by: 'created_by_string',
+ jobtype: 'unknowntype',
+ max_attempts: 1,
+ payload: {},
+ priority: 10,
+ timeout: 10000,
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts
new file mode 100644
index 00000000000000..1cb964a7bbfac2
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/store/store.ts
@@ -0,0 +1,169 @@
+/*
+ * 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 { ElasticsearchServiceSetup } from 'src/core/server';
+import { LevelLogger } from '../';
+import { ReportingCore } from '../../';
+import { LayoutInstance } from '../../export_types/common/layouts';
+import { indexTimestamp } from './index_timestamp';
+import { mapping } from './mapping';
+import { Report } from './report';
+
+export const statuses = {
+ JOB_STATUS_PENDING: 'pending',
+ JOB_STATUS_PROCESSING: 'processing',
+ JOB_STATUS_COMPLETED: 'completed',
+ JOB_STATUS_WARNINGS: 'completed_with_warnings',
+ JOB_STATUS_FAILED: 'failed',
+ JOB_STATUS_CANCELLED: 'cancelled',
+};
+
+interface AddReportOpts {
+ timeout: number;
+ created_by: string | boolean;
+ browser_type: string;
+ max_attempts: number;
+}
+
+interface UpdateQuery {
+ index: string;
+ id: string;
+ if_seq_no: unknown;
+ if_primary_term: unknown;
+ body: { doc: Partial };
+}
+
+/*
+ * A class to give an interface to historical reports in the reporting.index
+ * - track the state: pending, processing, completed, etc
+ * - handle updates and deletes to the reporting document
+ * - interface for downloading the report
+ */
+export class ReportingStore {
+ public readonly indexPrefix: string;
+ public readonly indexInterval: string;
+
+ private client: ElasticsearchServiceSetup['legacy']['client'];
+ private logger: LevelLogger;
+
+ constructor(reporting: ReportingCore, logger: LevelLogger) {
+ const config = reporting.getConfig();
+ const elasticsearch = reporting.getElasticsearchService();
+
+ this.client = elasticsearch.legacy.client;
+ this.indexPrefix = config.get('index');
+ this.indexInterval = config.get('queue', 'indexInterval');
+
+ this.logger = logger;
+ }
+
+ private async createIndex(indexName: string) {
+ return this.client
+ .callAsInternalUser('indices.exists', {
+ index: indexName,
+ })
+ .then((exists) => {
+ if (exists) {
+ return exists;
+ }
+
+ const indexSettings = {
+ number_of_shards: 1,
+ auto_expand_replicas: '0-1',
+ };
+ const body = {
+ settings: indexSettings,
+ mappings: {
+ properties: mapping,
+ },
+ };
+
+ return this.client
+ .callAsInternalUser('indices.create', {
+ index: indexName,
+ body,
+ })
+ .then(() => true)
+ .catch((err: Error) => {
+ const isIndexExistsError = err.message.match(/resource_already_exists_exception/);
+ if (isIndexExistsError) {
+ // Do not fail a job if the job runner hits the race condition.
+ this.logger.warn(`Automatic index creation failed: index already exists: ${err}`);
+ return;
+ }
+
+ throw err;
+ });
+ });
+ }
+
+ private async saveReport(report: Report) {
+ const payload = report.payload as { objectType: string; layout: LayoutInstance };
+
+ const indexParams = {
+ index: report._index,
+ id: report.id,
+ body: {
+ jobtype: report.jobtype,
+ meta: {
+ // We are copying these values out of payload because these fields are indexed and can be aggregated on
+ // for tracking stats, while payload contents are not.
+ objectType: payload.objectType,
+ layout: payload.layout ? payload.layout.id : 'none',
+ },
+ payload: report.payload,
+ created_by: report.created_by,
+ timeout: report.timeout,
+ process_expiration: new Date(0), // use epoch so the job query works
+ created_at: new Date(),
+ attempts: 0,
+ max_attempts: report.max_attempts,
+ status: statuses.JOB_STATUS_PENDING,
+ browser_type: report.browser_type,
+ },
+ };
+ return this.client.callAsInternalUser('index', indexParams);
+ }
+
+ private async refreshIndex(index: string) {
+ return this.client.callAsInternalUser('indices.refresh', { index });
+ }
+
+ public async addReport(type: string, payload: unknown, options: AddReportOpts): Promise {
+ const timestamp = indexTimestamp(this.indexInterval);
+ const index = `${this.indexPrefix}-${timestamp}`;
+ await this.createIndex(index);
+
+ const report = new Report({
+ index,
+ payload,
+ jobtype: type,
+ created_by: options.created_by,
+ browser_type: options.browser_type,
+ max_attempts: options.max_attempts,
+ timeout: options.timeout,
+ priority: 10, // unused
+ });
+
+ const doc = await this.saveReport(report);
+ report.updateWithDoc(doc);
+
+ await this.refreshIndex(index);
+ this.logger.info(`Successfully queued pending job: ${report._index}/${report.id}`);
+
+ return report;
+ }
+
+ public async updateReport(query: UpdateQuery): Promise {
+ return this.client.callAsInternalUser('update', {
+ index: query.index,
+ id: query.id,
+ if_seq_no: query.if_seq_no,
+ if_primary_term: query.if_primary_term,
+ body: { doc: query.body.doc },
+ });
+ }
+}
diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts
index 693b0917603fcd..cedc9dc14a2376 100644
--- a/x-pack/plugins/reporting/server/plugin.ts
+++ b/x-pack/plugins/reporting/server/plugin.ts
@@ -8,7 +8,13 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core
import { ReportingCore } from './';
import { initializeBrowserDriverFactory } from './browsers';
import { buildConfig, ReportingConfigType } from './config';
-import { createQueueFactory, enqueueJobFactory, LevelLogger, runValidations } from './lib';
+import {
+ createQueueFactory,
+ enqueueJobFactory,
+ LevelLogger,
+ runValidations,
+ ReportingStore,
+} from './lib';
import { registerRoutes } from './routes';
import { setFieldFormats } from './services';
import { ReportingSetup, ReportingSetupDeps, ReportingStart, ReportingStartDeps } from './types';
@@ -86,9 +92,9 @@ export class ReportingPlugin
const config = reportingCore.getConfig();
const browserDriverFactory = await initializeBrowserDriverFactory(config, logger);
-
- const esqueue = await createQueueFactory(reportingCore, logger); // starts polling for pending jobs
- const enqueueJob = enqueueJobFactory(reportingCore, logger); // called from generation routes
+ const store = new ReportingStore(reportingCore, logger);
+ const esqueue = await createQueueFactory(reportingCore, store, logger); // starts polling for pending jobs
+ const enqueueJob = enqueueJobFactory(reportingCore, store, logger); // called from generation routes
reportingCore.pluginStart({
browserDriverFactory,
@@ -96,6 +102,7 @@ export class ReportingPlugin
uiSettings: core.uiSettings,
esqueue,
enqueueJob,
+ store,
});
// run self-check validations
diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts
new file mode 100644
index 00000000000000..f5e9a44281cb69
--- /dev/null
+++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.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 { LevelLogger } from '../lib';
+
+export function createMockLevelLogger() {
+ // eslint-disable-next-line no-console
+ const consoleLogger = (tag: string) => (message: unknown) => console.log(tag, message);
+ const innerLogger = {
+ get: () => innerLogger,
+ debug: consoleLogger('debug'),
+ info: consoleLogger('info'),
+ warn: consoleLogger('warn'),
+ trace: consoleLogger('trace'),
+ error: consoleLogger('error'),
+ fatal: consoleLogger('fatal'),
+ log: consoleLogger('log'),
+ };
+ return new LevelLogger(innerLogger);
+}
diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts
index 579035a46f615e..427a6362a72581 100644
--- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts
+++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts
@@ -20,6 +20,8 @@ import {
} from '../browsers';
import { ReportingInternalSetup, ReportingInternalStart } from '../core';
import { ReportingStartDeps } from '../types';
+import { ReportingStore } from '../lib';
+import { createMockLevelLogger } from './create_mock_levellogger';
(initializeBrowserDriverFactory as jest.Mock<
Promise
@@ -37,13 +39,19 @@ const createMockPluginSetup = (setupMock?: any): ReportingInternalSetup => {
};
};
-const createMockPluginStart = (startMock?: any): ReportingInternalStart => {
+const createMockPluginStart = (
+ mockReportingCore: ReportingCore,
+ startMock?: any
+): ReportingInternalStart => {
+ const logger = createMockLevelLogger();
+ const store = new ReportingStore(mockReportingCore, logger);
return {
browserDriverFactory: startMock.browserDriverFactory,
enqueueJob: startMock.enqueueJob,
esqueue: startMock.esqueue,
savedObjects: startMock.savedObjects || { getScopedClient: jest.fn() },
uiSettings: startMock.uiSettings || { asScopedToClient: () => ({ get: jest.fn() }) },
+ store,
};
};
@@ -60,9 +68,22 @@ export const createMockStartDeps = (startMock?: any): ReportingStartDeps => ({
export const createMockReportingCore = async (
config: ReportingConfig,
- setupDepsMock: ReportingInternalSetup | undefined = createMockPluginSetup({}),
- startDepsMock: ReportingInternalStart | undefined = createMockPluginStart({})
+ setupDepsMock: ReportingInternalSetup | undefined = undefined,
+ startDepsMock: ReportingInternalStart | undefined = undefined
) => {
+ if (!setupDepsMock) {
+ setupDepsMock = createMockPluginSetup({});
+ }
+
+ const mockReportingCore = {
+ getConfig: () => config,
+ getElasticsearchService: () => setupDepsMock?.elasticsearch,
+ } as ReportingCore;
+
+ if (!startDepsMock) {
+ startDepsMock = createMockPluginStart(mockReportingCore, {});
+ }
+
config = config || {};
const core = new ReportingCore();
diff --git a/x-pack/plugins/security_solution/common/endpoint/types.ts b/x-pack/plugins/security_solution/common/endpoint/types.ts
index 4f13fd97ce4428..42f5f4b220da95 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types.ts
@@ -74,7 +74,7 @@ export interface ResolverNodeStats {
/**
* A child node can also have additional children so we need to provide a pagination cursor.
*/
-export interface ChildNode extends LifecycleNode {
+export interface ResolverChildNode extends ResolverLifecycleNode {
/**
* A child node's pagination cursor can be null for a couple reasons:
* 1. At the time of querying it could have no children in ES, in which case it will be marked as
@@ -89,7 +89,7 @@ export interface ChildNode extends LifecycleNode {
* has an array of lifecycle events.
*/
export interface ResolverChildren {
- childNodes: ChildNode[];
+ childNodes: ResolverChildNode[];
/**
* This is the children cursor for the origin of a tree.
*/
@@ -116,7 +116,7 @@ export interface ResolverTree {
/**
* The lifecycle events (start, end etc) for a node.
*/
-export interface LifecycleNode {
+export interface ResolverLifecycleNode {
entityID: string;
lifecycle: ResolverEvent[];
/**
@@ -132,7 +132,7 @@ export interface ResolverAncestry {
/**
* An array of ancestors with the lifecycle events grouped together
*/
- ancestors: LifecycleNode[];
+ ancestors: ResolverLifecycleNode[];
/**
* A cursor for retrieving additional ancestors for a particular node. `null` indicates that there were no additional
* ancestors when the request returned. More could have been ingested by ES after the fact though.
diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts
index a352a076e5a972..343b4e1a14478c 100644
--- a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts
@@ -12,7 +12,7 @@ import {
ResolverEvent,
ResolverChildren,
ResolverAncestry,
- LifecycleNode,
+ ResolverLifecycleNode,
ResolverNodeStats,
ResolverRelatedEvents,
} from '../../../common/endpoint/types';
@@ -25,10 +25,10 @@ type MiddlewareFactory = (
) => (next: Dispatch) => (action: ResolverAction) => unknown;
function getLifecycleEventsAndStats(
- nodes: LifecycleNode[],
+ nodes: ResolverLifecycleNode[],
stats: Map
): ResolverEvent[] {
- return nodes.reduce((flattenedEvents: ResolverEvent[], currentNode: LifecycleNode) => {
+ return nodes.reduce((flattenedEvents: ResolverEvent[], currentNode: ResolverLifecycleNode) => {
if (currentNode.lifecycle && currentNode.lifecycle.length > 0) {
flattenedEvents.push(...currentNode.lifecycle);
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts
index 7a3e1fc591e82e..e60e5087c30a9e 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/children_helper.ts
@@ -9,7 +9,11 @@ import {
parentEntityId,
isProcessStart,
} from '../../../../../common/endpoint/models/event';
-import { ChildNode, ResolverEvent, ResolverChildren } from '../../../../../common/endpoint/types';
+import {
+ ResolverChildNode,
+ ResolverEvent,
+ ResolverChildren,
+} from '../../../../../common/endpoint/types';
import { PaginationBuilder } from './pagination';
import { createChild } from './node';
@@ -17,7 +21,7 @@ import { createChild } from './node';
* This class helps construct the children structure when building a resolver tree.
*/
export class ChildrenNodesHelper {
- private readonly cache: Map = new Map();
+ private readonly cache: Map = new Map();
constructor(private readonly rootID: string) {
this.cache.set(rootID, createChild(rootID));
@@ -27,7 +31,7 @@ export class ChildrenNodesHelper {
* Constructs a ResolverChildren response based on the children that were previously add.
*/
getNodes(): ResolverChildren {
- const cacheCopy: Map = new Map(this.cache);
+ const cacheCopy: Map = new Map(this.cache);
const rootNode = cacheCopy.get(this.rootID);
let rootNextChild = null;
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts
index d448649ae447bf..0af2fca7106bef 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/fetch.ts
@@ -10,7 +10,7 @@ import {
ResolverRelatedEvents,
ResolverAncestry,
ResolverRelatedAlerts,
- LifecycleNode,
+ ResolverLifecycleNode,
ResolverEvent,
} from '../../../../../common/endpoint/types';
import {
@@ -143,7 +143,7 @@ export class Fetcher {
return tree;
}
- private async getNode(entityID: string): Promise {
+ private async getNode(entityID: string): Promise {
const query = new LifecycleQuery(this.eventsIndexPattern, this.endpointID);
const results = await query.search(this.client, entityID);
if (results.length === 0) {
@@ -186,7 +186,7 @@ export class Fetcher {
// bucket the start and end events together for a single node
const ancestryNodes = results.reduce(
- (nodes: Map, ancestorEvent: ResolverEvent) => {
+ (nodes: Map, ancestorEvent: ResolverEvent) => {
const nodeId = entityId(ancestorEvent);
let node = nodes.get(nodeId);
if (!node) {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/node.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/node.ts
index 58aa9efc1fc567..57a2ebfcc17929 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/node.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/utils/node.ts
@@ -7,10 +7,10 @@
import {
ResolverEvent,
ResolverAncestry,
- LifecycleNode,
+ ResolverLifecycleNode,
ResolverRelatedEvents,
ResolverTree,
- ChildNode,
+ ResolverChildNode,
ResolverRelatedAlerts,
} from '../../../../../common/endpoint/types';
@@ -49,7 +49,7 @@ export function createRelatedAlerts(
*
* @param entityID the entity_id of the child
*/
-export function createChild(entityID: string): ChildNode {
+export function createChild(entityID: string): ResolverChildNode {
const lifecycle = createLifecycle(entityID, []);
return {
...lifecycle,
@@ -70,7 +70,10 @@ export function createAncestry(): ResolverAncestry {
* @param id the entity_id that these lifecycle nodes should have
* @param lifecycle an array of lifecycle events
*/
-export function createLifecycle(entityID: string, lifecycle: ResolverEvent[]): LifecycleNode {
+export function createLifecycle(
+ entityID: string,
+ lifecycle: ResolverEvent[]
+): ResolverLifecycleNode {
return { entityID, lifecycle };
}
diff --git a/x-pack/test/api_integration/apis/endpoint/resolver.ts b/x-pack/test/api_integration/apis/endpoint/resolver.ts
index 67b828b8df30ec..eeca8ee54e32f3 100644
--- a/x-pack/test/api_integration/apis/endpoint/resolver.ts
+++ b/x-pack/test/api_integration/apis/endpoint/resolver.ts
@@ -6,8 +6,8 @@
import _ from 'lodash';
import expect from '@kbn/expect';
import {
- ChildNode,
- LifecycleNode,
+ ResolverChildNode,
+ ResolverLifecycleNode,
ResolverAncestry,
ResolverEvent,
ResolverRelatedEvents,
@@ -35,7 +35,7 @@ import { Options, GeneratedTrees } from '../../services/resolver';
* @param node a lifecycle node containing the start and end events for a node
* @param nodeMap a map of entity_ids to nodes to look for the passed in `node`
*/
-const expectLifecycleNodeInMap = (node: LifecycleNode, nodeMap: Map) => {
+const expectLifecycleNodeInMap = (node: ResolverLifecycleNode, nodeMap: Map) => {
const genNode = nodeMap.get(node.entityID);
expect(genNode).to.be.ok();
compareArrays(genNode!.lifecycle, node.lifecycle, true);
@@ -49,7 +49,11 @@ const expectLifecycleNodeInMap = (node: LifecycleNode, nodeMap: Map {
+const verifyAncestry = (
+ ancestors: ResolverLifecycleNode[],
+ tree: Tree,
+ verifyLastParent: boolean
+) => {
// group the ancestors by their entity_id mapped to a lifecycle node
const groupedAncestors = _.groupBy(ancestors, (ancestor) => ancestor.entityID);
// group by parent entity_id
@@ -97,7 +101,7 @@ const verifyAncestry = (ancestors: LifecycleNode[], tree: Tree, verifyLastParent
*
* @param ancestors an array of ancestor nodes
*/
-const retrieveDistantAncestor = (ancestors: LifecycleNode[]) => {
+const retrieveDistantAncestor = (ancestors: ResolverLifecycleNode[]) => {
// group the ancestors by their entity_id mapped to a lifecycle node
const groupedAncestors = _.groupBy(ancestors, (ancestor) => ancestor.entityID);
let node = ancestors[0];
@@ -124,7 +128,7 @@ const retrieveDistantAncestor = (ancestors: LifecycleNode[]) => {
* @param childrenPerParent an optional number to compare that there are a certain number of children for each parent
*/
const verifyChildren = (
- children: ChildNode[],
+ children: ResolverChildNode[],
tree: Tree,
numberOfParents?: number,
childrenPerParent?: number
@@ -210,7 +214,7 @@ const verifyStats = (
* @param categories the related event info used when generating the resolver tree
*/
const verifyLifecycleStats = (
- nodes: LifecycleNode[],
+ nodes: ResolverLifecycleNode[],
categories: RelatedEventInfo[],
relatedAlerts: number
) => {
diff --git a/x-pack/test/functional/apps/status_page/status_page.ts b/x-pack/test/functional/apps/status_page/status_page.ts
index b6f0fdce8b2897..eeb9bc9b84450b 100644
--- a/x-pack/test/functional/apps/status_page/status_page.ts
+++ b/x-pack/test/functional/apps/status_page/status_page.ts
@@ -13,7 +13,7 @@ export default function statusPageFunctonalTests({
const PageObjects = getPageObjects(['security', 'statusPage', 'home']);
describe('Status Page', function () {
- this.tags('includeFirefox');
+ this.tags(['skipCloud', 'includeFirefox']);
before(async () => await esArchiver.load('empty_kibana'));
after(async () => await esArchiver.unload('empty_kibana'));
diff --git a/x-pack/test/reporting_api_integration/services.ts b/x-pack/test/reporting_api_integration/services.ts
index dadb466d45982a..85f5a98c69b2e8 100644
--- a/x-pack/test/reporting_api_integration/services.ts
+++ b/x-pack/test/reporting_api_integration/services.ts
@@ -7,8 +7,7 @@
import expect from '@kbn/expect';
import * as Rx from 'rxjs';
import { filter, first, mapTo, switchMap, timeout } from 'rxjs/operators';
-// @ts-ignore no module definition
-import { indexTimestamp } from '../../plugins/reporting/server/lib/esqueue/helpers/index_timestamp';
+import { indexTimestamp } from '../../plugins/reporting/server/lib/store/index_timestamp';
import { services as xpackServices } from '../functional/services';
import { FtrProviderContext } from './ftr_provider_context';