;
diff --git a/src/plugins/data/public/search/aggs/utils/index.ts b/src/plugins/data/public/search/aggs/utils/index.ts
index 23606bd109342..169d872b17d3a 100644
--- a/src/plugins/data/public/search/aggs/utils/index.ts
+++ b/src/plugins/data/public/search/aggs/utils/index.ts
@@ -18,4 +18,5 @@
*/
export * from './calculate_auto_time_expression';
+export * from './prop_filter';
export * from './to_angular_json';
diff --git a/src/plugins/data/public/search/aggs/filter/prop_filter.test.ts b/src/plugins/data/public/search/aggs/utils/prop_filter.test.ts
similarity index 100%
rename from src/plugins/data/public/search/aggs/filter/prop_filter.test.ts
rename to src/plugins/data/public/search/aggs/utils/prop_filter.test.ts
diff --git a/src/plugins/data/public/search/aggs/filter/prop_filter.ts b/src/plugins/data/public/search/aggs/utils/prop_filter.ts
similarity index 97%
rename from src/plugins/data/public/search/aggs/filter/prop_filter.ts
rename to src/plugins/data/public/search/aggs/utils/prop_filter.ts
index e6b5f3831e65d..01e98a68d3949 100644
--- a/src/plugins/data/public/search/aggs/filter/prop_filter.ts
+++ b/src/plugins/data/public/search/aggs/utils/prop_filter.ts
@@ -28,7 +28,7 @@ type FilterFunc = (item: T[P]) => boolean;
*
* @returns the filter function which can be registered with angular
*/
-function propFilter
(prop: P) {
+export function propFilter
(prop: P) {
/**
* List filtering function which accepts an array or list of values that a property
* must contain
@@ -92,5 +92,3 @@ function propFilter
(prop: P) {
});
};
}
-
-export { propFilter };
diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts
index dd196074c8173..44082040b5b0b 100644
--- a/src/plugins/data/public/search/mocks.ts
+++ b/src/plugins/data/public/search/mocks.ts
@@ -18,7 +18,6 @@
*/
import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks';
-import { AggTypeFieldFilters } from './aggs/param_types/filter';
import { ISearchStart } from './types';
import { searchSourceMock, createSearchSourceMock } from './search_source/mocks';
@@ -34,13 +33,6 @@ const searchStartMock: jest.Mocked = {
search: jest.fn(),
searchSource: searchSourceMock,
__LEGACY: {
- AggConfig: jest.fn() as any,
- AggType: jest.fn(),
- aggTypeFieldFilters: new AggTypeFieldFilters(),
- FieldParamType: jest.fn(),
- MetricAggType: jest.fn(),
- parentPipelineAggHelper: jest.fn() as any,
- siblingPipelineAggHelper: jest.fn() as any,
esClient: {
search: jest.fn(),
msearch: jest.fn(),
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index b59524baa9fa7..4d183797dfef0 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -39,16 +39,9 @@ import { SearchInterceptor } from './search_interceptor';
import {
getAggTypes,
getAggTypesFunctions,
- AggType,
AggTypesRegistry,
- AggConfig,
AggConfigs,
- FieldParamType,
getCalculateAutoTimeExpression,
- MetricAggType,
- aggTypeFieldFilters,
- parentPipelineAggHelper,
- siblingPipelineAggHelper,
} from './aggs';
import { FieldFormatsStart } from '../field_formats';
import { ISearchGeneric } from './i_search';
@@ -156,13 +149,6 @@ export class SearchService implements Plugin {
const legacySearch = {
esClient: this.esClient!,
- AggConfig,
- AggType,
- aggTypeFieldFilters,
- FieldParamType,
- MetricAggType,
- parentPipelineAggHelper,
- siblingPipelineAggHelper,
};
const searchSourceDependencies: SearchSourceDependencies = {
diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts
index 99d111ce1574e..1687c8f983393 100644
--- a/src/plugins/data/public/search/types.ts
+++ b/src/plugins/data/public/search/types.ts
@@ -18,7 +18,7 @@
*/
import { CoreStart, SavedObjectReference } from 'kibana/public';
-import { SearchAggsSetup, SearchAggsStart, SearchAggsStartLegacy } from './aggs';
+import { SearchAggsSetup, SearchAggsStart } from './aggs';
import { ISearch, ISearchGeneric } from './i_search';
import { TStrategyTypes } from './strategy_types';
import { LegacyApiCaller } from './legacy/es_client';
@@ -88,5 +88,5 @@ export interface ISearchStart {
references: SavedObjectReference[]
) => Promise;
};
- __LEGACY: ISearchStartLegacy & SearchAggsStartLegacy;
+ __LEGACY: ISearchStartLegacy;
}
diff --git a/src/plugins/discover/public/components/doc_viewer/_doc_viewer.scss b/src/plugins/discover/public/components/doc_viewer/_doc_viewer.scss
index 25aa530976719..ec2beca15a546 100644
--- a/src/plugins/discover/public/components/doc_viewer/_doc_viewer.scss
+++ b/src/plugins/discover/public/components/doc_viewer/_doc_viewer.scss
@@ -43,6 +43,14 @@
}
.kbnDocViewer__buttons {
width: 60px;
+
+ // Show all icons if one is focused,
+ // IE doesn't support, but the fallback is just the focused button becomes visible
+ &:focus-within {
+ .kbnDocViewer__actionButton {
+ opacity: 1;
+ }
+ }
}
.kbnDocViewer__field {
@@ -51,7 +59,12 @@
.kbnDocViewer__actionButton {
opacity: 0;
+
+ &:focus {
+ opacity: 1;
+ }
}
+
.kbnDocViewer__warning {
margin-right: $euiSizeS;
}
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap
index fb3d6efa63826..1860a4625afc0 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap
@@ -260,19 +260,10 @@ exports[`SavedObjectsTable import should show the flyout 1`] = `
search={
Object {
"__LEGACY": Object {
- "AggConfig": [MockFunction],
- "AggType": [MockFunction],
- "FieldParamType": [MockFunction],
- "MetricAggType": [MockFunction],
- "aggTypeFieldFilters": AggTypeFieldFilters {
- "filters": Set {},
- },
"esClient": Object {
"msearch": [MockFunction],
"search": [MockFunction],
},
- "parentPipelineAggHelper": [MockFunction],
- "siblingPipelineAggHelper": [MockFunction],
},
"aggs": Object {
"calculateAutoTimeExpression": [Function],
diff --git a/src/plugins/vis_default_editor/public/agg_filters/agg_type_field_filters.ts b/src/plugins/vis_default_editor/public/agg_filters/agg_type_field_filters.ts
new file mode 100644
index 0000000000000..15df2f0acccd1
--- /dev/null
+++ b/src/plugins/vis_default_editor/public/agg_filters/agg_type_field_filters.ts
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { IAggConfig, IndexPatternField } from '../../../data/public';
+
+type AggTypeFieldFilter = (field: IndexPatternField, aggConfig: IAggConfig) => boolean;
+
+const filters: AggTypeFieldFilter[] = [
+ /**
+ * Check index pattern aggregation restrictions
+ * and limit available fields for a given aggType based on that.
+ */
+ (field, aggConfig) => {
+ const indexPattern = aggConfig.getIndexPattern();
+ const aggRestrictions = indexPattern.getAggregationRestrictions();
+
+ if (!aggRestrictions) {
+ return true;
+ }
+
+ const aggName = aggConfig.type && aggConfig.type.name;
+ const aggFields = aggRestrictions[aggName];
+ return !!aggFields && !!aggFields[field.name];
+ },
+];
+
+export function filterAggTypeFields(fields: IndexPatternField[], aggConfig: IAggConfig) {
+ const allowedAggTypeFields = fields.filter(field => {
+ const isAggTypeFieldAllowed = filters.every(filter => filter(field, aggConfig));
+ return isAggTypeFieldAllowed;
+ });
+ return allowedAggTypeFields;
+}
diff --git a/src/plugins/vis_default_editor/public/agg_filters/agg_type_filters.ts b/src/plugins/vis_default_editor/public/agg_filters/agg_type_filters.ts
new file mode 100644
index 0000000000000..2cf1acba4d228
--- /dev/null
+++ b/src/plugins/vis_default_editor/public/agg_filters/agg_type_filters.ts
@@ -0,0 +1,75 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { IAggType, IAggConfig, IndexPattern, search } from '../../../data/public';
+
+const { propFilter } = search.aggs;
+const filterByName = propFilter('name');
+
+type AggTypeFilter = (
+ aggType: IAggType,
+ indexPattern: IndexPattern,
+ aggConfig: IAggConfig,
+ aggFilter: string[]
+) => boolean;
+
+const filters: AggTypeFilter[] = [
+ /**
+ * This filter checks the defined aggFilter in the schemas of that visualization
+ * and limits available aggregations based on that.
+ */
+ (aggType, indexPattern, aggConfig, aggFilter) => {
+ const doesSchemaAllowAggType = filterByName([aggType], aggFilter).length !== 0;
+ return doesSchemaAllowAggType;
+ },
+ /**
+ * Check index pattern aggregation restrictions and limit available aggTypes.
+ */
+ (aggType, indexPattern, aggConfig, aggFilter) => {
+ const aggRestrictions = indexPattern.getAggregationRestrictions();
+
+ if (!aggRestrictions) {
+ return true;
+ }
+
+ const aggName = aggType.name;
+ // Only return agg types which are specified in the agg restrictions,
+ // except for `count` which should always be returned.
+ return (
+ aggName === 'count' ||
+ (!!aggRestrictions && Object.keys(aggRestrictions).includes(aggName)) ||
+ false
+ );
+ },
+];
+
+export function filterAggTypes(
+ aggTypes: IAggType[],
+ indexPattern: IndexPattern,
+ aggConfig: IAggConfig,
+ aggFilter: string[]
+) {
+ const allowedAggTypes = aggTypes.filter(aggType => {
+ const isAggTypeAllowed = filters.every(filter =>
+ filter(aggType, indexPattern, aggConfig, aggFilter)
+ );
+ return isAggTypeAllowed;
+ });
+ return allowedAggTypes;
+}
diff --git a/src/plugins/data/public/search/aggs/param_types/filter/index.ts b/src/plugins/vis_default_editor/public/agg_filters/index.ts
similarity index 91%
rename from src/plugins/data/public/search/aggs/param_types/filter/index.ts
rename to src/plugins/vis_default_editor/public/agg_filters/index.ts
index 2e0039c96a192..2b08449fb3161 100644
--- a/src/plugins/data/public/search/aggs/param_types/filter/index.ts
+++ b/src/plugins/vis_default_editor/public/agg_filters/index.ts
@@ -17,4 +17,5 @@
* under the License.
*/
-export { aggTypeFieldFilters, AggTypeFieldFilters } from './field_filters';
+export * from './agg_type_filters';
+export * from './agg_type_field_filters';
diff --git a/src/plugins/vis_default_editor/public/components/agg_common_props.ts b/src/plugins/vis_default_editor/public/components/agg_common_props.ts
index 0c130a96230b4..40d7b79bfbefc 100644
--- a/src/plugins/vis_default_editor/public/components/agg_common_props.ts
+++ b/src/plugins/vis_default_editor/public/components/agg_common_props.ts
@@ -18,7 +18,7 @@
*/
import { VisParams } from 'src/plugins/visualizations/public';
-import { IAggType, IAggConfig, IAggGroupNames } from 'src/plugins/data/public';
+import { IAggType, IAggConfig, AggGroupName } from 'src/plugins/data/public';
import { Schema } from '../schemas';
import { EditorVisState } from './sidebar/state/reducers';
@@ -30,7 +30,7 @@ export type ReorderAggs = (sourceAgg: IAggConfig, destinationAgg: IAggConfig) =>
export interface DefaultEditorCommonProps {
formIsTouched: boolean;
- groupName: IAggGroupNames;
+ groupName: AggGroupName;
metricAggs: IAggConfig[];
state: EditorVisState;
setAggParamValue: (
diff --git a/src/plugins/vis_default_editor/public/components/agg_group.tsx b/src/plugins/vis_default_editor/public/components/agg_group.tsx
index 72515d0845926..fae9de6959ef1 100644
--- a/src/plugins/vis_default_editor/public/components/agg_group.tsx
+++ b/src/plugins/vis_default_editor/public/components/agg_group.tsx
@@ -30,7 +30,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { AggGroupNames, search, IAggConfig, TimeRange } from '../../../data/public';
+import { AggGroupNames, AggGroupLabels, IAggConfig, TimeRange } from '../../../data/public';
import { DefaultEditorAgg } from './agg';
import { DefaultEditorAggAdd } from './agg_add';
import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props';
@@ -70,7 +70,7 @@ function DefaultEditorAggGroup({
setValidity,
timeRange,
}: DefaultEditorAggGroupProps) {
- const groupNameLabel = (search.aggs.aggGroupNamesMap() as any)[groupName];
+ const groupNameLabel = AggGroupLabels[groupName];
// e.g. buckets can have no aggs
const schemaNames = schemas.map(s => s.name);
const group: IAggConfig[] = useMemo(
diff --git a/src/plugins/vis_default_editor/public/components/agg_param_props.ts b/src/plugins/vis_default_editor/public/components/agg_param_props.ts
index aec332e8674d7..076bddc9551ea 100644
--- a/src/plugins/vis_default_editor/public/components/agg_param_props.ts
+++ b/src/plugins/vis_default_editor/public/components/agg_param_props.ts
@@ -17,7 +17,12 @@
* under the License.
*/
-import { IAggConfig, AggParam, IndexPatternField } from 'src/plugins/data/public';
+import {
+ IAggConfig,
+ AggParam,
+ IndexPatternField,
+ OptionedValueProp,
+} from 'src/plugins/data/public';
import { ComboBoxGroupedOptions } from '../utils';
import { EditorConfig } from './utils';
import { Schema } from '../schemas';
@@ -46,3 +51,9 @@ export interface AggParamEditorProps extends AggParamCommonProp
setValidity(isValid: boolean): void;
setTouched(): void;
}
+
+export interface OptionedParamEditorProps {
+ aggParam: {
+ options: T[];
+ };
+}
diff --git a/src/plugins/vis_default_editor/public/components/agg_params.tsx b/src/plugins/vis_default_editor/public/components/agg_params.tsx
index 3674e39b558d2..d36c2d0e7625b 100644
--- a/src/plugins/vis_default_editor/public/components/agg_params.tsx
+++ b/src/plugins/vis_default_editor/public/components/agg_params.tsx
@@ -112,20 +112,8 @@ function DefaultEditorAggParams({
fieldName,
]);
const params = useMemo(
- () =>
- getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas, hideCustomLabel },
- services.data.search.__LEGACY.aggTypeFieldFilters
- ),
- [
- agg,
- editorConfig,
- metricAggs,
- state,
- schemas,
- hideCustomLabel,
- services.data.search.__LEGACY.aggTypeFieldFilters,
- ]
+ () => getAggParamsToRender({ agg, editorConfig, metricAggs, state, schemas, hideCustomLabel }),
+ [agg, editorConfig, metricAggs, state, schemas, hideCustomLabel]
);
const allParams = [...params.basic, ...params.advanced];
const [paramsState, onChangeParamsState] = useReducer(
diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts
index bed2561341737..834ad8b70ad0d 100644
--- a/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts
+++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts
@@ -23,7 +23,6 @@ import {
IAggConfig,
IAggType,
IndexPattern,
- IndexPatternField,
} from 'src/plugins/data/public';
import {
getAggParamsToRender,
@@ -39,12 +38,6 @@ jest.mock('../utils', () => ({
groupAndSortBy: jest.fn(() => ['indexedFields']),
}));
-const mockFilter: any = {
- filter(fields: IndexPatternField[]): IndexPatternField[] {
- return fields;
- },
-};
-
describe('DefaultEditorAggParams helpers', () => {
describe('getAggParamsToRender', () => {
let agg: IAggConfig;
@@ -72,20 +65,14 @@ describe('DefaultEditorAggParams helpers', () => {
},
schema: 'metric',
} as IAggConfig;
- const params = getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas },
- mockFilter
- );
+ const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state, schemas });
expect(params).toEqual(emptyParams);
});
it('should not create any param if there is no agg type', () => {
agg = { schema: 'metric' } as IAggConfig;
- const params = getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas },
- mockFilter
- );
+ const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state, schemas });
expect(params).toEqual(emptyParams);
});
@@ -101,10 +88,7 @@ describe('DefaultEditorAggParams helpers', () => {
hidden: true,
},
};
- const params = getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas },
- mockFilter
- );
+ const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state, schemas });
expect(params).toEqual(emptyParams);
});
@@ -116,10 +100,7 @@ describe('DefaultEditorAggParams helpers', () => {
},
schema: 'metric2',
} as any) as IAggConfig;
- const params = getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas },
- mockFilter
- );
+ const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state, schemas });
expect(params).toEqual(emptyParams);
});
@@ -152,16 +133,14 @@ describe('DefaultEditorAggParams helpers', () => {
{ name: '@timestamp', type: 'date' },
{ name: 'geo_desc', type: 'string' },
],
+ getAggregationRestrictions: jest.fn(),
})),
params: {
orderBy: 'orderBy',
field: 'field',
},
} as any) as IAggConfig;
- const params = getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas },
- mockFilter
- );
+ const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state, schemas });
expect(params).toEqual({
basic: [
@@ -190,7 +169,6 @@ describe('DefaultEditorAggParams helpers', () => {
],
advanced: [],
});
- expect(agg.getIndexPattern).toBeCalledTimes(1);
});
});
diff --git a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts
index a32bd76bafa5a..9977f1e5e71fc 100644
--- a/src/plugins/vis_default_editor/public/components/agg_params_helper.ts
+++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts
@@ -20,7 +20,6 @@
import { get, isEmpty } from 'lodash';
import {
- AggTypeFieldFilters,
IAggConfig,
AggParam,
IFieldParamType,
@@ -28,13 +27,13 @@ import {
IndexPattern,
IndexPatternField,
} from 'src/plugins/data/public';
+import { filterAggTypes, filterAggTypeFields } from '../agg_filters';
import { groupAndSortBy, ComboBoxGroupedOptions } from '../utils';
import { AggTypeState, AggParamsState } from './agg_params_state';
import { AggParamEditorProps } from './agg_param_props';
import { aggParamsMap } from './agg_params_map';
import { EditorConfig } from './utils';
import { Schema, getSchemaByName } from '../schemas';
-import { search } from '../../../data/public';
import { EditorVisState } from './sidebar/state/reducers';
interface ParamInstanceBase {
@@ -53,10 +52,14 @@ export interface ParamInstance extends ParamInstanceBase {
value: unknown;
}
-function getAggParamsToRender(
- { agg, editorConfig, metricAggs, state, schemas, hideCustomLabel }: ParamInstanceBase,
- aggTypeFieldFilters: AggTypeFieldFilters
-) {
+function getAggParamsToRender({
+ agg,
+ editorConfig,
+ metricAggs,
+ state,
+ schemas,
+ hideCustomLabel,
+}: ParamInstanceBase) {
const params = {
basic: [] as ParamInstance[],
advanced: [] as ParamInstance[],
@@ -89,7 +92,7 @@ function getAggParamsToRender(
availableFields = availableFields.filter(field => field.type === 'number');
}
}
- fields = aggTypeFieldFilters.filter(availableFields, agg);
+ fields = filterAggTypeFields(availableFields, agg);
indexedFields = groupAndSortBy(fields, 'type', 'name');
if (fields && !indexedFields.length && index > 0) {
@@ -138,12 +141,7 @@ function getAggTypeOptions(
groupName: string,
allowedAggs: string[]
): ComboBoxGroupedOptions {
- const aggTypeOptions = search.aggs.aggTypeFilters.filter(
- aggTypes[groupName],
- indexPattern,
- agg,
- allowedAggs
- );
+ const aggTypeOptions = filterAggTypes(aggTypes[groupName], indexPattern, agg, allowedAggs);
return groupAndSortBy(aggTypeOptions as any[], 'subtype', 'title');
}
diff --git a/src/plugins/vis_default_editor/public/components/controls/order.tsx b/src/plugins/vis_default_editor/public/components/controls/order.tsx
index e609bf9adf790..3c0224564300a 100644
--- a/src/plugins/vis_default_editor/public/components/controls/order.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/order.tsx
@@ -21,8 +21,8 @@ import React, { useEffect } from 'react';
import { EuiFormRow, EuiSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { OptionedValueProp, OptionedParamEditorProps } from 'src/plugins/data/public';
-import { AggParamEditorProps } from '../agg_param_props';
+import { OptionedValueProp } from 'src/plugins/data/public';
+import { AggParamEditorProps, OptionedParamEditorProps } from '../agg_param_props';
function OrderParamEditor({
aggParam,
diff --git a/src/plugins/vis_default_editor/public/components/controls/top_aggregate.tsx b/src/plugins/vis_default_editor/public/components/controls/top_aggregate.tsx
index ad23cec87800f..66abb88b97d29 100644
--- a/src/plugins/vis_default_editor/public/components/controls/top_aggregate.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/top_aggregate.tsx
@@ -26,10 +26,9 @@ import {
IAggConfig,
AggParam,
OptionedValueProp,
- OptionedParamEditorProps,
OptionedParamType,
} from 'src/plugins/data/public';
-import { AggParamEditorProps } from '../agg_param_props';
+import { AggParamEditorProps, OptionedParamEditorProps } from '../agg_param_props';
export interface AggregateValueProp extends OptionedValueProp {
isCompatible(aggConfig: IAggConfig): boolean;
diff --git a/src/plugins/vis_default_editor/public/default_editor.tsx b/src/plugins/vis_default_editor/public/default_editor.tsx
index 1c2ddbc314f99..8088921ba7fda 100644
--- a/src/plugins/vis_default_editor/public/default_editor.tsx
+++ b/src/plugins/vis_default_editor/public/default_editor.tsx
@@ -22,7 +22,6 @@ import React, { useEffect, useRef, useState, useCallback } from 'react';
import { EditorRenderProps } from 'src/plugins/visualize/public';
import { PanelsContainer, Panel } from '../../kibana_react/public';
-import './vis_type_agg_filter';
import { DefaultEditorSideBar } from './components/sidebar';
import { DefaultEditorControllerState } from './default_editor_controller';
import { getInitialWidth } from './editor_size';
diff --git a/src/plugins/vis_default_editor/public/schemas.ts b/src/plugins/vis_default_editor/public/schemas.ts
index 05ba5fa9c9419..26d1cbe91b996 100644
--- a/src/plugins/vis_default_editor/public/schemas.ts
+++ b/src/plugins/vis_default_editor/public/schemas.ts
@@ -21,7 +21,7 @@ import _, { defaults } from 'lodash';
import { Optional } from '@kbn/utility-types';
-import { AggGroupNames, AggParam, IAggGroupNames } from '../../data/public';
+import { AggGroupNames, AggParam, AggGroupName } from '../../data/public';
export interface ISchemas {
[AggGroupNames.Buckets]: Schema[];
@@ -32,7 +32,7 @@ export interface ISchemas {
export interface Schema {
aggFilter: string[];
editor: boolean | string;
- group: IAggGroupNames;
+ group: AggGroupName;
max: number;
min: number;
name: string;
diff --git a/src/plugins/vis_default_editor/public/vis_type_agg_filter.ts b/src/plugins/vis_default_editor/public/vis_type_agg_filter.ts
deleted file mode 100644
index bf5661f42a9f5..0000000000000
--- a/src/plugins/vis_default_editor/public/vis_type_agg_filter.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import { IAggType, IAggConfig, IndexPattern, search } from '../../data/public';
-
-const { aggTypeFilters, propFilter } = search.aggs;
-const filterByName = propFilter('name');
-
-/**
- * This filter checks the defined aggFilter in the schemas of that visualization
- * and limits available aggregations based on that.
- */
-aggTypeFilters.addFilter(
- (aggType: IAggType, indexPatterns: IndexPattern, aggConfig: IAggConfig, aggFilter: string[]) => {
- const doesSchemaAllowAggType = filterByName([aggType], aggFilter).length !== 0;
- return doesSchemaAllowAggType;
- }
-);
diff --git a/x-pack/legacy/plugins/canvas/i18n/components.ts b/x-pack/legacy/plugins/canvas/i18n/components.ts
index 7bd16c4933ce1..de16bc2101e8c 100644
--- a/x-pack/legacy/plugins/canvas/i18n/components.ts
+++ b/x-pack/legacy/plugins/canvas/i18n/components.ts
@@ -804,17 +804,6 @@ export const ComponentStrings = {
}),
},
SidebarHeader: {
- getAlignmentMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.alignmentMenuItemLabel', {
- defaultMessage: 'Alignment',
- description:
- 'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' +
- 'alignment options of the selected elements',
- }),
- getBottomAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel', {
- defaultMessage: 'Bottom',
- }),
getBringForwardAriaLabel: () =>
i18n.translate('xpack.canvas.sidebarHeader.bringForwardArialLabel', {
defaultMessage: 'Move element up one layer',
@@ -823,56 +812,6 @@ export const ComponentStrings = {
i18n.translate('xpack.canvas.sidebarHeader.bringToFrontArialLabel', {
defaultMessage: 'Move element to top layer',
}),
- getCenterAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.centerAlignMenuItemLabel', {
- defaultMessage: 'Center',
- description: 'This refers to alignment centered horizontally.',
- }),
- getContextMenuTitle: () =>
- i18n.translate('xpack.canvas.sidebarHeader.contextMenuAriaLabel', {
- defaultMessage: 'Element options',
- }),
- getCreateElementModalTitle: () =>
- i18n.translate('xpack.canvas.sidebarHeader.createElementModalTitle', {
- defaultMessage: 'Create new element',
- }),
- getDistributionMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.distributionMenutItemLabel', {
- defaultMessage: 'Distribution',
- description:
- 'This refers to the options to evenly spacing the selected elements horizontall or vertically.',
- }),
- getGroupMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.groupMenuItemLabel', {
- defaultMessage: 'Group',
- description: 'This refers to grouping multiple selected elements.',
- }),
- getHorizontalDistributionMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel', {
- defaultMessage: 'Horizontal',
- }),
- getLeftAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.leftAlignMenuItemLabel', {
- defaultMessage: 'Left',
- }),
- getMiddleAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.middleAlignMenuItemLabel', {
- defaultMessage: 'Middle',
- description: 'This refers to alignment centered vertically.',
- }),
- getOrderMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.orderMenuItemLabel', {
- defaultMessage: 'Order',
- description: 'Refers to the order of the elements displayed on the page from front to back',
- }),
- getRightAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.rightAlignMenuItemLabel', {
- defaultMessage: 'Right',
- }),
- getSaveElementMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.savedElementMenuItemLabel', {
- defaultMessage: 'Save as new element',
- }),
getSendBackwardAriaLabel: () =>
i18n.translate('xpack.canvas.sidebarHeader.sendBackwardArialLabel', {
defaultMessage: 'Move element down one layer',
@@ -881,19 +820,6 @@ export const ComponentStrings = {
i18n.translate('xpack.canvas.sidebarHeader.sendToBackArialLabel', {
defaultMessage: 'Move element to bottom layer',
}),
- getTopAlignMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.topAlignMenuItemLabel', {
- defaultMessage: 'Top',
- }),
- getUngroupMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.ungroupMenuItemLabel', {
- defaultMessage: 'Ungroup',
- description: 'This refers to ungrouping a grouped element',
- }),
- getVerticalDistributionMenuItemLabel: () =>
- i18n.translate('xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel', {
- defaultMessage: 'Vertical',
- }),
},
TextStylePicker: {
getAlignCenterOption: () =>
@@ -1079,12 +1005,6 @@ export const ComponentStrings = {
defaultMessage: 'Refresh elements',
}),
},
- WorkpadHeaderControlSettings: {
- getButtonLabel: () =>
- i18n.translate('xpack.canvas.workpadHeaderControlSettings.buttonLabel', {
- defaultMessage: 'Options',
- }),
- },
WorkpadHeaderCustomInterval: {
getButtonLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel', {
@@ -1105,6 +1025,94 @@ export const ComponentStrings = {
defaultMessage: 'Set a custom interval',
}),
},
+ WorkpadHeaderEditMenu: {
+ getAlignmentMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel', {
+ defaultMessage: 'Alignment',
+ description:
+ 'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' +
+ 'alignment options of the selected elements',
+ }),
+ getBottomAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel', {
+ defaultMessage: 'Bottom',
+ }),
+ getCenterAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel', {
+ defaultMessage: 'Center',
+ description: 'This refers to alignment centered horizontally.',
+ }),
+ getCreateElementModalTitle: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.createElementModalTitle', {
+ defaultMessage: 'Create new element',
+ }),
+ getDistributionMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel', {
+ defaultMessage: 'Distribution',
+ description:
+ 'This refers to the options to evenly spacing the selected elements horizontall or vertically.',
+ }),
+ getEditMenuButtonLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuButtonLabel', {
+ defaultMessage: 'Edit',
+ }),
+ getEditMenuLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuLabel', {
+ defaultMessage: 'Edit options',
+ }),
+ getGroupMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel', {
+ defaultMessage: 'Group',
+ description: 'This refers to grouping multiple selected elements.',
+ }),
+ getHorizontalDistributionMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel', {
+ defaultMessage: 'Horizontal',
+ }),
+ getLeftAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel', {
+ defaultMessage: 'Left',
+ }),
+ getMiddleAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel', {
+ defaultMessage: 'Middle',
+ description: 'This refers to alignment centered vertically.',
+ }),
+ getOrderMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel', {
+ defaultMessage: 'Order',
+ description: 'Refers to the order of the elements displayed on the page from front to back',
+ }),
+ getRedoMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.redoMenuItemLabel', {
+ defaultMessage: 'Redo',
+ }),
+ getRightAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel', {
+ defaultMessage: 'Right',
+ }),
+ getSaveElementMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel', {
+ defaultMessage: 'Save as new element',
+ }),
+ getTopAlignMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.topAlignMenuItemLabel', {
+ defaultMessage: 'Top',
+ }),
+ getUndoMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.undoMenuItemLabel', {
+ defaultMessage: 'Undo',
+ }),
+ getUngroupMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.ungroupMenuItemLabel', {
+ defaultMessage: 'Ungroup',
+ description: 'This refers to ungrouping a grouped element',
+ }),
+ getVerticalDistributionMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderEditMenu.verticalDistributionMenutItemLabel', {
+ defaultMessage: 'Vertical',
+ }),
+ },
WorkpadHeaderElementMenu: {
getAssetsMenuItemLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderElementMenu.manageAssetsMenuItemLabel', {
@@ -1305,6 +1313,18 @@ export const ComponentStrings = {
}),
},
WorkpadHeaderViewMenu: {
+ getAutoplayOffMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplayOffMenuItemLabel', {
+ defaultMessage: 'Turn autoplay off',
+ }),
+ getAutoplayOnMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplayOnMenuItemLabel', {
+ defaultMessage: 'Turn autoplay on',
+ }),
+ getAutoplaySettingsMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.autoplaySettingsMenuItemLabel', {
+ defaultMessage: 'Autoplay settings',
+ }),
getFullscreenMenuItemLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderViewMenu.fullscreenMenuLabel', {
defaultMessage: 'Enter fullscreen mode',
@@ -1317,6 +1337,10 @@ export const ComponentStrings = {
i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshMenuItemLabel', {
defaultMessage: 'Refresh data',
}),
+ getRefreshSettingsMenuItemLabel: () =>
+ i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshSettingsMenuItemLabel', {
+ defaultMessage: 'Auto refresh settings',
+ }),
getShowEditModeLabel: () =>
i18n.translate('xpack.canvas.workpadHeaderViewMenu.showEditModeLabel', {
defaultMessage: 'Show editing controls',
diff --git a/x-pack/legacy/plugins/canvas/i18n/shortcuts.ts b/x-pack/legacy/plugins/canvas/i18n/shortcuts.ts
index 32b07a45c17db..124d70ff3095f 100644
--- a/x-pack/legacy/plugins/canvas/i18n/shortcuts.ts
+++ b/x-pack/legacy/plugins/canvas/i18n/shortcuts.ts
@@ -42,10 +42,10 @@ export const ShortcutStrings = {
defaultMessage: 'Delete',
}),
BRING_FORWARD: i18n.translate('xpack.canvas.keyboardShortcuts.bringFowardShortcutHelpText', {
- defaultMessage: 'Bring to front',
+ defaultMessage: 'Bring forward',
}),
BRING_TO_FRONT: i18n.translate('xpack.canvas.keyboardShortcuts.bringToFrontShortcutHelpText', {
- defaultMessage: 'Bring forward',
+ defaultMessage: 'Bring to front',
}),
SEND_BACKWARD: i18n.translate('xpack.canvas.keyboardShortcuts.sendBackwardShortcutHelpText', {
defaultMessage: 'Send backward',
diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js
index b8e1bece6ac30..fc3ac9922355a 100644
--- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js
+++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.js
@@ -43,7 +43,7 @@ export class WorkpadApp extends React.PureComponent {
-
+ {})} />
- {})} />
+
)}
diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
index 503677687ba12..d59d03578a363 100644
--- a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
+++ b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot
@@ -212,7 +212,7 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = `
- Bring forward
+ Bring to front
- Bring to front
+ Bring forward
({
selectedToplevelNodes: getSelectedToplevelNodes(state),
selectedElementId: getSelectedElementId(state),
- state,
});
-const mergeProps = (
- { state, ...restStateProps },
- { dispatch, ...restDispatchProps },
- ownProps
-) => ({
- ...ownProps,
- ...restDispatchProps,
- ...restStateProps,
- updateGlobalState: globalStateUpdater(dispatch, state),
-});
-
-const withGlobalState = (commit, updateGlobalState) => (type, payload) => {
- const newLayoutState = commit(type, payload);
- if (newLayoutState.currentScene.gestureEnd) {
- updateGlobalState(newLayoutState);
- }
-};
-
-const MultiElementSidebar = ({ commit, updateGlobalState }) => (
+const MultiElementSidebar = () => (
-
+
);
-const GroupedElementSidebar = ({ commit, updateGlobalState }) => (
+const GroupedElementSidebar = () => (
-
+
@@ -92,7 +65,4 @@ const branches = [
),
];
-export const SidebarContent = compose(
- connect(mapStateToProps, null, mergeProps),
- ...branches
-)(GlobalConfig);
+export const SidebarContent = compose(connect(mapStateToProps), ...branches)(GlobalConfig);
diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot
index ac25cbe0b6832..4d5b9570ee20f 100644
--- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot
+++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot
@@ -20,80 +20,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = `
Selected layer
-
`;
@@ -224,72 +150,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = `
-
-
-
-
diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx
index 9baff380fc3eb..11c66906a6ef6 100644
--- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx
@@ -10,26 +10,10 @@ import { action } from '@storybook/addon-actions';
import { SidebarHeader } from '../sidebar_header';
const handlers = {
- cloneNodes: action('cloneNodes'),
- copyNodes: action('copyNodes'),
- cutNodes: action('cutNodes'),
- pasteNodes: action('pasteNodes'),
- deleteNodes: action('deleteNodes'),
bringToFront: action('bringToFront'),
bringForward: action('bringForward'),
sendBackward: action('sendBackward'),
sendToBack: action('sendToBack'),
- createCustomElement: action('createCustomElement'),
- groupNodes: action('groupNodes'),
- ungroupNodes: action('ungroupNodes'),
- alignLeft: action('alignLeft'),
- alignMiddle: action('alignMiddle'),
- alignRight: action('alignRight'),
- alignTop: action('alignTop'),
- alignCenter: action('alignCenter'),
- alignBottom: action('alignBottom'),
- distributeHorizontally: action('distributeHorizontally'),
- distributeVertically: action('distributeVertically'),
};
storiesOf('components/Sidebar/SidebarHeader', module)
diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx
index 925e68a565f04..024a2dbb41a24 100644
--- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx
@@ -4,25 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Component, Fragment } from 'react';
+import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiTitle,
- EuiButtonIcon,
- EuiContextMenu,
- EuiToolTip,
- EuiContextMenuPanelItemDescriptor,
- EuiContextMenuPanelDescriptor,
- EuiOverlayMask,
-} from '@elastic/eui';
-import { Popover } from '../popover';
-import { CustomElementModal } from '../custom_element_modal';
+import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { ToolTipShortcut } from '../tool_tip_shortcut/';
import { ComponentStrings } from '../../../i18n/components';
import { ShortcutStrings } from '../../../i18n/shortcuts';
-import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../common/lib/constants';
const { SidebarHeader: strings } = ComponentStrings;
const shortcutHelp = ShortcutStrings.getShortcutHelp();
@@ -36,26 +23,6 @@ interface Props {
* indicated whether or not layer controls should be displayed
*/
showLayerControls?: boolean;
- /**
- * cuts selected elements
- */
- cutNodes: () => void;
- /**
- * copies selected elements to clipboard
- */
- copyNodes: () => void;
- /**
- * pastes elements stored in clipboard to page
- */
- pasteNodes: () => void;
- /**
- * clones selected elements
- */
- cloneNodes: () => void;
- /**
- * deletes selected elements
- */
- deleteNodes: () => void;
/**
* moves selected element to top layer
*/
@@ -72,493 +39,117 @@ interface Props {
* moves selected element to bottom layer
*/
sendToBack: () => void;
- /**
- * saves the selected elements as an custom-element saved object
- */
- createCustomElement: (name: string, description: string, image: string) => void;
- /**
- * indicated whether the selected element is a group or not
- */
- groupIsSelected: boolean;
- /**
- * only more than one selected element can be grouped
- */
- selectedNodes: string[];
- /**
- * groups selected elements
- */
- groupNodes: () => void;
- /**
- * ungroups selected group
- */
- ungroupNodes: () => void;
- /**
- * left align selected elements
- */
- alignLeft: () => void;
- /**
- * center align selected elements
- */
- alignCenter: () => void;
- /**
- * right align selected elements
- */
- alignRight: () => void;
- /**
- * top align selected elements
- */
- alignTop: () => void;
- /**
- * middle align selected elements
- */
- alignMiddle: () => void;
- /**
- * bottom align selected elements
- */
- alignBottom: () => void;
- /**
- * horizontally distribute selected elements
- */
- distributeHorizontally: () => void;
- /**
- * vertically distribute selected elements
- */
- distributeVertically: () => void;
-}
-
-interface State {
- /**
- * indicates whether or not the custom element modal is open
- */
- isModalVisible: boolean;
-}
-
-interface MenuTuple {
- menuItem: EuiContextMenuPanelItemDescriptor;
- panel: EuiContextMenuPanelDescriptor;
}
-const contextMenuButton = (handleClick: React.MouseEventHandler) => (
-
-);
-
-export class SidebarHeader extends Component {
- public static propTypes = {
- title: PropTypes.string.isRequired,
- showLayerControls: PropTypes.bool, // TODO: remove when we support relayering multiple elements
- cutNodes: PropTypes.func.isRequired,
- copyNodes: PropTypes.func.isRequired,
- pasteNodes: PropTypes.func.isRequired,
- cloneNodes: PropTypes.func.isRequired,
- deleteNodes: PropTypes.func.isRequired,
- bringToFront: PropTypes.func.isRequired,
- bringForward: PropTypes.func.isRequired,
- sendBackward: PropTypes.func.isRequired,
- sendToBack: PropTypes.func.isRequired,
- createCustomElement: PropTypes.func.isRequired,
- groupIsSelected: PropTypes.bool,
- selectedNodes: PropTypes.array,
- groupNodes: PropTypes.func.isRequired,
- ungroupNodes: PropTypes.func.isRequired,
- alignLeft: PropTypes.func.isRequired,
- alignCenter: PropTypes.func.isRequired,
- alignRight: PropTypes.func.isRequired,
- alignTop: PropTypes.func.isRequired,
- alignMiddle: PropTypes.func.isRequired,
- alignBottom: PropTypes.func.isRequired,
- distributeHorizontally: PropTypes.func.isRequired,
- distributeVertically: PropTypes.func.isRequired,
- };
-
- public static defaultProps = {
- groupIsSelected: false,
- showLayerControls: false,
- selectedNodes: [],
- };
-
- public state = {
- isModalVisible: false,
- };
-
- private _isMounted = false;
- private _showModal = () => this._isMounted && this.setState({ isModalVisible: true });
- private _hideModal = () => this._isMounted && this.setState({ isModalVisible: false });
-
- public componentDidMount() {
- this._isMounted = true;
- }
-
- public componentWillUnmount() {
- this._isMounted = false;
- }
-
- private _renderLayoutControls = () => {
- const { bringToFront, bringForward, sendBackward, sendToBack } = this.props;
- return (
-
-
-
- {shortcutHelp.BRING_TO_FRONT}
-
-
- }
- >
-
-
-
-
-
- {shortcutHelp.BRING_FORWARD}
-
-
- }
- >
-
-
-
-
-
- {shortcutHelp.SEND_BACKWARD}
-
-
- }
- >
-
-
-
-
-
- {shortcutHelp.SEND_TO_BACK}
-
-
- }
- >
-
-
-
-
- );
- };
-
- private _getLayerMenuItems = (): MenuTuple => {
- const { bringToFront, bringForward, sendBackward, sendToBack } = this.props;
-
- return {
- menuItem: {
- name: strings.getOrderMenuItemLabel(),
- className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
- panel: 1,
- },
- panel: {
- id: 1,
- title: strings.getOrderMenuItemLabel(),
- items: [
- {
- name: shortcutHelp.BRING_TO_FRONT, // TODO: check against current element position and disable if already top layer
- icon: 'sortUp',
- onClick: bringToFront,
- },
- {
- name: shortcutHelp.BRING_TO_FRONT, // TODO: same as above
- icon: 'arrowUp',
- onClick: bringForward,
- },
- {
- name: shortcutHelp.SEND_BACKWARD, // TODO: check against current element position and disable if already bottom layer
- icon: 'arrowDown',
- onClick: sendBackward,
- },
- {
- name: shortcutHelp.SEND_TO_BACK, // TODO: same as above
- icon: 'sortDown',
- onClick: sendToBack,
- },
- ],
- },
- };
- };
-
- private _getAlignmentMenuItems = (close: (fn: () => void) => () => void): MenuTuple => {
- const { alignLeft, alignCenter, alignRight, alignTop, alignMiddle, alignBottom } = this.props;
-
- return {
- menuItem: {
- name: strings.getAlignmentMenuItemLabel(),
- className: 'canvasContextMenu',
- panel: 2,
- },
- panel: {
- id: 2,
- title: strings.getAlignmentMenuItemLabel(),
- items: [
- {
- name: strings.getLeftAlignMenuItemLabel(),
- icon: 'editorItemAlignLeft',
- onClick: close(alignLeft),
- },
- {
- name: strings.getCenterAlignMenuItemLabel(),
- icon: 'editorItemAlignCenter',
- onClick: close(alignCenter),
- },
- {
- name: strings.getRightAlignMenuItemLabel(),
- icon: 'editorItemAlignRight',
- onClick: close(alignRight),
- },
- {
- name: strings.getTopAlignMenuItemLabel(),
- icon: 'editorItemAlignTop',
- onClick: close(alignTop),
- },
- {
- name: strings.getMiddleAlignMenuItemLabel(),
- icon: 'editorItemAlignMiddle',
- onClick: close(alignMiddle),
- },
- {
- name: strings.getBottomAlignMenuItemLabel(),
- icon: 'editorItemAlignBottom',
- onClick: close(alignBottom),
- },
- ],
- },
- };
- };
-
- private _getDistributionMenuItems = (close: (fn: () => void) => () => void): MenuTuple => {
- const { distributeHorizontally, distributeVertically } = this.props;
-
- return {
- menuItem: {
- name: strings.getDistributionMenuItemLabel(),
- className: 'canvasContextMenu',
- panel: 3,
- },
- panel: {
- id: 3,
- title: strings.getDistributionMenuItemLabel(),
- items: [
- {
- name: strings.getHorizontalDistributionMenuItemLabel(),
- icon: 'editorDistributeHorizontal',
- onClick: close(distributeHorizontally),
- },
- {
- name: strings.getVerticalDistributionMenuItemLabel(),
- icon: 'editorDistributeVertical',
- onClick: close(distributeVertically),
- },
- ],
- },
- };
- };
-
- private _getGroupMenuItems = (
- close: (fn: () => void) => () => void
- ): EuiContextMenuPanelItemDescriptor[] => {
- const { groupIsSelected, ungroupNodes, groupNodes, selectedNodes } = this.props;
- return groupIsSelected
- ? [
- {
- name: strings.getUngroupMenuItemLabel(),
- className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
- onClick: close(ungroupNodes),
- },
- ]
- : selectedNodes.length > 1
- ? [
- {
- name: strings.getGroupMenuItemLabel(),
- className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
- onClick: close(groupNodes),
- },
- ]
- : [];
- };
-
- private _getPanels = (closePopover: () => void): EuiContextMenuPanelDescriptor[] => {
- const {
- showLayerControls,
- cutNodes,
- copyNodes,
- pasteNodes,
- deleteNodes,
- cloneNodes,
- } = this.props;
-
- // closes popover after invoking fn
- const close = (fn: () => void) => () => {
- fn();
- closePopover();
- };
-
- const items: EuiContextMenuPanelItemDescriptor[] = [
- {
- name: shortcutHelp.CUT,
- icon: 'cut',
- onClick: close(cutNodes),
- },
- {
- name: shortcutHelp.COPY,
- icon: 'copy',
- onClick: copyNodes,
- },
- {
- name: shortcutHelp.PASTE, // TODO: can this be disabled if clipboard is empty?
- icon: 'copyClipboard',
- onClick: close(pasteNodes),
- },
- {
- name: shortcutHelp.DELETE,
- icon: 'trash',
- onClick: close(deleteNodes),
- },
- {
- name: shortcutHelp.CLONE,
- onClick: close(cloneNodes),
- },
- ...this._getGroupMenuItems(close),
- ];
-
- const panels: EuiContextMenuPanelDescriptor[] = [
- {
- id: 0,
- title: strings.getContextMenuTitle(),
- items,
- },
- ];
-
- const fillMenu = ({ menuItem, panel }: MenuTuple) => {
- items.push(menuItem); // add Order menu item to first panel
- panels.push(panel); // add nested panel for layers controls
- };
-
- if (showLayerControls) {
- fillMenu(this._getLayerMenuItems());
- }
-
- if (this.props.selectedNodes.length > 1) {
- fillMenu(this._getAlignmentMenuItems(close));
- }
-
- if (this.props.selectedNodes.length > 2) {
- fillMenu(this._getDistributionMenuItems(close));
- }
-
- items.push({
- name: strings.getSaveElementMenuItemLabel(),
- icon: 'indexOpen',
- className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
- onClick: this._showModal,
- });
-
- return panels;
- };
-
- private _renderContextMenu = () => (
-
- );
-
- private _handleSave = (name: string, description: string, image: string) => {
- const { createCustomElement } = this.props;
- createCustomElement(name, description, image);
- this._hideModal();
- };
-
- render() {
- const { title, showLayerControls } = this.props;
- const { isModalVisible } = this.state;
-
- return (
-
-
+export const SidebarHeader: FunctionComponent = ({
+ title,
+ showLayerControls,
+ bringToFront,
+ bringForward,
+ sendBackward,
+ sendToBack,
+}) => (
+
+
+
+ {title}
+
+
+ {showLayerControls ? (
+
+
-
- {title}
-
+
+ {shortcutHelp.BRING_TO_FRONT}
+
+
+ }
+ >
+
+
-
- {showLayerControls ? this._renderLayoutControls() : null}
-
-
-
-
-
- {this._renderContextMenu()}
-
+
+ {shortcutHelp.BRING_FORWARD}
+
+
+ }
+ >
+
+
+
+
+
+ {shortcutHelp.SEND_BACKWARD}
+
+
+ }
+ >
+
+
+
+
+
+ {shortcutHelp.SEND_TO_BACK}
+
+
+ }
+ >
+
+
- {isModalVisible ? (
-
-
-
- ) : null}
-
- );
- }
-}
+
+ ) : null}
+
+);
+
+SidebarHeader.propTypes = {
+ title: PropTypes.string.isRequired,
+ showLayerControls: PropTypes.bool, // TODO: remove when we support relayering multiple elements
+ bringToFront: PropTypes.func.isRequired,
+ bringForward: PropTypes.func.isRequired,
+ sendBackward: PropTypes.func.isRequired,
+ sendToBack: PropTypes.func.isRequired,
+};
+
+SidebarHeader.defaultProps = {
+ showLayerControls: false,
+};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss
deleted file mode 100644
index 3d217dd1fc180..0000000000000
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.canvasControlSettings__popover {
- width: 600px;
-}
-
-.canvasControlSettings__list {
- columns: 2;
-}
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.tsx
deleted file mode 100644
index adc57ff4f815a..0000000000000
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { MouseEventHandler } from 'react';
-import PropTypes from 'prop-types';
-import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
-// @ts-ignore untyped local
-import { Popover } from '../../popover';
-import { AutoRefreshControls } from './auto_refresh_controls';
-import { KioskControls } from './kiosk_controls';
-
-import { ComponentStrings } from '../../../../i18n';
-const { WorkpadHeaderControlSettings: strings } = ComponentStrings;
-
-interface Props {
- refreshInterval: number;
- setRefreshInterval: (interval: number | undefined) => void;
- autoplayEnabled: boolean;
- autoplayInterval: number;
- enableAutoplay: (enable: boolean) => void;
- setAutoplayInterval: (interval: number | undefined) => void;
-}
-
-export const ControlSettings = ({
- setRefreshInterval,
- refreshInterval,
- autoplayEnabled,
- autoplayInterval,
- enableAutoplay,
- setAutoplayInterval,
-}: Props) => {
- const setRefresh = (val: number | undefined) => setRefreshInterval(val);
-
- const disableInterval = () => {
- setRefresh(0);
- };
-
- const popoverButton = (handleClick: MouseEventHandler) => (
-
- {strings.getButtonLabel()}
-
- );
-
- return (
-
- {() => (
-
-
- setRefresh(val)}
- disableInterval={() => disableInterval()}
- />
-
-
-
-
-
- )}
-
- );
-};
-
-ControlSettings.propTypes = {
- refreshInterval: PropTypes.number,
- setRefreshInterval: PropTypes.func.isRequired,
- autoplayEnabled: PropTypes.bool,
- autoplayInterval: PropTypes.number,
- enableAutoplay: PropTypes.func.isRequired,
- setAutoplayInterval: PropTypes.func.isRequired,
-};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/index.ts
deleted file mode 100644
index 316a49c85c09d..0000000000000
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/index.ts
+++ /dev/null
@@ -1,35 +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 { connect } from 'react-redux';
-import {
- setRefreshInterval,
- enableAutoplay,
- setAutoplayInterval,
- // @ts-ignore untyped local
-} from '../../../state/actions/workpad';
-// @ts-ignore untyped local
-import { getRefreshInterval, getAutoplay } from '../../../state/selectors/workpad';
-import { State } from '../../../../types';
-import { ControlSettings as Component } from './control_settings';
-
-const mapStateToProps = (state: State) => {
- const { enabled, interval } = getAutoplay(state);
-
- return {
- refreshInterval: getRefreshInterval(state),
- autoplayEnabled: enabled,
- autoplayInterval: interval,
- };
-};
-
-const mapDispatchToProps = {
- setRefreshInterval,
- enableAutoplay,
- setAutoplayInterval,
-};
-
-export const ControlSettings = connect(mapStateToProps, mapDispatchToProps)(Component);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.examples.storyshot
new file mode 100644
index 0000000000000..42c59d41dca62
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/__snapshots__/edit_menu.examples.storyshot
@@ -0,0 +1,205 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots components/WorkpadHeader/EditMenu 2 elements selected 1`] = `
+
+`;
+
+exports[`Storyshots components/WorkpadHeader/EditMenu 3+ elements selected 1`] = `
+
+`;
+
+exports[`Storyshots components/WorkpadHeader/EditMenu clipboard data exists 1`] = `
+
+`;
+
+exports[`Storyshots components/WorkpadHeader/EditMenu default 1`] = `
+
+`;
+
+exports[`Storyshots components/WorkpadHeader/EditMenu single element selected 1`] = `
+
+`;
+
+exports[`Storyshots components/WorkpadHeader/EditMenu single grouped element selected 1`] = `
+
+`;
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.examples.tsx
new file mode 100644
index 0000000000000..a0ab8d53485f5
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/__examples__/edit_menu.examples.tsx
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import React from 'react';
+import { EditMenu } from '../edit_menu';
+
+const handlers = {
+ cutNodes: action('cutNodes'),
+ copyNodes: action('copyNodes'),
+ pasteNodes: action('pasteNodes'),
+ deleteNodes: action('deleteNodes'),
+ cloneNodes: action('cloneNodes'),
+ bringToFront: action('bringToFront'),
+ bringForward: action('bringForward'),
+ sendBackward: action('sendBackward'),
+ sendToBack: action('sendToBack'),
+ alignLeft: action('alignLeft'),
+ alignCenter: action('alignCenter'),
+ alignRight: action('alignRight'),
+ alignTop: action('alignTop'),
+ alignMiddle: action('alignMiddle'),
+ alignBottom: action('alignBottom'),
+ distributeHorizontally: action('distributeHorizontally'),
+ distributeVertically: action('distributeVertically'),
+ createCustomElement: action('createCustomElement'),
+ groupNodes: action('groupNodes'),
+ ungroupNodes: action('ungroupNodes'),
+ undoHistory: action('undoHistory'),
+ redoHistory: action('redoHistory'),
+};
+
+storiesOf('components/WorkpadHeader/EditMenu', module)
+ .add('default', () => (
+
+ ))
+ .add('clipboard data exists', () => (
+
+ ))
+ .add('single element selected', () => (
+
+ ))
+ .add('single grouped element selected', () => (
+
+ ))
+ .add('2 elements selected', () => (
+
+ ))
+ .add('3+ elements selected', () => (
+
+ ));
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.tsx
new file mode 100644
index 0000000000000..15191b8d416ff
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/edit_menu.tsx
@@ -0,0 +1,448 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Fragment, FunctionComponent, useState } from 'react';
+import PropTypes from 'prop-types';
+import { EuiButtonEmpty, EuiContextMenu, EuiIcon, EuiOverlayMask } from '@elastic/eui';
+import { ComponentStrings } from '../../../../i18n/components';
+import { ShortcutStrings } from '../../../../i18n/shortcuts';
+import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
+import { Popover, ClosePopoverFn } from '../../popover';
+import { CustomElementModal } from '../../custom_element_modal';
+import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib/constants';
+
+const { WorkpadHeaderEditMenu: strings } = ComponentStrings;
+const shortcutHelp = ShortcutStrings.getShortcutHelp();
+
+export interface Props {
+ /**
+ * cuts selected elements
+ */
+ cutNodes: () => void;
+ /**
+ * copies selected elements to clipboard
+ */
+ copyNodes: () => void;
+ /**
+ * pastes elements stored in clipboard to page
+ */
+ pasteNodes: () => void;
+ /**
+ * clones selected elements
+ */
+ cloneNodes: () => void;
+ /**
+ * deletes selected elements
+ */
+ deleteNodes: () => void;
+ /**
+ * moves selected element to top layer
+ */
+ bringToFront: () => void;
+ /**
+ * moves selected element up one layer
+ */
+ bringForward: () => void;
+ /**
+ * moves selected element down one layer
+ */
+ sendBackward: () => void;
+ /**
+ * moves selected element to bottom layer
+ */
+ sendToBack: () => void;
+ /**
+ * saves the selected elements as an custom-element saved object
+ */
+ createCustomElement: (name: string, description: string, image: string) => void;
+ /**
+ * indicated whether the selected element is a group or not
+ */
+ groupIsSelected: boolean;
+ /**
+ * only more than one selected element can be grouped
+ */
+ selectedNodes: string[];
+ /**
+ * groups selected elements
+ */
+ groupNodes: () => void;
+ /**
+ * ungroups selected group
+ */
+ ungroupNodes: () => void;
+ /**
+ * left align selected elements
+ */
+ alignLeft: () => void;
+ /**
+ * center align selected elements
+ */
+ alignCenter: () => void;
+ /**
+ * right align selected elements
+ */
+ alignRight: () => void;
+ /**
+ * top align selected elements
+ */
+ alignTop: () => void;
+ /**
+ * middle align selected elements
+ */
+ alignMiddle: () => void;
+ /**
+ * bottom align selected elements
+ */
+ alignBottom: () => void;
+ /**
+ * horizontally distribute selected elements
+ */
+ distributeHorizontally: () => void;
+ /**
+ * vertically distribute selected elements
+ */
+ distributeVertically: () => void;
+ /**
+ * Reverts last change to the workpad
+ */
+ undoHistory: () => void;
+ /**
+ * Reapplies last reverted change to the workpad
+ */
+ redoHistory: () => void;
+ /**
+ * Is there element clipboard data to paste?
+ */
+ hasPasteData: boolean;
+}
+
+export const EditMenu: FunctionComponent = ({
+ cutNodes,
+ copyNodes,
+ pasteNodes,
+ deleteNodes,
+ cloneNodes,
+ bringToFront,
+ bringForward,
+ sendBackward,
+ sendToBack,
+ alignLeft,
+ alignCenter,
+ alignRight,
+ alignTop,
+ alignMiddle,
+ alignBottom,
+ distributeHorizontally,
+ distributeVertically,
+ createCustomElement,
+ selectedNodes,
+ groupIsSelected,
+ groupNodes,
+ ungroupNodes,
+ undoHistory,
+ redoHistory,
+ hasPasteData,
+}) => {
+ const [isModalVisible, setModalVisible] = useState(false);
+ const showModal = () => setModalVisible(true);
+ const hideModal = () => setModalVisible(false);
+
+ const handleSave = (name: string, description: string, image: string) => {
+ createCustomElement(name, description, image);
+ hideModal();
+ };
+
+ const editControl = (togglePopover: React.MouseEventHandler) => (
+
+ {strings.getEditMenuButtonLabel()}
+
+ );
+
+ const getPanelTree = (closePopover: ClosePopoverFn) => {
+ const groupMenuItem = groupIsSelected
+ ? {
+ name: strings.getUngroupMenuItemLabel(),
+ className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
+ icon: ,
+ onClick: () => {
+ ungroupNodes();
+ closePopover();
+ },
+ }
+ : {
+ name: strings.getGroupMenuItemLabel(),
+ className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
+ icon: ,
+ disabled: selectedNodes.length < 2,
+ onClick: () => {
+ groupNodes();
+ closePopover();
+ },
+ };
+
+ const orderMenuItem = {
+ name: strings.getOrderMenuItemLabel(),
+ disabled: selectedNodes.length !== 1, // TODO: change to === 0 when we support relayering multiple elements
+ icon: ,
+ panel: {
+ id: 1,
+ title: strings.getOrderMenuItemLabel(),
+ items: [
+ {
+ name: shortcutHelp.BRING_TO_FRONT, // TODO: check against current element position and disable if already top layer
+ icon: 'sortUp',
+ onClick: bringToFront,
+ },
+ {
+ name: shortcutHelp.BRING_FORWARD, // TODO: same as above
+ icon: 'arrowUp',
+ onClick: bringForward,
+ },
+ {
+ name: shortcutHelp.SEND_BACKWARD, // TODO: check against current element position and disable if already bottom layer
+ icon: 'arrowDown',
+ onClick: sendBackward,
+ },
+ {
+ name: shortcutHelp.SEND_TO_BACK, // TODO: same as above
+ icon: 'sortDown',
+ onClick: sendToBack,
+ },
+ ],
+ },
+ };
+
+ const alignmentMenuItem = {
+ name: strings.getAlignmentMenuItemLabel(),
+ className: 'canvasContextMenu',
+ disabled: groupIsSelected || selectedNodes.length < 2,
+ icon: ,
+ panel: {
+ id: 2,
+ title: strings.getAlignmentMenuItemLabel(),
+ items: [
+ {
+ name: strings.getLeftAlignMenuItemLabel(),
+ icon: 'editorItemAlignLeft',
+ onClick: () => {
+ alignLeft();
+ closePopover();
+ },
+ },
+ {
+ name: strings.getCenterAlignMenuItemLabel(),
+ icon: 'editorItemAlignCenter',
+ onClick: () => {
+ alignCenter();
+ closePopover();
+ },
+ },
+ {
+ name: strings.getRightAlignMenuItemLabel(),
+ icon: 'editorItemAlignRight',
+ onClick: () => {
+ alignRight();
+ closePopover();
+ },
+ },
+ {
+ name: strings.getTopAlignMenuItemLabel(),
+ icon: 'editorItemAlignTop',
+ onClick: () => {
+ alignTop();
+ closePopover();
+ },
+ },
+ {
+ name: strings.getMiddleAlignMenuItemLabel(),
+ icon: 'editorItemAlignMiddle',
+ onClick: () => {
+ alignMiddle();
+ closePopover();
+ },
+ },
+ {
+ name: strings.getBottomAlignMenuItemLabel(),
+ icon: 'editorItemAlignBottom',
+ onClick: () => {
+ alignBottom();
+ closePopover();
+ },
+ },
+ ],
+ },
+ };
+
+ const distributionMenuItem = {
+ name: strings.getDistributionMenuItemLabel(),
+ className: 'canvasContextMenu',
+ disabled: groupIsSelected || selectedNodes.length < 3,
+ icon: ,
+ panel: {
+ id: 3,
+ title: strings.getAlignmentMenuItemLabel(),
+ items: [
+ {
+ name: strings.getHorizontalDistributionMenuItemLabel(),
+ icon: 'editorDistributeHorizontal',
+ onClick: () => {
+ distributeHorizontally();
+ closePopover();
+ },
+ },
+ {
+ name: strings.getVerticalDistributionMenuItemLabel(),
+ icon: 'editorDistributeVertical',
+ onClick: () => {
+ distributeVertically();
+ closePopover();
+ },
+ },
+ ],
+ },
+ };
+
+ const savedElementMenuItem = {
+ name: strings.getSaveElementMenuItemLabel(),
+ icon: ,
+ disabled: selectedNodes.length < 1,
+ className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
+ 'data-test-subj': 'canvasWorkpadEditMenu__saveElementButton',
+ onClick: () => {
+ showModal();
+ closePopover();
+ },
+ };
+
+ const items = [
+ {
+ // TODO: check history and disable when there are no more changes to revert
+ name: strings.getUndoMenuItemLabel(),
+ icon: ,
+ onClick: () => {
+ undoHistory();
+ },
+ },
+ {
+ // TODO: check history and disable when there are no more changes to reapply
+ name: strings.getRedoMenuItemLabel(),
+ icon: ,
+ onClick: () => {
+ redoHistory();
+ },
+ },
+ {
+ name: shortcutHelp.CUT,
+ icon: ,
+ className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
+ disabled: selectedNodes.length < 1,
+ onClick: () => {
+ cutNodes();
+ closePopover();
+ },
+ },
+ {
+ name: shortcutHelp.COPY,
+ disabled: selectedNodes.length < 1,
+ icon: ,
+ onClick: () => {
+ copyNodes();
+ },
+ },
+ {
+ name: shortcutHelp.PASTE, // TODO: can this be disabled if clipboard is empty?
+ icon: ,
+ disabled: !hasPasteData,
+ onClick: () => {
+ pasteNodes();
+ closePopover();
+ },
+ },
+ {
+ name: shortcutHelp.DELETE,
+ icon: ,
+ disabled: selectedNodes.length < 1,
+ onClick: () => {
+ deleteNodes();
+ closePopover();
+ },
+ },
+ {
+ name: shortcutHelp.CLONE,
+ icon: ,
+ disabled: selectedNodes.length < 1,
+ onClick: () => {
+ cloneNodes();
+ closePopover();
+ },
+ },
+ groupMenuItem,
+ orderMenuItem,
+ alignmentMenuItem,
+ distributionMenuItem,
+ savedElementMenuItem,
+ ];
+
+ return {
+ id: 0,
+ // title: strings.getEditMenuLabel(),
+ items,
+ };
+ };
+
+ return (
+
+
+ {({ closePopover }: { closePopover: ClosePopoverFn }) => (
+
+ )}
+
+ {isModalVisible ? (
+
+
+
+ ) : null}
+
+ );
+};
+
+EditMenu.propTypes = {
+ cutNodes: PropTypes.func.isRequired,
+ copyNodes: PropTypes.func.isRequired,
+ pasteNodes: PropTypes.func.isRequired,
+ deleteNodes: PropTypes.func.isRequired,
+ cloneNodes: PropTypes.func.isRequired,
+ bringToFront: PropTypes.func.isRequired,
+ bringForward: PropTypes.func.isRequired,
+ sendBackward: PropTypes.func.isRequired,
+ sendToBack: PropTypes.func.isRequired,
+ alignLeft: PropTypes.func.isRequired,
+ alignCenter: PropTypes.func.isRequired,
+ alignRight: PropTypes.func.isRequired,
+ alignTop: PropTypes.func.isRequired,
+ alignMiddle: PropTypes.func.isRequired,
+ alignBottom: PropTypes.func.isRequired,
+ distributeHorizontally: PropTypes.func.isRequired,
+ distributeVertically: PropTypes.func.isRequired,
+ createCustomElement: PropTypes.func.isRequired,
+ selectedNodes: PropTypes.arrayOf(PropTypes.string).isRequired,
+ groupIsSelected: PropTypes.bool.isRequired,
+ groupNodes: PropTypes.func.isRequired,
+ ungroupNodes: PropTypes.func.isRequired,
+};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/index.ts
new file mode 100644
index 0000000000000..a8bb7177dbd24
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/edit_menu/index.ts
@@ -0,0 +1,123 @@
+/*
+ * 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 { connect } from 'react-redux';
+import { compose, withHandlers, withProps } from 'recompose';
+import { Dispatch } from 'redux';
+import { State, PositionedElement } from '../../../../types';
+import { getClipboardData } from '../../../lib/clipboard';
+// @ts-ignore Untyped local
+import { flatten } from '../../../lib/aeroelastic/functional';
+// @ts-ignore Untyped local
+import { globalStateUpdater } from '../../workpad_page/integration_utils';
+// @ts-ignore Untyped local
+import { crawlTree } from '../../workpad_page/integration_utils';
+// @ts-ignore Untyped local
+import { insertNodes, elementLayer, removeElements } from '../../../state/actions/elements';
+// @ts-ignore Untyped local
+import { undoHistory, redoHistory } from '../../../state/actions/history';
+// @ts-ignore Untyped local
+import { selectToplevelNodes } from '../../../state/actions/transient';
+import {
+ getSelectedPage,
+ getNodes,
+ getSelectedToplevelNodes,
+} from '../../../state/selectors/workpad';
+import {
+ layerHandlerCreators,
+ clipboardHandlerCreators,
+ basicHandlerCreators,
+ groupHandlerCreators,
+ alignmentDistributionHandlerCreators,
+} from '../../../lib/element_handler_creators';
+import { EditMenu as Component, Props as ComponentProps } from './edit_menu';
+
+type LayoutState = any;
+
+type CommitFn = (type: string, payload: any) => LayoutState;
+
+interface OwnProps {
+ commit: CommitFn;
+}
+
+const withGlobalState = (
+ commit: CommitFn,
+ updateGlobalState: (layoutState: LayoutState) => void
+) => (type: string, payload: any) => {
+ const newLayoutState = commit(type, payload);
+ if (newLayoutState.currentScene.gestureEnd) {
+ updateGlobalState(newLayoutState);
+ }
+};
+
+/*
+ * TODO: this is all copied from interactive_workpad_page and workpad_shortcuts
+ */
+const mapStateToProps = (state: State) => {
+ const pageId = getSelectedPage(state);
+ const nodes = getNodes(state, pageId) as PositionedElement[];
+ const selectedToplevelNodes = getSelectedToplevelNodes(state);
+ const selectedPrimaryShapeObjects = selectedToplevelNodes
+ .map((id: string) => nodes.find((s: PositionedElement) => s.id === id))
+ .filter((shape?: PositionedElement) => shape) as PositionedElement[];
+ const selectedPersistentPrimaryNodes = flatten(
+ selectedPrimaryShapeObjects.map((shape: PositionedElement) =>
+ nodes.find((n: PositionedElement) => n.id === shape.id) // is it a leaf or a persisted group?
+ ? [shape.id]
+ : nodes.filter((s: PositionedElement) => s.position.parent === shape.id).map(s => s.id)
+ )
+ );
+ const selectedNodeIds = flatten(selectedPersistentPrimaryNodes.map(crawlTree(nodes)));
+
+ return {
+ pageId,
+ selectedToplevelNodes,
+ selectedNodes: selectedNodeIds.map((id: string) => nodes.find(s => s.id === id)),
+ state,
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+ insertNodes: (selectedNodes: PositionedElement[], pageId: string) =>
+ dispatch(insertNodes(selectedNodes, pageId)),
+ removeNodes: (nodeIds: string[], pageId: string) => dispatch(removeElements(nodeIds, pageId)),
+ selectToplevelNodes: (nodes: PositionedElement[]) =>
+ dispatch(
+ selectToplevelNodes(nodes.filter((e: PositionedElement) => !e.position.parent).map(e => e.id))
+ ),
+ elementLayer: (pageId: string, elementId: string, movement: number) => {
+ dispatch(elementLayer({ pageId, elementId, movement }));
+ },
+ undoHistory: () => dispatch(undoHistory()),
+ redoHistory: () => dispatch(redoHistory()),
+ dispatch,
+});
+
+const mergeProps = (
+ { state, selectedToplevelNodes, ...restStateProps }: ReturnType,
+ { dispatch, ...restDispatchProps }: ReturnType,
+ { commit }: OwnProps
+) => {
+ const updateGlobalState = globalStateUpdater(dispatch, state);
+
+ return {
+ ...restDispatchProps,
+ ...restStateProps,
+ commit: withGlobalState(commit, updateGlobalState),
+ groupIsSelected:
+ selectedToplevelNodes.length === 1 && selectedToplevelNodes[0].includes('group'),
+ };
+};
+
+export const EditMenu = compose(
+ connect(mapStateToProps, mapDispatchToProps, mergeProps),
+ withProps(() => ({ hasPasteData: Boolean(getClipboardData()) })),
+ withHandlers(basicHandlerCreators),
+ withHandlers(clipboardHandlerCreators),
+ withHandlers(layerHandlerCreators),
+ withHandlers(groupHandlerCreators),
+ withHandlers(alignmentDistributionHandlerCreators)
+)(Component);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx
index 5c420cf3a04c9..fbb5d70dfc55c 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx
@@ -139,7 +139,6 @@ export const ElementMenu: FunctionComponent = ({
return {
id: 0,
- title: strings.getElementMenuLabel(),
items: [
elementListToMenuItems(textElements),
elementListToMenuItems(shapeElements),
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.tsx
index 1768adf9be79d..d651e649128f9 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/refresh_control/refresh_control.tsx
@@ -32,6 +32,7 @@ export const RefreshControl = ({ doRefresh, inFlight }: Props) => (
iconType="refresh"
aria-label={strings.getRefreshAriaLabel()}
onClick={doRefresh}
+ data-test-subj="canvas-refresh-control"
/>
);
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx
index 621077c29c368..2ac0591a1bdd4 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx
@@ -62,7 +62,6 @@ export const ShareMenu: FunctionComponent = ({ onCopy, onExport, getExpor
const getPanelTree = (closePopover: ClosePopoverFn) => ({
id: 0,
- title: strings.getShareWorkpadMessage(),
items: [
{
name: strings.getShareDownloadJSONTitle(),
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot
index e1ecee0e152be..eb45f97452ae1 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot
@@ -65,3 +65,69 @@ exports[`Storyshots components/WorkpadHeader/ViewMenu read only mode 1`] = `
`;
+
+exports[`Storyshots components/WorkpadHeader/ViewMenu with autoplay enabled 1`] = `
+
+`;
+
+exports[`Storyshots components/WorkpadHeader/ViewMenu with refresh enabled 1`] = `
+
+`;
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx
index 60837ac1218e6..5b4de05da3a3d 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx
@@ -8,32 +8,58 @@ import { action } from '@storybook/addon-actions';
import React from 'react';
import { ViewMenu } from '../view_menu';
+const handlers = {
+ setZoomScale: action('setZoomScale'),
+ zoomIn: action('zoomIn'),
+ zoomOut: action('zoomOut'),
+ toggleWriteable: action('toggleWriteable'),
+ resetZoom: action('resetZoom'),
+ enterFullscreen: action('enterFullscreen'),
+ doRefresh: action('doRefresh'),
+ fitToWindow: action('fitToWindow'),
+ setRefreshInterval: action('setRefreshInterval'),
+ setAutoplayInterval: action('setAutoplayInterval'),
+ enableAutoplay: action('enableAutoplay'),
+};
+
storiesOf('components/WorkpadHeader/ViewMenu', module)
.add('edit mode', () => (
))
.add('read only mode', () => (
+ ))
+ .add('with refresh enabled', () => (
+
+ ))
+ .add('with autoplay enabled', () => (
+
));
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx
similarity index 83%
rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx
rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx
index 97d8920d50dd3..cfd599b1d9f3f 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/auto_refresh_controls.tsx
@@ -22,7 +22,6 @@ import {
htmlIdGenerator,
} from '@elastic/eui';
import { timeDuration } from '../../../lib/time_duration';
-import { RefreshControl } from '../refresh_control';
import { CustomInterval } from './custom_interval';
import { ComponentStrings, UnitStrings } from '../../../../i18n';
@@ -69,7 +68,11 @@ export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterv
const intervalTitleId = generateId();
return (
-
+
@@ -97,9 +100,6 @@ export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterv
) : null}
-
-
-
@@ -112,16 +112,6 @@ export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterv
-
-
-
-
-
-
setRefresh(value)} />
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/custom_interval.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
similarity index 100%
rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/custom_interval.tsx
rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts
index eee613183639c..e1ad9782c8aef 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts
@@ -15,13 +15,21 @@ import { fetchAllRenderables } from '../../../state/actions/elements';
// @ts-ignore Untyped local
import { setZoomScale, setFullscreen, selectToplevelNodes } from '../../../state/actions/transient';
// @ts-ignore Untyped local
-import { setWriteable } from '../../../state/actions/workpad';
+import {
+ setWriteable,
+ setRefreshInterval,
+ enableAutoplay,
+ setAutoplayInterval,
+ // @ts-ignore Untyped local
+} from '../../../state/actions/workpad';
import { getZoomScale, canUserWrite } from '../../../state/selectors/app';
import {
getWorkpadBoundingBox,
getWorkpadWidth,
getWorkpadHeight,
isWriteable,
+ getRefreshInterval,
+ getAutoplay,
} from '../../../state/selectors/workpad';
import { ViewMenu as Component, Props as ComponentProps } from './view_menu';
import { getFitZoomScale } from './lib/get_fit_zoom_scale';
@@ -40,24 +48,35 @@ interface DispatchProps {
setFullscreen: (showFullscreen: boolean) => void;
}
-const mapStateToProps = (state: State) => ({
- zoomScale: getZoomScale(state),
- boundingBox: getWorkpadBoundingBox(state),
- workpadWidth: getWorkpadWidth(state),
- workpadHeight: getWorkpadHeight(state),
- isWriteable: isWriteable(state) && canUserWrite(state),
-});
+const mapStateToProps = (state: State) => {
+ const { enabled, interval } = getAutoplay(state);
+
+ return {
+ zoomScale: getZoomScale(state),
+ boundingBox: getWorkpadBoundingBox(state),
+ workpadWidth: getWorkpadWidth(state),
+ workpadHeight: getWorkpadHeight(state),
+ isWriteable: isWriteable(state) && canUserWrite(state),
+ refreshInterval: getRefreshInterval(state),
+ autoplayEnabled: enabled,
+ autoplayInterval: interval,
+ };
+};
const mapDispatchToProps = (dispatch: Dispatch) => ({
setZoomScale: (scale: number) => dispatch(setZoomScale(scale)),
setWriteable: (isWorkpadWriteable: boolean) => dispatch(setWriteable(isWorkpadWriteable)),
setFullscreen: (value: boolean) => {
dispatch(setFullscreen(value));
+
if (value) {
dispatch(selectToplevelNodes([]));
}
},
doRefresh: () => dispatch(fetchAllRenderables()),
+ setRefreshInterval: (interval: number) => dispatch(setRefreshInterval(interval)),
+ enableAutoplay: (autoplay: number) => dispatch(enableAutoplay(autoplay)),
+ setAutoplayInterval: (interval: number) => dispatch(setAutoplayInterval(interval)),
});
const mergeProps = (
@@ -66,13 +85,15 @@ const mergeProps = (
ownProps: ComponentProps
): ComponentProps => {
const { boundingBox, workpadWidth, workpadHeight, ...remainingStateProps } = stateProps;
+
return {
...remainingStateProps,
...dispatchProps,
...ownProps,
toggleWriteable: () => dispatchProps.setWriteable(!stateProps.isWriteable),
enterFullscreen: () => dispatchProps.setFullscreen(true),
- fitToWindow: () => getFitZoomScale(boundingBox, workpadWidth, workpadHeight),
+ fitToWindow: () =>
+ dispatchProps.setZoomScale(getFitZoomScale(boundingBox, workpadWidth, workpadHeight)),
};
};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx
similarity index 86%
rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx
rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx
index 9e6f0a91c6120..e63eed9f9df53 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/kiosk_controls.tsx
@@ -14,7 +14,6 @@ import {
EuiHorizontalRule,
EuiLink,
EuiSpacer,
- EuiSwitch,
EuiText,
EuiFlexItem,
EuiFlexGroup,
@@ -29,9 +28,7 @@ const { time: timeStrings } = UnitStrings;
const { getSecondsText, getMinutesText } = timeStrings;
interface Props {
- autoplayEnabled: boolean;
autoplayInterval: number;
- onSetEnabled: (enabled: boolean) => void;
onSetInterval: (interval: number | undefined) => void;
}
@@ -54,12 +51,7 @@ const ListGroup = ({ children, ...rest }: ListGroupProps) => (
const generateId = htmlIdGenerator();
-export const KioskControls = ({
- autoplayEnabled,
- autoplayInterval,
- onSetEnabled,
- onSetInterval,
-}: Props) => {
+export const KioskControls = ({ autoplayInterval, onSetInterval }: Props) => {
const RefreshItem = ({ duration, label, descriptionId }: RefreshItemProps) => (
onSetInterval(duration)} aria-describedby={descriptionId}>
@@ -72,7 +64,11 @@ export const KioskControls = ({
const intervalTitleId = generateId();
return (
-
+
{strings.getTitle()}
@@ -81,14 +77,6 @@ export const KioskControls = ({
-
- onSetEnabled(ev.target.checked)}
- />
-
-
{strings.getCycleFormLabel()}
@@ -137,8 +125,6 @@ export const KioskControls = ({
};
KioskControls.propTypes = {
- autoplayEnabled: PropTypes.bool.isRequired,
autoplayInterval: PropTypes.number.isRequired,
- onSetEnabled: PropTypes.func.isRequired,
onSetInterval: PropTypes.func.isRequired,
};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.scss
new file mode 100644
index 0000000000000..c4e06881981c7
--- /dev/null
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.scss
@@ -0,0 +1,4 @@
+.canvasViewMenu__kioskSettings,
+.canvasViewMenu__refreshSettings {
+ padding: $euiSize;
+}
\ No newline at end of file
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx
index d1e08c5809579..b6f108cda37f6 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx
@@ -12,10 +12,16 @@ import {
EuiIcon,
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
-import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../../../common/lib/constants';
+import {
+ MAX_ZOOM_LEVEL,
+ MIN_ZOOM_LEVEL,
+ CONTEXT_MENU_TOP_BORDER_CLASSNAME,
+} from '../../../../common/lib/constants';
import { ComponentStrings } from '../../../../i18n/components';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { Popover, ClosePopoverFn } from '../../popover';
+import { AutoRefreshControls } from './auto_refresh_controls';
+import { KioskControls } from './kiosk_controls';
const { WorkpadHeaderViewMenu: strings } = ComponentStrings;
@@ -62,10 +68,33 @@ export interface Props {
* triggers a refresh of the workpad
*/
doRefresh: () => void;
+ /**
+ * Current auto refresh interval
+ */
+ refreshInterval: number;
+ /**
+ * Sets auto refresh interval
+ */
+ setRefreshInterval: (interval?: number) => void;
+ /**
+ * Is autoplay enabled?
+ */
+ autoplayEnabled: boolean;
+ /**
+ * Current autoplay interval
+ */
+ autoplayInterval: number;
+ /**
+ * Enables autoplay
+ */
+ enableAutoplay: (autoplay: boolean) => void;
+ /**
+ * Sets autoplay interval
+ */
+ setAutoplayInterval: (interval?: number) => void;
}
export const ViewMenu: FunctionComponent = ({
- doRefresh,
enterFullscreen,
fitToWindow,
isWriteable,
@@ -75,7 +104,20 @@ export const ViewMenu: FunctionComponent = ({
zoomIn,
zoomOut,
zoomScale,
+ doRefresh,
+ refreshInterval,
+ setRefreshInterval,
+ autoplayEnabled,
+ autoplayInterval,
+ enableAutoplay,
+ setAutoplayInterval,
}) => {
+ const setRefresh = (val: number | undefined) => setRefreshInterval(val);
+
+ const disableInterval = () => {
+ setRefresh(0);
+ };
+
const viewControl = (togglePopover: React.MouseEventHandler) => (
{strings.getViewMenuButtonLabel()}
@@ -121,36 +163,76 @@ export const ViewMenu: FunctionComponent = ({
const getPanelTree = (closePopover: ClosePopoverFn) => ({
id: 0,
- title: strings.getViewMenuLabel(),
items: [
+ {
+ name: strings.getRefreshMenuItemLabel(),
+ icon: 'refresh',
+ onClick: () => {
+ doRefresh();
+ },
+ },
+ {
+ name: strings.getRefreshSettingsMenuItemLabel(),
+ icon: 'empty',
+ panel: {
+ id: 1,
+ title: strings.getRefreshSettingsMenuItemLabel(),
+ content: (
+ setRefresh(val)}
+ disableInterval={() => disableInterval()}
+ />
+ ),
+ },
+ },
{
name: strings.getFullscreenMenuItemLabel(),
icon: ,
+ className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
onClick: () => {
enterFullscreen();
closePopover();
},
},
{
- name: isWriteable ? strings.getHideEditModeLabel() : strings.getShowEditModeLabel(),
- icon: ,
+ name: autoplayEnabled
+ ? strings.getAutoplayOffMenuItemLabel()
+ : strings.getAutoplayOnMenuItemLabel(),
+ icon: autoplayEnabled ? 'stop' : 'play',
onClick: () => {
- toggleWriteable();
+ enableAutoplay(!autoplayEnabled);
closePopover();
},
},
{
- name: strings.getRefreshMenuItemLabel(),
- icon: 'refresh',
+ name: strings.getAutoplaySettingsMenuItemLabel(),
+ icon: 'empty',
+ panel: {
+ id: 2,
+ title: strings.getAutoplaySettingsMenuItemLabel(),
+ content: (
+
+ ),
+ },
+ },
+ {
+ name: isWriteable ? strings.getHideEditModeLabel() : strings.getShowEditModeLabel(),
+ icon: ,
+ className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
onClick: () => {
- doRefresh();
+ toggleWriteable();
+ closePopover();
},
},
{
name: strings.getZoomMenuItemLabel(),
icon: 'magnifyWithPlus',
panel: {
- id: 1,
+ id: 3,
title: strings.getZoomMenuItemLabel(),
items: getZoomMenuItems(),
},
@@ -161,7 +243,11 @@ export const ViewMenu: FunctionComponent = ({
return (
{({ closePopover }: { closePopover: ClosePopoverFn }) => (
-
+
)}
);
@@ -169,4 +255,19 @@ export const ViewMenu: FunctionComponent = ({
ViewMenu.propTypes = {
isWriteable: PropTypes.bool.isRequired,
+ zoomScale: PropTypes.number.isRequired,
+ fitToWindow: PropTypes.func.isRequired,
+ setZoomScale: PropTypes.func.isRequired,
+ zoomIn: PropTypes.func.isRequired,
+ zoomOut: PropTypes.func.isRequired,
+ resetZoom: PropTypes.func.isRequired,
+ toggleWriteable: PropTypes.func.isRequired,
+ enterFullscreen: PropTypes.func.isRequired,
+ doRefresh: PropTypes.func.isRequired,
+ refreshInterval: PropTypes.number.isRequired,
+ setRefreshInterval: PropTypes.func.isRequired,
+ autoplayEnabled: PropTypes.bool.isRequired,
+ autoplayInterval: PropTypes.number.isRequired,
+ enableAutoplay: PropTypes.func.isRequired,
+ setAutoplayInterval: PropTypes.func.isRequired,
};
diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx
index 253e6c68cfc9e..4aab8280a9f24 100644
--- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx
+++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx
@@ -11,11 +11,11 @@ import { Shortcuts } from 'react-shortcuts';
import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { ComponentStrings } from '../../../i18n';
import { ToolTipShortcut } from '../tool_tip_shortcut/';
-import { ControlSettings } from './control_settings';
// @ts-ignore untyped local
import { RefreshControl } from './refresh_control';
// @ts-ignore untyped local
import { FullscreenControl } from './fullscreen_control';
+import { EditMenu } from './edit_menu';
import { ElementMenu } from './element_menu';
import { ShareMenu } from './share_menu';
import { ViewMenu } from './view_menu';
@@ -26,12 +26,14 @@ export interface Props {
isWriteable: boolean;
toggleWriteable: () => void;
canUserWrite: boolean;
+ commit: (type: string, payload: any) => any;
}
export const WorkpadHeader: FunctionComponent = ({
isWriteable,
canUserWrite,
toggleWriteable,
+ commit,
}) => {
const keyHandler = (action: string) => {
if (action === 'EDITING') {
@@ -101,10 +103,10 @@ export const WorkpadHeader: FunctionComponent = ({
-
+
-
+
@@ -122,7 +124,7 @@ export const WorkpadHeader: FunctionComponent = ({
)}
void;
export type onSetIntervalFn = (interval: string) => void;
diff --git a/x-pack/legacy/plugins/canvas/types/elements.ts b/x-pack/legacy/plugins/canvas/types/elements.ts
index 86356f5bd32a9..5de6b4968545f 100644
--- a/x-pack/legacy/plugins/canvas/types/elements.ts
+++ b/x-pack/legacy/plugins/canvas/types/elements.ts
@@ -75,4 +75,8 @@ export interface ElementPosition {
parent: string | null;
}
-export type PositionedElement = CanvasElement & { ast: ExpressionAstExpression };
+export type PositionedElement = CanvasElement & {
+ ast: ExpressionAstExpression;
+} & {
+ position: ElementPosition;
+};
diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx
index 272c4b3add415..b03960861e0ad 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx
@@ -4,15 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { render, wait } from '@testing-library/react';
+import { render } from '@testing-library/react';
import React from 'react';
import { ApmIndices } from '.';
import * as hooks from '../../../../hooks/useFetcher';
import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext';
describe('ApmIndices', () => {
- it('should not get stuck in infinite loop', async () => {
- spyOn(hooks, 'useFetcher').and.returnValue({
+ it('should not get stuck in infinite loop', () => {
+ const spy = spyOn(hooks, 'useFetcher').and.returnValue({
data: undefined,
status: 'loading'
});
@@ -30,6 +30,6 @@ describe('ApmIndices', () => {
`);
- await wait();
+ expect(spy).toHaveBeenCalledTimes(2);
});
});
diff --git a/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js b/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js
index cab55a2526202..aeccd403c5ce6 100644
--- a/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js
+++ b/x-pack/plugins/apm/scripts/optimize-tsconfig/paths.js
@@ -5,7 +5,7 @@
*/
const path = require('path');
-const xpackRoot = path.resolve(__dirname, '../../../../..');
+const xpackRoot = path.resolve(__dirname, '../../../..');
const kibanaRoot = path.resolve(xpackRoot, '..');
const tsconfigTpl = path.resolve(__dirname, './tsconfig.json');
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap
index 580cafff95e0c..64f06ad0a81cd 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap
@@ -16,6 +16,9 @@ Array [
"p95": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
],
@@ -126,6 +129,9 @@ Array [
"p95": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
],
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap
index 1096c1638f3f2..b93f842b878cb 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap
@@ -14,6 +14,9 @@ Object {
"p95": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
],
@@ -120,6 +123,9 @@ Object {
"p95": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
],
diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts
index 39f2be551ab6e..fb1aafc2d6c95 100644
--- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts
+++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts
@@ -83,7 +83,11 @@ export function transactionGroupsFetcher(
sample: { top_hits: { size: 1, sort } },
avg: { avg: { field: TRANSACTION_DURATION } },
p95: {
- percentiles: { field: TRANSACTION_DURATION, percents: [95] }
+ percentiles: {
+ field: TRANSACTION_DURATION,
+ percents: [95],
+ hdr: { number_of_significant_value_digits: 2 }
+ }
},
sum: { sum: { field: TRANSACTION_DURATION } }
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap
index 49e0e0669c241..cc5900919f829 100644
--- a/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/transactions/__snapshots__/queries.test.ts.snap
@@ -333,6 +333,9 @@ Object {
"pct": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
99,
@@ -425,6 +428,9 @@ Object {
"pct": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
99,
@@ -522,6 +528,9 @@ Object {
"pct": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
99,
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap
index 6c8430a3e71cf..25ebb15fd73e8 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap
@@ -21,6 +21,9 @@ Array [
"pct": Object {
"percentiles": Object {
"field": "transaction.duration.us",
+ "hdr": Object {
+ "number_of_significant_value_digits": 2,
+ },
"percents": Array [
95,
99,
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts
index 8a2e01c9a7891..e33b98592da2d 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts
@@ -69,7 +69,11 @@ export function timeseriesFetcher({
aggs: {
avg: { avg: { field: TRANSACTION_DURATION } },
pct: {
- percentiles: { field: TRANSACTION_DURATION, percents: [95, 99] }
+ percentiles: {
+ field: TRANSACTION_DURATION,
+ percents: [95, 99],
+ hdr: { number_of_significant_value_digits: 2 }
+ }
}
}
},
diff --git a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts
index 8a8d256cf4273..0739e8e6120bf 100644
--- a/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts
+++ b/x-pack/plugins/apm/typings/elasticsearch/aggregations.ts
@@ -86,6 +86,7 @@ export interface AggregationOptionsByType {
percentiles: {
field: string;
percents?: number[];
+ hdr?: { number_of_significant_value_digits: number };
};
extended_stats: {
field: string;
diff --git a/x-pack/plugins/infra/public/utils/formatters/bytes.test.ts b/x-pack/plugins/infra/common/formatters/bytes.test.ts
similarity index 93%
rename from x-pack/plugins/infra/public/utils/formatters/bytes.test.ts
rename to x-pack/plugins/infra/common/formatters/bytes.test.ts
index 4c872bcee057d..ccdeed120acca 100644
--- a/x-pack/plugins/infra/public/utils/formatters/bytes.test.ts
+++ b/x-pack/plugins/infra/common/formatters/bytes.test.ts
@@ -3,9 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
-import { InfraWaffleMapDataFormat } from '../../lib/lib';
+import { InfraWaffleMapDataFormat } from './types';
import { createBytesFormatter } from './bytes';
+
describe('createDataFormatter', () => {
it('should format bytes as bytesDecimal', () => {
const formatter = createBytesFormatter(InfraWaffleMapDataFormat.bytesDecimal);
diff --git a/x-pack/plugins/infra/public/utils/formatters/bytes.ts b/x-pack/plugins/infra/common/formatters/bytes.ts
similarity index 96%
rename from x-pack/plugins/infra/public/utils/formatters/bytes.ts
rename to x-pack/plugins/infra/common/formatters/bytes.ts
index 80a5603ed6994..3a45caa8b5e15 100644
--- a/x-pack/plugins/infra/public/utils/formatters/bytes.ts
+++ b/x-pack/plugins/infra/common/formatters/bytes.ts
@@ -3,9 +3,8 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
-import { InfraWaffleMapDataFormat } from '../../lib/lib';
import { formatNumber } from './number';
+import { InfraWaffleMapDataFormat } from './types';
/**
* The labels are derived from these two Wikipedia articles.
diff --git a/x-pack/plugins/infra/public/utils/formatters/datetime.ts b/x-pack/plugins/infra/common/formatters/datetime.ts
similarity index 100%
rename from x-pack/plugins/infra/public/utils/formatters/datetime.ts
rename to x-pack/plugins/infra/common/formatters/datetime.ts
diff --git a/x-pack/plugins/infra/public/utils/formatters/high_precision.ts b/x-pack/plugins/infra/common/formatters/high_precision.ts
similarity index 100%
rename from x-pack/plugins/infra/public/utils/formatters/high_precision.ts
rename to x-pack/plugins/infra/common/formatters/high_precision.ts
diff --git a/x-pack/plugins/infra/public/utils/formatters/index.ts b/x-pack/plugins/infra/common/formatters/index.ts
similarity index 90%
rename from x-pack/plugins/infra/public/utils/formatters/index.ts
rename to x-pack/plugins/infra/common/formatters/index.ts
index 3c60dba747825..096085696bd6b 100644
--- a/x-pack/plugins/infra/public/utils/formatters/index.ts
+++ b/x-pack/plugins/infra/common/formatters/index.ts
@@ -5,12 +5,12 @@
*/
import Mustache from 'mustache';
-import { InfraWaffleMapDataFormat } from '../../lib/lib';
import { createBytesFormatter } from './bytes';
import { formatNumber } from './number';
import { formatPercent } from './percent';
-import { InventoryFormatterType } from '../../../common/inventory_models/types';
+import { InventoryFormatterType } from '../inventory_models/types';
import { formatHighPercision } from './high_precision';
+import { InfraWaffleMapDataFormat } from './types';
export const FORMATTERS = {
number: formatNumber,
diff --git a/x-pack/plugins/infra/public/utils/formatters/number.ts b/x-pack/plugins/infra/common/formatters/number.ts
similarity index 100%
rename from x-pack/plugins/infra/public/utils/formatters/number.ts
rename to x-pack/plugins/infra/common/formatters/number.ts
diff --git a/x-pack/plugins/infra/public/utils/formatters/percent.ts b/x-pack/plugins/infra/common/formatters/percent.ts
similarity index 100%
rename from x-pack/plugins/infra/public/utils/formatters/percent.ts
rename to x-pack/plugins/infra/common/formatters/percent.ts
diff --git a/x-pack/plugins/infra/common/formatters/snapshot_metric_formats.ts b/x-pack/plugins/infra/common/formatters/snapshot_metric_formats.ts
new file mode 100644
index 0000000000000..8b4ae27cb3061
--- /dev/null
+++ b/x-pack/plugins/infra/common/formatters/snapshot_metric_formats.ts
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+enum InfraFormatterType {
+ number = 'number',
+ abbreviatedNumber = 'abbreviatedNumber',
+ bytes = 'bytes',
+ bits = 'bits',
+ percent = 'percent',
+}
+
+interface MetricFormatter {
+ formatter: InfraFormatterType;
+ template: string;
+ bounds?: { min: number; max: number };
+}
+
+interface MetricFormatters {
+ [key: string]: MetricFormatter;
+}
+
+export const METRIC_FORMATTERS: MetricFormatters = {
+ ['count']: { formatter: InfraFormatterType.number, template: '{{value}}' },
+ ['cpu']: {
+ formatter: InfraFormatterType.percent,
+ template: '{{value}}',
+ },
+ ['memory']: {
+ formatter: InfraFormatterType.percent,
+ template: '{{value}}',
+ },
+ ['rx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
+ ['tx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
+ ['logRate']: {
+ formatter: InfraFormatterType.abbreviatedNumber,
+ template: '{{value}}/s',
+ },
+ ['diskIOReadBytes']: {
+ formatter: InfraFormatterType.bytes,
+ template: '{{value}}/s',
+ },
+ ['diskIOWriteBytes']: {
+ formatter: InfraFormatterType.bytes,
+ template: '{{value}}/s',
+ },
+ ['s3BucketSize']: {
+ formatter: InfraFormatterType.bytes,
+ template: '{{value}}',
+ },
+ ['s3TotalRequests']: {
+ formatter: InfraFormatterType.abbreviatedNumber,
+ template: '{{value}}',
+ },
+ ['s3NumberOfObjects']: {
+ formatter: InfraFormatterType.abbreviatedNumber,
+ template: '{{value}}',
+ },
+ ['s3UploadBytes']: {
+ formatter: InfraFormatterType.bytes,
+ template: '{{value}}',
+ },
+ ['s3DownloadBytes']: {
+ formatter: InfraFormatterType.bytes,
+ template: '{{value}}',
+ },
+ ['sqsOldestMessage']: {
+ formatter: InfraFormatterType.number,
+ template: '{{value}} seconds',
+ },
+};
diff --git a/x-pack/plugins/infra/common/formatters/types.ts b/x-pack/plugins/infra/common/formatters/types.ts
new file mode 100644
index 0000000000000..c438ec2d4205d
--- /dev/null
+++ b/x-pack/plugins/infra/common/formatters/types.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export enum InfraWaffleMapDataFormat {
+ bytesDecimal = 'bytesDecimal',
+ bitsDecimal = 'bitsDecimal',
+ abbreviatedNumber = 'abbreviatedNumber',
+}
diff --git a/x-pack/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx
index b2da7dec3f2e0..764db2164b711 100644
--- a/x-pack/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx
@@ -11,27 +11,29 @@ import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_
import { CloudToolbarItems } from '../shared/components/cloud_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const ec2MetricTypes: SnapshotMetricType[] = [
+ 'cpu',
+ 'rx',
+ 'tx',
+ 'diskIOReadBytes',
+ 'diskIOWriteBytes',
+];
+
+export const ec2groupByFields = [
+ 'cloud.availability_zone',
+ 'cloud.machine.type',
+ 'aws.ec2.instance.image.id',
+ 'aws.ec2.instance.state.name',
+];
+
export const AwsEC2ToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = [
- 'cpu',
- 'rx',
- 'tx',
- 'diskIOReadBytes',
- 'diskIOWriteBytes',
- ];
- const groupByFields = [
- 'cloud.availability_zone',
- 'cloud.machine.type',
- 'aws.ec2.instance.image.id',
- 'aws.ec2.instance.state.name',
- ];
return (
<>
>
);
diff --git a/x-pack/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx
index 2a8394b9dd3a4..3eebdee22b2c3 100644
--- a/x-pack/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx
@@ -11,26 +11,28 @@ import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_
import { CloudToolbarItems } from '../shared/components/cloud_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const rdsMetricTypes: SnapshotMetricType[] = [
+ 'cpu',
+ 'rdsConnections',
+ 'rdsQueriesExecuted',
+ 'rdsActiveTransactions',
+ 'rdsLatency',
+];
+
+export const rdsGroupByFields = [
+ 'cloud.availability_zone',
+ 'aws.rds.db_instance.class',
+ 'aws.rds.db_instance.status',
+];
+
export const AwsRDSToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = [
- 'cpu',
- 'rdsConnections',
- 'rdsQueriesExecuted',
- 'rdsActiveTransactions',
- 'rdsLatency',
- ];
- const groupByFields = [
- 'cloud.availability_zone',
- 'aws.rds.db_instance.class',
- 'aws.rds.db_instance.status',
- ];
return (
<>
>
);
diff --git a/x-pack/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx
index 324bdd0586029..ede618b1bf19d 100644
--- a/x-pack/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx
@@ -11,22 +11,24 @@ import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_
import { CloudToolbarItems } from '../shared/components/cloud_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const s3MetricTypes: SnapshotMetricType[] = [
+ 's3BucketSize',
+ 's3NumberOfObjects',
+ 's3TotalRequests',
+ 's3DownloadBytes',
+ 's3UploadBytes',
+];
+
+export const s3GroupByFields = ['cloud.region'];
+
export const AwsS3ToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = [
- 's3BucketSize',
- 's3NumberOfObjects',
- 's3TotalRequests',
- 's3DownloadBytes',
- 's3UploadBytes',
- ];
- const groupByFields = ['cloud.region'];
return (
<>
>
);
diff --git a/x-pack/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx
index 3229c07034772..e77f3af578197 100644
--- a/x-pack/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx
@@ -11,22 +11,23 @@ import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_
import { CloudToolbarItems } from '../shared/components/cloud_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const sqsMetricTypes: SnapshotMetricType[] = [
+ 'sqsMessagesVisible',
+ 'sqsMessagesDelayed',
+ 'sqsMessagesSent',
+ 'sqsMessagesEmpty',
+ 'sqsOldestMessage',
+];
+export const sqsGroupByFields = ['cloud.region'];
+
export const AwsSQSToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = [
- 'sqsMessagesVisible',
- 'sqsMessagesDelayed',
- 'sqsMessagesSent',
- 'sqsMessagesEmpty',
- 'sqsOldestMessage',
- ];
- const groupByFields = ['cloud.region'];
return (
<>
>
);
diff --git a/x-pack/plugins/infra/common/inventory_models/container/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/container/toolbar_items.tsx
index f6c707726d9ca..f193adbf6aadc 100644
--- a/x-pack/plugins/infra/common/inventory_models/container/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/container/toolbar_items.tsx
@@ -10,21 +10,22 @@ import { ToolbarProps } from '../../../public/pages/metrics/inventory_view/compo
import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_groupby_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const containerMetricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'rx', 'tx'];
+export const containerGroupByFields = [
+ 'host.name',
+ 'cloud.availability_zone',
+ 'cloud.machine.type',
+ 'cloud.project.id',
+ 'cloud.provider',
+ 'service.type',
+];
+
export const ContainerToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'rx', 'tx'];
- const groupByFields = [
- 'host.name',
- 'cloud.availability_zone',
- 'cloud.machine.type',
- 'cloud.project.id',
- 'cloud.provider',
- 'service.type',
- ];
return (
);
};
diff --git a/x-pack/plugins/infra/common/inventory_models/host/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/host/toolbar_items.tsx
index 136264c0e26f4..8ed684b3885de 100644
--- a/x-pack/plugins/infra/common/inventory_models/host/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/host/toolbar_items.tsx
@@ -10,20 +10,27 @@ import { ToolbarProps } from '../../../public/pages/metrics/inventory_view/compo
import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_groupby_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const hostMetricTypes: SnapshotMetricType[] = [
+ 'cpu',
+ 'memory',
+ 'load',
+ 'rx',
+ 'tx',
+ 'logRate',
+];
+export const hostGroupByFields = [
+ 'cloud.availability_zone',
+ 'cloud.machine.type',
+ 'cloud.project.id',
+ 'cloud.provider',
+ 'service.type',
+];
export const HostToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'load', 'rx', 'tx', 'logRate'];
- const groupByFields = [
- 'cloud.availability_zone',
- 'cloud.machine.type',
- 'cloud.project.id',
- 'cloud.provider',
- 'service.type',
- ];
return (
);
};
diff --git a/x-pack/plugins/infra/common/inventory_models/pod/toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/pod/toolbar_items.tsx
index c1cd375ff47bf..54a32e3e0180a 100644
--- a/x-pack/plugins/infra/common/inventory_models/pod/toolbar_items.tsx
+++ b/x-pack/plugins/infra/common/inventory_models/pod/toolbar_items.tsx
@@ -10,14 +10,15 @@ import { ToolbarProps } from '../../../public/pages/metrics/inventory_view/compo
import { MetricsAndGroupByToolbarItems } from '../shared/components/metrics_and_groupby_toolbar_items';
import { SnapshotMetricType } from '../types';
+export const podMetricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'rx', 'tx'];
+export const podGroupByFields = ['kubernetes.namespace', 'kubernetes.node.name', 'service.type'];
+
export const PodToolbarItems = (props: ToolbarProps) => {
- const metricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'rx', 'tx'];
- const groupByFields = ['kubernetes.namespace', 'kubernetes.node.name', 'service.type'];
return (
);
};
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx
index bb664f4067662..8bcf0e9ed5be5 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx
@@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { AlertFlyout } from './alert_flyout';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
-export const AlertDropdown = () => {
+export const MetricsAlertDropdown = () => {
const [popoverOpen, setPopoverOpen] = useState(false);
const [flyoutVisible, setFlyoutVisible] = useState(false);
const kibana = useKibana();
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
index 352ac1927479e..5e14babddcb07 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
@@ -34,6 +34,8 @@ import { MetricsExplorerKueryBar } from '../../../pages/metrics/metrics_explorer
import { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options';
import { MetricsExplorerGroupBy } from '../../../pages/metrics/metrics_explorer/components/group_by';
import { useSourceViaHttp } from '../../../containers/source/use_source_via_http';
+import { convertKueryToElasticSearchQuery } from '../../../utils/kuery';
+
import { ExpressionRow } from './expression_row';
import { AlertContextMeta, TimeUnit, MetricExpression } from '../types';
import { ExpressionChart } from './expression_chart';
@@ -45,6 +47,7 @@ interface Props {
groupBy?: string;
filterQuery?: string;
sourceId?: string;
+ filterQueryText?: string;
alertOnNoData?: boolean;
};
alertsContext: AlertsContextValue;
@@ -111,11 +114,15 @@ export const Expressions: React.FC = props => {
[setAlertParams, alertParams.criteria]
);
- const onFilterQuerySubmit = useCallback(
+ const onFilterChange = useCallback(
(filter: any) => {
- setAlertParams('filterQuery', filter);
+ setAlertParams('filterQueryText', filter);
+ setAlertParams(
+ 'filterQuery',
+ convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || ''
+ );
},
- [setAlertParams]
+ [setAlertParams, derivedIndexPattern]
);
const onGroupByChange = useCallback(
@@ -180,10 +187,19 @@ export const Expressions: React.FC = props => {
if (md.currentOptions) {
if (md.currentOptions.filterQuery) {
- setAlertParams('filterQuery', md.currentOptions.filterQuery);
+ setAlertParams('filterQueryText', md.currentOptions.filterQuery);
+ setAlertParams(
+ 'filterQuery',
+ convertKueryToElasticSearchQuery(md.currentOptions.filterQuery, derivedIndexPattern) ||
+ ''
+ );
} else if (md.currentOptions.groupBy && md.series) {
const filter = `${md.currentOptions.groupBy}: "${md.series.id}"`;
- setAlertParams('filterQuery', filter);
+ setAlertParams('filterQueryText', filter);
+ setAlertParams(
+ 'filterQuery',
+ convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || ''
+ );
}
setAlertParams('groupBy', md.currentOptions.groupBy);
@@ -200,8 +216,8 @@ export const Expressions: React.FC = props => {
}, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps
const handleFieldSearchChange = useCallback(
- (e: ChangeEvent) => onFilterQuerySubmit(e.target.value),
- [onFilterQuerySubmit]
+ (e: ChangeEvent) => onFilterChange(e.target.value),
+ [onFilterChange]
);
return (
@@ -304,13 +320,14 @@ export const Expressions: React.FC = props => {
{(alertsContext.metadata && (
)) || (
)}
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx
new file mode 100644
index 0000000000000..d2904206875c7
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState, useCallback, useMemo } from 'react';
+import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { AlertFlyout } from './alert_flyout';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+
+export const InventoryAlertDropdown = () => {
+ const [popoverOpen, setPopoverOpen] = useState(false);
+ const [flyoutVisible, setFlyoutVisible] = useState(false);
+ const kibana = useKibana();
+
+ const closePopover = useCallback(() => {
+ setPopoverOpen(false);
+ }, [setPopoverOpen]);
+
+ const openPopover = useCallback(() => {
+ setPopoverOpen(true);
+ }, [setPopoverOpen]);
+
+ const menuItems = useMemo(() => {
+ return [
+ setFlyoutVisible(true)}>
+
+ ,
+
+
+ ,
+ ];
+ }, [kibana.services]);
+
+ return (
+ <>
+
+
+
+ }
+ isOpen={popoverOpen}
+ closePopover={closePopover}
+ >
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_flyout.tsx
new file mode 100644
index 0000000000000..83298afd4fc5a
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_flyout.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useContext } from 'react';
+import { AlertsContextProvider, AlertAdd } from '../../../../../triggers_actions_ui/public';
+import { TriggerActionsContext } from '../../../utils/triggers_actions_context';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
+import { InfraWaffleMapOptions } from '../../../lib/lib';
+import { InventoryItemType } from '../../../../common/inventory_models/types';
+
+interface Props {
+ visible?: boolean;
+ options?: Partial;
+ nodeType?: InventoryItemType;
+ filter?: string;
+ setVisible: React.Dispatch>;
+}
+
+export const AlertFlyout = (props: Props) => {
+ const { triggersActionsUI } = useContext(TriggerActionsContext);
+ const { services } = useKibana();
+
+ return (
+ <>
+ {triggersActionsUI && (
+
+
+
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx
new file mode 100644
index 0000000000000..15cad770836bd
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/expression.tsx
@@ -0,0 +1,498 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, useMemo, useEffect, useState, ChangeEvent } from 'react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonIcon,
+ EuiSpacer,
+ EuiText,
+ EuiFormRow,
+ EuiButtonEmpty,
+ EuiFieldSearch,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import {
+ Comparator,
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../../../server/lib/alerting/metric_threshold/types';
+import { euiStyled } from '../../../../../observability/public';
+import {
+ ThresholdExpression,
+ ForLastExpression,
+ // eslint-disable-next-line @kbn/eslint/no-restricted-paths
+} from '../../../../../triggers_actions_ui/public/common';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/application/context/alerts_context';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MetricsExplorerKueryBar } from '../../../pages/metrics/metrics_explorer/components/kuery_bar';
+import { useSourceViaHttp } from '../../../containers/source/use_source_via_http';
+import { toMetricOpt } from '../../../pages/metrics/inventory_view/components/toolbars/toolbar_wrapper';
+import { sqsMetricTypes } from '../../../../common/inventory_models/aws_sqs/toolbar_items';
+import { ec2MetricTypes } from '../../../../common/inventory_models/aws_ec2/toolbar_items';
+import { s3MetricTypes } from '../../../../common/inventory_models/aws_s3/toolbar_items';
+import { rdsMetricTypes } from '../../../../common/inventory_models/aws_rds/toolbar_items';
+import { hostMetricTypes } from '../../../../common/inventory_models/host/toolbar_items';
+import { containerMetricTypes } from '../../../../common/inventory_models/container/toolbar_items';
+import { podMetricTypes } from '../../../../common/inventory_models/pod/toolbar_items';
+import { findInventoryModel } from '../../../../common/inventory_models';
+import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { InventoryMetricConditions } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
+import { MetricExpression } from './metric';
+import { NodeTypeExpression } from './node_type';
+import { InfraWaffleMapOptions } from '../../../lib/lib';
+import { convertKueryToElasticSearchQuery } from '../../../utils/kuery';
+
+interface AlertContextMeta {
+ options?: Partial;
+ nodeType?: InventoryItemType;
+ filter?: string;
+}
+
+interface Props {
+ errors: IErrorObject[];
+ alertParams: {
+ criteria: InventoryMetricConditions[];
+ nodeType: InventoryItemType;
+ groupBy?: string;
+ filterQuery?: string;
+ filterQueryText?: string;
+ sourceId?: string;
+ };
+ alertsContext: AlertsContextValue;
+ setAlertParams(key: string, value: any): void;
+ setAlertProperty(key: string, value: any): void;
+}
+
+type TimeUnit = 's' | 'm' | 'h' | 'd';
+
+const defaultExpression = {
+ metric: 'cpu' as SnapshotMetricType,
+ comparator: Comparator.GT,
+ threshold: [],
+ timeSize: 1,
+ timeUnit: 'm',
+} as InventoryMetricConditions;
+
+export const Expressions: React.FC = props => {
+ const { setAlertParams, alertParams, errors, alertsContext } = props;
+ const { source, createDerivedIndexPattern } = useSourceViaHttp({
+ sourceId: 'default',
+ type: 'metrics',
+ fetch: alertsContext.http.fetch,
+ toastWarning: alertsContext.toastNotifications.addWarning,
+ });
+ const [timeSize, setTimeSize] = useState(1);
+ const [timeUnit, setTimeUnit] = useState('m');
+
+ const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [
+ createDerivedIndexPattern,
+ ]);
+
+ const updateParams = useCallback(
+ (id, e: InventoryMetricConditions) => {
+ const exp = alertParams.criteria ? alertParams.criteria.slice() : [];
+ exp[id] = { ...exp[id], ...e };
+ setAlertParams('criteria', exp);
+ },
+ [setAlertParams, alertParams.criteria]
+ );
+
+ const addExpression = useCallback(() => {
+ const exp = alertParams.criteria.slice();
+ exp.push(defaultExpression);
+ setAlertParams('criteria', exp);
+ }, [setAlertParams, alertParams.criteria]);
+
+ const removeExpression = useCallback(
+ (id: number) => {
+ const exp = alertParams.criteria.slice();
+ if (exp.length > 1) {
+ exp.splice(id, 1);
+ setAlertParams('criteria', exp);
+ }
+ },
+ [setAlertParams, alertParams.criteria]
+ );
+
+ const onFilterChange = useCallback(
+ (filter: any) => {
+ setAlertParams('filterQueryText', filter || '');
+ setAlertParams(
+ 'filterQuery',
+ convertKueryToElasticSearchQuery(filter, derivedIndexPattern) || ''
+ );
+ },
+ [derivedIndexPattern, setAlertParams]
+ );
+
+ const emptyError = useMemo(() => {
+ return {
+ aggField: [],
+ timeSizeUnit: [],
+ timeWindowSize: [],
+ };
+ }, []);
+
+ const updateTimeSize = useCallback(
+ (ts: number | undefined) => {
+ const criteria = alertParams.criteria.map(c => ({
+ ...c,
+ timeSize: ts,
+ }));
+ setTimeSize(ts || undefined);
+ setAlertParams('criteria', criteria);
+ },
+ [alertParams.criteria, setAlertParams]
+ );
+
+ const updateTimeUnit = useCallback(
+ (tu: string) => {
+ const criteria = alertParams.criteria.map(c => ({
+ ...c,
+ timeUnit: tu,
+ }));
+ setTimeUnit(tu as TimeUnit);
+ setAlertParams('criteria', criteria);
+ },
+ [alertParams.criteria, setAlertParams]
+ );
+
+ const updateNodeType = useCallback(
+ (nt: any) => {
+ setAlertParams('nodeType', nt);
+ },
+ [setAlertParams]
+ );
+
+ const handleFieldSearchChange = useCallback(
+ (e: ChangeEvent) => onFilterChange(e.target.value),
+ [onFilterChange]
+ );
+
+ useEffect(() => {
+ const md = alertsContext.metadata;
+ if (!alertParams.nodeType) {
+ if (md && md.nodeType) {
+ setAlertParams('nodeType', md.nodeType);
+ } else {
+ setAlertParams('nodeType', 'host');
+ }
+ }
+
+ if (!alertParams.criteria) {
+ if (md && md.options) {
+ setAlertParams('criteria', [
+ {
+ ...defaultExpression,
+ metric: md.options.metric!.type,
+ } as InventoryMetricConditions,
+ ]);
+ } else {
+ setAlertParams('criteria', [defaultExpression]);
+ }
+ }
+
+ if (!alertParams.filterQuery) {
+ if (md && md.filter) {
+ setAlertParams('filterQueryText', md.filter);
+ setAlertParams(
+ 'filterQuery',
+ convertKueryToElasticSearchQuery(md.filter, derivedIndexPattern) || ''
+ );
+ }
+ }
+
+ if (!alertParams.sourceId) {
+ setAlertParams('sourceId', source?.id);
+ }
+ }, [alertsContext.metadata, derivedIndexPattern, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {alertParams.criteria &&
+ alertParams.criteria.map((e, idx) => {
+ return (
+ 1}
+ remove={removeExpression}
+ addExpression={addExpression}
+ key={idx} // idx's don't usually make good key's but here the index has semantic meaning
+ expressionId={idx}
+ setAlertParams={updateParams}
+ errors={errors[idx] || emptyError}
+ expression={e || {}}
+ />
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+ {(alertsContext.metadata && (
+
+ )) || (
+
+ )}
+
+
+
+ >
+ );
+};
+
+interface ExpressionRowProps {
+ nodeType: InventoryItemType;
+ expressionId: number;
+ expression: Omit & {
+ metric?: SnapshotMetricType;
+ };
+ errors: IErrorObject;
+ canDelete: boolean;
+ addExpression(): void;
+ remove(id: number): void;
+ setAlertParams(id: number, params: Partial): void;
+}
+
+const StyledExpressionRow = euiStyled(EuiFlexGroup)`
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 -4px;
+`;
+
+const StyledExpression = euiStyled.div`
+ padding: 0 4px;
+`;
+
+export const ExpressionRow: React.FC = props => {
+ const { setAlertParams, expression, errors, expressionId, remove, canDelete } = props;
+ const { metric, comparator = Comparator.GT, threshold = [] } = expression;
+
+ const updateMetric = useCallback(
+ (m?: SnapshotMetricType) => {
+ setAlertParams(expressionId, { ...expression, metric: m });
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ const updateComparator = useCallback(
+ (c?: string) => {
+ setAlertParams(expressionId, { ...expression, comparator: c as Comparator | undefined });
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ const updateThreshold = useCallback(
+ t => {
+ if (t.join() !== expression.threshold.join()) {
+ setAlertParams(expressionId, { ...expression, threshold: t });
+ }
+ },
+ [expressionId, expression, setAlertParams]
+ );
+
+ const ofFields = useMemo(() => {
+ let myMetrics = hostMetricTypes;
+
+ switch (props.nodeType) {
+ case 'awsEC2':
+ myMetrics = ec2MetricTypes;
+ break;
+ case 'awsRDS':
+ myMetrics = rdsMetricTypes;
+ break;
+ case 'awsS3':
+ myMetrics = s3MetricTypes;
+ break;
+ case 'awsSQS':
+ myMetrics = sqsMetricTypes;
+ break;
+ case 'host':
+ myMetrics = hostMetricTypes;
+ break;
+ case 'pod':
+ myMetrics = podMetricTypes;
+ break;
+ case 'container':
+ myMetrics = containerMetricTypes;
+ break;
+ }
+ return myMetrics.map(toMetricOpt);
+ }, [props.nodeType]);
+
+ return (
+ <>
+
+
+
+
+ v?.value === metric)?.text || '',
+ }}
+ metrics={
+ ofFields.filter(m => m !== undefined && m.value !== undefined) as Array<{
+ value: SnapshotMetricType;
+ text: string;
+ }>
+ }
+ onChange={updateMetric}
+ errors={errors}
+ />
+
+
+
+
+ {metric && (
+
+
+
{metricUnit[metric]?.label || ''}
+
+
+ )}
+
+
+ {canDelete && (
+
+ remove(expressionId)}
+ />
+
+ )}
+
+
+ >
+ );
+};
+
+const getDisplayNameForType = (type: InventoryItemType) => {
+ const inventoryModel = findInventoryModel(type);
+ return inventoryModel.displayName;
+};
+
+export const nodeTypes: { [key: string]: any } = {
+ host: {
+ text: getDisplayNameForType('host'),
+ value: 'host',
+ },
+ pod: {
+ text: getDisplayNameForType('pod'),
+ value: 'pod',
+ },
+ container: {
+ text: getDisplayNameForType('container'),
+ value: 'container',
+ },
+ awsEC2: {
+ text: getDisplayNameForType('awsEC2'),
+ value: 'awsEC2',
+ },
+ awsS3: {
+ text: getDisplayNameForType('awsS3'),
+ value: 'awsS3',
+ },
+ awsRDS: {
+ text: getDisplayNameForType('awsRDS'),
+ value: 'awsRDS',
+ },
+ awsSQS: {
+ text: getDisplayNameForType('awsSQS'),
+ value: 'awsSQS',
+ },
+};
+
+const metricUnit: Record = {
+ count: { label: '' },
+ cpu: { label: '%' },
+ memory: { label: '%' },
+ rx: { label: 'bits/s' },
+ tx: { label: 'bits/s' },
+ logRate: { label: '/s' },
+ diskIOReadBytes: { label: 'bytes/s' },
+ diskIOWriteBytes: { label: 'bytes/s' },
+ s3BucketSize: { label: 'bytes' },
+ s3TotalRequests: { label: '' },
+ s3NumberOfObjects: { label: '' },
+ s3UploadBytes: { label: 'bytes' },
+ s3DownloadBytes: { label: 'bytes' },
+ sqsOldestMessage: { label: 'seconds' },
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/metric.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/metric.tsx
new file mode 100644
index 0000000000000..faafdf1b81eed
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/metric.tsx
@@ -0,0 +1,150 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiExpression,
+ EuiPopover,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiComboBox,
+} from '@elastic/eui';
+import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { IErrorObject } from '../../../../../triggers_actions_ui/public/types';
+import { SnapshotMetricType } from '../../../../common/inventory_models/types';
+
+interface Props {
+ metric?: { value: SnapshotMetricType; text: string };
+ metrics: Array<{ value: string; text: string }>;
+ errors: IErrorObject;
+ onChange: (metric: SnapshotMetricType) => void;
+ popupPosition?:
+ | 'upCenter'
+ | 'upLeft'
+ | 'upRight'
+ | 'downCenter'
+ | 'downLeft'
+ | 'downRight'
+ | 'leftCenter'
+ | 'leftUp'
+ | 'leftDown'
+ | 'rightCenter'
+ | 'rightUp'
+ | 'rightDown';
+}
+
+export const MetricExpression = ({ metric, metrics, errors, onChange, popupPosition }: Props) => {
+ const [aggFieldPopoverOpen, setAggFieldPopoverOpen] = useState(false);
+ const firstFieldOption = {
+ text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel', {
+ defaultMessage: 'Select a metric',
+ }),
+ value: '',
+ };
+
+ const availablefieldsOptions = metrics.map(m => {
+ return { label: m.text, value: m.value };
+ }, []);
+
+ return (
+ {
+ setAggFieldPopoverOpen(true);
+ }}
+ color={metric ? 'secondary' : 'danger'}
+ />
+ }
+ isOpen={aggFieldPopoverOpen}
+ closePopover={() => {
+ setAggFieldPopoverOpen(false);
+ }}
+ withTitle
+ anchorPosition={popupPosition ?? 'downRight'}
+ zIndex={8000}
+ >
+
+ setAggFieldPopoverOpen(false)}>
+
+
+
+
+ 0 && metric !== undefined}
+ error={errors.metric}
+ >
+ 0 && metric !== undefined}
+ placeholder={firstFieldOption.text}
+ options={availablefieldsOptions}
+ noSuggestions={!availablefieldsOptions.length}
+ selectedOptions={
+ metric ? availablefieldsOptions.filter(a => a.value === metric.value) : []
+ }
+ renderOption={(o: any) => o.label}
+ onChange={selectedOptions => {
+ if (selectedOptions.length > 0) {
+ onChange(selectedOptions[0].value as SnapshotMetricType);
+ setAggFieldPopoverOpen(false);
+ }
+ }}
+ />
+
+
+
+
+
+ );
+};
+
+interface ClosablePopoverTitleProps {
+ children: JSX.Element;
+ onClose: () => void;
+}
+
+export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => {
+ return (
+
+
+ {children}
+
+ onClose()}
+ />
+
+
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/metric_inventory_threshold_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/inventory/metric_inventory_threshold_alert_type.ts
new file mode 100644
index 0000000000000..b7abaf5b36373
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/metric_inventory_threshold_alert_type.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { i18n } from '@kbn/i18n';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types';
+import { Expressions } from './expression';
+import { validateMetricThreshold } from './validation';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
+
+export function getInventoryMetricAlertType(): AlertTypeModel {
+ return {
+ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
+ name: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertName', {
+ defaultMessage: 'Inventory',
+ }),
+ iconClass: 'bell',
+ alertParamsExpression: Expressions,
+ validate: validateMetricThreshold,
+ defaultActionMessage: i18n.translate(
+ 'xpack.infra.metrics.alerting.inventory.threshold.defaultActionMessage',
+ {
+ defaultMessage: `\\{\\{alertName\\}\\} - \\{\\{context.group\\}\\}
+
+\\{\\{context.metricOf.condition0\\}\\} has crossed a threshold of \\{\\{context.thresholdOf.condition0\\}\\}
+Current value is \\{\\{context.valueOf.condition0\\}\\}
+`,
+ }
+ ),
+ };
+}
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/node_type.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/node_type.tsx
new file mode 100644
index 0000000000000..1623fc4e24dcb
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/node_type.tsx
@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiExpression, EuiPopover, EuiFlexGroup, EuiFlexItem, EuiSelect } from '@elastic/eui';
+import { EuiPopoverTitle, EuiButtonIcon } from '@elastic/eui';
+import { InventoryItemType } from '../../../../common/inventory_models/types';
+
+interface WhenExpressionProps {
+ value: InventoryItemType;
+ options: { [key: string]: { text: string; value: InventoryItemType } };
+ onChange: (value: InventoryItemType) => void;
+ popupPosition?:
+ | 'upCenter'
+ | 'upLeft'
+ | 'upRight'
+ | 'downCenter'
+ | 'downLeft'
+ | 'downRight'
+ | 'leftCenter'
+ | 'leftUp'
+ | 'leftDown'
+ | 'rightCenter'
+ | 'rightUp'
+ | 'rightDown';
+}
+
+export const NodeTypeExpression = ({
+ value,
+ options,
+ onChange,
+ popupPosition,
+}: WhenExpressionProps) => {
+ const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false);
+
+ return (
+ {
+ setAggTypePopoverOpen(true);
+ }}
+ />
+ }
+ isOpen={aggTypePopoverOpen}
+ closePopover={() => {
+ setAggTypePopoverOpen(false);
+ }}
+ ownFocus
+ withTitle
+ anchorPosition={popupPosition ?? 'downLeft'}
+ >
+
+ setAggTypePopoverOpen(false)}>
+
+
+ {
+ onChange(e.target.value as InventoryItemType);
+ setAggTypePopoverOpen(false);
+ }}
+ options={Object.values(options).map(o => o)}
+ />
+
+
+ );
+};
+
+interface ClosablePopoverTitleProps {
+ children: JSX.Element;
+ onClose: () => void;
+}
+
+export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => {
+ return (
+
+
+ {children}
+
+ onClose()}
+ />
+
+
+
+ );
+};
diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/validation.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/validation.tsx
new file mode 100644
index 0000000000000..803893dd5a323
--- /dev/null
+++ b/x-pack/plugins/infra/public/components/alerting/inventory/validation.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { MetricExpressionParams } from '../../../../server/lib/alerting/metric_threshold/types';
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { ValidationResult } from '../../../../../triggers_actions_ui/public/types';
+
+export function validateMetricThreshold({
+ criteria,
+}: {
+ criteria: MetricExpressionParams[];
+}): ValidationResult {
+ const validationResult = { errors: {} };
+ const errors: {
+ [id: string]: {
+ timeSizeUnit: string[];
+ timeWindowSize: string[];
+ threshold0: string[];
+ threshold1: string[];
+ metric: string[];
+ };
+ } = {};
+ validationResult.errors = errors;
+
+ if (!criteria || !criteria.length) {
+ return validationResult;
+ }
+
+ criteria.forEach((c, idx) => {
+ // Create an id for each criteria, so we can map errors to specific criteria.
+ const id = idx.toString();
+
+ errors[id] = errors[id] || {
+ timeSizeUnit: [],
+ timeWindowSize: [],
+ threshold0: [],
+ threshold1: [],
+ metric: [],
+ };
+
+ if (!c.threshold || !c.threshold.length) {
+ errors[id].threshold0.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
+ defaultMessage: 'Threshold is required.',
+ })
+ );
+ }
+
+ if (c.comparator === 'between' && (!c.threshold || c.threshold.length < 2)) {
+ errors[id].threshold1.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
+ defaultMessage: 'Threshold is required.',
+ })
+ );
+ }
+
+ if (!c.timeSize) {
+ errors[id].timeWindowSize.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.timeRequred', {
+ defaultMessage: 'Time size is Required.',
+ })
+ );
+ }
+
+ if (!c.metric && c.aggType !== 'count') {
+ errors[id].metric.push(
+ i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', {
+ defaultMessage: 'Metric is required.',
+ })
+ );
+ }
+ });
+
+ return validationResult;
+}
diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx
index 72d6aea5ecfc6..c713839a1bba8 100644
--- a/x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx
@@ -22,7 +22,7 @@ import {
} from './log_entry_column';
import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel';
import { LogPositionState } from '../../../containers/logs/log_position';
-import { localizedDate } from '../../../utils/formatters/datetime';
+import { localizedDate } from '../../../../common/formatters/datetime';
export const LogColumnHeaders: React.FunctionComponent<{
columnConfigurations: LogColumnConfiguration[];
diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx
index fbc450950b828..144caed744bab 100644
--- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx
+++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_date_row.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiTitle } from '@elastic/eui';
-import { localizedDate } from '../../../utils/formatters/datetime';
+import { localizedDate } from '../../../../common/formatters/datetime';
interface LogDateRowProps {
timestamp: number;
diff --git a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts
index bc6374a6538e3..aad54bd2222b7 100644
--- a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts
+++ b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { useEffect, useMemo } from 'react';
+import { useEffect, useMemo, useCallback } from 'react';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
@@ -69,12 +69,15 @@ export const useSourceViaHttp = ({
})();
}, [makeRequest]);
- const createDerivedIndexPattern = (indexType: 'logs' | 'metrics' | 'both' = type) => {
- return {
- fields: response?.source ? response.status.indexFields : [],
- title: pickIndexPattern(response?.source, indexType),
- };
- };
+ const createDerivedIndexPattern = useCallback(
+ (indexType: 'logs' | 'metrics' | 'both' = type) => {
+ return {
+ fields: response?.source ? response.status.indexFields : [],
+ title: pickIndexPattern(response?.source, indexType),
+ };
+ },
+ [response, type]
+ );
const source = useMemo(() => {
return response ? { ...response.source, status: response.status } : null;
diff --git a/x-pack/plugins/infra/public/index.ts b/x-pack/plugins/infra/public/index.ts
index 4465bde377c12..1dfdf827f203b 100644
--- a/x-pack/plugins/infra/public/index.ts
+++ b/x-pack/plugins/infra/public/index.ts
@@ -16,7 +16,7 @@ export const plugin: PluginInitializer<
return new Plugin(context);
};
-export { FORMATTERS } from './utils/formatters';
+export { FORMATTERS } from '../common/formatters';
export { InfraFormatterType } from './lib/lib';
export type InfraAppId = 'logs' | 'metrics';
diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts
index e4de0caf9bb8b..9043b4d9f6979 100644
--- a/x-pack/plugins/infra/public/lib/lib.ts
+++ b/x-pack/plugins/infra/public/lib/lib.ts
@@ -186,12 +186,6 @@ export enum InfraFormatterType {
percent = 'percent',
}
-export enum InfraWaffleMapDataFormat {
- bytesDecimal = 'bytesDecimal',
- bitsDecimal = 'bitsDecimal',
- abbreviatedNumber = 'abbreviatedNumber',
-}
-
export interface InfraGroupByOptions {
text: string;
field: string;
diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx
index 5dc9802fefd25..dbf71665ea869 100644
--- a/x-pack/plugins/infra/public/pages/metrics/index.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx
@@ -28,7 +28,9 @@ import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options';
import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time';
import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters';
-import { AlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown';
+
+import { InventoryAlertDropdown } from '../../components/alerting/inventory/alert_dropdown';
+import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown';
export const InfrastructurePage = ({ match }: RouteComponentProps) => {
const uiCapabilities = useKibana().services.application?.capabilities;
@@ -96,7 +98,8 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
/>
-
+
+
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx
index c528aa885346e..d576f08108649 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node_context_menu.tsx
@@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo, useState } from 'react';
-import { AlertFlyout } from '../../../../../alerting/metric_threshold/components/alert_flyout';
+import { AlertFlyout } from '../../../../../components/alerting/inventory/alert_flyout';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib';
import { getNodeDetailUrl, getNodeLogsUrl } from '../../../../link_to';
import { createUptimeLink } from '../../lib/create_uptime_link';
@@ -24,6 +24,8 @@ import {
SectionSubtitle,
SectionLinks,
SectionLink,
+ withTheme,
+ EuiTheme,
} from '../../../../../../../observability/public';
import { useLinkProps } from '../../../../../hooks/use_link_props';
@@ -37,157 +39,178 @@ interface Props {
popoverPosition: EuiPopoverProps['anchorPosition'];
}
-export const NodeContextMenu: React.FC = ({
- options,
- currentTime,
- children,
- node,
- isPopoverOpen,
- closePopover,
- nodeType,
- popoverPosition,
-}) => {
- const [flyoutVisible, setFlyoutVisible] = useState(false);
- const inventoryModel = findInventoryModel(nodeType);
- const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
- const uiCapabilities = useKibana().services.application?.capabilities;
- // Due to the changing nature of the fields between APM and this UI,
- // We need to have some exceptions until 7.0 & ECS is finalized. Reference
- // #26620 for the details for these fields.
- // TODO: This is tech debt, remove it after 7.0 & ECS migration.
- const apmField = nodeType === 'host' ? 'host.hostname' : inventoryModel.fields.id;
+export const NodeContextMenu: React.FC = withTheme(
+ ({
+ options,
+ currentTime,
+ children,
+ node,
+ isPopoverOpen,
+ closePopover,
+ nodeType,
+ popoverPosition,
+ theme,
+ }) => {
+ const [flyoutVisible, setFlyoutVisible] = useState(false);
+ const inventoryModel = findInventoryModel(nodeType);
+ const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
+ const uiCapabilities = useKibana().services.application?.capabilities;
+ // Due to the changing nature of the fields between APM and this UI,
+ // We need to have some exceptions until 7.0 & ECS is finalized. Reference
+ // #26620 for the details for these fields.
+ // TODO: This is tech debt, remove it after 7.0 & ECS migration.
+ const apmField = nodeType === 'host' ? 'host.hostname' : inventoryModel.fields.id;
- const showDetail = inventoryModel.crosslinkSupport.details;
- const showLogsLink =
- inventoryModel.crosslinkSupport.logs && node.id && uiCapabilities?.logs?.show;
- const showAPMTraceLink =
- inventoryModel.crosslinkSupport.apm && uiCapabilities?.apm && uiCapabilities?.apm.show;
- const showUptimeLink =
- inventoryModel.crosslinkSupport.uptime && (['pod', 'container'].includes(nodeType) || node.ip);
+ const showDetail = inventoryModel.crosslinkSupport.details;
+ const showLogsLink =
+ inventoryModel.crosslinkSupport.logs && node.id && uiCapabilities?.logs?.show;
+ const showAPMTraceLink =
+ inventoryModel.crosslinkSupport.apm && uiCapabilities?.apm && uiCapabilities?.apm.show;
+ const showUptimeLink =
+ inventoryModel.crosslinkSupport.uptime &&
+ (['pod', 'container'].includes(nodeType) || node.ip);
- const inventoryId = useMemo(() => {
- if (nodeType === 'host') {
- if (node.ip) {
- return { label: host.ip , value: node.ip };
+ const inventoryId = useMemo(() => {
+ if (nodeType === 'host') {
+ if (node.ip) {
+ return { label: host.ip , value: node.ip };
+ }
+ } else {
+ if (options.fields) {
+ const { id } = findInventoryFields(nodeType, options.fields);
+ return {
+ label: {id} ,
+ value: node.id,
+ };
+ }
}
- } else {
- if (options.fields) {
- const { id } = findInventoryFields(nodeType, options.fields);
- return {
- label: {id} ,
- value: node.id,
- };
- }
- }
- return { label: '', value: '' };
- }, [nodeType, node.ip, node.id, options.fields]);
+ return { label: '', value: '' };
+ }, [nodeType, node.ip, node.id, options.fields]);
+
+ const nodeLogsMenuItemLinkProps = useLinkProps({
+ app: 'logs',
+ ...getNodeLogsUrl({
+ nodeType,
+ nodeId: node.id,
+ time: currentTime,
+ }),
+ });
+ const nodeDetailMenuItemLinkProps = useLinkProps({
+ ...getNodeDetailUrl({
+ nodeType,
+ nodeId: node.id,
+ from: nodeDetailFrom,
+ to: currentTime,
+ }),
+ });
+ const apmTracesMenuItemLinkProps = useLinkProps({
+ app: 'apm',
+ hash: 'traces',
+ search: {
+ kuery: `${apmField}:"${node.id}"`,
+ },
+ });
+ const uptimeMenuItemLinkProps = useLinkProps(createUptimeLink(options, nodeType, node));
- const nodeLogsMenuItemLinkProps = useLinkProps({
- app: 'logs',
- ...getNodeLogsUrl({
- nodeType,
- nodeId: node.id,
- time: currentTime,
- }),
- });
- const nodeDetailMenuItemLinkProps = useLinkProps({
- ...getNodeDetailUrl({
- nodeType,
- nodeId: node.id,
- from: nodeDetailFrom,
- to: currentTime,
- }),
- });
- const apmTracesMenuItemLinkProps = useLinkProps({
- app: 'apm',
- hash: 'traces',
- search: {
- kuery: `${apmField}:"${node.id}"`,
- },
- });
- const uptimeMenuItemLinkProps = useLinkProps(createUptimeLink(options, nodeType, node));
+ const nodeLogsMenuItem: SectionLinkProps = {
+ label: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', {
+ defaultMessage: '{inventoryName} logs',
+ values: { inventoryName: inventoryModel.singularDisplayName },
+ }),
+ ...nodeLogsMenuItemLinkProps,
+ 'data-test-subj': 'viewLogsContextMenuItem',
+ isDisabled: !showLogsLink,
+ };
- const nodeLogsMenuItem: SectionLinkProps = {
- label: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', {
- defaultMessage: '{inventoryName} logs',
- values: { inventoryName: inventoryModel.singularDisplayName },
- }),
- ...nodeLogsMenuItemLinkProps,
- 'data-test-subj': 'viewLogsContextMenuItem',
- isDisabled: !showLogsLink,
- };
+ const nodeDetailMenuItem: SectionLinkProps = {
+ label: i18n.translate('xpack.infra.nodeContextMenu.viewMetricsName', {
+ defaultMessage: '{inventoryName} metrics',
+ values: { inventoryName: inventoryModel.singularDisplayName },
+ }),
+ ...nodeDetailMenuItemLinkProps,
+ isDisabled: !showDetail,
+ };
- const nodeDetailMenuItem: SectionLinkProps = {
- label: i18n.translate('xpack.infra.nodeContextMenu.viewMetricsName', {
- defaultMessage: '{inventoryName} metrics',
- values: { inventoryName: inventoryModel.singularDisplayName },
- }),
- ...nodeDetailMenuItemLinkProps,
- isDisabled: !showDetail,
- };
+ const apmTracesMenuItem: SectionLinkProps = {
+ label: i18n.translate('xpack.infra.nodeContextMenu.viewAPMTraces', {
+ defaultMessage: '{inventoryName} APM traces',
+ values: { inventoryName: inventoryModel.singularDisplayName },
+ }),
+ ...apmTracesMenuItemLinkProps,
+ 'data-test-subj': 'viewApmTracesContextMenuItem',
+ isDisabled: !showAPMTraceLink,
+ };
- const apmTracesMenuItem: SectionLinkProps = {
- label: i18n.translate('xpack.infra.nodeContextMenu.viewAPMTraces', {
- defaultMessage: '{inventoryName} APM traces',
- values: { inventoryName: inventoryModel.singularDisplayName },
- }),
- ...apmTracesMenuItemLinkProps,
- 'data-test-subj': 'viewApmTracesContextMenuItem',
- isDisabled: !showAPMTraceLink,
- };
+ const uptimeMenuItem: SectionLinkProps = {
+ label: i18n.translate('xpack.infra.nodeContextMenu.viewUptimeLink', {
+ defaultMessage: '{inventoryName} in Uptime',
+ values: { inventoryName: inventoryModel.singularDisplayName },
+ }),
+ ...uptimeMenuItemLinkProps,
+ isDisabled: !showUptimeLink,
+ };
- const uptimeMenuItem: SectionLinkProps = {
- label: i18n.translate('xpack.infra.nodeContextMenu.viewUptimeLink', {
- defaultMessage: '{inventoryName} in Uptime',
- values: { inventoryName: inventoryModel.singularDisplayName },
- }),
- ...uptimeMenuItemLinkProps,
- isDisabled: !showUptimeLink,
- };
+ const createAlertMenuItem: SectionLinkProps = {
+ label: i18n.translate('xpack.infra.nodeContextMenu.createAlertLink', {
+ defaultMessage: 'Create alert',
+ }),
+ style: { color: theme?.eui.euiLinkColor || '#006BB4', fontWeight: 500, padding: 0 },
+ onClick: () => {
+ setFlyoutVisible(true);
+ },
+ };
- return (
- <>
-
-
-
-
-
-
- {inventoryId.label && (
-
-
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
+ return (
+ <>
+
+
+
+
+
+
+ {inventoryId.label && (
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+);
diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts
index acd71e5137694..f8c7a10f12831 100644
--- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts
@@ -5,13 +5,13 @@
*/
import { get } from 'lodash';
-import { createFormatter } from '../../../../utils/formatters';
import { InfraFormatterType } from '../../../../lib/lib';
import {
SnapshotMetricInput,
SnapshotCustomMetricInputRT,
} from '../../../../../common/http_api/snapshot_api';
import { createFormatterForMetric } from '../../metrics_explorer/components/helpers/create_formatter_for_metric';
+import { createFormatter } from '../../../../../common/formatters';
interface MetricFormatter {
formatter: InfraFormatterType;
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx
index 0aab676b7d6c5..0f53ced80888b 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/gauges_section_vis.tsx
@@ -17,7 +17,7 @@ import { get, last, max } from 'lodash';
import React, { ReactText } from 'react';
import { euiStyled } from '../../../../../../observability/public';
-import { createFormatter } from '../../../../utils/formatters';
+import { createFormatter } from '../../../../../common/formatters';
import { InventoryFormatterType } from '../../../../../common/inventory_models/types';
import { SeriesOverrides, VisSectionProps } from '../types';
import { getChartName } from './helpers';
diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts
index bb4ad32660952..0b8773db2dddf 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts
@@ -7,7 +7,7 @@
import { ReactText } from 'react';
import Color from 'color';
import { get, first, last, min, max } from 'lodash';
-import { createFormatter } from '../../../../utils/formatters';
+import { createFormatter } from '../../../../../common/formatters';
import { InfraDataSeries } from '../../../../graphql/types';
import {
InventoryVisTypeRT,
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts
index d07a6b45f02be..46bd7b006446a 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts
@@ -5,7 +5,7 @@
*/
import { MetricsExplorerMetric } from '../../../../../../common/http_api/metrics_explorer';
-import { createFormatter } from '../../../../../utils/formatters';
+import { createFormatter } from '../../../../../../common/formatters';
import { InfraFormatterType } from '../../../../../lib/lib';
import { metricToFormat } from './metric_to_format';
export const createFormatterForMetric = (metric?: MetricsExplorerMetric) => {
diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx
index e9826e1ff3955..04661bbc37702 100644
--- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx
@@ -14,6 +14,7 @@ import { esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data
interface Props {
derivedIndexPattern: IIndexPattern;
onSubmit: (query: string) => void;
+ onChange?: (query: string) => void;
value?: string | null;
placeholder?: string;
}
@@ -30,6 +31,7 @@ function validateQuery(query: string) {
export const MetricsExplorerKueryBar = ({
derivedIndexPattern,
onSubmit,
+ onChange,
value,
placeholder,
}: Props) => {
@@ -46,6 +48,9 @@ export const MetricsExplorerKueryBar = ({
const handleChange = (query: string) => {
setValidation(validateQuery(query));
setDraftQuery(query);
+ if (onChange) {
+ onChange(query);
+ }
};
const filteredDerivedIndexPattern = {
diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts
index 8cdfc4f381f43..d61ef7fc4a631 100644
--- a/x-pack/plugins/infra/public/plugin.ts
+++ b/x-pack/plugins/infra/public/plugin.ts
@@ -22,6 +22,7 @@ import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public
import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public';
import { getAlertType as getLogsAlertType } from './components/alerting/logs/log_threshold_alert_type';
+import { getInventoryMetricAlertType } from './components/alerting/inventory/metric_inventory_threshold_alert_type';
import { createMetricThresholdAlertType } from './alerting/metric_threshold';
export type ClientSetup = void;
@@ -53,6 +54,7 @@ export class Plugin
setup(core: CoreSetup, pluginsSetup: ClientPluginsSetup) {
registerFeatures(pluginsSetup.home);
+ pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getInventoryMetricAlertType());
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getLogsAlertType());
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(createMetricThresholdAlertType());
diff --git a/x-pack/plugins/infra/server/graphql/sources/resolvers.ts b/x-pack/plugins/infra/server/graphql/sources/resolvers.ts
index f880eca933241..cffab4ba4f6f0 100644
--- a/x-pack/plugins/infra/server/graphql/sources/resolvers.ts
+++ b/x-pack/plugins/infra/server/graphql/sources/resolvers.ts
@@ -101,7 +101,9 @@ export const createSourcesResolvers = (
return requestedSourceConfiguration;
},
async allSources(root, args, { req }) {
- const sourceConfigurations = await libs.sources.getAllSourceConfigurations(req);
+ const sourceConfigurations = await libs.sources.getAllSourceConfigurations(
+ req.core.savedObjects.client
+ );
return sourceConfigurations;
},
@@ -131,7 +133,7 @@ export const createSourcesResolvers = (
Mutation: {
async createSource(root, args, { req }) {
const sourceConfiguration = await libs.sources.createSourceConfiguration(
- req,
+ req.core.savedObjects.client,
args.id,
compactObject({
...args.sourceProperties,
@@ -147,7 +149,7 @@ export const createSourcesResolvers = (
};
},
async deleteSource(root, args, { req }) {
- await libs.sources.deleteSourceConfiguration(req, args.id);
+ await libs.sources.deleteSourceConfiguration(req.core.savedObjects.client, args.id);
return {
id: args.id,
@@ -155,7 +157,7 @@ export const createSourcesResolvers = (
},
async updateSource(root, args, { req }) {
const updatedSourceConfiguration = await libs.sources.updateSourceConfiguration(
- req,
+ req.core.savedObjects.client,
args.id,
compactObject({
...args.sourceProperties,
diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts
index 5a5f9d0f8f529..62f324e01f8d9 100644
--- a/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts
@@ -18,6 +18,7 @@ import {
InventoryMetricRT,
} from '../../../../common/inventory_models/types';
import { calculateMetricInterval } from '../../../utils/calculate_metric_interval';
+import { CallWithRequestParams, InfraDatabaseSearchResponse } from '../framework';
export class KibanaMetricsAdapter implements InfraMetricsAdapter {
private framework: KibanaFramework;
@@ -120,9 +121,14 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
indexPattern,
options.timerange.interval
);
+
+ const client = (
+ opts: CallWithRequestParams
+ ): Promise> =>
+ this.framework.callWithRequest(requestContext, 'search', opts);
+
const calculatedInterval = await calculateMetricInterval(
- this.framework,
- requestContext,
+ client,
{
indexPattern: `${options.sourceConfiguration.logAlias},${options.sourceConfiguration.metricAlias}`,
timestampField: options.sourceConfiguration.fields.timestamp,
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
new file mode 100644
index 0000000000000..cc8a35f6e47a1
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts
@@ -0,0 +1,214 @@
+/*
+ * 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 { mapValues, last, get } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import moment from 'moment';
+import {
+ InfraDatabaseSearchResponse,
+ CallWithRequestParams,
+} from '../../adapters/framework/adapter_types';
+import { Comparator, AlertStates, InventoryMetricConditions } from './types';
+import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server';
+import { InfraSnapshot } from '../../snapshot';
+import { parseFilterQuery } from '../../../utils/serialized_query';
+import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
+import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api';
+import { InfraSourceConfiguration } from '../../sources';
+import { InfraBackendLibs } from '../../infra_types';
+import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats';
+import { createFormatter } from '../../../../common/formatters';
+
+interface InventoryMetricThresholdParams {
+ criteria: InventoryMetricConditions[];
+ groupBy: string | undefined;
+ filterQuery: string | undefined;
+ nodeType: InventoryItemType;
+ sourceId?: string;
+}
+
+export const createInventoryMetricThresholdExecutor = (
+ libs: InfraBackendLibs,
+ alertId: string
+) => async ({ services, params }: AlertExecutorOptions) => {
+ const { criteria, filterQuery, sourceId, nodeType } = params as InventoryMetricThresholdParams;
+
+ const source = await libs.sources.getSourceConfiguration(
+ services.savedObjectsClient,
+ sourceId || 'default'
+ );
+
+ const results = await Promise.all(
+ criteria.map(c => evaluateCondtion(c, nodeType, source.configuration, services, filterQuery))
+ );
+
+ const invenotryItems = Object.keys(results[0]);
+ for (const item of invenotryItems) {
+ const alertInstance = services.alertInstanceFactory(`${alertId}-${item}`);
+ // AND logic; all criteria must be across the threshold
+ const shouldAlertFire = results.every(result => result[item].shouldFire);
+
+ // AND logic; because we need to evaluate all criteria, if one of them reports no data then the
+ // whole alert is in a No Data/Error state
+ const isNoData = results.some(result => result[item].isNoData);
+ const isError = results.some(result => result[item].isError);
+
+ if (shouldAlertFire) {
+ alertInstance.scheduleActions(FIRED_ACTIONS.id, {
+ group: item,
+ item,
+ valueOf: mapToConditionsLookup(results, result =>
+ formatMetric(result[item].metric, result[item].currentValue)
+ ),
+ thresholdOf: mapToConditionsLookup(criteria, c => c.threshold),
+ metricOf: mapToConditionsLookup(criteria, c => c.metric),
+ });
+ }
+
+ alertInstance.replaceState({
+ alertState: isError
+ ? AlertStates.ERROR
+ : isNoData
+ ? AlertStates.NO_DATA
+ : shouldAlertFire
+ ? AlertStates.ALERT
+ : AlertStates.OK,
+ });
+ }
+};
+
+interface ConditionResult {
+ shouldFire: boolean;
+ currentValue?: number | null;
+ isNoData: boolean;
+ isError: boolean;
+}
+
+const evaluateCondtion = async (
+ condition: InventoryMetricConditions,
+ nodeType: InventoryItemType,
+ sourceConfiguration: InfraSourceConfiguration,
+ services: AlertServices,
+ filterQuery?: string
+): Promise> => {
+ const { comparator, metric } = condition;
+ let { threshold } = condition;
+
+ const currentValues = await getData(
+ services,
+ nodeType,
+ metric,
+ {
+ to: Date.now(),
+ from: moment()
+ .subtract(condition.timeSize, condition.timeUnit)
+ .toDate()
+ .getTime(),
+ interval: condition.timeUnit,
+ },
+ sourceConfiguration,
+ filterQuery
+ );
+
+ threshold = threshold.map(n => convertMetricValue(metric, n));
+
+ const comparisonFunction = comparatorMap[comparator];
+
+ return mapValues(currentValues, value => ({
+ shouldFire: value !== undefined && value !== null && comparisonFunction(value, threshold),
+ metric,
+ currentValue: value,
+ isNoData: value === null,
+ isError: value === undefined,
+ }));
+};
+
+const getData = async (
+ services: AlertServices,
+ nodeType: InventoryItemType,
+ metric: SnapshotMetricType,
+ timerange: InfraTimerangeInput,
+ sourceConfiguration: InfraSourceConfiguration,
+ filterQuery?: string
+) => {
+ const snapshot = new InfraSnapshot();
+ const esClient = (
+ options: CallWithRequestParams
+ ): Promise> =>
+ services.callCluster('search', options);
+
+ const options = {
+ filterQuery: parseFilterQuery(filterQuery),
+ nodeType,
+ groupBy: [],
+ sourceConfiguration,
+ metric: { type: metric },
+ timerange,
+ };
+
+ const { nodes } = await snapshot.getNodes(esClient, options);
+
+ return nodes.reduce((acc, n) => {
+ const nodePathItem = last(n.path);
+ acc[nodePathItem.label] = n.metric && n.metric.value;
+ return acc;
+ }, {} as Record);
+};
+
+const comparatorMap = {
+ [Comparator.BETWEEN]: (value: number, [a, b]: number[]) =>
+ value >= Math.min(a, b) && value <= Math.max(a, b),
+ // `threshold` is always an array of numbers in case the BETWEEN comparator is
+ // used; all other compartors will just destructure the first value in the array
+ [Comparator.GT]: (a: number, [b]: number[]) => a > b,
+ [Comparator.LT]: (a: number, [b]: number[]) => a < b,
+ [Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b,
+ [Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b,
+ [Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b,
+};
+
+const mapToConditionsLookup = (
+ list: any[],
+ mapFn: (value: any, index: number, array: any[]) => unknown
+) =>
+ list
+ .map(mapFn)
+ .reduce(
+ (result: Record, value, i) => ({ ...result, [`condition${i}`]: value }),
+ {}
+ );
+
+export const FIRED_ACTIONS = {
+ id: 'metrics.invenotry_threshold.fired',
+ name: i18n.translate('xpack.infra.metrics.alerting.inventory.threshold.fired', {
+ defaultMessage: 'Fired',
+ }),
+};
+
+// Some metrics in the UI are in a different unit that what we store in ES.
+const convertMetricValue = (metric: SnapshotMetricType, value: number) => {
+ if (converters[metric]) {
+ return converters[metric](value);
+ } else {
+ return value;
+ }
+};
+const converters: Record number> = {
+ cpu: n => Number(n) / 100,
+ memory: n => Number(n) / 100,
+};
+
+const formatMetric = (metric: SnapshotMetricType, value: number) => {
+ // if (SnapshotCustomMetricInputRT.is(metric)) {
+ // const formatter = createFormatterForMetric(metric);
+ // return formatter(val);
+ // }
+ const metricFormatter = get(METRIC_FORMATTERS, metric, METRIC_FORMATTERS.count);
+ if (value == null) {
+ return '';
+ }
+ const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template);
+ return formatter(value);
+};
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts
new file mode 100644
index 0000000000000..3b6a1b5557bc6
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts
@@ -0,0 +1,92 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { i18n } from '@kbn/i18n';
+import { schema } from '@kbn/config-schema';
+import { curry } from 'lodash';
+import uuid from 'uuid';
+import {
+ createInventoryMetricThresholdExecutor,
+ FIRED_ACTIONS,
+} from './inventory_metric_threshold_executor';
+import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from './types';
+import { InfraBackendLibs } from '../../infra_types';
+
+const condition = schema.object({
+ threshold: schema.arrayOf(schema.number()),
+ comparator: schema.oneOf([
+ schema.literal('>'),
+ schema.literal('<'),
+ schema.literal('>='),
+ schema.literal('<='),
+ schema.literal('between'),
+ schema.literal('outside'),
+ ]),
+ timeUnit: schema.string(),
+ timeSize: schema.number(),
+ metric: schema.string(),
+});
+
+export const registerMetricInventoryThresholdAlertType = (libs: InfraBackendLibs) => ({
+ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
+ name: 'Inventory',
+ validate: {
+ params: schema.object(
+ {
+ criteria: schema.arrayOf(condition),
+ nodeType: schema.string(),
+ filterQuery: schema.maybe(schema.string()),
+ sourceId: schema.string(),
+ },
+ { unknowns: 'allow' }
+ ),
+ },
+ defaultActionGroupId: FIRED_ACTIONS.id,
+ actionGroups: [FIRED_ACTIONS],
+ executor: curry(createInventoryMetricThresholdExecutor)(libs, uuid.v4()),
+ actionVariables: {
+ context: [
+ {
+ name: 'group',
+ description: i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.groupActionVariableDescription',
+ {
+ defaultMessage: 'Name of the group reporting data',
+ }
+ ),
+ },
+ {
+ name: 'valueOf',
+ description: i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.valueOfActionVariableDescription',
+ {
+ defaultMessage:
+ 'Record of the current value of the watched metric; grouped by condition, i.e valueOf.condition0, valueOf.condition1, etc.',
+ }
+ ),
+ },
+ {
+ name: 'thresholdOf',
+ description: i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.thresholdOfActionVariableDescription',
+ {
+ defaultMessage:
+ 'Record of the alerting threshold; grouped by condition, i.e thresholdOf.condition0, thresholdOf.condition1, etc.',
+ }
+ ),
+ },
+ {
+ name: 'metricOf',
+ description: i18n.translate(
+ 'xpack.infra.metrics.alerting.threshold.alerting.metricOfActionVariableDescription',
+ {
+ defaultMessage:
+ 'Record of the watched metric; grouped by condition, i.e metricOf.condition0, metricOf.condition1, etc.',
+ }
+ ),
+ },
+ ],
+ },
+});
diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts
new file mode 100644
index 0000000000000..73ee1ab6b7615
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/types.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { SnapshotMetricType } from '../../../../common/inventory_models/types';
+
+export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold';
+
+export enum Comparator {
+ GT = '>',
+ LT = '<',
+ GT_OR_EQ = '>=',
+ LT_OR_EQ = '<=',
+ BETWEEN = 'between',
+ OUTSIDE_RANGE = 'outside',
+}
+
+export enum AlertStates {
+ OK,
+ ALERT,
+ NO_DATA,
+ ERROR,
+}
+
+export type TimeUnit = 's' | 'm' | 'h' | 'd';
+
+export interface InventoryMetricConditions {
+ metric: SnapshotMetricType;
+ timeSize: number;
+ timeUnit: TimeUnit;
+ sourceId?: string;
+ threshold: number[];
+ comparator: Comparator;
+}
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index 0007b8bd719f4..2531e939792af 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -3,7 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor';
import { Comparator, AlertStates } from './types';
import * as mocks from './test_mocks';
@@ -13,81 +12,14 @@ import {
AlertServicesMock,
AlertInstanceMock,
} from '../../../../../alerting/server/mocks';
-
-const executor = createMetricThresholdExecutor('test') as (opts: {
- params: AlertExecutorOptions['params'];
- services: { callCluster: AlertExecutorOptions['params']['callCluster'] };
-}) => Promise;
-
-const services: AlertServicesMock = alertsMock.createAlertServices();
-services.callCluster.mockImplementation(async (_: string, { body, index }: any) => {
- if (index === 'alternatebeat-*') return mocks.changedSourceIdResponse;
- const metric = body.query.bool.filter[1]?.exists.field;
- if (body.aggs.groupings) {
- if (body.aggs.groupings.composite.after) {
- return mocks.compositeEndResponse;
- }
- if (metric === 'test.metric.2') {
- return mocks.alternateCompositeResponse;
- }
- return mocks.basicCompositeResponse;
- }
- if (metric === 'test.metric.2') {
- return mocks.alternateMetricResponse;
- } else if (metric === 'test.metric.3') {
- return mocks.emptyMetricResponse;
- }
- return mocks.basicMetricResponse;
-});
-services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => {
- if (sourceId === 'alternate')
- return {
- id: 'alternate',
- attributes: { metricAlias: 'alternatebeat-*' },
- type,
- references: [],
- };
- return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] };
-});
+import { InfraSources } from '../../sources';
interface AlertTestInstance {
instance: AlertInstanceMock;
actionQueue: any[];
state: any;
}
-const alertInstances = new Map();
-services.alertInstanceFactory.mockImplementation((instanceID: string) => {
- const alertInstance: AlertTestInstance = {
- instance: alertsMock.createAlertInstanceFactory(),
- actionQueue: [],
- state: {},
- };
- alertInstances.set(instanceID, alertInstance);
- alertInstance.instance.replaceState.mockImplementation((newState: any) => {
- alertInstance.state = newState;
- return alertInstance.instance;
- });
- alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => {
- alertInstance.actionQueue.push({ id, action });
- return alertInstance.instance;
- });
- return alertInstance.instance;
-});
-
-function mostRecentAction(id: string) {
- return alertInstances.get(id)!.actionQueue.pop();
-}
-function getState(id: string) {
- return alertInstances.get(id)!.state;
-}
-
-const baseCriterion = {
- aggType: 'avg',
- metric: 'test.metric.1',
- timeSize: 1,
- timeUnit: 'm',
-};
describe('The metric threshold alert type', () => {
describe('querying the entire infrastructure', () => {
const instanceID = 'test-*';
@@ -167,14 +99,6 @@ describe('The metric threshold alert type', () => {
expect(action.reason).toContain('threshold of 0.75');
expect(action.reason).toContain('test.metric.1');
});
- test('fetches the index pattern dynamically', async () => {
- await execute(Comparator.LT, [17], 'alternate');
- expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id);
- expect(getState(instanceID).alertState).toBe(AlertStates.ALERT);
- await execute(Comparator.LT, [1.5], 'alternate');
- expect(mostRecentAction(instanceID)).toBe(undefined);
- expect(getState(instanceID).alertState).toBe(AlertStates.OK);
- });
});
describe('querying with a groupBy parameter', () => {
@@ -338,3 +262,117 @@ describe('The metric threshold alert type', () => {
});
});
});
+
+const createMockStaticConfiguration = (sources: any) => ({
+ enabled: true,
+ query: {
+ partitionSize: 1,
+ partitionFactor: 1,
+ },
+ sources,
+});
+
+const mockLibs: any = {
+ sources: new InfraSources({
+ config: createMockStaticConfiguration({}),
+ }),
+ configuration: createMockStaticConfiguration({}),
+};
+
+const executor = createMetricThresholdExecutor(mockLibs, 'test') as (opts: {
+ params: AlertExecutorOptions['params'];
+ services: { callCluster: AlertExecutorOptions['params']['callCluster'] };
+}) => Promise;
+
+const services: AlertServicesMock = alertsMock.createAlertServices();
+services.callCluster.mockImplementation(async (_: string, { body, index }: any) => {
+ if (index === 'alternatebeat-*') return mocks.changedSourceIdResponse;
+ const metric = body.query.bool.filter[1]?.exists.field;
+ if (body.aggs.groupings) {
+ if (body.aggs.groupings.composite.after) {
+ return mocks.compositeEndResponse;
+ }
+ if (metric === 'test.metric.2') {
+ return mocks.alternateCompositeResponse;
+ }
+ return mocks.basicCompositeResponse;
+ }
+ if (metric === 'test.metric.2') {
+ return mocks.alternateMetricResponse;
+ }
+ return mocks.basicMetricResponse;
+});
+services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => {
+ if (sourceId === 'alternate')
+ return {
+ id: 'alternate',
+ attributes: { metricAlias: 'alternatebeat-*' },
+ type,
+ references: [],
+ };
+ return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] };
+});
+
+services.callCluster.mockImplementation(async (_: string, { body, index }: any) => {
+ if (index === 'alternatebeat-*') return mocks.changedSourceIdResponse;
+ const metric = body.query.bool.filter[1]?.exists.field;
+ if (body.aggs.groupings) {
+ if (body.aggs.groupings.composite.after) {
+ return mocks.compositeEndResponse;
+ }
+ if (metric === 'test.metric.2') {
+ return mocks.alternateCompositeResponse;
+ }
+ return mocks.basicCompositeResponse;
+ }
+ if (metric === 'test.metric.2') {
+ return mocks.alternateMetricResponse;
+ } else if (metric === 'test.metric.3') {
+ return mocks.emptyMetricResponse;
+ }
+ return mocks.basicMetricResponse;
+});
+services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => {
+ if (sourceId === 'alternate')
+ return {
+ id: 'alternate',
+ attributes: { metricAlias: 'alternatebeat-*' },
+ type,
+ references: [],
+ };
+ return { id: 'default', attributes: { metricAlias: 'metricbeat-*' }, type, references: [] };
+});
+
+const alertInstances = new Map();
+services.alertInstanceFactory.mockImplementation((instanceID: string) => {
+ const alertInstance: AlertTestInstance = {
+ instance: alertsMock.createAlertInstanceFactory(),
+ actionQueue: [],
+ state: {},
+ };
+ alertInstances.set(instanceID, alertInstance);
+ alertInstance.instance.replaceState.mockImplementation((newState: any) => {
+ alertInstance.state = newState;
+ return alertInstance.instance;
+ });
+ alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => {
+ alertInstance.actionQueue.push({ id, action });
+ return alertInstance.instance;
+ });
+ return alertInstance.instance;
+});
+
+function mostRecentAction(id: string) {
+ return alertInstances.get(id)!.actionQueue.pop();
+}
+
+function getState(id: string) {
+ return alertInstances.get(id)!.state;
+}
+
+const baseCriterion = {
+ aggType: 'avg',
+ metric: 'test.metric.1',
+ timeSize: 1,
+ timeUnit: 'm',
+};
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
index bd77e5e2daf42..5c34a058577a1 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts
@@ -5,8 +5,6 @@
*/
import { mapValues } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { convertSavedObjectToSavedSourceConfiguration } from '../../sources/sources';
-import { infraSourceConfigurationSavedObjectType } from '../../sources/saved_object_mappings';
import { InfraDatabaseSearchResponse } from '../../adapters/framework/adapter_types';
import { createAfterKeyHandler } from '../../../utils/create_afterkey_handler';
import { getAllCompositeData } from '../../../utils/get_all_composite_data';
@@ -22,9 +20,9 @@ import {
import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server';
import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
import { getDateHistogramOffset } from '../../snapshot/query_helpers';
+import { InfraBackendLibs } from '../../infra_types';
const TOTAL_BUCKETS = 5;
-const DEFAULT_INDEX_PATTERN = 'metricbeat-*';
interface Aggregation {
aggregatedIntervals: {
@@ -76,6 +74,7 @@ const getParsedFilterQuery: (
export const getElasticsearchMetricQuery = (
{ metric, aggType, timeUnit, timeSize }: MetricExpressionParams,
+ timefield: string,
groupBy?: string,
filterQuery?: string
) => {
@@ -109,7 +108,7 @@ export const getElasticsearchMetricQuery = (
const baseAggs = {
aggregatedIntervals: {
date_histogram: {
- field: '@timestamp',
+ field: timefield,
fixed_interval: interval,
offset,
extended_bounds: {
@@ -181,43 +180,23 @@ export const getElasticsearchMetricQuery = (
};
};
-const getIndexPattern: (
- services: AlertServices,
- sourceId?: string
-) => Promise = async function({ savedObjectsClient }, sourceId = 'default') {
- try {
- const sourceConfiguration = await savedObjectsClient.get(
- infraSourceConfigurationSavedObjectType,
- sourceId
- );
- const { metricAlias } = convertSavedObjectToSavedSourceConfiguration(
- sourceConfiguration
- ).configuration;
- return metricAlias || DEFAULT_INDEX_PATTERN;
- } catch (e) {
- if (e.output.statusCode === 404) {
- return DEFAULT_INDEX_PATTERN;
- } else {
- throw e;
- }
- }
-};
-
const getMetric: (
services: AlertServices,
params: MetricExpressionParams,
index: string,
+ timefield: string,
groupBy: string | undefined,
filterQuery: string | undefined
) => Promise> = async function(
- { savedObjectsClient, callCluster },
+ { callCluster },
params,
index,
+ timefield,
groupBy,
filterQuery
) {
const { aggType } = params;
- const searchBody = getElasticsearchMetricQuery(params, groupBy, filterQuery);
+ const searchBody = getElasticsearchMetricQuery(params, timefield, groupBy, filterQuery);
try {
if (groupBy) {
@@ -265,7 +244,7 @@ const comparatorMap = {
[Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b,
};
-export const createMetricThresholdExecutor = (alertUUID: string) =>
+export const createMetricThresholdExecutor = (libs: InfraBackendLibs, alertId: string) =>
async function({ services, params }: AlertExecutorOptions) {
const { criteria, groupBy, filterQuery, sourceId, alertOnNoData } = params as {
criteria: MetricExpressionParams[];
@@ -275,11 +254,22 @@ export const createMetricThresholdExecutor = (alertUUID: string) =>
alertOnNoData: boolean;
};
+ const source = await libs.sources.getSourceConfiguration(
+ services.savedObjectsClient,
+ sourceId || 'default'
+ );
+ const config = source.configuration;
const alertResults = await Promise.all(
- criteria.map(criterion =>
- (async () => {
- const index = await getIndexPattern(services, sourceId);
- const currentValues = await getMetric(services, criterion, index, groupBy, filterQuery);
+ criteria.map(criterion => {
+ return (async () => {
+ const currentValues = await getMetric(
+ services,
+ criterion,
+ config.fields.timestamp,
+ config.metricAlias,
+ groupBy,
+ filterQuery
+ );
const { threshold, comparator } = criterion;
const comparisonFunction = comparatorMap[comparator];
return mapValues(currentValues, value => ({
@@ -291,13 +281,14 @@ export const createMetricThresholdExecutor = (alertUUID: string) =>
isNoData: value === null,
isError: value === undefined,
}));
- })()
- )
+ })();
+ })
);
+ // Because each alert result has the same group definitions, just grap the groups from the first one.
const groups = Object.keys(alertResults[0]);
for (const group of groups) {
- const alertInstance = services.alertInstanceFactory(`${alertUUID}-${group}`);
+ const alertInstance = services.alertInstanceFactory(`${alertId}-${group}`);
// AND logic; all criteria must be across the threshold
const shouldAlertFire = alertResults.every(result => result[group].shouldFire);
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
index 029491c1168cf..23611559a184f 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
@@ -6,11 +6,11 @@
import { i18n } from '@kbn/i18n';
import uuid from 'uuid';
import { schema } from '@kbn/config-schema';
-import { PluginSetupContract } from '../../../../../alerting/server';
+import { curry } from 'lodash';
import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer';
import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor';
-import { InfraBackendLibs } from '../../infra_types';
import { METRIC_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types';
+import { InfraBackendLibs } from '../../infra_types';
const oneOfLiterals = (arrayOfLiterals: Readonly) =>
schema.string({
@@ -18,17 +18,7 @@ const oneOfLiterals = (arrayOfLiterals: Readonly) =>
arrayOfLiterals.includes(value) ? undefined : `must be one of ${arrayOfLiterals.join(' | ')}`,
});
-export async function registerMetricThresholdAlertType(
- alertingPlugin: PluginSetupContract,
- libs: InfraBackendLibs
-) {
- if (!alertingPlugin) {
- throw new Error(
- 'Cannot register metric threshold alert type. Both the actions and alerting plugins need to be enabled.'
- );
- }
- const alertUUID = uuid.v4();
-
+export function registerMetricThresholdAlertType(libs: InfraBackendLibs) {
const baseCriterion = {
threshold: schema.arrayOf(schema.number()),
comparator: oneOfLiterals(Object.values(Comparator)),
@@ -70,21 +60,24 @@ export async function registerMetricThresholdAlertType(
}
);
- alertingPlugin.registerType({
+ return {
id: METRIC_THRESHOLD_ALERT_TYPE_ID,
name: 'Metric threshold',
validate: {
- params: schema.object({
- criteria: schema.arrayOf(schema.oneOf([countCriterion, nonCountCriterion])),
- groupBy: schema.maybe(schema.string()),
- filterQuery: schema.maybe(schema.string()),
- sourceId: schema.string(),
- alertOnNoData: schema.maybe(schema.boolean()),
- }),
+ params: schema.object(
+ {
+ criteria: schema.arrayOf(schema.oneOf([countCriterion, nonCountCriterion])),
+ groupBy: schema.maybe(schema.string()),
+ filterQuery: schema.maybe(schema.string()),
+ sourceId: schema.string(),
+ alertOnNoData: schema.maybe(schema.boolean()),
+ },
+ { unknowns: 'allow' }
+ ),
},
defaultActionGroupId: FIRED_ACTIONS.id,
actionGroups: [FIRED_ACTIONS],
- executor: createMetricThresholdExecutor(alertUUID),
+ executor: curry(createMetricThresholdExecutor)(libs, uuid.v4()),
actionVariables: {
context: [
{ name: 'group', description: groupActionVariableDescription },
@@ -92,5 +85,5 @@ export async function registerMetricThresholdAlertType(
{ name: 'reason', description: reasonActionVariableDescription },
],
},
- });
+ };
}
diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
index 9760873ff7478..44d30d7281f20 100644
--- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
@@ -6,13 +6,16 @@
import { PluginSetupContract } from '../../../../alerting/server';
import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type';
+import { registerMetricInventoryThresholdAlertType } from './inventory_metric_threshold/register_inventory_metric_threshold_alert_type';
import { registerLogThresholdAlertType } from './log_threshold/register_log_threshold_alert_type';
import { InfraBackendLibs } from '../infra_types';
const registerAlertTypes = (alertingPlugin: PluginSetupContract, libs: InfraBackendLibs) => {
if (alertingPlugin) {
- const registerFns = [registerMetricThresholdAlertType, registerLogThresholdAlertType];
+ alertingPlugin.registerType(registerMetricThresholdAlertType(libs));
+ alertingPlugin.registerType(registerMetricInventoryThresholdAlertType(libs));
+ const registerFns = [registerLogThresholdAlertType];
registerFns.forEach(fn => {
fn(alertingPlugin, libs);
});
diff --git a/x-pack/plugins/infra/server/lib/compose/kibana.ts b/x-pack/plugins/infra/server/lib/compose/kibana.ts
index f100726b5b92e..d22ca2961cfa5 100644
--- a/x-pack/plugins/infra/server/lib/compose/kibana.ts
+++ b/x-pack/plugins/infra/server/lib/compose/kibana.ts
@@ -28,7 +28,7 @@ export function compose(core: CoreSetup, config: InfraConfig, plugins: InfraServ
const sourceStatus = new InfraSourceStatus(new InfraElasticsearchSourceStatusAdapter(framework), {
sources,
});
- const snapshot = new InfraSnapshot({ sources, framework });
+ const snapshot = new InfraSnapshot();
const logEntryCategoriesAnalysis = new LogEntryCategoriesAnalysis({ framework });
const logEntryRateAnalysis = new LogEntryRateAnalysis({ framework });
diff --git a/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts
index cf2b1e59b2a22..c75ee6d644044 100644
--- a/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts
+++ b/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts
@@ -5,26 +5,23 @@
*/
import { uniq } from 'lodash';
-import { RequestHandlerContext } from 'kibana/server';
import { InfraSnapshotRequestOptions } from './types';
import { getMetricsAggregations } from './query_helpers';
import { calculateMetricInterval } from '../../utils/calculate_metric_interval';
import { SnapshotModel, SnapshotModelMetricAggRT } from '../../../common/inventory_models/types';
-import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
import { getDatasetForField } from '../../routes/metrics_explorer/lib/get_dataset_for_field';
import { InfraTimerangeInput } from '../../../common/http_api/snapshot_api';
+import { ESSearchClient } from '.';
export const createTimeRangeWithInterval = async (
- framework: KibanaFramework,
- requestContext: RequestHandlerContext,
+ client: ESSearchClient,
options: InfraSnapshotRequestOptions
): Promise => {
const aggregations = getMetricsAggregations(options);
- const modules = await aggregationsToModules(framework, requestContext, aggregations, options);
+ const modules = await aggregationsToModules(client, aggregations, options);
const interval = Math.max(
(await calculateMetricInterval(
- framework,
- requestContext,
+ client,
{
indexPattern: options.sourceConfiguration.metricAlias,
timestampField: options.sourceConfiguration.fields.timestamp,
@@ -43,8 +40,7 @@ export const createTimeRangeWithInterval = async (
};
const aggregationsToModules = async (
- framework: KibanaFramework,
- requestContext: RequestHandlerContext,
+ client: ESSearchClient,
aggregations: SnapshotModel,
options: InfraSnapshotRequestOptions
): Promise => {
@@ -59,12 +55,7 @@ const aggregationsToModules = async (
const fields = await Promise.all(
uniqueFields.map(
async field =>
- await getDatasetForField(
- framework,
- requestContext,
- field as string,
- options.sourceConfiguration.metricAlias
- )
+ await getDatasetForField(client, field as string, options.sourceConfiguration.metricAlias)
)
);
return fields.filter(f => f) as string[];
diff --git a/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts b/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts
index 07abfa5fd474a..4057ed246ccaf 100644
--- a/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts
+++ b/x-pack/plugins/infra/server/lib/snapshot/snapshot.ts
@@ -3,11 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-
-import { RequestHandlerContext } from 'src/core/server';
-import { InfraDatabaseSearchResponse } from '../adapters/framework';
-import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
-import { InfraSources } from '../sources';
+import { InfraDatabaseSearchResponse, CallWithRequestParams } from '../adapters/framework';
import { JsonObject } from '../../../common/typed_json';
import { SNAPSHOT_COMPOSITE_REQUEST_SIZE } from './constants';
@@ -31,36 +27,26 @@ import { InfraSnapshotRequestOptions } from './types';
import { createTimeRangeWithInterval } from './create_timerange_with_interval';
import { SnapshotNode } from '../../../common/http_api/snapshot_api';
+export type ESSearchClient = (
+ options: CallWithRequestParams
+) => Promise>;
export class InfraSnapshot {
- constructor(private readonly libs: { sources: InfraSources; framework: KibanaFramework }) {}
-
public async getNodes(
- requestContext: RequestHandlerContext,
+ client: ESSearchClient,
options: InfraSnapshotRequestOptions
): Promise<{ nodes: SnapshotNode[]; interval: string }> {
// Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch
// in order to page through the results of their respective composite aggregations.
// Both chains of requests are supposed to run in parallel, and their results be merged
// when they have both been completed.
- const timeRangeWithIntervalApplied = await createTimeRangeWithInterval(
- this.libs.framework,
- requestContext,
- options
- );
+ const timeRangeWithIntervalApplied = await createTimeRangeWithInterval(client, options);
const optionsWithTimerange = { ...options, timerange: timeRangeWithIntervalApplied };
- const groupedNodesPromise = requestGroupedNodes(
- requestContext,
- optionsWithTimerange,
- this.libs.framework
- );
- const nodeMetricsPromise = requestNodeMetrics(
- requestContext,
- optionsWithTimerange,
- this.libs.framework
- );
+ const groupedNodesPromise = requestGroupedNodes(client, optionsWithTimerange);
+ const nodeMetricsPromise = requestNodeMetrics(client, optionsWithTimerange);
const groupedNodeBuckets = await groupedNodesPromise;
const nodeMetricBuckets = await nodeMetricsPromise;
+
return {
nodes: mergeNodeBuckets(groupedNodeBuckets, nodeMetricBuckets, options),
interval: timeRangeWithIntervalApplied.interval,
@@ -77,15 +63,12 @@ const handleAfterKey = createAfterKeyHandler(
input => input?.aggregations?.nodes?.after_key
);
-const callClusterFactory = (framework: KibanaFramework, requestContext: RequestHandlerContext) => (
- opts: any
-) =>
- framework.callWithRequest<{}, InfraSnapshotAggregationResponse>(requestContext, 'search', opts);
+const callClusterFactory = (search: ESSearchClient) => (opts: any) =>
+ search<{}, InfraSnapshotAggregationResponse>(opts);
const requestGroupedNodes = async (
- requestContext: RequestHandlerContext,
- options: InfraSnapshotRequestOptions,
- framework: KibanaFramework
+ client: ESSearchClient,
+ options: InfraSnapshotRequestOptions
): Promise => {
const inventoryModel = findInventoryModel(options.nodeType);
const query = {
@@ -124,13 +107,12 @@ const requestGroupedNodes = async (
return await getAllCompositeData<
InfraSnapshotAggregationResponse,
InfraSnapshotNodeGroupByBucket
- >(callClusterFactory(framework, requestContext), query, bucketSelector, handleAfterKey);
+ >(callClusterFactory(client), query, bucketSelector, handleAfterKey);
};
const requestNodeMetrics = async (
- requestContext: RequestHandlerContext,
- options: InfraSnapshotRequestOptions,
- framework: KibanaFramework
+ client: ESSearchClient,
+ options: InfraSnapshotRequestOptions
): Promise => {
const index =
options.metric.type === 'logRate'
@@ -175,7 +157,7 @@ const requestNodeMetrics = async (
return await getAllCompositeData<
InfraSnapshotAggregationResponse,
InfraSnapshotNodeMetricsBucket
- >(callClusterFactory(framework, requestContext), query, bucketSelector, handleAfterKey);
+ >(callClusterFactory(client), query, bucketSelector, handleAfterKey);
};
// buckets can be InfraSnapshotNodeGroupByBucket[] or InfraSnapshotNodeMetricsBucket[]
diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts
index 0368c7bfd6db8..71682c9e798a6 100644
--- a/x-pack/plugins/infra/server/lib/sources/sources.ts
+++ b/x-pack/plugins/infra/server/lib/sources/sources.ts
@@ -9,7 +9,7 @@ import { failure } from 'io-ts/lib/PathReporter';
import { identity, constant } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
-import { RequestHandlerContext, SavedObjectsClientContract } from 'src/core/server';
+import { SavedObjectsClientContract } from 'src/core/server';
import { defaultSourceConfiguration } from './defaults';
import { NotFoundError } from './errors';
import { infraSourceConfigurationSavedObjectType } from './saved_object_mappings';
@@ -41,7 +41,6 @@ export class InfraSources {
sourceId: string
): Promise {
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
-
const savedSourceConfiguration = await this.getInternalSourceConfiguration(sourceId)
.then(internalSourceConfiguration => ({
id: sourceId,
@@ -79,10 +78,12 @@ export class InfraSources {
return savedSourceConfiguration;
}
- public async getAllSourceConfigurations(requestContext: RequestHandlerContext) {
+ public async getAllSourceConfigurations(savedObjectsClient: SavedObjectsClientContract) {
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
- const savedSourceConfigurations = await this.getAllSavedSourceConfigurations(requestContext);
+ const savedSourceConfigurations = await this.getAllSavedSourceConfigurations(
+ savedObjectsClient
+ );
return savedSourceConfigurations.map(savedSourceConfiguration => ({
...savedSourceConfiguration,
@@ -94,7 +95,7 @@ export class InfraSources {
}
public async createSourceConfiguration(
- requestContext: RequestHandlerContext,
+ savedObjectsClient: SavedObjectsClientContract,
sourceId: string,
source: InfraSavedSourceConfiguration
) {
@@ -106,7 +107,7 @@ export class InfraSources {
);
const createdSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
- await requestContext.core.savedObjects.client.create(
+ await savedObjectsClient.create(
infraSourceConfigurationSavedObjectType,
pickSavedSourceConfiguration(newSourceConfiguration) as any,
{ id: sourceId }
@@ -122,22 +123,22 @@ export class InfraSources {
};
}
- public async deleteSourceConfiguration(requestContext: RequestHandlerContext, sourceId: string) {
- await requestContext.core.savedObjects.client.delete(
- infraSourceConfigurationSavedObjectType,
- sourceId
- );
+ public async deleteSourceConfiguration(
+ savedObjectsClient: SavedObjectsClientContract,
+ sourceId: string
+ ) {
+ await savedObjectsClient.delete(infraSourceConfigurationSavedObjectType, sourceId);
}
public async updateSourceConfiguration(
- requestContext: RequestHandlerContext,
+ savedObjectsClient: SavedObjectsClientContract,
sourceId: string,
sourceProperties: InfraSavedSourceConfiguration
) {
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
const { configuration, version } = await this.getSourceConfiguration(
- requestContext.core.savedObjects.client,
+ savedObjectsClient,
sourceId
);
@@ -147,7 +148,7 @@ export class InfraSources {
);
const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
- await requestContext.core.savedObjects.client.update(
+ await savedObjectsClient.update(
infraSourceConfigurationSavedObjectType,
sourceId,
pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any,
@@ -213,8 +214,8 @@ export class InfraSources {
return convertSavedObjectToSavedSourceConfiguration(savedObject);
}
- private async getAllSavedSourceConfigurations(requestContext: RequestHandlerContext) {
- const savedObjects = await requestContext.core.savedObjects.client.find({
+ private async getAllSavedSourceConfigurations(savedObjectsClient: SavedObjectsClientContract) {
+ const savedObjects = await savedObjectsClient.find({
type: infraSourceConfigurationSavedObjectType,
});
diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts
index d4dfa60ac67a0..db34033c1d4f8 100644
--- a/x-pack/plugins/infra/server/plugin.ts
+++ b/x-pack/plugins/infra/server/plugin.ts
@@ -109,7 +109,7 @@ export class InfraServerPlugin {
sources,
}
);
- const snapshot = new InfraSnapshot({ sources, framework });
+ const snapshot = new InfraSnapshot();
const logEntryCategoriesAnalysis = new LogEntryCategoriesAnalysis({ framework });
const logEntryRateAnalysis = new LogEntryRateAnalysis({ framework });
diff --git a/x-pack/plugins/infra/server/routes/log_sources/configuration.ts b/x-pack/plugins/infra/server/routes/log_sources/configuration.ts
index 0ce594675773c..46929954431f5 100644
--- a/x-pack/plugins/infra/server/routes/log_sources/configuration.ts
+++ b/x-pack/plugins/infra/server/routes/log_sources/configuration.ts
@@ -82,12 +82,12 @@ export const initLogSourceConfigurationRoutes = ({ framework, sources }: InfraBa
const sourceConfigurationExists = sourceConfiguration.origin === 'stored';
const patchedSourceConfiguration = await (sourceConfigurationExists
? sources.updateSourceConfiguration(
- requestContext,
+ requestContext.core.savedObjects.client,
sourceId,
patchedSourceConfigurationProperties
)
: sources.createSourceConfiguration(
- requestContext,
+ requestContext.core.savedObjects.client,
sourceId,
patchedSourceConfigurationProperties
));
diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts
index 66f0ca8fc706a..94e91d32b14bb 100644
--- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts
+++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts
@@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { RequestHandlerContext } from 'kibana/server';
-import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
+import { ESSearchClient } from '../../../lib/snapshot';
interface EventDatasetHit {
_source: {
@@ -16,8 +15,7 @@ interface EventDatasetHit {
}
export const getDatasetForField = async (
- framework: KibanaFramework,
- requestContext: RequestHandlerContext,
+ client: ESSearchClient,
field: string,
indexPattern: string
) => {
@@ -33,11 +31,8 @@ export const getDatasetForField = async (
},
};
- const response = await framework.callWithRequest(
- requestContext,
- 'search',
- params
- );
+ const response = await client(params);
+
if (response.hits.total.value === 0) {
return null;
}
diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts
index e735a26d96a91..a709cbdeeb680 100644
--- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts
+++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts
@@ -17,6 +17,10 @@ import { createMetricModel } from './create_metrics_model';
import { JsonObject } from '../../../../common/typed_json';
import { calculateMetricInterval } from '../../../utils/calculate_metric_interval';
import { getDatasetForField } from './get_dataset_for_field';
+import {
+ CallWithRequestParams,
+ InfraDatabaseSearchResponse,
+} from '../../../lib/adapters/framework';
export const populateSeriesWithTSVBData = (
request: KibanaRequest,
@@ -52,17 +56,21 @@ export const populateSeriesWithTSVBData = (
}
const timerange = { min: options.timerange.from, max: options.timerange.to };
+ const client = (
+ opts: CallWithRequestParams
+ ): Promise> =>
+ framework.callWithRequest(requestContext, 'search', opts);
+
// Create the TSVB model based on the request options
const model = createMetricModel(options);
const modules = await Promise.all(
uniq(options.metrics.filter(m => m.field)).map(
- async m =>
- await getDatasetForField(framework, requestContext, m.field as string, options.indexPattern)
+ async m => await getDatasetForField(client, m.field as string, options.indexPattern)
)
);
+
const calculatedInterval = await calculateMetricInterval(
- framework,
- requestContext,
+ client,
{
indexPattern: options.indexPattern,
timestampField: options.timerange.field,
diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts
index d1dc03893a0d9..2d951d426b03a 100644
--- a/x-pack/plugins/infra/server/routes/snapshot/index.ts
+++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts
@@ -13,6 +13,7 @@ import { UsageCollector } from '../../usage/usage_collector';
import { parseFilterQuery } from '../../utils/serialized_query';
import { SnapshotRequestRT, SnapshotNodeResponseRT } from '../../../common/http_api/snapshot_api';
import { throwErrors } from '../../../common/runtime_types';
+import { CallWithRequestParams, InfraDatabaseSearchResponse } from '../../lib/adapters/framework';
const escapeHatch = schema.object({}, { unknowns: 'allow' });
@@ -57,7 +58,13 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => {
metric,
timerange,
};
- const nodesWithInterval = await libs.snapshot.getNodes(requestContext, options);
+
+ const searchES = (
+ opts: CallWithRequestParams
+ ): Promise> =>
+ framework.callWithRequest(requestContext, 'search', opts);
+
+ const nodesWithInterval = await libs.snapshot.getNodes(searchES, options);
return response.ok({
body: SnapshotNodeResponseRT.encode(nodesWithInterval),
});
diff --git a/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts b/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts
index 7cbbdc0f2145b..43e109b009f48 100644
--- a/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts
+++ b/x-pack/plugins/infra/server/utils/calculate_metric_interval.ts
@@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { RequestHandlerContext } from 'src/core/server';
+// import { RequestHandlerContext } from 'src/core/server';
import { findInventoryModel } from '../../common/inventory_models';
-import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter';
+// import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter';
import { InventoryItemType } from '../../common/inventory_models/types';
+import { ESSearchClient } from '../lib/snapshot';
interface Options {
indexPattern: string;
@@ -23,8 +24,7 @@ interface Options {
* This is useful for visualizing metric modules like s3 that only send metrics once per day.
*/
export const calculateMetricInterval = async (
- framework: KibanaFramework,
- requestContext: RequestHandlerContext,
+ client: ESSearchClient,
options: Options,
modules?: string[],
nodeType?: InventoryItemType // TODO: check that this type still makes sense
@@ -73,11 +73,7 @@ export const calculateMetricInterval = async (
},
};
- const resp = await framework.callWithRequest<{}, PeriodAggregationData>(
- requestContext,
- 'search',
- query
- );
+ const resp = await client<{}, PeriodAggregationData>(query);
// if ES doesn't return an aggregations key, something went seriously wrong.
if (!resp.aggregations) {
diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts
index f8779a879a049..82de90e4735f2 100644
--- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts
+++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts
@@ -205,7 +205,6 @@ export interface RegistryVarsEntry {
interface PackageAdditions {
title: string;
latestVersion: string;
- installedVersion?: string;
assets: AssetsGroupedByServiceByType;
}
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx
index d20350c5db631..cf51296d468a9 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx
@@ -29,7 +29,12 @@ const Text = styled.span`
type HeaderProps = PackageInfo & { iconType?: IconType };
export function Header(props: HeaderProps) {
- const { iconType, name, title, version, installedVersion, latestVersion } = props;
+ const { iconType, name, title, version, latestVersion } = props;
+
+ let installedVersion;
+ if ('savedObject' in props) {
+ installedVersion = props.savedObject.attributes.version;
+ }
const hasWriteCapabilites = useCapabilities().write;
const { toListView } = useLinks();
const ADD_DATASOURCE_URI = useLink(`${EPM_PATH}/${name}-${version}/add-datasource`);
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx
index 1f3eb2cc9362e..848d278819d1d 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx
@@ -32,7 +32,10 @@ export function Detail() {
const packageInfo = response.data?.response;
const title = packageInfo?.title;
const name = packageInfo?.name;
- const installedVersion = packageInfo?.installedVersion;
+ let installedVersion;
+ if (packageInfo && 'savedObject' in packageInfo) {
+ installedVersion = packageInfo.savedObject.attributes.version;
+ }
const status: InstallStatus = packageInfo?.status as any;
// track install status state
diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx
index bf785147502b5..983a322de1088 100644
--- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx
+++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx
@@ -67,29 +67,34 @@ export function EPMHomePage() {
function InstalledPackages() {
const { data: allPackages, isLoading: isLoadingPackages } = useGetPackages();
const [selectedCategory, setSelectedCategory] = useState('');
- const packages =
- allPackages && allPackages.response && selectedCategory === ''
- ? allPackages.response.filter(pkg => pkg.status === 'installed')
- : [];
const title = i18n.translate('xpack.ingestManager.epmList.installedTitle', {
defaultMessage: 'Installed integrations',
});
+ const allInstalledPackages =
+ allPackages && allPackages.response
+ ? allPackages.response.filter(pkg => pkg.status === 'installed')
+ : [];
+
+ const updatablePackages = allInstalledPackages.filter(
+ item => 'savedObject' in item && item.version > item.savedObject.attributes.version
+ );
+
const categories = [
{
id: '',
title: i18n.translate('xpack.ingestManager.epmList.allFilterLinkText', {
defaultMessage: 'All',
}),
- count: packages.length,
+ count: allInstalledPackages.length,
},
{
id: 'updates_available',
title: i18n.translate('xpack.ingestManager.epmList.updatesAvailableFilterLinkText', {
defaultMessage: 'Updates available',
}),
- count: 0, // TODO: Update with real count when available
+ count: updatablePackages.length,
},
];
@@ -106,7 +111,7 @@ function InstalledPackages() {
isLoading={isLoadingPackages}
controls={controls}
title={title}
- list={packages}
+ list={selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages}
/>
);
}
@@ -134,7 +139,6 @@ function AvailablePackages() {
},
...(categoriesRes ? categoriesRes.response : []),
];
-
const controls = categories ? (
(
? {
...from,
status: InstallationStatus.installed,
- installedVersion: savedObject.attributes.version,
savedObject,
}
: {
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts
index 8f51c4d78305c..632bc3ac9b69f 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts
@@ -5,6 +5,7 @@
*/
import { SavedObject, SavedObjectsClientContract } from 'src/core/server';
+import Boom from 'boom';
import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants';
import {
AssetReference,
@@ -93,11 +94,18 @@ export async function installPackage(options: {
const { savedObjectsClient, pkgkey, callCluster } = options;
// TODO: change epm API to /packageName/version so we don't need to do this
const [pkgName, pkgVersion] = pkgkey.split('-');
+
// see if some version of this package is already installed
+ // TODO: calls to getInstallationObject, Registry.fetchInfo, and Registry.fetchFindLatestPackge
+ // and be replaced by getPackageInfo after adjusting for it to not group/use archive assets
const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName });
- const reinstall = pkgVersion === installedPkg?.attributes.version;
-
const registryPackageInfo = await Registry.fetchInfo(pkgName, pkgVersion);
+ const latestPackage = await Registry.fetchFindLatestPackage(pkgName);
+
+ if (pkgVersion < latestPackage.version)
+ throw Boom.badRequest('Cannot install or update to an out-of-date package');
+
+ const reinstall = pkgVersion === installedPkg?.attributes.version;
const { internal = false, removable = true } = registryPackageInfo;
// delete the previous version's installation's SO kibana assets before installing new ones
diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts
index befb4722b6504..0d9db1697d886 100644
--- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts
+++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts
@@ -5,6 +5,7 @@
*/
import { SavedObjectsClientContract } from 'src/core/server';
+import Boom from 'boom';
import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants';
import { AssetReference, AssetType, ElasticsearchAssetType } from '../../../types';
import { CallESAsCurrentUser } from '../../../types';
@@ -20,9 +21,9 @@ export async function removeInstallation(options: {
// TODO: the epm api should change to /name/version so we don't need to do this
const [pkgName] = pkgkey.split('-');
const installation = await getInstallation({ savedObjectsClient, pkgName });
- if (!installation) throw new Error('integration does not exist');
+ if (!installation) throw Boom.badRequest(`${pkgName} is not installed`);
if (installation.removable === false)
- throw new Error(`The ${pkgName} integration is installed by default and cannot be removed`);
+ throw Boom.badRequest(`${pkgName} is installed by default and cannot be removed`);
const installedObjects = installation.installed || [];
// Delete the manager saved object with references to the asset objects
diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
new file mode 100644
index 0000000000000..f295f88a58e5f
--- /dev/null
+++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
@@ -0,0 +1,118 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+
+import { AppMountParameters, CoreSetup } from 'kibana/public';
+import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
+import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
+import { render, unmountComponentAtNode } from 'react-dom';
+
+import rison from 'rison-node';
+import { DashboardConstants } from '../../../../../src/plugins/dashboard/public';
+import { Storage } from '../../../../../src/plugins/kibana_utils/public';
+
+import { LensReportManager, setReportManager, trackUiEvent } from '../lens_ui_telemetry';
+
+import { App } from './app';
+import { EditorFrameStart } from '../types';
+import { addEmbeddableToDashboardUrl, getUrlVars, isRisonObject } from '../helpers';
+import { addHelpMenuToAppChrome } from '../help_menu_util';
+import { SavedObjectIndexStore } from '../persistence';
+import { LensPluginStartDependencies } from '../plugin';
+
+export async function mountApp(
+ core: CoreSetup,
+ params: AppMountParameters,
+ createEditorFrame: EditorFrameStart['createInstance']
+) {
+ const [coreStart, startDependencies] = await core.getStartServices();
+ const { data: dataStart, navigation } = startDependencies;
+ const savedObjectsClient = coreStart.savedObjects.client;
+ addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks);
+
+ const instance = await createEditorFrame();
+
+ setReportManager(
+ new LensReportManager({
+ storage: new Storage(localStorage),
+ http: core.http,
+ })
+ );
+ const updateUrlTime = (urlVars: Record): void => {
+ const decoded = rison.decode(urlVars._g);
+ if (!isRisonObject(decoded)) {
+ return;
+ }
+ // @ts-ignore
+ decoded.time = dataStart.query.timefilter.timefilter.getTime();
+ urlVars._g = rison.encode(decoded);
+ };
+ const redirectTo = (
+ routeProps: RouteComponentProps<{ id?: string }>,
+ addToDashboardMode: boolean,
+ id?: string
+ ) => {
+ if (!id) {
+ routeProps.history.push('/lens');
+ } else if (!addToDashboardMode) {
+ routeProps.history.push(`/lens/edit/${id}`);
+ } else if (addToDashboardMode && id) {
+ routeProps.history.push(`/lens/edit/${id}`);
+ const lastDashboardLink = coreStart.chrome.navLinks.get('kibana:dashboard');
+ if (!lastDashboardLink || !lastDashboardLink.url) {
+ throw new Error('Cannot get last dashboard url');
+ }
+ const urlVars = getUrlVars(lastDashboardLink.url);
+ updateUrlTime(urlVars); // we need to pass in timerange in query params directly
+ const dashboardUrl = addEmbeddableToDashboardUrl(lastDashboardLink.url, id, urlVars);
+ window.history.pushState({}, '', dashboardUrl);
+ }
+ };
+
+ const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
+ trackUiEvent('loaded');
+ const addToDashboardMode =
+ !!routeProps.location.search &&
+ routeProps.location.search.includes(
+ DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM
+ );
+ return (
+ redirectTo(routeProps, addToDashboardMode, id)}
+ addToDashboardMode={addToDashboardMode}
+ />
+ );
+ };
+
+ function NotFound() {
+ trackUiEvent('loaded_404');
+ return ;
+ }
+
+ render(
+
+
+
+
+
+
+
+
+ ,
+ params.element
+ );
+ return () => {
+ instance.unmount();
+ unmountComponentAtNode(params.element);
+ };
+}
diff --git a/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap
new file mode 100644
index 0000000000000..76063d230bdb6
--- /dev/null
+++ b/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`datatable_expression DatatableComponent it renders the title and value 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/lens/public/datatable_visualization/_visualization.scss b/x-pack/plugins/lens/public/datatable_visualization/_visualization.scss
index e36326d710f72..7d95d73143870 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/_visualization.scss
+++ b/x-pack/plugins/lens/public/datatable_visualization/_visualization.scss
@@ -1,3 +1,13 @@
.lnsDataTable {
align-self: flex-start;
}
+
+.lnsDataTable__filter {
+ opacity: 0;
+ transition: opacity $euiAnimSpeedNormal ease-in-out;
+}
+
+.lnsDataTable__cell:hover .lnsDataTable__filter,
+.lnsDataTable__filter:focus-within {
+ opacity: 1;
+}
diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx
new file mode 100644
index 0000000000000..6d5b1153ad1bc
--- /dev/null
+++ b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx
@@ -0,0 +1,156 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { datatable, DatatableComponent } from './expression';
+import { LensMultiTable } from '../types';
+import { DatatableProps } from './expression';
+import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks';
+import { IFieldFormat } from '../../../../../src/plugins/data/public';
+import { IAggType } from 'src/plugins/data/public';
+const executeTriggerActions = jest.fn();
+
+function sampleArgs() {
+ const data: LensMultiTable = {
+ type: 'lens_multitable',
+ tables: {
+ l1: {
+ type: 'kibana_datatable',
+ columns: [
+ { id: 'a', name: 'a', meta: { type: 'count' } },
+ { id: 'b', name: 'b', meta: { type: 'date_histogram', aggConfigParams: { field: 'b' } } },
+ { id: 'c', name: 'c', meta: { type: 'cardinality' } },
+ ],
+ rows: [{ a: 10110, b: 1588024800000, c: 3 }],
+ },
+ },
+ };
+
+ const args: DatatableProps['args'] = {
+ title: 'My fanci metric chart',
+ columns: {
+ columnIds: ['a', 'b', 'c'],
+ type: 'lens_datatable_columns',
+ },
+ };
+
+ return { data, args };
+}
+
+describe('datatable_expression', () => {
+ describe('datatable renders', () => {
+ test('it renders with the specified data and args', () => {
+ const { data, args } = sampleArgs();
+ const result = datatable.fn(data, args, createMockExecutionContext());
+
+ expect(result).toEqual({
+ type: 'render',
+ as: 'lens_datatable_renderer',
+ value: { data, args },
+ });
+ });
+ });
+
+ describe('DatatableComponent', () => {
+ test('it renders the title and value', () => {
+ const { data, args } = sampleArgs();
+
+ expect(
+ shallow(
+ x as IFieldFormat}
+ executeTriggerActions={executeTriggerActions}
+ getType={jest.fn()}
+ />
+ )
+ ).toMatchSnapshot();
+ });
+
+ test('it invokes executeTriggerActions with correct context on click on top value', () => {
+ const { args, data } = sampleArgs();
+
+ const wrapper = mountWithIntl(
+ x as IFieldFormat}
+ executeTriggerActions={executeTriggerActions}
+ getType={jest.fn(() => ({ type: 'buckets' } as IAggType))}
+ />
+ );
+
+ wrapper
+ .find('[data-test-subj="lensDatatableFilterOut"]')
+ .first()
+ .simulate('click');
+
+ expect(executeTriggerActions).toHaveBeenCalledWith('VALUE_CLICK_TRIGGER', {
+ data: {
+ data: [
+ {
+ column: 0,
+ row: 0,
+ table: data.tables.l1,
+ value: 10110,
+ },
+ ],
+ negate: true,
+ },
+ timeFieldName: undefined,
+ });
+ });
+
+ test('it invokes executeTriggerActions with correct context on click on timefield', () => {
+ const { args, data } = sampleArgs();
+
+ const wrapper = mountWithIntl(
+ x as IFieldFormat}
+ executeTriggerActions={executeTriggerActions}
+ getType={jest.fn(() => ({ type: 'buckets' } as IAggType))}
+ />
+ );
+
+ wrapper
+ .find('[data-test-subj="lensDatatableFilterFor"]')
+ .at(3)
+ .simulate('click');
+
+ expect(executeTriggerActions).toHaveBeenCalledWith('VALUE_CLICK_TRIGGER', {
+ data: {
+ data: [
+ {
+ column: 1,
+ row: 0,
+ table: data.tables.l1,
+ value: 1588024800000,
+ },
+ ],
+ negate: false,
+ },
+ timeFieldName: 'b',
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx
index 772ee13168d02..71d29be1744bb 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx
+++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx
@@ -7,7 +7,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { i18n } from '@kbn/i18n';
-import { EuiBasicTable } from '@elastic/eui';
+import { I18nProvider } from '@kbn/i18n/react';
+import { EuiBasicTable, EuiFlexGroup, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui';
+import { IAggType } from 'src/plugins/data/public';
import { FormatFactory, LensMultiTable } from '../types';
import {
ExpressionFunctionDefinition,
@@ -15,7 +17,10 @@ import {
IInterpreterRenderHandlers,
} from '../../../../../src/plugins/expressions/public';
import { VisualizationContainer } from '../visualization_container';
-
+import { ValueClickTriggerContext } from '../../../../../src/plugins/embeddable/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public';
+import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
+import { getExecuteTriggerActions } from '../services';
export interface DatatableColumns {
columnIds: string[];
}
@@ -30,6 +35,12 @@ export interface DatatableProps {
args: Args;
}
+type DatatableRenderProps = DatatableProps & {
+ formatFactory: FormatFactory;
+ executeTriggerActions: UiActionsStart['executeTriggerActions'];
+ getType: (name: string) => IAggType;
+};
+
export interface DatatableRender {
type: 'render';
as: 'lens_datatable_renderer';
@@ -100,9 +111,10 @@ export const datatableColumns: ExpressionFunctionDefinition<
},
};
-export const getDatatableRenderer = (
- formatFactory: Promise
-): ExpressionRenderDefinition => ({
+export const getDatatableRenderer = (dependencies: {
+ formatFactory: Promise;
+ getType: Promise<(name: string) => IAggType>;
+}): ExpressionRenderDefinition => ({
name: 'lens_datatable_renderer',
displayName: i18n.translate('xpack.lens.datatable.visualizationName', {
defaultMessage: 'Datatable',
@@ -115,9 +127,18 @@ export const getDatatableRenderer = (
config: DatatableProps,
handlers: IInterpreterRenderHandlers
) => {
- const resolvedFormatFactory = await formatFactory;
+ const resolvedFormatFactory = await dependencies.formatFactory;
+ const executeTriggerActions = getExecuteTriggerActions();
+ const resolvedGetType = await dependencies.getType;
ReactDOM.render(
- ,
+
+
+ ,
domNode,
() => {
handlers.done();
@@ -127,7 +148,7 @@ export const getDatatableRenderer = (
},
});
-function DatatableComponent(props: DatatableProps & { formatFactory: FormatFactory }) {
+export function DatatableComponent(props: DatatableRenderProps) {
const [firstTable] = Object.values(props.data.tables);
const formatters: Record> = {};
@@ -135,6 +156,29 @@ function DatatableComponent(props: DatatableProps & { formatFactory: FormatFacto
formatters[column.id] = props.formatFactory(column.formatHint);
});
+ const handleFilterClick = (field: string, value: unknown, colIndex: number, negate = false) => {
+ const col = firstTable.columns[colIndex];
+ const isDateHistogram = col.meta?.type === 'date_histogram';
+ const timeFieldName = negate && isDateHistogram ? undefined : col?.meta?.aggConfigParams?.field;
+ const rowIndex = firstTable.rows.findIndex(row => row[field] === value);
+
+ const context: ValueClickTriggerContext = {
+ data: {
+ negate,
+ data: [
+ {
+ row: rowIndex,
+ column: colIndex,
+ value,
+ table: firstTable,
+ },
+ ],
+ },
+ timeFieldName,
+ };
+ props.executeTriggerActions(VIS_EVENT_TO_TRIGGER.filter, context);
+ };
+
return (
{
const col = firstTable.columns.find(c => c.id === field);
+ const colIndex = firstTable.columns.findIndex(c => c.id === field);
+
+ const filterable = col?.meta?.type && props.getType(col.meta.type)?.type === 'buckets';
return {
field,
name: (col && col.name) || '',
+ render: (value: unknown) => {
+ const formattedValue = formatters[field]?.convert(value);
+ const fieldName = col?.meta?.aggConfigParams?.field;
+
+ if (filterable) {
+ return (
+
+ {formattedValue}
+
+
+
+ handleFilterClick(field, value, colIndex)}
+ />
+
+
+
+ handleFilterClick(field, value, colIndex, true)}
+ />
+
+
+
+
+
+ );
+ }
+ return {formattedValue} ;
+ },
};
})
.filter(({ field }) => !!field)}
- items={
- firstTable
- ? firstTable.rows.map(row => {
- const formattedRow: Record = {};
- Object.entries(formatters).forEach(([columnId, formatter]) => {
- formattedRow[columnId] = formatter.convert(row[columnId]);
- });
- return formattedRow;
- })
- : []
- }
+ items={firstTable ? firstTable.rows : []}
/>
);
diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts
index ff036aadfd4cf..44894d31da51d 100644
--- a/x-pack/plugins/lens/public/datatable_visualization/index.ts
+++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts
@@ -4,12 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { CoreSetup } from 'kibana/public';
+import { CoreSetup, CoreStart } from 'kibana/public';
import { datatableVisualization } from './visualization';
import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public';
import { datatable, datatableColumns, getDatatableRenderer } from './expression';
import { EditorFrameSetup, FormatFactory } from '../types';
+import { setExecuteTriggerActions } from '../services';
+import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
+import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
+interface DatatableVisualizationPluginStartPlugins {
+ uiActions: UiActionsStart;
+ data: DataPublicPluginStart;
+}
export interface DatatableVisualizationPluginSetupPlugins {
expressions: ExpressionsSetup;
formatFactory: Promise;
@@ -20,12 +27,22 @@ export class DatatableVisualization {
constructor() {}
setup(
- _core: CoreSetup | null,
+ core: CoreSetup,
{ expressions, formatFactory, editorFrame }: DatatableVisualizationPluginSetupPlugins
) {
expressions.registerFunction(() => datatableColumns);
expressions.registerFunction(() => datatable);
- expressions.registerRenderer(() => getDatatableRenderer(formatFactory));
+ expressions.registerRenderer(() =>
+ getDatatableRenderer({
+ formatFactory,
+ getType: core
+ .getStartServices()
+ .then(([_, { data: dataStart }]) => dataStart.search.aggs.types.get),
+ })
+ );
editorFrame.registerVisualization(datatableVisualization);
}
+ start(core: CoreStart, { uiActions }: DatatableVisualizationPluginStartPlugins) {
+ setExecuteTriggerActions(uiActions.executeTriggerActions);
+ }
}
diff --git a/x-pack/plugins/lens/public/helpers/index.ts b/x-pack/plugins/lens/public/helpers/index.ts
index f464b5dcc97a3..69a22d19ffbef 100644
--- a/x-pack/plugins/lens/public/helpers/index.ts
+++ b/x-pack/plugins/lens/public/helpers/index.ts
@@ -5,3 +5,4 @@
*/
export { addEmbeddableToDashboardUrl, getUrlVars } from './url_helper';
+export { isRisonObject } from './is_rison_object';
diff --git a/x-pack/plugins/lens/public/helpers/is_rison_object.ts b/x-pack/plugins/lens/public/helpers/is_rison_object.ts
new file mode 100644
index 0000000000000..81976c9a83320
--- /dev/null
+++ b/x-pack/plugins/lens/public/helpers/is_rison_object.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { RisonObject, RisonValue } from 'rison-node';
+import { isObject } from 'lodash';
+
+export const isRisonObject = (value: RisonValue): value is RisonObject => {
+ return isObject(value);
+};
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
new file mode 100644
index 0000000000000..a6acc61922177
--- /dev/null
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -0,0 +1,103 @@
+/*
+ * 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 { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
+import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public';
+import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
+import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public';
+import { VisualizationsSetup } from 'src/plugins/visualizations/public';
+import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
+import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public';
+import { EditorFrameService } from './editor_frame_service';
+import { IndexPatternDatasource } from './indexpattern_datasource';
+import { XyVisualization } from './xy_visualization';
+import { MetricVisualization } from './metric_visualization';
+import { DatatableVisualization } from './datatable_visualization';
+import { stopReportManager } from './lens_ui_telemetry';
+
+import { UiActionsStart } from '../../../../src/plugins/ui_actions/public';
+import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common';
+import { EditorFrameStart } from './types';
+import { getLensAliasConfig } from './vis_type_alias';
+
+import './index.scss';
+
+export interface LensPluginSetupDependencies {
+ kibanaLegacy: KibanaLegacySetup;
+ expressions: ExpressionsSetup;
+ data: DataPublicPluginSetup;
+ embeddable?: EmbeddableSetup;
+ visualizations: VisualizationsSetup;
+}
+
+export interface LensPluginStartDependencies {
+ data: DataPublicPluginStart;
+ embeddable: EmbeddableStart;
+ expressions: ExpressionsStart;
+ navigation: NavigationPublicPluginStart;
+ uiActions: UiActionsStart;
+}
+
+export class LensPlugin {
+ private datatableVisualization: DatatableVisualization;
+ private editorFrameService: EditorFrameService;
+ private createEditorFrame: EditorFrameStart['createInstance'] | null = null;
+ private indexpatternDatasource: IndexPatternDatasource;
+ private xyVisualization: XyVisualization;
+ private metricVisualization: MetricVisualization;
+
+ constructor() {
+ this.datatableVisualization = new DatatableVisualization();
+ this.editorFrameService = new EditorFrameService();
+ this.indexpatternDatasource = new IndexPatternDatasource();
+ this.xyVisualization = new XyVisualization();
+ this.metricVisualization = new MetricVisualization();
+ }
+
+ setup(
+ core: CoreSetup,
+ { kibanaLegacy, expressions, data, embeddable, visualizations }: LensPluginSetupDependencies
+ ) {
+ const editorFrameSetupInterface = this.editorFrameService.setup(core, {
+ data,
+ embeddable,
+ expressions,
+ });
+ const dependencies = {
+ expressions,
+ data,
+ editorFrame: editorFrameSetupInterface,
+ formatFactory: core
+ .getStartServices()
+ .then(([_, { data: dataStart }]) => dataStart.fieldFormats.deserialize),
+ };
+ this.indexpatternDatasource.setup(core, dependencies);
+ this.xyVisualization.setup(core, dependencies);
+ this.datatableVisualization.setup(core, dependencies);
+ this.metricVisualization.setup(core, dependencies);
+
+ visualizations.registerAlias(getLensAliasConfig());
+
+ kibanaLegacy.registerLegacyApp({
+ id: 'lens',
+ title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
+ mount: async (params: AppMountParameters) => {
+ const { mountApp } = await import('./app_plugin/mounter');
+ return mountApp(core, params, this.createEditorFrame!);
+ },
+ });
+ }
+
+ start(core: CoreStart, startDependencies: LensPluginStartDependencies) {
+ this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance;
+ this.xyVisualization.start(core, startDependencies);
+ this.datatableVisualization.start(core, startDependencies);
+ }
+
+ stop() {
+ stopReportManager();
+ }
+}
diff --git a/x-pack/plugins/lens/public/plugin.tsx b/x-pack/plugins/lens/public/plugin.tsx
deleted file mode 100644
index 8d760eb0df501..0000000000000
--- a/x-pack/plugins/lens/public/plugin.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
-import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom';
-import { render, unmountComponentAtNode } from 'react-dom';
-import rison, { RisonObject, RisonValue } from 'rison-node';
-import { isObject } from 'lodash';
-
-import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
-import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public';
-import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public';
-import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public';
-import { VisualizationsSetup } from 'src/plugins/visualizations/public';
-import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
-import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public';
-import { DashboardConstants } from '../../../../src/plugins/dashboard/public';
-import { Storage } from '../../../../src/plugins/kibana_utils/public';
-import { EditorFrameService } from './editor_frame_service';
-import { IndexPatternDatasource } from './indexpattern_datasource';
-import { addHelpMenuToAppChrome } from './help_menu_util';
-import { SavedObjectIndexStore } from './persistence';
-import { XyVisualization } from './xy_visualization';
-import { MetricVisualization } from './metric_visualization';
-import { DatatableVisualization } from './datatable_visualization';
-import { App } from './app_plugin';
-import {
- LensReportManager,
- setReportManager,
- stopReportManager,
- trackUiEvent,
-} from './lens_ui_telemetry';
-
-import { UiActionsStart } from '../../../../src/plugins/ui_actions/public';
-import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common';
-import { addEmbeddableToDashboardUrl, getUrlVars } from './helpers';
-import { EditorFrameStart } from './types';
-import { getLensAliasConfig } from './vis_type_alias';
-
-import './index.scss';
-
-export interface LensPluginSetupDependencies {
- kibanaLegacy: KibanaLegacySetup;
- expressions: ExpressionsSetup;
- data: DataPublicPluginSetup;
- embeddable?: EmbeddableSetup;
- visualizations: VisualizationsSetup;
-}
-
-export interface LensPluginStartDependencies {
- data: DataPublicPluginStart;
- embeddable: EmbeddableStart;
- expressions: ExpressionsStart;
- navigation: NavigationPublicPluginStart;
- uiActions: UiActionsStart;
-}
-
-export const isRisonObject = (value: RisonValue): value is RisonObject => {
- return isObject(value);
-};
-export class LensPlugin {
- private datatableVisualization: DatatableVisualization;
- private editorFrameService: EditorFrameService;
- private createEditorFrame: EditorFrameStart['createInstance'] | null = null;
- private indexpatternDatasource: IndexPatternDatasource;
- private xyVisualization: XyVisualization;
- private metricVisualization: MetricVisualization;
-
- constructor() {
- this.datatableVisualization = new DatatableVisualization();
- this.editorFrameService = new EditorFrameService();
- this.indexpatternDatasource = new IndexPatternDatasource();
- this.xyVisualization = new XyVisualization();
- this.metricVisualization = new MetricVisualization();
- }
-
- setup(
- core: CoreSetup,
- { kibanaLegacy, expressions, data, embeddable, visualizations }: LensPluginSetupDependencies
- ) {
- const editorFrameSetupInterface = this.editorFrameService.setup(core, {
- data,
- embeddable,
- expressions,
- });
- const dependencies = {
- expressions,
- data,
- editorFrame: editorFrameSetupInterface,
- formatFactory: core
- .getStartServices()
- .then(([_, { data: dataStart }]) => dataStart.fieldFormats.deserialize),
- };
- this.indexpatternDatasource.setup(core, dependencies);
- this.xyVisualization.setup(core, dependencies);
- this.datatableVisualization.setup(core, dependencies);
- this.metricVisualization.setup(core, dependencies);
-
- visualizations.registerAlias(getLensAliasConfig());
-
- kibanaLegacy.registerLegacyApp({
- id: 'lens',
- title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
- mount: async (params: AppMountParameters) => {
- const [coreStart, startDependencies] = await core.getStartServices();
- const { data: dataStart, navigation } = startDependencies;
- const savedObjectsClient = coreStart.savedObjects.client;
- addHelpMenuToAppChrome(coreStart.chrome, coreStart.docLinks);
-
- const instance = await this.createEditorFrame!();
-
- setReportManager(
- new LensReportManager({
- storage: new Storage(localStorage),
- http: core.http,
- })
- );
- const updateUrlTime = (urlVars: Record): void => {
- const decoded = rison.decode(urlVars._g);
- if (!isRisonObject(decoded)) {
- return;
- }
- // @ts-ignore
- decoded.time = dataStart.query.timefilter.timefilter.getTime();
- urlVars._g = rison.encode(decoded);
- };
- const redirectTo = (
- routeProps: RouteComponentProps<{ id?: string }>,
- addToDashboardMode: boolean,
- id?: string
- ) => {
- if (!id) {
- routeProps.history.push('/lens');
- } else if (!addToDashboardMode) {
- routeProps.history.push(`/lens/edit/${id}`);
- } else if (addToDashboardMode && id) {
- routeProps.history.push(`/lens/edit/${id}`);
- const lastDashboardLink = coreStart.chrome.navLinks.get('kibana:dashboard');
- if (!lastDashboardLink || !lastDashboardLink.url) {
- throw new Error('Cannot get last dashboard url');
- }
- const urlVars = getUrlVars(lastDashboardLink.url);
- updateUrlTime(urlVars); // we need to pass in timerange in query params directly
- const dashboardUrl = addEmbeddableToDashboardUrl(lastDashboardLink.url, id, urlVars);
- window.history.pushState({}, '', dashboardUrl);
- }
- };
-
- const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
- trackUiEvent('loaded');
- const addToDashboardMode =
- !!routeProps.location.search &&
- routeProps.location.search.includes(
- DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM
- );
- return (
- redirectTo(routeProps, addToDashboardMode, id)}
- addToDashboardMode={addToDashboardMode}
- />
- );
- };
-
- function NotFound() {
- trackUiEvent('loaded_404');
- return ;
- }
-
- render(
-
-
-
-
-
-
-
-
- ,
- params.element
- );
- return () => {
- instance.unmount();
- unmountComponentAtNode(params.element);
- };
- },
- });
- }
-
- start(core: CoreStart, startDependencies: LensPluginStartDependencies) {
- this.createEditorFrame = this.editorFrameService.start(core, startDependencies).createInstance;
- this.xyVisualization.start(core, startDependencies);
- }
-
- stop() {
- stopReportManager();
- }
-}
diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json
index 4c7dcb48a4d3f..f897051d3ed8a 100644
--- a/x-pack/plugins/rollup/kibana.json
+++ b/x-pack/plugins/rollup/kibana.json
@@ -7,8 +7,7 @@
"requiredPlugins": [
"indexPatternManagement",
"management",
- "licensing",
- "data"
+ "licensing"
],
"optionalPlugins": [
"home",
diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts
index fd1b90fbc9855..5bb678ac35d06 100644
--- a/x-pack/plugins/rollup/public/plugin.ts
+++ b/x-pack/plugins/rollup/public/plugin.ts
@@ -11,10 +11,6 @@ import { rollupBadgeExtension, rollupToggleExtension } from './extend_index_mana
import { RollupIndexPatternCreationConfig } from './index_pattern_creation/rollup_index_pattern_creation_config';
// @ts-ignore
import { RollupIndexPatternListConfig } from './index_pattern_list/rollup_index_pattern_list_config';
-// @ts-ignore
-import { initAggTypeFilter } from './visualize/agg_type_filter';
-// @ts-ignore
-import { initAggTypeFieldFilter } from './visualize/agg_type_field_filter';
import { CONFIG_ROLLUPS, UIM_APP_NAME } from '../common';
import {
FeatureCatalogueCategory,
@@ -25,7 +21,6 @@ import { CRUD_APP_BASE_PATH } from './crud_app/constants';
import { ManagementSetup } from '../../../../src/plugins/management/public';
import { IndexManagementPluginSetup } from '../../index_management/public';
import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public';
-import { DataPublicPluginStart, search } from '../../../../src/plugins/data/public';
// @ts-ignore
import { setEsBaseAndXPackBase, setHttp } from './crud_app/services/index';
import { setNotifications, setFatalErrors, setUiStatsReporter } from './kibana_services';
@@ -39,10 +34,6 @@ export interface RollupPluginSetupDependencies {
usageCollection?: UsageCollectionSetup;
}
-export interface RollupPluginStartDependencies {
- data: DataPublicPluginStart;
-}
-
export class RollupPlugin implements Plugin {
setup(
core: CoreSetup,
@@ -108,16 +99,9 @@ export class RollupPlugin implements Plugin {
}
}
- start(core: CoreStart, plugins: RollupPluginStartDependencies) {
+ start(core: CoreStart) {
setHttp(core.http);
setNotifications(core.notifications);
setEsBaseAndXPackBase(core.docLinks.ELASTIC_WEBSITE_URL, core.docLinks.DOC_LINK_VERSION);
-
- const isRollupIndexPatternsEnabled = core.uiSettings.get(CONFIG_ROLLUPS);
-
- if (isRollupIndexPatternsEnabled) {
- initAggTypeFilter(search.aggs.aggTypeFilters);
- initAggTypeFieldFilter(plugins.data.search.__LEGACY.aggTypeFieldFilters);
- }
}
}
diff --git a/x-pack/plugins/rollup/public/visualize/agg_type_field_filter.js b/x-pack/plugins/rollup/public/visualize/agg_type_field_filter.js
deleted file mode 100644
index 6f44e0ef90efd..0000000000000
--- a/x-pack/plugins/rollup/public/visualize/agg_type_field_filter.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export function initAggTypeFieldFilter(aggTypeFieldFilters) {
- /**
- * If rollup index pattern, check its capabilities
- * and limit available fields for a given aggType based on that.
- */
- aggTypeFieldFilters.addFilter((field, aggConfig) => {
- const indexPattern = aggConfig.getIndexPattern();
- if (!indexPattern || indexPattern.type !== 'rollup') {
- return true;
- }
- const aggName = aggConfig.type && aggConfig.type.name;
- const aggFields =
- indexPattern.typeMeta && indexPattern.typeMeta.aggs && indexPattern.typeMeta.aggs[aggName];
- return aggFields && aggFields[field.name];
- });
-}
diff --git a/x-pack/plugins/rollup/public/visualize/agg_type_filter.js b/x-pack/plugins/rollup/public/visualize/agg_type_filter.js
deleted file mode 100644
index 5f9fab3061a19..0000000000000
--- a/x-pack/plugins/rollup/public/visualize/agg_type_filter.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export function initAggTypeFilter(aggTypeFilters) {
- /**
- * If rollup index pattern, check its capabilities
- * and limit available aggregations based on that.
- */
- aggTypeFilters.addFilter((aggType, indexPattern) => {
- if (indexPattern.type !== 'rollup') {
- return true;
- }
- const aggName = aggType.name;
- const aggs = indexPattern.typeMeta && indexPattern.typeMeta.aggs;
-
- // Return doc_count (which is collected by default for rollup date histogram, histogram, and terms)
- // and the rest of the defined metrics from capabilities.
- return aggName === 'count' || Object.keys(aggs).includes(aggName);
- });
-}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 9267252d23ef0..18f2ce2bbaf65 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -5521,26 +5521,10 @@
"xpack.canvas.sidebarContent.groupedElementSidebarTitle": "グループ化されたエレメント",
"xpack.canvas.sidebarContent.multiElementSidebarTitle": "複数エレメント",
"xpack.canvas.sidebarContent.singleElementSidebarTitle": "選択されたエレメント",
- "xpack.canvas.sidebarHeader.alignmentMenuItemLabel": "アラインメント",
- "xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel": "一番下",
"xpack.canvas.sidebarHeader.bringForwardArialLabel": "エレメントを 1 つ上のレイヤーに移動",
"xpack.canvas.sidebarHeader.bringToFrontArialLabel": "エレメントを一番上のレイヤーに移動",
- "xpack.canvas.sidebarHeader.centerAlignMenuItemLabel": "中央",
- "xpack.canvas.sidebarHeader.contextMenuAriaLabel": "エレメントオプション",
- "xpack.canvas.sidebarHeader.createElementModalTitle": "新規エレメントの作成",
- "xpack.canvas.sidebarHeader.distributionMenutItemLabel": "分布",
- "xpack.canvas.sidebarHeader.groupMenuItemLabel": "グループ",
- "xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel": "横",
- "xpack.canvas.sidebarHeader.leftAlignMenuItemLabel": "左",
- "xpack.canvas.sidebarHeader.middleAlignMenuItemLabel": "真ん中",
- "xpack.canvas.sidebarHeader.orderMenuItemLabel": "順序",
- "xpack.canvas.sidebarHeader.rightAlignMenuItemLabel": "右",
- "xpack.canvas.sidebarHeader.savedElementMenuItemLabel": "新規エレメントとして保存",
"xpack.canvas.sidebarHeader.sendBackwardArialLabel": "エレメントを 1 つ下のレイヤーに移動",
"xpack.canvas.sidebarHeader.sendToBackArialLabel": "エレメントを一番下のレイヤーに移動",
- "xpack.canvas.sidebarHeader.topAlignMenuItemLabel": "一番上",
- "xpack.canvas.sidebarHeader.ungroupMenuItemLabel": "グループ解除",
- "xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel": "縦",
"xpack.canvas.tags.presentationTag": "プレゼンテーション",
"xpack.canvas.tags.reportTag": "レポート",
"xpack.canvas.templates.darkHelp": "ダークカラーテーマのプレゼンテーションデッキです",
@@ -5854,6 +5838,18 @@
"xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel": "設定",
"xpack.canvas.workpadHeaderCustomInterval.formDescription": "{secondsExample}、{minutesExample}、{hoursExample} のような短い表記を使用します",
"xpack.canvas.workpadHeaderCustomInterval.formLabel": "カスタム間隔を設定",
+ "xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel": "アラインメント",
+ "xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel": "一番下",
+ "xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel": "中央",
+ "xpack.canvas.workpadHeaderEditMenu.createElementModalTitle": "新規エレメントの作成",
+ "xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel": "分布",
+ "xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel": "グループ",
+ "xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel": "横",
+ "xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel": "左",
+ "xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel": "真ん中",
+ "xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel": "順序",
+ "xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel": "右",
+ "xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel": "新規エレメントとして保存",
"xpack.canvas.workpadHeaderKioskControl.controlTitle": "全画面ページのサイクル",
"xpack.canvas.workpadHeaderKioskControl.cycleFormLabel": "サイクル間隔を変更",
"xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "スライドを自動的にサイクル",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 530adb08431b9..e888576f8d1a2 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -5522,26 +5522,10 @@
"xpack.canvas.sidebarContent.groupedElementSidebarTitle": "已分组元素",
"xpack.canvas.sidebarContent.multiElementSidebarTitle": "多个元素",
"xpack.canvas.sidebarContent.singleElementSidebarTitle": "选定元素",
- "xpack.canvas.sidebarHeader.alignmentMenuItemLabel": "对齐方式",
- "xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel": "下",
"xpack.canvas.sidebarHeader.bringForwardArialLabel": "将元素上移一层",
"xpack.canvas.sidebarHeader.bringToFrontArialLabel": "将元素移到顶层",
- "xpack.canvas.sidebarHeader.centerAlignMenuItemLabel": "中",
- "xpack.canvas.sidebarHeader.contextMenuAriaLabel": "元素选项",
- "xpack.canvas.sidebarHeader.createElementModalTitle": "创建新元素",
- "xpack.canvas.sidebarHeader.distributionMenutItemLabel": "分布",
- "xpack.canvas.sidebarHeader.groupMenuItemLabel": "分组",
- "xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel": "水平",
- "xpack.canvas.sidebarHeader.leftAlignMenuItemLabel": "左",
- "xpack.canvas.sidebarHeader.middleAlignMenuItemLabel": "中",
- "xpack.canvas.sidebarHeader.orderMenuItemLabel": "顺序",
- "xpack.canvas.sidebarHeader.rightAlignMenuItemLabel": "右",
- "xpack.canvas.sidebarHeader.savedElementMenuItemLabel": "另存为新元素",
"xpack.canvas.sidebarHeader.sendBackwardArialLabel": "将元素下移一层",
"xpack.canvas.sidebarHeader.sendToBackArialLabel": "将元素移到底层",
- "xpack.canvas.sidebarHeader.topAlignMenuItemLabel": "上",
- "xpack.canvas.sidebarHeader.ungroupMenuItemLabel": "取消分组",
- "xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel": "垂直",
"xpack.canvas.tags.presentationTag": "演示",
"xpack.canvas.tags.reportTag": "报告",
"xpack.canvas.templates.darkHelp": "深色主题的演示幻灯片",
@@ -5856,6 +5840,21 @@
"xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel": "设置",
"xpack.canvas.workpadHeaderCustomInterval.formDescription": "使用速记表示法,如 {secondsExample}、{minutesExample} 或 {hoursExample}",
"xpack.canvas.workpadHeaderCustomInterval.formLabel": "设置定制时间间隔",
+ "xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel": "对齐方式",
+ "xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel": "下",
+ "xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel": "中",
+ "xpack.canvas.workpadHeaderEditMenu.createElementModalTitle": "创建新元素",
+ "xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel": "分布",
+ "xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel": "分组",
+ "xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel": "水平",
+ "xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel": "左",
+ "xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel": "中",
+ "xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel": "顺序",
+ "xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel": "右",
+ "xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel": "另存为新元素",
+ "xpack.canvas.workpadHeaderEditMenu.topAlignMenuItemLabel": "上",
+ "xpack.canvas.workpadHeaderEditMenu.ungroupMenuItemLabel": "取消分组",
+ "xpack.canvas.workpadHeaderEditMenu.verticalDistributionMenutItemLabel": "垂直",
"xpack.canvas.workpadHeaderKioskControl.controlTitle": "循环播放全屏页面",
"xpack.canvas.workpadHeaderKioskControl.cycleFormLabel": "更改循环播放时间间隔",
"xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "自动循环播放幻灯片",
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx
index 55a219ca94aea..861d6ad7284c2 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index.tsx
@@ -86,7 +86,9 @@ const IndexActionConnectorFields: React.FunctionComponent([]);
- const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]);
+ const [timeFieldOptions, setTimeFieldOptions] = useState>([
+ firstFieldOption,
+ ]);
const [isIndiciesLoading, setIsIndiciesLoading] = useState(false);
useEffect(() => {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx
index 0027837c913d1..531e9e1926ff4 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx
@@ -106,10 +106,6 @@ export const ActionForm = ({
index[actionTypeItem.id] = actionTypeItem;
}
setActionTypesIndex(index);
- const hasActionsDisabled = actions.some(action => !index[action.actionTypeId].enabled);
- if (setHasActionsDisabled) {
- setHasActionsDisabled(hasActionsDisabled);
- }
} catch (e) {
toastNotifications.addDanger({
title: i18n.translate(
@@ -129,7 +125,8 @@ export const ActionForm = ({
(async () => {
try {
setIsLoadingConnectors(true);
- setConnectors(await loadConnectors({ http }));
+ const loadedConnectors = await loadConnectors({ http });
+ setConnectors(loadedConnectors);
} catch (e) {
toastNotifications.addDanger({
title: i18n.translate(
@@ -146,6 +143,27 @@ export const ActionForm = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ useEffect(() => {
+ const setActionTypesAvalilability = () => {
+ const preconfiguredConnectors = connectors.filter(connector => connector.isPreconfigured);
+ const hasActionsDisabled = actions.some(
+ action =>
+ !actionTypesIndex![action.actionTypeId].enabled &&
+ !checkActionFormActionTypeEnabled(
+ actionTypesIndex![action.actionTypeId],
+ preconfiguredConnectors
+ ).isEnabled
+ );
+ if (setHasActionsDisabled) {
+ setHasActionsDisabled(hasActionsDisabled);
+ }
+ };
+ if (connectors.length > 0 && actionTypesIndex) {
+ setActionTypesAvalilability();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [connectors, actionTypesIndex]);
+
const preconfiguredMessage = i18n.translate(
'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage',
{
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx
index b9d08abae1684..a02b44523e26c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx
@@ -32,7 +32,8 @@ export const AlertInstancesRoute: React.FunctionComponent =
useEffect(() => {
getAlertState(alert.id, loadAlertState, setAlertState, toastNotifications);
- }, [alert, loadAlertState, toastNotifications]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [alert]);
return alertState ? (
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx
index 0620ced6365a9..651f2cdba34af 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx
@@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useReducer, useState } from 'react';
+import React, { useCallback, useReducer, useState, useEffect } from 'react';
import { isObject } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
import {
@@ -60,6 +60,9 @@ export const AlertAdd = ({
const setAlert = (value: any) => {
dispatch({ command: { type: 'setAlert' }, payload: { key: 'alert', value } });
};
+ const setAlertProperty = (key: string, value: any) => {
+ dispatch({ command: { type: 'setProperty' }, payload: { key, value } });
+ };
const {
reloadAlerts,
@@ -70,6 +73,10 @@ export const AlertAdd = ({
docLinks,
} = useAlertsContext();
+ useEffect(() => {
+ setAlertProperty('alertTypeId', alertTypeId);
+ }, [alertTypeId]);
+
const closeFlyout = useCallback(() => {
setAddFlyoutVisibility(false);
setAlert(initialAlert);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx
index 4255eca83be47..00bc9874face1 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx
@@ -43,6 +43,9 @@ export const AlertEdit = ({
const [{ alert }, dispatch] = useReducer(alertReducer, { alert: initialAlert });
const [isSaving, setIsSaving] = useState(false);
const [hasActionsDisabled, setHasActionsDisabled] = useState(false);
+ const setAlert = (key: string, value: any) => {
+ dispatch({ command: { type: 'setAlert' }, payload: { key, value } });
+ };
const {
reloadAlerts,
@@ -55,6 +58,8 @@ export const AlertEdit = ({
const closeFlyout = useCallback(() => {
setEditFlyoutVisibility(false);
+ setAlert('alert', initialAlert);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [setEditFlyoutVisibility]);
if (!editFlyoutVisible) {
@@ -145,6 +150,7 @@ export const AlertEdit = ({
size="s"
color="danger"
iconType="alert"
+ data-test-subj="hasActionsDisabled"
title={i18n.translate(
'xpack.triggersActionsUI.sections.alertEdit.disabledActionsWarningTitle',
{ defaultMessage: 'This alert has actions that are disabled' }
diff --git a/x-pack/plugins/uptime/common/constants/ui.ts b/x-pack/plugins/uptime/common/constants/ui.ts
index 29e8dabf53f92..3bf3e3cc0a2cc 100644
--- a/x-pack/plugins/uptime/common/constants/ui.ts
+++ b/x-pack/plugins/uptime/common/constants/ui.ts
@@ -10,6 +10,8 @@ export const OVERVIEW_ROUTE = '/';
export const SETTINGS_ROUTE = '/settings';
+export const CERTIFICATES_ROUTE = '/certificates';
+
export enum STATUS {
UP = 'up',
DOWN = 'down',
@@ -41,3 +43,10 @@ export const SHORT_TIMESPAN_LOCALE = {
yy: '%d Yr',
},
};
+
+export enum CERT_STATUS {
+ OK = 'OK',
+ EXPIRING_SOON = 'EXPIRING_SOON',
+ EXPIRED = 'EXPIRED',
+ TOO_OLD = 'TOO_OLD',
+}
diff --git a/x-pack/plugins/uptime/common/runtime_types/certs.ts b/x-pack/plugins/uptime/common/runtime_types/certs.ts
index e8be67abf3a44..e9071e76b6d75 100644
--- a/x-pack/plugins/uptime/common/runtime_types/certs.ts
+++ b/x-pack/plugins/uptime/common/runtime_types/certs.ts
@@ -8,35 +8,45 @@ import * as t from 'io-ts';
export const GetCertsParamsType = t.intersection([
t.type({
- from: t.string,
- to: t.string,
index: t.number,
size: t.number,
+ sortBy: t.string,
+ direction: t.string,
}),
t.partial({
search: t.string,
+ from: t.string,
+ to: t.string,
}),
]);
export type GetCertsParams = t.TypeOf;
+export const CertMonitorType = t.partial({
+ name: t.string,
+ id: t.string,
+ url: t.string,
+});
+
export const CertType = t.intersection([
t.type({
- monitors: t.array(
- t.partial({
- name: t.string,
- id: t.string,
- })
- ),
+ monitors: t.array(CertMonitorType),
sha256: t.string,
}),
t.partial({
- certificate_not_valid_after: t.string,
- certificate_not_valid_before: t.string,
+ not_after: t.string,
+ not_before: t.string,
common_name: t.string,
issuer: t.string,
sha1: t.string,
}),
]);
+export const CertResultType = t.type({
+ certs: t.array(CertType),
+ total: t.number,
+});
+
export type Cert = t.TypeOf;
+export type CertMonitor = t.TypeOf;
+export type CertResult = t.TypeOf;
diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts
index ee14b298f3810..d8dc7fc89d94b 100644
--- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts
+++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts
@@ -17,8 +17,8 @@ export const HttpResponseBodyType = t.partial({
export type HttpResponseBody = t.TypeOf;
export const TlsType = t.partial({
- certificate_not_valid_after: t.string,
- certificate_not_valid_before: t.string,
+ not_after: t.string,
+ not_before: t.string,
});
export type Tls = t.TypeOf;
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_monitors.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_monitors.test.tsx.snap
new file mode 100644
index 0000000000000..a79fb0f0d3deb
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_monitors.test.tsx.snap
@@ -0,0 +1,134 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CertMonitors renders expected elements for valid props 1`] = `
+
+
+
+
+
+ bad-ssl-dashboard
+
+
+
+
+
+ ,
+
+
+
+ elastic
+
+
+
+
+
+ ,
+
+
+
+ extended-validation
+
+
+
+
+
+`;
+
+exports[`CertMonitors shallow renders expected elements for valid props 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_search.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_search.test.tsx.snap
new file mode 100644
index 0000000000000..0706198a099a5
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_search.test.tsx.snap
@@ -0,0 +1,93 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CertificatesSearch renders expected elements for valid props 1`] = `
+.c0 {
+ min-width: 700px;
+}
+
+
+`;
+
+exports[`CertificatesSearch shallow renders expected elements for valid props 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_status.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_status.test.tsx.snap
new file mode 100644
index 0000000000000..089d272a075c6
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/cert_status.test.tsx.snap
@@ -0,0 +1,100 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CertStatus renders expected elements for valid props 1`] = `
+
+
+
+`;
+
+exports[`CertStatus shallow renders expected elements for valid props 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/certificates_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/certificates_list.test.tsx.snap
new file mode 100644
index 0000000000000..fd90db793b26e
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/certificates_list.test.tsx.snap
@@ -0,0 +1,70 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CertificateList shallow renders expected elements for valid props 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/fingerprint_col.test.tsx.snap b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/fingerprint_col.test.tsx.snap
new file mode 100644
index 0000000000000..c9b17db5532f4
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/__snapshots__/fingerprint_col.test.tsx.snap
@@ -0,0 +1,171 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FingerprintCol renders expected elements for valid props 1`] = `
+Array [
+ .c1 .euiButtonEmpty__content {
+ padding-right: 0px;
+}
+
+.c0 {
+ margin-right: 8px;
+}
+
+
+
+
+
+
+ SHA 1
+
+
+
+
+
+
+
+
+
+ ,
+ .c1 .euiButtonEmpty__content {
+ padding-right: 0px;
+}
+
+.c0 {
+ margin-right: 8px;
+}
+
+
+
+
+
+
+ SHA 256
+
+
+
+
+
+
+
+
+
+ ,
+]
+`;
+
+exports[`FingerprintCol shallow renders expected elements for valid props 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_monitors.test.tsx b/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_monitors.test.tsx
new file mode 100644
index 0000000000000..bc4c770d5cd24
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_monitors.test.tsx
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { CertMonitors } from '../cert_monitors';
+import { renderWithRouter, shallowWithRouter } from '../../../lib';
+
+describe('CertMonitors', () => {
+ const certMons = [
+ { name: '', id: 'bad-ssl-dashboard', url: 'https://badssl.com/dashboard/' },
+ { name: 'elastic', id: 'elastic-co', url: 'https://www.elastic.co/' },
+ { name: '', id: 'extended-validation', url: 'https://extended-validation.badssl.com/' },
+ ];
+ it('shallow renders expected elements for valid props', () => {
+ expect(shallowWithRouter( )).toMatchSnapshot();
+ });
+
+ it('renders expected elements for valid props', () => {
+ expect(renderWithRouter( )).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_search.test.tsx b/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_search.test.tsx
new file mode 100644
index 0000000000000..27d3bb18f17c2
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_search.test.tsx
@@ -0,0 +1,18 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { renderWithRouter, shallowWithRouter } from '../../../lib';
+import { CertificateSearch } from '../cert_search';
+
+describe('CertificatesSearch', () => {
+ it('shallow renders expected elements for valid props', () => {
+ expect(shallowWithRouter( )).toMatchSnapshot();
+ });
+ it('renders expected elements for valid props', () => {
+ expect(renderWithRouter( )).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_status.test.tsx b/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_status.test.tsx
new file mode 100644
index 0000000000000..6f91994fb89c4
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/cert_status.test.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { renderWithRouter, shallowWithRouter } from '../../../lib';
+import { CertStatus } from '../cert_status';
+import * as redux from 'react-redux';
+import moment from 'moment';
+
+describe('CertStatus', () => {
+ beforeEach(() => {
+ const spy = jest.spyOn(redux, 'useDispatch');
+ spy.mockReturnValue(jest.fn());
+
+ const spy1 = jest.spyOn(redux, 'useSelector');
+ spy1.mockReturnValue(true);
+ });
+
+ const cert = {
+ monitors: [{ name: '', id: 'github', url: 'https://github.com/' }],
+ not_after: '2020-05-08T00:00:00.000Z',
+ not_before: '2018-05-08T00:00:00.000Z',
+ issuer: 'DigiCert SHA2 Extended Validation Server CA',
+ sha1: 'ca06f56b258b7a0d4f2b05470939478651151984',
+ sha256: '3111500c4a66012cdae333ec3fca1c9dde45c954440e7ee413716bff3663c074',
+ common_name: 'github.com',
+ };
+
+ it('shallow renders expected elements for valid props', () => {
+ expect(shallowWithRouter( )).toMatchSnapshot();
+ });
+
+ it('renders expected elements for valid props', () => {
+ cert.not_after = moment()
+ .add('4', 'months')
+ .toISOString();
+ expect(renderWithRouter( )).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/certificates_list.test.tsx b/x-pack/plugins/uptime/public/components/certificates/__tests__/certificates_list.test.tsx
new file mode 100644
index 0000000000000..a8b60900ec65c
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/certificates_list.test.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallowWithRouter } from '../../../lib';
+import { CertificateList, CertSort } from '../certificates_list';
+
+describe('CertificateList', () => {
+ it('shallow renders expected elements for valid props', () => {
+ const page = {
+ index: 0,
+ size: 10,
+ };
+ const sort: CertSort = {
+ field: 'not_after',
+ direction: 'asc',
+ };
+
+ expect(
+ shallowWithRouter( )
+ ).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/certificates/__tests__/fingerprint_col.test.tsx b/x-pack/plugins/uptime/public/components/certificates/__tests__/fingerprint_col.test.tsx
new file mode 100644
index 0000000000000..609b876e24849
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/__tests__/fingerprint_col.test.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { renderWithRouter, shallowWithRouter } from '../../../lib';
+import { FingerprintCol } from '../fingerprint_col';
+import moment from 'moment';
+
+describe('FingerprintCol', () => {
+ const cert = {
+ monitors: [{ name: '', id: 'github', url: 'https://github.com/' }],
+ not_after: '2020-05-08T00:00:00.000Z',
+ not_before: '2018-05-08T00:00:00.000Z',
+ issuer: 'DigiCert SHA2 Extended Validation Server CA',
+ sha1: 'ca06f56b258b7a0d4f2b05470939478651151984',
+ sha256: '3111500c4a66012cdae333ec3fca1c9dde45c954440e7ee413716bff3663c074',
+ common_name: 'github.com',
+ };
+
+ it('shallow renders expected elements for valid props', () => {
+ expect(shallowWithRouter( )).toMatchSnapshot();
+ });
+
+ it('renders expected elements for valid props', () => {
+ cert.not_after = moment()
+ .add('4', 'months')
+ .toISOString();
+
+ expect(renderWithRouter( )).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/components/certificates/cert_monitors.tsx b/x-pack/plugins/uptime/public/components/certificates/cert_monitors.tsx
new file mode 100644
index 0000000000000..bfd309e59d013
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/cert_monitors.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiToolTip } from '@elastic/eui';
+import { CertMonitor } from '../../../common/runtime_types';
+import { MonitorPageLink } from '../common/monitor_page_link';
+
+interface Props {
+ monitors: CertMonitor[];
+}
+
+export const CertMonitors: React.FC = ({ monitors }) => {
+ return (
+
+ {monitors.map((mon: CertMonitor, ind: number) => (
+
+ {ind > 0 && ', '}
+
+
+ {mon.name || mon.id}
+
+
+
+ ))}
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx b/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx
new file mode 100644
index 0000000000000..282b623f0f662
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/cert_search.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { ChangeEvent } from 'react';
+import { EuiFieldSearch } from '@elastic/eui';
+import styled from 'styled-components';
+import * as labels from './translations';
+
+const WrapFieldSearch = styled(EuiFieldSearch)`
+ min-width: 700px;
+`;
+
+interface Props {
+ setSearch: (val: string) => void;
+}
+
+export const CertificateSearch: React.FC = ({ setSearch }) => {
+ const onChange = (e: ChangeEvent) => {
+ setSearch(e.target.value);
+ };
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/certificates/cert_status.tsx b/x-pack/plugins/uptime/public/components/certificates/cert_status.tsx
new file mode 100644
index 0000000000000..e7a86ce98fa3c
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/cert_status.tsx
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiHealth } from '@elastic/eui';
+import { Cert } from '../../../common/runtime_types';
+import { useCertStatus } from '../../hooks';
+import * as labels from './translations';
+import { CERT_STATUS } from '../../../common/constants';
+
+interface Props {
+ cert: Cert;
+}
+
+export const CertStatus: React.FC = ({ cert }) => {
+ const certStatus = useCertStatus(cert?.not_after, cert?.not_before);
+
+ if (certStatus === CERT_STATUS.EXPIRING_SOON) {
+ return (
+
+ {labels.EXPIRES_SOON}
+
+ );
+ }
+ if (certStatus === CERT_STATUS.EXPIRED) {
+ return (
+
+ {labels.EXPIRED}
+
+ );
+ }
+
+ if (certStatus === CERT_STATUS.TOO_OLD) {
+ return (
+
+ {labels.TOO_OLD}
+
+ );
+ }
+
+ return (
+
+ {labels.OK}
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx b/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx
new file mode 100644
index 0000000000000..595aa03c99c73
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/certificates_list.tsx
@@ -0,0 +1,113 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import moment from 'moment';
+import { useSelector } from 'react-redux';
+import { Direction, EuiBasicTable } from '@elastic/eui';
+import { certificatesSelector } from '../../state/certificates/certificates';
+import { CertStatus } from './cert_status';
+import { CertMonitors } from './cert_monitors';
+import * as labels from './translations';
+import { Cert, CertMonitor } from '../../../common/runtime_types';
+import { FingerprintCol } from './fingerprint_col';
+
+interface Page {
+ index: number;
+ size: number;
+}
+
+export type CertFields =
+ | 'sha256'
+ | 'sha1'
+ | 'issuer'
+ | 'common_name'
+ | 'monitors'
+ | 'not_after'
+ | 'not_before';
+
+export interface CertSort {
+ field: CertFields;
+ direction: Direction;
+}
+
+interface Props {
+ page: Page;
+ sort: CertSort;
+ onChange: (page: Page, sort: CertSort) => void;
+}
+
+export const CertificateList: React.FC = ({ page, sort, onChange }) => {
+ const certificates = useSelector(certificatesSelector);
+
+ const onTableChange = (newVal: Partial) => {
+ onChange(newVal.page as Page, newVal.sort as CertSort);
+ };
+
+ const pagination = {
+ pageIndex: page.index,
+ pageSize: page.size,
+ totalItemCount: certificates?.total ?? 0,
+ pageSizeOptions: [10, 25, 50, 100],
+ hidePerPageOptions: false,
+ };
+
+ const columns = [
+ {
+ field: 'not_after',
+ name: labels.STATUS_COL,
+ sortable: true,
+ render: (val: string, item: Cert) => ,
+ },
+ {
+ name: labels.COMMON_NAME_COL,
+ field: 'common_name',
+ sortable: true,
+ },
+ {
+ name: labels.MONITORS_COL,
+ field: 'monitors',
+ render: (monitors: CertMonitor[]) => ,
+ },
+ {
+ name: labels.ISSUED_BY_COL,
+ field: 'issuer',
+ sortable: true,
+ },
+ {
+ name: labels.VALID_UNTIL_COL,
+ field: 'not_after',
+ sortable: true,
+ render: (value: string) => moment(value).format('L LT'),
+ },
+ {
+ name: labels.AGE_COL,
+ field: 'not_before',
+ sortable: true,
+ render: (value: string) => moment().diff(moment(value), 'days') + ' ' + labels.DAYS,
+ },
+ {
+ name: labels.FINGERPRINTS_COL,
+ field: 'sha256',
+ render: (val: string, item: Cert) => ,
+ },
+ ];
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/certificates/fingerprint_col.tsx b/x-pack/plugins/uptime/public/components/certificates/fingerprint_col.tsx
new file mode 100644
index 0000000000000..4101573907924
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/fingerprint_col.tsx
@@ -0,0 +1,46 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { EuiButtonEmpty, EuiButtonIcon, EuiCopy, EuiToolTip } from '@elastic/eui';
+import styled from 'styled-components';
+import { Cert } from '../../../common/runtime_types';
+import { COPY_FINGERPRINT } from './translations';
+
+const EmptyButton = styled(EuiButtonEmpty)`
+ .euiButtonEmpty__content {
+ padding-right: 0px;
+ }
+`;
+
+const Span = styled.span`
+ margin-right: 8px;
+`;
+
+interface Props {
+ cert: Cert;
+}
+
+export const FingerprintCol: React.FC = ({ cert }) => {
+ const ShaComponent = ({ text, val }: { text: string; val: string }) => {
+ return (
+
+
+ {text}
+
+
+ {copy => }
+
+
+ );
+ };
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/uptime/public/components/certificates/index.ts b/x-pack/plugins/uptime/public/components/certificates/index.ts
new file mode 100644
index 0000000000000..82f3f7ab67c91
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export * from './cert_monitors';
+export * from './cert_search';
+export * from './cert_status';
+export * from './certificates_list';
+export * from './fingerprint_col';
diff --git a/x-pack/plugins/uptime/public/components/certificates/translations.ts b/x-pack/plugins/uptime/public/components/certificates/translations.ts
new file mode 100644
index 0000000000000..518eddf1211a4
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/certificates/translations.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const OK = i18n.translate('xpack.uptime.certs.ok', {
+ defaultMessage: 'OK',
+});
+
+export const EXPIRED = i18n.translate('xpack.uptime.certs.expired', {
+ defaultMessage: 'Expired',
+});
+
+export const EXPIRES_SOON = i18n.translate('xpack.uptime.certs.expireSoon', {
+ defaultMessage: 'Expires soon',
+});
+
+export const SEARCH_CERTS = i18n.translate('xpack.uptime.certs.searchCerts', {
+ defaultMessage: 'Search certificates',
+});
+
+export const STATUS_COL = i18n.translate('xpack.uptime.certs.list.status', {
+ defaultMessage: 'Status',
+});
+
+export const TOO_OLD = i18n.translate('xpack.uptime.certs.list.status.old', {
+ defaultMessage: 'Too old',
+});
+
+export const COMMON_NAME_COL = i18n.translate('xpack.uptime.certs.list.commonName', {
+ defaultMessage: 'Common name',
+});
+
+export const MONITORS_COL = i18n.translate('xpack.uptime.certs.list.monitors', {
+ defaultMessage: 'Monitors',
+});
+
+export const ISSUED_BY_COL = i18n.translate('xpack.uptime.certs.list.issuedBy', {
+ defaultMessage: 'Issued by',
+});
+
+export const VALID_UNTIL_COL = i18n.translate('xpack.uptime.certs.list.validUntil', {
+ defaultMessage: 'Valid until',
+});
+
+export const AGE_COL = i18n.translate('xpack.uptime.certs.list.ageCol', {
+ defaultMessage: 'Age',
+});
+
+export const DAYS = i18n.translate('xpack.uptime.certs.list.days', {
+ defaultMessage: 'days',
+});
+
+export const FINGERPRINTS_COL = i18n.translate('xpack.uptime.certs.list.expirationDate', {
+ defaultMessage: 'Fingerprints',
+});
+
+export const COPY_FINGERPRINT = i18n.translate('xpack.uptime.certs.list.copyFingerprint', {
+ defaultMessage: 'Click to copy fingerprint value',
+});
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_page_link.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/__tests__/__snapshots__/monitor_page_link.test.tsx.snap
similarity index 100%
rename from x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_page_link.test.tsx.snap
rename to x-pack/plugins/uptime/public/components/common/__tests__/__snapshots__/monitor_page_link.test.tsx.snap
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_page_link.test.tsx b/x-pack/plugins/uptime/public/components/common/__tests__/monitor_page_link.test.tsx
similarity index 100%
rename from x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_page_link.test.tsx
rename to x-pack/plugins/uptime/public/components/common/__tests__/monitor_page_link.test.tsx
index dd6e9c66d395b..36ebeb6615648 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_page_link.test.tsx
+++ b/x-pack/plugins/uptime/public/components/common/__tests__/monitor_page_link.test.tsx
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
+import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { MonitorPageLink } from '../monitor_page_link';
describe('MonitorPageLink component', () => {
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_page_link.tsx b/x-pack/plugins/uptime/public/components/common/monitor_page_link.tsx
similarity index 89%
rename from x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_page_link.tsx
rename to x-pack/plugins/uptime/public/components/common/monitor_page_link.tsx
index 803b399810508..77faa8edfc5c8 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_page_link.tsx
+++ b/x-pack/plugins/uptime/public/components/common/monitor_page_link.tsx
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import React, { FC } from 'react';
import { EuiLink } from '@elastic/eui';
import { Link } from 'react-router-dom';
-import React, { FunctionComponent } from 'react';
interface DetailPageLinkProps {
/**
@@ -19,7 +19,7 @@ interface DetailPageLinkProps {
linkParameters: string | undefined;
}
-export const MonitorPageLink: FunctionComponent = ({
+export const MonitorPageLink: FC = ({
children,
monitorId,
linkParameters,
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/monitor_ssl_certificate.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/monitor_ssl_certificate.test.tsx.snap
deleted file mode 100644
index 605fc3cdb6b38..0000000000000
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/monitor_ssl_certificate.test.tsx.snap
+++ /dev/null
@@ -1,31 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`MonitorStatusBar component renders 1`] = `
-Array [
-
,
-
- SSL certificate expires
-
-
-
- in 2 months
-
-
-
-
,
-]
-`;
-
-exports[`MonitorStatusBar component renders null if invalid date 1`] = `null`;
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap
new file mode 100644
index 0000000000000..2f4473ba54cf9
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/__snapshots__/ssl_certificate.test.tsx.snap
@@ -0,0 +1,119 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SSL Certificate component renders 1`] = `
+Array [
+
+ Certificate
+
,
+
,
+
+
+
+ Expires
+
+
+
+ in 2 months
+
+
+
+
+
+
+
,
+]
+`;
+
+exports[`SSL Certificate component renders null if invalid date 1`] = `null`;
+
+exports[`SSL Certificate component shallow renders 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_status.bar.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_status.bar.test.tsx
index 5fd32c808da42..b39a1cb537583 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_status.bar.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_status.bar.test.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import { renderWithIntl } from 'test_utils/enzyme_helpers';
import { MonitorStatusBarComponent } from '../monitor_status_bar';
import { Ping } from '../../../../../common/runtime_types';
+import * as redux from 'react-redux';
describe('MonitorStatusBar component', () => {
let monitorStatus: Ping;
@@ -46,6 +47,12 @@ describe('MonitorStatusBar component', () => {
},
],
};
+
+ const spy = jest.spyOn(redux, 'useDispatch');
+ spy.mockReturnValue(jest.fn());
+
+ const spy1 = jest.spyOn(redux, 'useSelector');
+ spy1.mockReturnValue(true);
});
it('renders duration in ms, not us', () => {
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/ssl_certificate.test.tsx
similarity index 55%
rename from x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx
rename to x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/ssl_certificate.test.tsx
index 57ed09cc30ef1..70a161a2394ec 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/__test__/ssl_certificate.test.tsx
@@ -6,13 +6,14 @@
import React from 'react';
import moment from 'moment';
-import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { EuiBadge } from '@elastic/eui';
-import { renderWithIntl } from 'test_utils/enzyme_helpers';
import { Tls } from '../../../../../common/runtime_types';
import { MonitorSSLCertificate } from '../monitor_status_bar';
+import * as redux from 'react-redux';
+import { mountWithRouter, renderWithRouter, shallowWithRouter } from '../../../../lib';
+import { DYNAMIC_SETTINGS_DEFAULTS } from '../../../../../common/constants';
-describe('MonitorStatusBar component', () => {
+describe('SSL Certificate component', () => {
let monitorTls: Tls;
beforeEach(() => {
@@ -21,37 +22,52 @@ describe('MonitorStatusBar component', () => {
.toString();
monitorTls = {
- certificate_not_valid_after: dateInTwoMonths,
+ not_after: dateInTwoMonths,
};
+
+ const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
+ useDispatchSpy.mockReturnValue(jest.fn());
+
+ const useSelectorSpy = jest.spyOn(redux, 'useSelector');
+ useSelectorSpy.mockReturnValue({ settings: DYNAMIC_SETTINGS_DEFAULTS });
+ });
+
+ it('shallow renders', () => {
+ const monitorTls1 = {
+ not_after: '2020-04-24T11:41:38.200Z',
+ };
+ const component = shallowWithRouter( );
+ expect(component).toMatchSnapshot();
});
it('renders', () => {
- const component = renderWithIntl( );
+ const component = renderWithRouter( );
expect(component).toMatchSnapshot();
});
it('renders null if invalid date', () => {
monitorTls = {
- certificate_not_valid_after: 'i am so invalid date',
+ not_after: 'i am so invalid date',
};
- const component = renderWithIntl( );
+ const component = renderWithRouter( );
expect(component).toMatchSnapshot();
});
- it('renders expiration date with a warning state if ssl expiry date is less than 30 days', () => {
- const dateIn15Days = moment()
- .add(15, 'day')
+ it('renders expiration date with a warning state if ssl expiry date is less than 5 days', () => {
+ const dateIn5Days = moment()
+ .add(5, 'day')
.toString();
monitorTls = {
- certificate_not_valid_after: dateIn15Days,
+ not_after: dateIn5Days,
};
- const component = mountWithIntl( );
+ const component = mountWithRouter( );
const badgeComponent = component.find(EuiBadge);
+
expect(badgeComponent.props().color).toBe('warning');
const badgeComponentText = component.find('.euiBadge__text');
- expect(badgeComponentText.text()).toBe(moment(dateIn15Days).fromNow());
+ expect(badgeComponentText.text()).toBe(moment(dateIn5Days).fromNow());
expect(badgeComponent.find('span.euiBadge--warning')).toBeTruthy();
});
@@ -61,9 +77,9 @@ describe('MonitorStatusBar component', () => {
.add(40, 'day')
.toString();
monitorTls = {
- certificate_not_valid_after: dateIn40Days,
+ not_after: dateIn40Days,
};
- const component = mountWithIntl( );
+ const component = mountWithRouter( );
const badgeComponent = component.find(EuiBadge);
expect(badgeComponent.props().color).toBe('default');
diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx
index d92534aecd175..734a68f00f7de 100644
--- a/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx
+++ b/x-pack/plugins/uptime/public/components/monitor/monitor_status_details/monitor_status_bar/ssl_certificate.tsx
@@ -6,10 +6,13 @@
import React from 'react';
import moment from 'moment';
-import { EuiSpacer, EuiText, EuiBadge } from '@elastic/eui';
-import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
+import { Link } from 'react-router-dom';
+import { EuiSpacer, EuiText, EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
import { Tls } from '../../../../../common/runtime_types';
+import { useCertStatus } from '../../../../hooks';
+import { CERT_STATUS, CERTIFICATES_ROUTE } from '../../../../../common/constants';
interface Props {
/**
@@ -19,40 +22,79 @@ interface Props {
}
export const MonitorSSLCertificate = ({ tls }: Props) => {
- const certValidityDate = new Date(tls?.certificate_not_valid_after ?? '');
+ const certStatus = useCertStatus(tls?.not_after);
- const isValidDate = !isNaN(certValidityDate.valueOf());
+ const isExpiringSoon = certStatus === CERT_STATUS.EXPIRING_SOON;
- const dateIn30Days = moment().add('30', 'days');
+ const isExpired = certStatus === CERT_STATUS.EXPIRED;
- const isExpiringInMonth = isValidDate && dateIn30Days > moment(certValidityDate);
+ const relativeDate = moment(tls?.not_after).fromNow();
- return isValidDate ? (
+ return certStatus ? (
<>
-
-
-
- {moment(certValidityDate).fromNow()}
-
- ),
- }}
- />
+
+ {i18n.translate('xpack.uptime.monitorStatusBar.sslCertificate.title', {
+ defaultMessage: 'Certificate',
+ })}
+
+
+
+
+ {isExpired ? (
+ {relativeDate},
+ }}
+ />
+ ) : (
+
+ {relativeDate}
+
+ ),
+ }}
+ />
+ )}
+
+
+
+
+
+ {i18n.translate('xpack.uptime.monitorStatusBar.sslCertificate.overview', {
+ defaultMessage: 'Certificate overview',
+ })}
+
+
+
+
>
) : null;
};
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap
index ed5602323d254..0d6638e7070d6 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap
+++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap
@@ -556,13 +556,35 @@ exports[`MonitorList component renders the monitor list 1`] = `
-
- Monitor status
-
+
+
+ Monitor status
+
+
+
+
+
+
+
+
+
+ -
+
+
+
@@ -951,6 +1005,22 @@ exports[`MonitorList component renders the monitor list 1`] = `
+
+
+
+
+ -
+
+
+
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx
index 9b1d799a23e37..9dd44f5176664 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/__tests__/monitor_list.test.tsx
@@ -12,12 +12,19 @@ import {
} from '../../../../../common/runtime_types';
import { MonitorListComponent } from '../monitor_list';
import { renderWithRouter, shallowWithRouter } from '../../../../lib';
+import * as redux from 'react-redux';
describe('MonitorList component', () => {
let result: MonitorSummaryResult;
let localStorageMock: any;
beforeEach(() => {
+ const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
+ useDispatchSpy.mockReturnValue(jest.fn());
+
+ const useSelectorSpy = jest.spyOn(redux, 'useSelector');
+ useSelectorSpy.mockReturnValue(true);
+
localStorageMock = {
getItem: jest.fn().mockImplementation(() => '25'),
setItem: jest.fn(),
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx
new file mode 100644
index 0000000000000..d9380476eaf45
--- /dev/null
+++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/cert_status_column.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import moment from 'moment';
+import styled from 'styled-components';
+import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui';
+import { Cert } from '../../../../common/runtime_types';
+import { useCertStatus } from '../../../hooks';
+import { EXPIRED, EXPIRES_SOON } from '../../certificates/translations';
+import { CERT_STATUS } from '../../../../common/constants';
+
+interface Props {
+ cert: Cert;
+}
+
+const Span = styled.span`
+ margin-left: 5px;
+ vertical-align: middle;
+`;
+
+export const CertStatusColumn: React.FC = ({ cert }) => {
+ const certStatus = useCertStatus(cert?.not_after);
+
+ const relativeDate = moment(cert?.not_after).fromNow();
+
+ const CertStatus = ({ color, text }: { color: string; text: string }) => {
+ return (
+
+
+
+
+ {text} {relativeDate}
+
+
+
+ );
+ };
+
+ if (certStatus === CERT_STATUS.EXPIRING_SOON) {
+ return ;
+ }
+ if (certStatus === CERT_STATUS.EXPIRED) {
+ return ;
+ }
+
+ return certStatus ? : - ;
+};
diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx
index 7e9536689470e..616d8fbd76043 100644
--- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx
+++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx
@@ -18,12 +18,13 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
+import { Link } from 'react-router-dom';
import { HistogramPoint, FetchMonitorStatesQueryArgs } from '../../../../common/runtime_types';
import { MonitorSummary } from '../../../../common/runtime_types';
import { MonitorListStatusColumn } from './monitor_list_status_column';
import { ExpandedRowMap } from './types';
import { MonitorBarSeries } from '../../common/charts';
-import { MonitorPageLink } from './monitor_page_link';
+import { MonitorPageLink } from '../../common/monitor_page_link';
import { OverviewPageLink } from './overview_page_link';
import * as labels from './translations';
import { MonitorListPageSizeSelect } from './monitor_list_page_size_select';
@@ -31,6 +32,8 @@ import { MonitorListDrawer } from './monitor_list_drawer/list_drawer_container';
import { MonitorListProps } from './monitor_list_container';
import { MonitorList } from '../../../state/reducers/monitor_list';
import { useUrlParams } from '../../../hooks';
+import { CERTIFICATES_ROUTE } from '../../../../common/constants';
+import { CertStatusColumn } from './cert_status_column';
interface Props extends MonitorListProps {
lastRefresh: number;
@@ -143,6 +146,12 @@ export const MonitorListComponent: React.FC = ({
),
},
+ {
+ align: 'center' as const,
+ field: 'state.tls',
+ name: labels.TLS_COLUMN_LABEL,
+ render: (tls: any) => ,
+ },
{
align: 'center' as const,
field: 'histogram.points',
@@ -181,15 +190,32 @@ export const MonitorListComponent: React.FC = ({
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
return i18n.translate('xpack.uptime.monitorList.expandDrawerButton.ariaLabel', {
defaultMessage: 'Expand row for monitor with ID {id}',
diff --git a/x-pack/plugins/uptime/public/hooks/index.ts b/x-pack/plugins/uptime/public/hooks/index.ts
index 1f50e995eda49..b92d2d4cf7df5 100644
--- a/x-pack/plugins/uptime/public/hooks/index.ts
+++ b/x-pack/plugins/uptime/public/hooks/index.ts
@@ -8,3 +8,4 @@ export * from './use_monitor';
export * from './use_url_params';
export * from './use_telemetry';
export * from './update_kuery_string';
+export * from './use_cert_status';
diff --git a/x-pack/plugins/uptime/public/hooks/use_cert_status.ts b/x-pack/plugins/uptime/public/hooks/use_cert_status.ts
new file mode 100644
index 0000000000000..cb54b05af9dd1
--- /dev/null
+++ b/x-pack/plugins/uptime/public/hooks/use_cert_status.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import moment from 'moment';
+import { useSelector } from 'react-redux';
+import { selectDynamicSettings } from '../state/selectors';
+import { CERT_STATUS } from '../../common/constants';
+
+export const useCertStatus = (expiryDate?: string, issueDate?: string) => {
+ const dss = useSelector(selectDynamicSettings);
+
+ const expiryThreshold = dss.settings?.certThresholds?.expiration;
+
+ const ageThreshold = dss.settings?.certThresholds?.age;
+
+ const certValidityDate = new Date(expiryDate ?? '');
+
+ const isValidDate = !isNaN(certValidityDate.valueOf());
+
+ if (!isValidDate) {
+ return false;
+ }
+
+ const isExpiringSoon = moment(certValidityDate).diff(moment(), 'days') < expiryThreshold!;
+
+ const isTooOld = moment().diff(moment(issueDate), 'days') > ageThreshold!;
+
+ const isExpired = moment(certValidityDate) < moment();
+
+ if (isExpired) {
+ return CERT_STATUS.EXPIRED;
+ }
+
+ return isExpiringSoon
+ ? CERT_STATUS.EXPIRING_SOON
+ : isTooOld
+ ? CERT_STATUS.TOO_OLD
+ : CERT_STATUS.OK;
+};
diff --git a/x-pack/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/plugins/uptime/public/hooks/use_telemetry.ts
index a2012b8ac5636..9b4a441fe5ade 100644
--- a/x-pack/plugins/uptime/public/hooks/use_telemetry.ts
+++ b/x-pack/plugins/uptime/public/hooks/use_telemetry.ts
@@ -13,6 +13,7 @@ export enum UptimePage {
Overview = 'Overview',
Monitor = 'Monitor',
Settings = 'Settings',
+ Certificates = 'Certificates',
NotFound = '__not-found__',
}
diff --git a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/certificates.test.tsx.snap b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/certificates.test.tsx.snap
new file mode 100644
index 0000000000000..53b2ea27864bc
--- /dev/null
+++ b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/certificates.test.tsx.snap
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CertificatesPage shallow renders expected elements for valid props 1`] = `
+
+
+
+`;
diff --git a/x-pack/plugins/uptime/public/pages/__tests__/certificates.test.tsx b/x-pack/plugins/uptime/public/pages/__tests__/certificates.test.tsx
new file mode 100644
index 0000000000000..8dfb6fba3d6be
--- /dev/null
+++ b/x-pack/plugins/uptime/public/pages/__tests__/certificates.test.tsx
@@ -0,0 +1,15 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import { shallowWithRouter } from '../../lib';
+import { CertificatesPage } from '../certificates';
+
+describe('CertificatesPage', () => {
+ it('shallow renders expected elements for valid props', () => {
+ expect(shallowWithRouter( )).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx
new file mode 100644
index 0000000000000..d6c1b8e2b4568
--- /dev/null
+++ b/x-pack/plugins/uptime/public/pages/certificates.tsx
@@ -0,0 +1,136 @@
+/*
+ * 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 { useDispatch, useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPanel,
+ EuiSpacer,
+} from '@elastic/eui';
+import React, { useContext, useEffect, useState } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { useTrackPageview } from '../../../observability/public';
+import { PageHeader } from './page_header';
+import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
+import { OVERVIEW_ROUTE, SETTINGS_ROUTE } from '../../common/constants';
+import { getDynamicSettings } from '../state/actions/dynamic_settings';
+import { UptimeRefreshContext } from '../contexts';
+import * as labels from './translations';
+import { UptimePage, useUptimeTelemetry } from '../hooks';
+import { certificatesSelector, getCertificatesAction } from '../state/certificates/certificates';
+import { CertificateList, CertificateSearch, CertSort } from '../components/certificates';
+
+const DEFAULT_PAGE_SIZE = 10;
+const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize';
+const getPageSizeValue = () => {
+ const value = parseInt(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '', 10);
+ if (isNaN(value)) {
+ return DEFAULT_PAGE_SIZE;
+ }
+ return value;
+};
+
+export const CertificatesPage: React.FC = () => {
+ useUptimeTelemetry(UptimePage.Certificates);
+
+ useTrackPageview({ app: 'uptime', path: 'certificates' });
+ useTrackPageview({ app: 'uptime', path: 'certificates', delay: 15000 });
+
+ useBreadcrumbs([{ text: 'Certificates' }]);
+
+ const [page, setPage] = useState({ index: 0, size: getPageSizeValue() });
+ const [sort, setSort] = useState({
+ field: 'not_after',
+ direction: 'asc',
+ });
+ const [search, setSearch] = useState('');
+
+ const dispatch = useDispatch();
+
+ const { lastRefresh, refreshApp } = useContext(UptimeRefreshContext);
+
+ useEffect(() => {
+ dispatch(getDynamicSettings());
+ }, [dispatch]);
+
+ useEffect(() => {
+ dispatch(
+ getCertificatesAction.get({
+ search,
+ ...page,
+ sortBy: sort.field,
+ direction: sort.direction,
+ })
+ );
+ }, [dispatch, page, search, sort.direction, sort.field, lastRefresh]);
+
+ const certificates = useSelector(certificatesSelector);
+
+ return (
+ <>
+
+
+
+
+ {labels.RETURN_TO_OVERVIEW}
+
+
+
+
+
+
+ {labels.SETTINGS_ON_CERT}
+
+
+
+
+ {
+ refreshApp();
+ }}
+ data-test-subj="superDatePickerApplyTimeButton"
+ >
+ {labels.REFRESH_CERT}
+
+
+
+
+
+
+ {certificates?.total ?? 0},
+ }}
+ />
+ }
+ datePicker={false}
+ />
+
+
+
+ {
+ setPage(pageVal);
+ setSort(sortVal);
+ localStorage.setItem(LOCAL_STORAGE_KEY, pageVal.size.toString());
+ }}
+ sort={sort}
+ />
+
+ >
+ );
+};
diff --git a/x-pack/plugins/uptime/public/pages/monitor.tsx b/x-pack/plugins/uptime/public/pages/monitor.tsx
index 8a309db75acd2..fc796e679a2f6 100644
--- a/x-pack/plugins/uptime/public/pages/monitor.tsx
+++ b/x-pack/plugins/uptime/public/pages/monitor.tsx
@@ -5,8 +5,8 @@
*/
import { EuiSpacer } from '@elastic/eui';
-import React from 'react';
-import { useSelector } from 'react-redux';
+import React, { useEffect } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
import { monitorStatusSelector } from '../state/selectors';
import { PageHeader } from './page_header';
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
@@ -14,8 +14,15 @@ import { useTrackPageview } from '../../../observability/public';
import { useMonitorId, useUptimeTelemetry, UptimePage } from '../hooks';
import { MonitorCharts } from '../components/monitor';
import { MonitorStatusDetails, PingList } from '../components/monitor';
+import { getDynamicSettings } from '../state/actions/dynamic_settings';
export const MonitorPage: React.FC = () => {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(getDynamicSettings());
+ }, [dispatch]);
+
const monitorId = useMonitorId();
const selectedMonitor = useSelector(monitorStatusSelector);
diff --git a/x-pack/plugins/uptime/public/pages/page_header.tsx b/x-pack/plugins/uptime/public/pages/page_header.tsx
index b10bc6ba44f8a..b6791e6a93445 100644
--- a/x-pack/plugins/uptime/public/pages/page_header.tsx
+++ b/x-pack/plugins/uptime/public/pages/page_header.tsx
@@ -13,7 +13,7 @@ import { SETTINGS_ROUTE } from '../../common/constants';
import { ToggleAlertFlyoutButton } from '../components/overview/alerts/alerts_containers';
interface PageHeaderProps {
- headingText: string;
+ headingText: string | JSX.Element;
extraLinks?: boolean;
datePicker?: boolean;
}
diff --git a/x-pack/plugins/uptime/public/pages/translations.ts b/x-pack/plugins/uptime/public/pages/translations.ts
index 85e4e3f931c46..74fb2eeb1416b 100644
--- a/x-pack/plugins/uptime/public/pages/translations.ts
+++ b/x-pack/plugins/uptime/public/pages/translations.ts
@@ -6,6 +6,21 @@
import { i18n } from '@kbn/i18n';
+export const SETTINGS_ON_CERT = i18n.translate('xpack.uptime.certificates.settingsLinkLabel', {
+ defaultMessage: 'Settings',
+});
+
+export const RETURN_TO_OVERVIEW = i18n.translate(
+ 'xpack.uptime.certificates.returnToOverviewLinkLabel',
+ {
+ defaultMessage: 'Return to overview',
+ }
+);
+
+export const REFRESH_CERT = i18n.translate('xpack.uptime.certificates.refresh', {
+ defaultMessage: 'Refresh',
+});
+
export const settings = {
breadcrumbText: i18n.translate('xpack.uptime.settingsBreadcrumbText', {
defaultMessage: 'Settings',
diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx
index eb0587c0417a2..ca97858998df7 100644
--- a/x-pack/plugins/uptime/public/routes.tsx
+++ b/x-pack/plugins/uptime/public/routes.tsx
@@ -8,8 +8,14 @@ import React, { FC } from 'react';
import { Route, Switch } from 'react-router-dom';
import { DataPublicPluginSetup } from '../../../../src/plugins/data/public';
import { OverviewPage } from './components/overview/overview_container';
-import { MONITOR_ROUTE, OVERVIEW_ROUTE, SETTINGS_ROUTE } from '../common/constants';
+import {
+ CERTIFICATES_ROUTE,
+ MONITOR_ROUTE,
+ OVERVIEW_ROUTE,
+ SETTINGS_ROUTE,
+} from '../common/constants';
import { MonitorPage, NotFoundPage, SettingsPage } from './pages';
+import { CertificatesPage } from './pages/certificates';
interface RouterProps {
autocomplete: DataPublicPluginSetup['autocomplete'];
@@ -27,6 +33,11 @@ export const PageRouter: FC = ({ autocomplete }) => (
+
+
+
+
+
diff --git a/x-pack/plugins/uptime/public/state/api/certificates.ts b/x-pack/plugins/uptime/public/state/api/certificates.ts
new file mode 100644
index 0000000000000..78267e659d233
--- /dev/null
+++ b/x-pack/plugins/uptime/public/state/api/certificates.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { API_URLS } from '../../../common/constants';
+import { apiService } from './utils';
+import { CertResultType, GetCertsParams } from '../../../common/runtime_types';
+
+export const fetchCertificates = async (params: GetCertsParams) => {
+ return await apiService.get(API_URLS.CERTS, params, CertResultType);
+};
diff --git a/x-pack/plugins/uptime/public/state/certificates/certificates.ts b/x-pack/plugins/uptime/public/state/certificates/certificates.ts
new file mode 100644
index 0000000000000..18cbcf6bcb614
--- /dev/null
+++ b/x-pack/plugins/uptime/public/state/certificates/certificates.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 { handleActions } from 'redux-actions';
+import { takeLatest } from 'redux-saga/effects';
+import { createAsyncAction } from '../actions/utils';
+import { getAsyncInitialState, handleAsyncAction } from '../reducers/utils';
+import { CertResult, GetCertsParams } from '../../../common/runtime_types';
+import { AppState } from '../index';
+import { AsyncInitialState } from '../reducers/types';
+import { fetchEffectFactory } from '../effects/fetch_effect';
+import { fetchCertificates } from '../api/certificates';
+
+export const getCertificatesAction = createAsyncAction
(
+ 'GET_CERTIFICATES'
+);
+
+interface CertificatesState {
+ certs: AsyncInitialState;
+}
+
+const initialState = {
+ certs: getAsyncInitialState(),
+};
+
+export const certificatesReducer = handleActions(
+ {
+ ...handleAsyncAction('certs', getCertificatesAction),
+ },
+ initialState
+);
+
+export function* fetchCertificatesEffect() {
+ yield takeLatest(
+ getCertificatesAction.get,
+ fetchEffectFactory(fetchCertificates, getCertificatesAction.success, getCertificatesAction.fail)
+ );
+}
+
+export const certificatesSelector = ({ certificates }: AppState) => certificates.certs.data;
diff --git a/x-pack/plugins/uptime/public/state/effects/index.ts b/x-pack/plugins/uptime/public/state/effects/index.ts
index 739179c5bbeae..211067c840d54 100644
--- a/x-pack/plugins/uptime/public/state/effects/index.ts
+++ b/x-pack/plugins/uptime/public/state/effects/index.ts
@@ -16,6 +16,7 @@ import { fetchPingsEffect, fetchPingHistogramEffect } from './ping';
import { fetchMonitorDurationEffect } from './monitor_duration';
import { fetchMLJobEffect } from './ml_anomaly';
import { fetchIndexStatusEffect } from './index_status';
+import { fetchCertificatesEffect } from '../certificates/certificates';
export function* rootEffect() {
yield fork(fetchMonitorDetailsEffect);
@@ -31,4 +32,5 @@ export function* rootEffect() {
yield fork(fetchMLJobEffect);
yield fork(fetchMonitorDurationEffect);
yield fork(fetchIndexStatusEffect);
+ yield fork(fetchCertificatesEffect);
}
diff --git a/x-pack/plugins/uptime/public/state/reducers/index.ts b/x-pack/plugins/uptime/public/state/reducers/index.ts
index 294bde2f277ec..ead7f5b46431b 100644
--- a/x-pack/plugins/uptime/public/state/reducers/index.ts
+++ b/x-pack/plugins/uptime/public/state/reducers/index.ts
@@ -18,6 +18,7 @@ import { pingListReducer } from './ping_list';
import { monitorDurationReducer } from './monitor_duration';
import { indexStatusReducer } from './index_status';
import { mlJobsReducer } from './ml_anomaly';
+import { certificatesReducer } from '../certificates/certificates';
export const rootReducer = combineReducers({
monitor: monitorReducer,
@@ -33,4 +34,5 @@ export const rootReducer = combineReducers({
ml: mlJobsReducer,
monitorDuration: monitorDurationReducer,
indexStatus: indexStatusReducer,
+ certificates: certificatesReducer,
});
diff --git a/x-pack/plugins/uptime/public/state/reducers/ml_anomaly.ts b/x-pack/plugins/uptime/public/state/reducers/ml_anomaly.ts
index 61e03a9592921..9a4a949ac4ede 100644
--- a/x-pack/plugins/uptime/public/state/reducers/ml_anomaly.ts
+++ b/x-pack/plugins/uptime/public/state/reducers/ml_anomaly.ts
@@ -15,7 +15,6 @@ import {
getMLCapabilitiesAction,
} from '../actions';
import { getAsyncInitialState, handleAsyncAction } from './utils';
-import { IHttpFetchError } from '../../../../../../target/types/core/public/http';
import { AsyncInitialState } from './types';
import { MlCapabilitiesResponse } from '../../../../../plugins/ml/common/types/capabilities';
import { CreateMLJobSuccess, DeleteJobResults } from '../actions/types';
@@ -37,15 +36,13 @@ const initialState: MLJobState = {
mlCapabilities: getAsyncInitialState(),
};
-type Payload = IHttpFetchError;
-
export const mlJobsReducer = handleActions(
{
- ...handleAsyncAction('mlJob', getExistingMLJobAction),
- ...handleAsyncAction('mlCapabilities', getMLCapabilitiesAction),
- ...handleAsyncAction('createJob', createMLJobAction),
- ...handleAsyncAction('deleteJob', deleteMLJobAction),
- ...handleAsyncAction('anomalies', getAnomalyRecordsAction),
+ ...handleAsyncAction('mlJob', getExistingMLJobAction),
+ ...handleAsyncAction('mlCapabilities', getMLCapabilitiesAction),
+ ...handleAsyncAction('createJob', createMLJobAction),
+ ...handleAsyncAction('deleteJob', deleteMLJobAction),
+ ...handleAsyncAction('anomalies', getAnomalyRecordsAction),
...{
[String(resetMLState)]: state => ({
...state,
diff --git a/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts b/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts
index cf895aebeb755..59a794a549d57 100644
--- a/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts
+++ b/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts
@@ -9,9 +9,9 @@ import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '..
import { MonitorSummaryResult } from '../../../common/runtime_types';
export interface MonitorList {
- list: MonitorSummaryResult;
error?: Error;
loading: boolean;
+ list: MonitorSummaryResult;
}
export const initialState: MonitorList = {
diff --git a/x-pack/plugins/uptime/public/state/reducers/utils.ts b/x-pack/plugins/uptime/public/state/reducers/utils.ts
index d7a7f237c1154..15e49e7f6de8b 100644
--- a/x-pack/plugins/uptime/public/state/reducers/utils.ts
+++ b/x-pack/plugins/uptime/public/state/reducers/utils.ts
@@ -7,7 +7,7 @@
import { Action } from 'redux-actions';
import { AsyncAction } from '../actions/types';
-export function handleAsyncAction(
+export function handleAsyncAction(
storeKey: string,
asyncAction: AsyncAction
) {
@@ -24,7 +24,7 @@ export function handleAsyncAction(
...state,
[storeKey]: {
...(state as any)[storeKey],
- data: action.payload === null ? action.payload : { ...action.payload },
+ data: action.payload,
loading: false,
},
}),
diff --git a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts
index ba5e5abf588b8..1c4c12f5f52d2 100644
--- a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts
+++ b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts
@@ -101,6 +101,12 @@ describe('state selectors', () => {
loading: false,
},
},
+ certificates: {
+ certs: {
+ data: null,
+ loading: false,
+ },
+ },
};
it('selects base path from state', () => {
diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts
index 894e2316dc927..4aec376ceadf0 100644
--- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts
@@ -19,9 +19,10 @@ describe('getCerts', () => {
_score: 0,
_source: {
tls: {
- certificate_not_valid_before: '2019-08-16T01:40:25.000Z',
server: {
x509: {
+ not_before: '2019-08-16T01:40:25.000Z',
+ not_after: '2020-07-16T03:15:39.000Z',
subject: {
common_name: 'r2.shared.global.fastly.net',
},
@@ -34,12 +35,14 @@ describe('getCerts', () => {
sha256: '12b00d04db0db8caa302bfde043e88f95baceb91e86ac143e93830b4bbec726d',
},
},
- certificate_not_valid_after: '2020-07-16T03:15:39.000Z',
},
monitor: {
name: 'Real World Test',
id: 'real-world-test',
},
+ url: {
+ full: 'https://fullurl.com',
+ },
},
fields: {
'tls.server.hash.sha256': [
@@ -96,24 +99,30 @@ describe('getCerts', () => {
to: 'now+1h',
search: 'my_common_name',
size: 30,
+ sortBy: 'not_after',
+ direction: 'desc',
});
expect(result).toMatchInlineSnapshot(`
- Array [
- Object {
- "certificate_not_valid_after": "2020-07-16T03:15:39.000Z",
- "certificate_not_valid_before": "2019-08-16T01:40:25.000Z",
- "common_name": "r2.shared.global.fastly.net",
- "issuer": "GlobalSign CloudSSL CA - SHA256 - G3",
- "monitors": Array [
- Object {
- "id": "real-world-test",
- "name": "Real World Test",
- },
- ],
- "sha1": "b7b4b89ef0d0caf39d223736f0fdbb03c7b426f1",
- "sha256": "12b00d04db0db8caa302bfde043e88f95baceb91e86ac143e93830b4bbec726d",
- },
- ]
+ Object {
+ "certs": Array [
+ Object {
+ "common_name": "r2.shared.global.fastly.net",
+ "issuer": "GlobalSign CloudSSL CA - SHA256 - G3",
+ "monitors": Array [
+ Object {
+ "id": "real-world-test",
+ "name": "Real World Test",
+ "url": undefined,
+ },
+ ],
+ "not_after": "2020-07-16T03:15:39.000Z",
+ "not_before": "2019-08-16T01:40:25.000Z",
+ "sha1": "b7b4b89ef0d0caf39d223736f0fdbb03c7b426f1",
+ "sha256": "12b00d04db0db8caa302bfde043e88f95baceb91e86ac143e93830b4bbec726d",
+ },
+ ],
+ "total": 0,
+ }
`);
expect(mockCallES.mock.calls).toMatchInlineSnapshot(`
Array [
@@ -128,9 +137,16 @@ describe('getCerts', () => {
"tls.server.x509.subject.common_name",
"tls.server.hash.sha1",
"tls.server.hash.sha256",
- "tls.certificate_not_valid_before",
- "tls.certificate_not_valid_after",
+ "tls.server.x509.not_after",
+ "tls.server.x509.not_before",
],
+ "aggs": Object {
+ "total": Object {
+ "cardinality": Object {
+ "field": "tls.server.hash.sha256",
+ },
+ },
+ },
"collapse": Object {
"field": "tls.server.hash.sha256",
"inner_hits": Object {
@@ -138,6 +154,7 @@ describe('getCerts', () => {
"includes": Array [
"monitor.id",
"monitor.name",
+ "url.full",
],
},
"collapse": Object {
@@ -151,13 +168,13 @@ describe('getCerts', () => {
],
},
},
- "from": 1,
+ "from": 30,
"query": Object {
"bool": Object {
"filter": Array [
Object {
"exists": Object {
- "field": "tls",
+ "field": "tls.server",
},
},
Object {
@@ -169,39 +186,32 @@ describe('getCerts', () => {
},
},
],
+ "minimum_should_match": 1,
"should": Array [
Object {
- "wildcard": Object {
- "tls.server.issuer": Object {
- "value": "*my_common_name*",
- },
- },
- },
- Object {
- "wildcard": Object {
- "tls.common_name": Object {
- "value": "*my_common_name*",
- },
- },
- },
- Object {
- "wildcard": Object {
- "monitor.id": Object {
- "value": "*my_common_name*",
- },
- },
- },
- Object {
- "wildcard": Object {
- "monitor.name": Object {
- "value": "*my_common_name*",
- },
+ "multi_match": Object {
+ "fields": Array [
+ "monitor.id.text",
+ "monitor.name.text",
+ "url.full.text",
+ "tls.server.x509.subject.common_name.text",
+ "tls.server.x509.issuer.common_name.text",
+ ],
+ "query": "my_common_name",
+ "type": "phrase_prefix",
},
},
],
},
},
"size": 30,
+ "sort": Array [
+ Object {
+ "tls.server.x509.not_after": Object {
+ "order": "desc",
+ },
+ },
+ ],
},
"index": "heartbeat*",
},
diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts
index 75bf5096bd997..f8a335c387f2e 100644
--- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_latest_monitor.test.ts
@@ -32,7 +32,14 @@ describe('getLatestMonitor', () => {
},
},
size: 1,
- _source: ['url', 'monitor', 'observer', 'tls', '@timestamp'],
+ _source: [
+ 'url',
+ 'monitor',
+ 'observer',
+ '@timestamp',
+ 'tls.server.x509.not_after',
+ 'tls.server.x509.not_before',
+ ],
sort: {
'@timestamp': { order: 'desc' },
},
@@ -83,6 +90,10 @@ describe('getLatestMonitor', () => {
"type": "http",
},
"timestamp": "123456",
+ "tls": Object {
+ "not_after": undefined,
+ "not_before": undefined,
+ },
}
`);
expect(result.timestamp).toBe('123456');
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
index b427e7cae1a7e..6820cd69376d1 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts
@@ -5,9 +5,16 @@
*/
import { UMElasticsearchQueryFn } from '../adapters';
-import { Cert, GetCertsParams } from '../../../common/runtime_types';
+import { CertResult, GetCertsParams } from '../../../common/runtime_types';
-export const getCerts: UMElasticsearchQueryFn = async ({
+enum SortFields {
+ 'issuer' = 'tls.server.x509.issuer.common_name',
+ 'not_after' = 'tls.server.x509.not_after',
+ 'not_before' = 'tls.server.x509.not_before',
+ 'common_name' = 'tls.server.x509.subject.common_name',
+}
+
+export const getCerts: UMElasticsearchQueryFn = async ({
callES,
dynamicSettings,
index,
@@ -15,19 +22,29 @@ export const getCerts: UMElasticsearchQueryFn = async ({
to,
search,
size,
+ sortBy,
+ direction,
}) => {
- const searchWrapper = `*${search}*`;
+ const sort = SortFields[sortBy as keyof typeof SortFields];
+
const params: any = {
index: dynamicSettings.heartbeatIndices,
body: {
- from: index,
+ from: index * size,
size,
+ sort: [
+ {
+ [sort]: {
+ order: direction,
+ },
+ },
+ ],
query: {
bool: {
filter: [
{
exists: {
- field: 'tls',
+ field: 'tls.server',
},
},
{
@@ -48,14 +65,14 @@ export const getCerts: UMElasticsearchQueryFn = async ({
'tls.server.x509.subject.common_name',
'tls.server.hash.sha1',
'tls.server.hash.sha256',
- 'tls.certificate_not_valid_before',
- 'tls.certificate_not_valid_after',
+ 'tls.server.x509.not_after',
+ 'tls.server.x509.not_before',
],
collapse: {
field: 'tls.server.hash.sha256',
inner_hits: {
_source: {
- includes: ['monitor.id', 'monitor.name'],
+ includes: ['monitor.id', 'monitor.name', 'url.full'],
},
collapse: {
field: 'monitor.id',
@@ -64,72 +81,67 @@ export const getCerts: UMElasticsearchQueryFn = async ({
sort: [{ 'monitor.id': 'asc' }],
},
},
+ aggs: {
+ total: {
+ cardinality: {
+ field: 'tls.server.hash.sha256',
+ },
+ },
+ },
},
};
if (search) {
+ params.body.query.bool.minimum_should_match = 1;
params.body.query.bool.should = [
{
- wildcard: {
- 'tls.server.issuer': {
- value: searchWrapper,
- },
- },
- },
- {
- wildcard: {
- 'tls.common_name': {
- value: searchWrapper,
- },
- },
- },
- {
- wildcard: {
- 'monitor.id': {
- value: searchWrapper,
- },
- },
- },
- {
- wildcard: {
- 'monitor.name': {
- value: searchWrapper,
- },
+ multi_match: {
+ query: escape(search),
+ type: 'phrase_prefix',
+ fields: [
+ 'monitor.id.text',
+ 'monitor.name.text',
+ 'url.full.text',
+ 'tls.server.x509.subject.common_name.text',
+ 'tls.server.x509.issuer.common_name.text',
+ ],
},
},
];
}
const result = await callES('search', params);
- const formatted = (result?.hits?.hits ?? []).map((hit: any) => {
+
+ const certs = (result?.hits?.hits ?? []).map((hit: any) => {
const {
_source: {
- tls: {
- server: {
- x509: {
- issuer: { common_name: issuer },
- subject: { common_name },
- },
- hash: { sha1, sha256 },
- },
- certificate_not_valid_after,
- certificate_not_valid_before,
- },
+ tls: { server },
},
} = hit;
+
+ const notAfter = server?.x509?.not_after;
+ const notBefore = server?.x509?.not_before;
+ const issuer = server?.x509?.issuer?.common_name;
+ const commonName = server?.x509?.subject?.common_name;
+ const sha1 = server?.hash?.sha1;
+ const sha256 = server?.hash?.sha256;
+
const monitors = hit.inner_hits.monitors.hits.hits.map((monitor: any) => ({
name: monitor._source?.monitor.name,
id: monitor._source?.monitor.id,
+ url: monitor._source?.url?.full,
}));
+
return {
monitors,
- certificate_not_valid_after,
- certificate_not_valid_before,
issuer,
sha1,
sha256,
- common_name,
+ not_after: notAfter,
+ not_before: notBefore,
+ common_name: commonName,
};
});
- return formatted;
+ const total = result?.aggregations?.total?.value ?? 0;
+ return { certs, total };
};
diff --git a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts
index 98ce449002f21..db34de5159213 100644
--- a/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts
+++ b/x-pack/plugins/uptime/server/lib/requests/get_latest_monitor.ts
@@ -45,7 +45,14 @@ export const getLatestMonitor: UMElasticsearchQueryFn = UMElasticsearchQueryFn;
export interface UptimeRequests {
- getCerts: ESQ;
+ getCerts: ESQ;
getFilterBar: ESQ;
getIndexPattern: ESQ<{}, {}>;
getLatestMonitor: ESQ;
diff --git a/x-pack/plugins/uptime/server/rest_api/certs.ts b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts
similarity index 63%
rename from x-pack/plugins/uptime/server/rest_api/certs.ts
rename to x-pack/plugins/uptime/server/rest_api/certs/certs.ts
index f2e1700b23e7d..a5ca6e264d299 100644
--- a/x-pack/plugins/uptime/server/rest_api/certs.ts
+++ b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts
@@ -5,14 +5,16 @@
*/
import { schema } from '@kbn/config-schema';
-import { UMServerLibs } from '../lib/lib';
-import { UMRestApiRouteFactory } from '.';
-import { API_URLS } from '../../common/constants';
+import { API_URLS } from '../../../common/constants';
+import { UMServerLibs } from '../../lib/lib';
+import { UMRestApiRouteFactory } from '../types';
const DEFAULT_INDEX = 0;
const DEFAULT_SIZE = 25;
const DEFAULT_FROM = 'now-1d';
const DEFAULT_TO = 'now';
+const DEFAULT_SORT = 'not_after';
+const DEFAULT_DIRECTION = 'asc';
export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
method: 'GET',
@@ -24,30 +26,33 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) =
search: schema.maybe(schema.string()),
index: schema.maybe(schema.number()),
size: schema.maybe(schema.number()),
+ sortBy: schema.maybe(schema.string()),
+ direction: schema.maybe(schema.string()),
}),
},
- writeAccess: false,
- options: {
- tags: ['access:uptime-read'],
- },
handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => {
const index = request.query?.index ?? DEFAULT_INDEX;
const size = request.query?.size ?? DEFAULT_SIZE;
const from = request.query?.from ?? DEFAULT_FROM;
const to = request.query?.to ?? DEFAULT_TO;
+ const sortBy = request.query?.sortBy ?? DEFAULT_SORT;
+ const direction = request.query?.direction ?? DEFAULT_DIRECTION;
const { search } = request.query;
-
+ const result = await libs.requests.getCerts({
+ callES,
+ dynamicSettings,
+ index,
+ search,
+ size,
+ from,
+ to,
+ sortBy,
+ direction,
+ });
return response.ok({
body: {
- certs: await libs.requests.getCerts({
- callES,
- dynamicSettings,
- index,
- search,
- size,
- from,
- to,
- }),
+ certs: result.certs,
+ total: result.total,
},
});
},
diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts
index a7a63342d11d4..2b598be284e1c 100644
--- a/x-pack/plugins/uptime/server/rest_api/index.ts
+++ b/x-pack/plugins/uptime/server/rest_api/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { createGetCertsRoute } from './certs';
+import { createGetCertsRoute } from './certs/certs';
import { createGetOverviewFilters } from './overview_filters';
import { createGetPingHistogramRoute, createGetPingsRoute } from './pings';
import { createGetDynamicSettingsRoute, createPostDynamicSettingsRoute } from './dynamic_settings';
@@ -19,6 +19,7 @@ import {
} from './monitors';
import { createGetMonitorDurationRoute } from './monitors/monitors_durations';
import { createGetIndexPatternRoute, createGetIndexStatusRoute } from './index_state';
+
export * from './types';
export { createRouteWithAuth } from './create_route_with_auth';
export { uptimeRouteWrapper } from './uptime_route_wrapper';
diff --git a/x-pack/test/api_integration/apis/infra/metrics_alerting.ts b/x-pack/test/api_integration/apis/infra/metrics_alerting.ts
index 19879f5761ab2..5c43e8938a8c1 100644
--- a/x-pack/test/api_integration/apis/infra/metrics_alerting.ts
+++ b/x-pack/test/api_integration/apis/infra/metrics_alerting.ts
@@ -32,7 +32,7 @@ export default function({ getService }: FtrProviderContext) {
describe('querying the entire infrastructure', () => {
for (const aggType of aggs) {
it(`should work with the ${aggType} aggregator`, async () => {
- const searchBody = getElasticsearchMetricQuery(getSearchParams(aggType));
+ const searchBody = getElasticsearchMetricQuery(getSearchParams(aggType), '@timestamp');
const result = await client.search({
index,
body: searchBody,
@@ -45,6 +45,7 @@ export default function({ getService }: FtrProviderContext) {
it('should work with a filterQuery', async () => {
const searchBody = getElasticsearchMetricQuery(
getSearchParams('avg'),
+ '@timestamp',
undefined,
'{"bool":{"should":[{"match_phrase":{"agent.hostname":"foo"}}],"minimum_should_match":1}}'
);
@@ -59,6 +60,7 @@ export default function({ getService }: FtrProviderContext) {
it('should work with a filterQuery in KQL format', async () => {
const searchBody = getElasticsearchMetricQuery(
getSearchParams('avg'),
+ '@timestamp',
undefined,
'"agent.hostname":"foo"'
);
@@ -74,7 +76,11 @@ export default function({ getService }: FtrProviderContext) {
describe('querying with a groupBy parameter', () => {
for (const aggType of aggs) {
it(`should work with the ${aggType} aggregator`, async () => {
- const searchBody = getElasticsearchMetricQuery(getSearchParams(aggType), 'agent.id');
+ const searchBody = getElasticsearchMetricQuery(
+ getSearchParams(aggType),
+ '@timestamp',
+ 'agent.id'
+ );
const result = await client.search({
index,
body: searchBody,
@@ -87,6 +93,7 @@ export default function({ getService }: FtrProviderContext) {
it('should work with a filterQuery', async () => {
const searchBody = getElasticsearchMetricQuery(
getSearchParams('avg'),
+ '@timestamp',
'agent.id',
'{"bool":{"should":[{"match_phrase":{"agent.hostname":"foo"}}],"minimum_should_match":1}}'
);
diff --git a/x-pack/test/api_integration/apis/uptime/rest/certs.ts b/x-pack/test/api_integration/apis/uptime/rest/certs.ts
index a3a15d8f8b014..4917917fdd6bc 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/certs.ts
+++ b/x-pack/test/api_integration/apis/uptime/rest/certs.ts
@@ -21,7 +21,7 @@ export default function({ getService }: FtrProviderContext) {
describe('empty index', async () => {
it('returns empty array for no data', async () => {
const apiResponse = await supertest.get(API_URLS.CERTS);
- expect(JSON.stringify(apiResponse.body)).to.eql('{"certs":[]}');
+ expect(JSON.stringify(apiResponse.body)).to.eql('{"certs":[],"total":0}');
});
});
@@ -39,10 +39,10 @@ export default function({ getService }: FtrProviderContext) {
10000,
{
tls: {
- certificate_not_valid_after: cnva,
- certificate_not_valid_before: cnvb,
server: {
x509: {
+ not_after: cnva,
+ not_before: cnvb,
issuer: {
common_name: 'issuer-common-name',
},
@@ -78,9 +78,12 @@ export default function({ getService }: FtrProviderContext) {
const cert = body.certs[0];
expect(Array.isArray(cert.monitors)).to.be(true);
- expect(cert.monitors[0]).to.eql({ id: monitorId });
- expect(cert.certificate_not_valid_after).to.eql(cnva);
- expect(cert.certificate_not_valid_before).to.eql(cnvb);
+ expect(cert.monitors[0]).to.eql({
+ id: monitorId,
+ url: 'http://localhost:5678/pattern?r=200x5,500x1',
+ });
+ expect(cert.not_after).to.eql(cnva);
+ expect(cert.not_before).to.eql(cnvb);
expect(cert.common_name).to.eql('subject-common-name');
expect(cert.issuer).to.eql('issuer-common-name');
});
diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json
index 9a33be807670e..1baff443bd97f 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json
+++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json
@@ -27,5 +27,6 @@
"full": "http://localhost:5678/pattern?r=200x1"
},
"docId": "h5toHm0B0I9WX_CznN_V",
- "timestamp": "2019-09-11T03:40:34.371Z"
-}
\ No newline at end of file
+ "timestamp": "2019-09-11T03:40:34.371Z",
+ "tls": {}
+}
diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts
index ae326c8b2aee0..5f62a3c55a2eb 100644
--- a/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts
+++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_checks.ts
@@ -6,119 +6,36 @@
import uuid from 'uuid';
import { merge, flattenDeep } from 'lodash';
-
-const INDEX_NAME = 'heartbeat-8-generated-test';
-
-export const makePing = async (
- es: any,
- monitorId: string,
- fields: { [key: string]: any },
- mogrify: (doc: any) => any,
- refresh: boolean = true
-) => {
- const baseDoc = {
- tcp: {
- rtt: {
- connect: {
- us: 14687,
- },
- },
- },
- observer: {
- geo: {
- name: 'mpls',
- location: '37.926868, -78.024902',
- },
- hostname: 'avc-x1e',
- },
- agent: {
- hostname: 'avc-x1e',
- id: '10730a1a-4cb7-45ce-8524-80c4820476ab',
- type: 'heartbeat',
- ephemeral_id: '0d9a8dc6-f604-49e3-86a0-d8f9d6f2cbad',
- version: '8.0.0',
- },
- '@timestamp': new Date().toISOString(),
- resolve: {
- rtt: {
- us: 350,
- },
- ip: '127.0.0.1',
- },
- ecs: {
- version: '1.1.0',
- },
- host: {
- name: 'avc-x1e',
- },
- http: {
- rtt: {
- response_header: {
- us: 19349,
- },
- total: {
- us: 48954,
- },
- write_request: {
- us: 33,
- },
- content: {
- us: 51,
- },
- validate: {
- us: 19400,
- },
- },
- response: {
- status_code: 200,
- body: {
- bytes: 3,
- hash: '27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf',
- },
- },
- },
- monitor: {
- duration: {
- us: 49347,
- },
- ip: '127.0.0.1',
- id: monitorId,
- check_group: uuid.v4(),
- type: 'http',
- status: 'up',
- },
- event: {
- dataset: 'uptime',
- },
- url: {
- path: '/pattern',
- scheme: 'http',
- port: 5678,
- domain: 'localhost',
- query: 'r=200x5,500x1',
- full: 'http://localhost:5678/pattern?r=200x5,500x1',
- },
- };
-
- const doc = mogrify(merge(baseDoc, fields));
-
- await es.index({
- index: INDEX_NAME,
- refresh,
- body: doc,
- });
-
- return doc;
+import { makePing } from './make_ping';
+import { TlsProps } from './make_tls';
+
+interface CheckProps {
+ es: any;
+ monitorId?: string;
+ numIps?: number;
+ fields?: { [key: string]: any };
+ mogrify?: (doc: any) => any;
+ refresh?: boolean;
+ tls?: boolean | TlsProps;
+}
+
+const getRandomMonitorId = () => {
+ return (
+ 'monitor-' +
+ Math.random()
+ .toString(36)
+ .substring(7)
+ );
};
-
-export const makeCheck = async (
- es: any,
- monitorId: string,
- numIps: number,
- fields: { [key: string]: any },
- mogrify: (doc: any) => any,
- refresh: boolean = true
-) => {
+export const makeCheck = async ({
+ es,
+ monitorId = getRandomMonitorId(),
+ numIps = 1,
+ fields = {},
+ mogrify = d => d,
+ refresh = true,
+ tls = false,
+}: CheckProps): Promise<{ monitorId: string; docs: any }> => {
const cgFields = {
monitor: {
check_group: uuid.v4(),
@@ -139,7 +56,7 @@ export const makeCheck = async (
if (i === numIps - 1) {
pingFields.summary = summary;
}
- const doc = await makePing(es, monitorId, pingFields, mogrify, false);
+ const doc = await makePing(es, monitorId, pingFields, mogrify, false, tls as any);
docs.push(doc);
// @ts-ignore
summary[doc.monitor.status]++;
@@ -149,15 +66,15 @@ export const makeCheck = async (
await es.indices.refresh();
}
- return docs;
+ return { monitorId, docs };
};
export const makeChecks = async (
es: any,
monitorId: string,
- numChecks: number,
- numIps: number,
- every: number, // number of millis between checks
+ numChecks: number = 1,
+ numIps: number = 1,
+ every: number = 10000, // number of millis between checks
fields: { [key: string]: any } = {},
mogrify: (doc: any) => any = d => d,
refresh: boolean = true
@@ -177,7 +94,8 @@ export const makeChecks = async (
},
},
});
- checks.push(await makeCheck(es, monitorId, numIps, fields, mogrify, false));
+ const { docs } = await makeCheck({ es, monitorId, numIps, fields, mogrify, refresh: false });
+ checks.push(docs);
}
if (refresh) {
diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts
new file mode 100644
index 0000000000000..908c571e07e06
--- /dev/null
+++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts
@@ -0,0 +1,118 @@
+/*
+ * 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 uuid from 'uuid';
+import { merge } from 'lodash';
+import { makeTls, TlsProps } from './make_tls';
+
+const INDEX_NAME = 'heartbeat-8-generated-test';
+
+export const makePing = async (
+ es: any,
+ monitorId: string,
+ fields: { [key: string]: any },
+ mogrify: (doc: any) => any,
+ refresh: boolean = true,
+ tls: boolean | TlsProps = false
+) => {
+ const baseDoc: any = {
+ tcp: {
+ rtt: {
+ connect: {
+ us: 14687,
+ },
+ },
+ },
+ observer: {
+ geo: {
+ name: 'mpls',
+ location: '37.926868, -78.024902',
+ },
+ hostname: 'avc-x1e',
+ },
+ agent: {
+ hostname: 'avc-x1e',
+ id: '10730a1a-4cb7-45ce-8524-80c4820476ab',
+ type: 'heartbeat',
+ ephemeral_id: '0d9a8dc6-f604-49e3-86a0-d8f9d6f2cbad',
+ version: '8.0.0',
+ },
+ '@timestamp': new Date().toISOString(),
+ resolve: {
+ rtt: {
+ us: 350,
+ },
+ ip: '127.0.0.1',
+ },
+ ecs: {
+ version: '1.1.0',
+ },
+ host: {
+ name: 'avc-x1e',
+ },
+ http: {
+ rtt: {
+ response_header: {
+ us: 19349,
+ },
+ total: {
+ us: 48954,
+ },
+ write_request: {
+ us: 33,
+ },
+ content: {
+ us: 51,
+ },
+ validate: {
+ us: 19400,
+ },
+ },
+ response: {
+ status_code: 200,
+ body: {
+ bytes: 3,
+ hash: '27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf',
+ },
+ },
+ },
+ monitor: {
+ duration: {
+ us: 49347,
+ },
+ ip: '127.0.0.1',
+ id: monitorId,
+ check_group: uuid.v4(),
+ type: 'http',
+ status: 'up',
+ },
+ event: {
+ dataset: 'uptime',
+ },
+ url: {
+ path: '/pattern',
+ scheme: 'http',
+ port: 5678,
+ domain: 'localhost',
+ query: 'r=200x5,500x1',
+ full: 'http://localhost:5678/pattern?r=200x5,500x1',
+ },
+ };
+
+ if (tls) {
+ baseDoc.tls = makeTls(tls as any);
+ }
+
+ const doc = mogrify(merge(baseDoc, fields));
+
+ await es.index({
+ index: INDEX_NAME,
+ refresh,
+ body: doc,
+ });
+
+ return doc;
+};
diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts
new file mode 100644
index 0000000000000..3606462522024
--- /dev/null
+++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import moment from 'moment';
+import crypto from 'crypto';
+
+export interface TlsProps {
+ valid?: boolean;
+ commonName?: string;
+ expiry?: string;
+ sha256?: string;
+}
+
+type Props = TlsProps & boolean;
+
+// Note This is just a mock sha256 value, this doesn't actually generate actually sha 256 val
+export const getSha256 = () => {
+ return crypto
+ .randomBytes(64)
+ .toString('hex')
+ .toUpperCase();
+};
+
+export const makeTls = ({ valid = true, commonName = '*.elastic.co', expiry, sha256 }: Props) => {
+ const expiryDate =
+ expiry ??
+ moment()
+ .add(valid ? 2 : -2, 'months')
+ .toISOString();
+
+ return {
+ version: '1.3',
+ cipher: 'TLS-AES-128-GCM-SHA256',
+ certificate_not_valid_before: '2020-03-01T00:00:00.000Z',
+ certificate_not_valid_after: expiryDate,
+ server: {
+ x509: {
+ not_before: '2020-03-01T00:00:00.000Z',
+ not_after: '2020-05-30T12:00:00.000Z',
+ issuer: {
+ distinguished_name:
+ 'CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US',
+ common_name: 'DigiCert SHA2 High Assurance Server CA',
+ },
+ subject: {
+ common_name: commonName,
+ distinguished_name: 'CN=*.facebook.com,O=Facebook Inc.,L=Menlo Park,ST=California,C=US',
+ },
+ serial_number: '10043199409725537507026285099403602396',
+ signature_algorithm: 'SHA256-RSA',
+ public_key_algorithm: 'ECDSA',
+ public_key_curve: 'P-256',
+ },
+ hash: {
+ sha256: sha256 ?? '1a48f1db13c3bd1482ba1073441e74a1bb1308dc445c88749e0dc4f1889a88a4',
+ sha1: '23291c758d925b9f4bb3584de3763317e94c6ce9',
+ },
+ },
+ established: true,
+ rtt: {
+ handshake: {
+ us: 33103,
+ },
+ },
+ version_protocol: 'tls',
+ };
+};
diff --git a/x-pack/test/functional/apps/canvas/custom_elements.ts b/x-pack/test/functional/apps/canvas/custom_elements.ts
index d4e1702368879..3bd3749ec6935 100644
--- a/x-pack/test/functional/apps/canvas/custom_elements.ts
+++ b/x-pack/test/functional/apps/canvas/custom_elements.ts
@@ -40,8 +40,11 @@ export default function canvasCustomElementTest({
// find the first workpad element (a markdown element) and click it to select it
await testSubjects.click('canvasWorkpadPage > canvasWorkpadPageElementContent', 20000);
+ // click "Edit" menu
+ await testSubjects.click('canvasWorkpadEditMenuButton', 20000);
+
// click the "Save as new element" button
- await testSubjects.click('canvasSidebarHeader__saveElementButton', 20000);
+ await testSubjects.click('canvasWorkpadEditMenu__saveElementButton', 20000);
// fill out the custom element form and submit it
await PageObjects.canvas.fillOutCustomElementForm(
diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts
index be7a2faae6711..082008bccddd1 100644
--- a/x-pack/test/functional/apps/lens/smokescreen.ts
+++ b/x-pack/test/functional/apps/lens/smokescreen.ts
@@ -26,12 +26,12 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const filterBar = getService('filterBar');
- async function assertExpectedMetric() {
+ async function assertExpectedMetric(metricCount: string = '19,986') {
await PageObjects.lens.assertExactText(
'[data-test-subj="lns_metric_title"]',
'Maximum of bytes'
);
- await PageObjects.lens.assertExactText('[data-test-subj="lns_metric_value"]', '19,986');
+ await PageObjects.lens.assertExactText('[data-test-subj="lns_metric_value"]', metricCount);
}
async function assertExpectedTable() {
@@ -40,8 +40,12 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
'Maximum of bytes'
);
await PageObjects.lens.assertExactText(
- '[data-test-subj="lnsDataTable"] tbody .euiTableCellContent__text',
- '19,986'
+ '[data-test-subj="lnsDataTable"] [data-test-subj="lnsDataTableCellValue"]',
+ '19,985'
+ );
+ await PageObjects.lens.assertExactText(
+ '[data-test-subj="lnsDataTable"] [data-test-subj="lnsDataTableCellValueFilterable"]',
+ 'IN'
);
}
@@ -86,7 +90,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await assertExpectedMetric();
});
- it('click on the bar in XYChart adds proper filters/timerange', async () => {
+ it('click on the bar in XYChart adds proper filters/timerange in dashboard', async () => {
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.clickNewDashboard();
await dashboardAddPanel.clickOpenAddPanel();
@@ -102,15 +106,22 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
expect(hasIpFilter).to.be(true);
});
- it('should allow seamless transition to and from table view', async () => {
+ it('should allow seamless transition to and from table view and add a filter', async () => {
await PageObjects.visualize.gotoVisualizationLandingPage();
await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
await PageObjects.lens.goToTimeRange();
await assertExpectedMetric();
await PageObjects.lens.switchToVisualization('lnsChartSwitchPopover_lnsDatatable');
+ await PageObjects.lens.configureDimension({
+ dimension: '[data-test-subj="lnsDatatable_column"] [data-test-subj="lns-empty-dimension"]',
+ operation: 'terms',
+ field: 'geo.dest',
+ });
+ await PageObjects.lens.save('Artistpreviouslyknownaslens');
+ await find.clickByCssSelector('[data-test-subj="lensDatatableFilterOut"]');
await assertExpectedTable();
await PageObjects.lens.switchToVisualization('lnsChartSwitchPopover_lnsMetric');
- await assertExpectedMetric();
+ await assertExpectedMetric('19,985');
});
it('should allow creation of lens visualizations', async () => {
diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts
new file mode 100644
index 0000000000000..05967e0f3acaf
--- /dev/null
+++ b/x-pack/test/functional/apps/uptime/certificates.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 { FtrProviderContext } from '../../ftr_provider_context';
+import { makeCheck } from '../../../api_integration/apis/uptime/rest/helper/make_checks';
+import { getSha256 } from '../../../api_integration/apis/uptime/rest/helper/make_tls';
+
+export default ({ getPageObjects, getService }: FtrProviderContext) => {
+ const { uptime } = getPageObjects(['uptime']);
+ const uptimeService = getService('uptime');
+
+ const es = getService('es');
+
+ describe('certificate page', function() {
+ before(async () => {
+ await uptime.goToRoot(true);
+ });
+
+ beforeEach(async () => {
+ await makeCheck({ es, tls: true });
+ await uptimeService.navigation.refreshApp();
+ });
+
+ it('can navigate to cert page', async () => {
+ await uptimeService.navigation.refreshApp();
+ await uptimeService.cert.hasViewCertButton();
+ await uptimeService.navigation.goToCertificates();
+ });
+
+ it('displays certificates', async () => {
+ await uptimeService.cert.hasCertificates();
+ });
+
+ it('displays specific certificates', async () => {
+ const certId = getSha256();
+ const { monitorId } = await makeCheck({
+ es,
+ tls: {
+ sha256: certId,
+ },
+ });
+
+ await uptimeService.navigation.refreshApp();
+ await uptimeService.cert.certificateExists({ certId, monitorId });
+ });
+
+ it('performs search against monitor id', async () => {
+ const certId = getSha256();
+ const { monitorId } = await makeCheck({
+ es,
+ tls: {
+ sha256: certId,
+ },
+ });
+ await uptimeService.navigation.refreshApp();
+ await uptimeService.cert.searchIsWorking(monitorId);
+ });
+ });
+};
diff --git a/x-pack/test/functional/apps/uptime/index.ts b/x-pack/test/functional/apps/uptime/index.ts
index f47214dc2ad2f..6ecd39f696312 100644
--- a/x-pack/test/functional/apps/uptime/index.ts
+++ b/x-pack/test/functional/apps/uptime/index.ts
@@ -53,6 +53,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => {
loadTestFile(require.resolve('./locations'));
loadTestFile(require.resolve('./settings'));
+ loadTestFile(require.resolve('./certificates'));
});
describe('with real-world data', () => {
before(async () => {
diff --git a/x-pack/test/functional/es_archives/uptime/blank/mappings.json b/x-pack/test/functional/es_archives/uptime/blank/mappings.json
index fff4ef47bce0c..dd7f5cb9aa778 100644
--- a/x-pack/test/functional/es_archives/uptime/blank/mappings.json
+++ b/x-pack/test/functional/es_archives/uptime/blank/mappings.json
@@ -1665,6 +1665,13 @@
},
"id": {
"type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
"ignore_above": 1024
},
"ip": {
@@ -1672,6 +1679,13 @@
},
"name": {
"type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
"ignore_above": 1024
},
"status": {
@@ -3079,10 +3093,21 @@
},
"x509": {
"properties": {
+ "alternative_names": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"issuer": {
"properties": {
"common_name": {
"type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
"ignore_above": 1024
},
"distinguished_name": {
@@ -3092,14 +3117,16 @@
}
},
"not_after": {
- "type": "keyword",
- "ignore_above": 1024
+ "type": "date"
},
"not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
"type": "keyword",
"ignore_above": 1024
},
- "public_key_algorithm": {
+ "public_key_curve": {
"type": "keyword",
"ignore_above": 1024
},
@@ -3121,6 +3148,13 @@
"properties": {
"common_name": {
"type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
"ignore_above": 1024
},
"distinguished_name": {
@@ -3128,6 +3162,10 @@
"ignore_above": 1024
}
}
+ },
+ "version_number": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
}
diff --git a/x-pack/test/functional/es_archives/uptime/full_heartbeat/mappings.json b/x-pack/test/functional/es_archives/uptime/full_heartbeat/mappings.json
index 2b6002ddb3fab..97b72510da286 100644
--- a/x-pack/test/functional/es_archives/uptime/full_heartbeat/mappings.json
+++ b/x-pack/test/functional/es_archives/uptime/full_heartbeat/mappings.json
@@ -12,79 +12,115 @@
"beat": "heartbeat",
"version": "8.0.0"
},
- "date_detection": false,
"dynamic_templates": [
{
"labels": {
+ "path_match": "labels.*",
+ "match_mapping_type": "string",
"mapping": {
"type": "keyword"
- },
- "match_mapping_type": "string",
- "path_match": "labels.*"
+ }
}
},
{
"container.labels": {
+ "path_match": "container.labels.*",
+ "match_mapping_type": "string",
"mapping": {
"type": "keyword"
- },
- "match_mapping_type": "string",
- "path_match": "container.labels.*"
+ }
}
},
{
"dns.answers": {
+ "path_match": "dns.answers.*",
+ "match_mapping_type": "string",
"mapping": {
"type": "keyword"
- },
+ }
+ }
+ },
+ {
+ "log.syslog": {
+ "path_match": "log.syslog.*",
"match_mapping_type": "string",
- "path_match": "dns.answers.*"
+ "mapping": {
+ "type": "keyword"
+ }
}
},
{
- "fields": {
+ "network.inner": {
+ "path_match": "network.inner.*",
+ "match_mapping_type": "string",
"mapping": {
"type": "keyword"
- },
+ }
+ }
+ },
+ {
+ "observer.egress": {
+ "path_match": "observer.egress.*",
"match_mapping_type": "string",
- "path_match": "fields.*"
+ "mapping": {
+ "type": "keyword"
+ }
}
},
{
- "docker.container.labels": {
+ "observer.ingress": {
+ "path_match": "observer.ingress.*",
+ "match_mapping_type": "string",
"mapping": {
"type": "keyword"
- },
+ }
+ }
+ },
+ {
+ "fields": {
+ "path_match": "fields.*",
+ "match_mapping_type": "string",
+ "mapping": {
+ "type": "keyword"
+ }
+ }
+ },
+ {
+ "docker.container.labels": {
+ "path_match": "docker.container.labels.*",
"match_mapping_type": "string",
- "path_match": "docker.container.labels.*"
+ "mapping": {
+ "type": "keyword"
+ }
}
},
{
"kubernetes.labels.*": {
+ "path_match": "kubernetes.labels.*",
"mapping": {
"type": "keyword"
- },
- "path_match": "kubernetes.labels.*"
+ }
}
},
{
"kubernetes.annotations.*": {
+ "path_match": "kubernetes.annotations.*",
"mapping": {
"type": "keyword"
- },
- "path_match": "kubernetes.annotations.*"
+ }
}
},
{
"strings_as_keyword": {
+ "match_mapping_type": "string",
"mapping": {
"ignore_above": 1024,
"type": "keyword"
- },
- "match_mapping_type": "string"
+ }
}
}
],
+ "date_detection": false,
"properties": {
"@timestamp": {
"type": "date"
@@ -92,28 +128,28 @@
"agent": {
"properties": {
"ephemeral_id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"hostname": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -125,8 +161,14 @@
"organization": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -135,8 +177,8 @@
"client": {
"properties": {
"address": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"as": {
"properties": {
@@ -146,8 +188,14 @@
"organization": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -157,41 +205,41 @@
"type": "long"
},
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -199,8 +247,8 @@
"type": "ip"
},
"mac": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"nat": {
"properties": {
@@ -218,43 +266,67 @@
"port": {
"type": "long"
},
+ "registered_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "top_level_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"user": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"email": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -265,76 +337,97 @@
"account": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"availability_zone": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"image": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"instance": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"machine": {
"properties": {
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"project": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"provider": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "code_signature": {
+ "properties": {
+ "exists": {
+ "type": "boolean"
+ },
+ "status": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subject_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
}
}
},
"container": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"image": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"tag": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -342,20 +435,20 @@
"type": "object"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"runtime": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"destination": {
"properties": {
"address": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"as": {
"properties": {
@@ -365,8 +458,14 @@
"organization": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -376,41 +475,41 @@
"type": "long"
},
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -418,8 +517,8 @@
"type": "ip"
},
"mac": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"nat": {
"properties": {
@@ -437,43 +536,144 @@
"port": {
"type": "long"
},
+ "registered_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "top_level_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"user": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"email": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ }
+ }
+ }
+ }
+ },
+ "dll": {
+ "properties": {
+ "code_signature": {
+ "properties": {
+ "exists": {
+ "type": "boolean"
+ },
+ "status": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subject_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha1": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha256": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha512": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "path": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "pe": {
+ "properties": {
+ "company": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "file_version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "original_file_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "product": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
}
@@ -484,55 +684,63 @@
"answers": {
"properties": {
"class": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"data": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"ttl": {
"type": "long"
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"header_flags": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"op_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"question": {
"properties": {
"class": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"registered_domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subdomain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "top_level_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -540,12 +748,12 @@
"type": "ip"
},
"response_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -563,51 +771,61 @@
"ecs": {
"properties": {
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"error": {
"properties": {
"code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"message": {
- "norms": false,
- "type": "text"
+ "type": "text",
+ "norms": false
+ },
+ "stack_trace": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"event": {
"properties": {
"action": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"category": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"created": {
"type": "date"
},
"dataset": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"duration": {
"type": "long"
@@ -616,32 +834,39 @@
"type": "date"
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "ingested": {
+ "type": "date"
},
"kind": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"module": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"original": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"outcome": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"provider": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "reference": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"risk_score": {
"type": "float"
@@ -659,12 +884,16 @@
"type": "date"
},
"timezone": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "url": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -676,6 +905,31 @@
"accessed": {
"type": "date"
},
+ "attributes": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "code_signature": {
+ "properties": {
+ "exists": {
+ "type": "boolean"
+ },
+ "status": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subject_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
"created": {
"type": "date"
},
@@ -683,254 +937,318 @@
"type": "date"
},
"device": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"directory": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "drive_letter": {
+ "type": "keyword",
+ "ignore_above": 1
},
"extension": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"gid": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"group": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"hash": {
"properties": {
"md5": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"sha1": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"sha256": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"sha512": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"inode": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "mime_type": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"mode": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"mtime": {
"type": "date"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"owner": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"path": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "pe": {
+ "properties": {
+ "company": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "file_version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "original_file_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "product": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
},
"size": {
"type": "long"
},
"target_path": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"uid": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
"properties": {
"md5": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"sha1": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"sha256": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"sha512": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"host": {
"properties": {
"architecture": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"containerized": {
"type": "boolean"
},
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hostname": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"ip": {
"type": "ip"
},
"mac": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"os": {
"properties": {
"build": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"codename": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"family": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"kernel": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"platform": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"uptime": {
"type": "long"
@@ -938,40 +1256,56 @@
"user": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"email": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -987,8 +1321,14 @@
"type": "long"
},
"content": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
},
@@ -996,12 +1336,12 @@
"type": "long"
},
"method": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"referrer": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1013,18 +1353,28 @@
"type": "long"
},
"content": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"bytes": {
"type": "long"
},
+ "redirects": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"status_code": {
"type": "long"
}
@@ -1077,8 +1427,8 @@
}
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1096,17 +1446,33 @@
}
}
},
+ "interface": {
+ "properties": {
+ "alias": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
"jolokia": {
"properties": {
"agent": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1116,22 +1482,22 @@
"server": {
"properties": {
"product": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"vendor": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"url": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1147,20 +1513,20 @@
"container": {
"properties": {
"image": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"deployment": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1172,42 +1538,42 @@
}
},
"namespace": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"node": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"pod": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"uid": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"replicaset": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"statefulset": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
}
@@ -1219,28 +1585,76 @@
"log": {
"properties": {
"level": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"logger": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "origin": {
+ "properties": {
+ "file": {
+ "properties": {
+ "line": {
+ "type": "long"
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "function": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
},
"original": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "syslog": {
+ "properties": {
+ "facility": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "priority": {
+ "type": "long"
+ },
+ "severity": {
+ "properties": {
+ "code": {
+ "type": "long"
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ }
+ }
}
}
},
"message": {
- "norms": false,
- "type": "text"
+ "type": "text",
+ "norms": false
},
"monitor": {
"properties": {
"check_group": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"duration": {
"properties": {
@@ -1250,238 +1664,683 @@
}
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
+ "ignore_above": 1024
},
"ip": {
"type": "ip"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
+ "ignore_above": 1024
},
"status": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "timespan": {
+ "type": "date_range"
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"network": {
"properties": {
"application": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"bytes": {
"type": "long"
},
"community_id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"direction": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"forwarded_ip": {
"type": "ip"
},
"iana_number": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "inner": {
+ "properties": {
+ "vlan": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ }
+ }
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"packets": {
"type": "long"
},
"protocol": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"transport": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
}
}
},
"observer": {
"properties": {
+ "egress": {
+ "properties": {
+ "interface": {
+ "properties": {
+ "alias": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "zone": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hostname": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "ingress": {
+ "properties": {
+ "interface": {
+ "properties": {
+ "alias": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "zone": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
},
"ip": {
"type": "ip"
},
"mac": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"os": {
"properties": {
"family": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"kernel": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"platform": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
+ "product": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"serial_number": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"vendor": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"organization": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
},
"os": {
"properties": {
"family": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"kernel": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"platform": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "package": {
+ "properties": {
+ "architecture": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "build_version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "checksum": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "install_scope": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "installed": {
+ "type": "date"
+ },
+ "license": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "path": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "reference": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "size": {
+ "type": "long"
+ },
+ "type": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "pe": {
+ "properties": {
+ "company": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "file_version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "original_file_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "product": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"process": {
"properties": {
"args": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
- "executable": {
- "ignore_above": 1024,
- "type": "keyword"
+ "args_count": {
+ "type": "long"
},
- "hash": {
+ "code_signature": {
"properties": {
- "md5": {
- "ignore_above": 1024,
- "type": "keyword"
+ "exists": {
+ "type": "boolean"
},
- "sha1": {
- "ignore_above": 1024,
- "type": "keyword"
+ "status": {
+ "type": "keyword",
+ "ignore_above": 1024
},
- "sha256": {
- "ignore_above": 1024,
- "type": "keyword"
+ "subject_name": {
+ "type": "keyword",
+ "ignore_above": 1024
},
- "sha512": {
- "ignore_above": 1024,
- "type": "keyword"
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "command_line": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "entity_id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "executable": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha1": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha256": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha512": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "parent": {
+ "properties": {
+ "args": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "args_count": {
+ "type": "long"
+ },
+ "code_signature": {
+ "properties": {
+ "exists": {
+ "type": "boolean"
+ },
+ "status": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subject_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "trusted": {
+ "type": "boolean"
+ },
+ "valid": {
+ "type": "boolean"
+ }
+ }
+ },
+ "command_line": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "entity_id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "executable": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "exit_code": {
+ "type": "long"
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha1": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha256": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha512": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "name": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "pgid": {
+ "type": "long"
+ },
+ "pid": {
+ "type": "long"
+ },
+ "ppid": {
+ "type": "long"
+ },
+ "start": {
+ "type": "date"
+ },
+ "thread": {
+ "properties": {
+ "id": {
+ "type": "long"
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "title": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "uptime": {
+ "type": "long"
+ },
+ "working_directory": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ }
+ }
+ },
+ "pe": {
+ "properties": {
+ "company": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "file_version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "original_file_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "product": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
},
"pgid": {
"type": "long"
@@ -1501,28 +2360,84 @@
"type": "long"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"title": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"uptime": {
"type": "long"
},
"working_directory": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ }
+ }
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "bytes": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "strings": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "type": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "hive": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "key": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "path": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "value": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"related": {
"properties": {
+ "hash": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"ip": {
"type": "ip"
+ },
+ "user": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1540,11 +2455,55 @@
}
}
},
+ "rule": {
+ "properties": {
+ "author": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "category": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "license": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "reference": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "ruleset": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "uuid": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
"server": {
"properties": {
"address": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"as": {
"properties": {
@@ -1554,8 +2513,14 @@
"organization": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -1565,41 +2530,41 @@
"type": "long"
},
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1607,8 +2572,8 @@
"type": "ip"
},
"mac": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"nat": {
"properties": {
@@ -1626,43 +2591,67 @@
"port": {
"type": "long"
},
+ "registered_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "top_level_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"user": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"email": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -1671,28 +2660,36 @@
"service": {
"properties": {
"ephemeral_id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "node": {
+ "properties": {
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
},
"state": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"type": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1714,8 +2711,8 @@
"source": {
"properties": {
"address": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"as": {
"properties": {
@@ -1725,8 +2722,14 @@
"organization": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -1736,41 +2739,41 @@
"type": "long"
},
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"geo": {
"properties": {
"city_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"continent_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"country_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"location": {
"type": "geo_point"
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_iso_code": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"region_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1778,8 +2781,8 @@
"type": "ip"
},
"mac": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"nat": {
"properties": {
@@ -1797,43 +2800,67 @@
"port": {
"type": "long"
},
+ "registered_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "top_level_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"user": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"email": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
}
@@ -1850,8 +2877,8 @@
}
},
"tags": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"tcp": {
"properties": {
@@ -1875,11 +2902,57 @@
}
}
},
+ "threat": {
+ "properties": {
+ "framework": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "tactic": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "reference": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "technique": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "reference": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ }
+ }
+ },
"timeseries": {
"properties": {
"instance": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1891,6 +2964,78 @@
"certificate_not_valid_before": {
"type": "date"
},
+ "cipher": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "client": {
+ "properties": {
+ "certificate": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "certificate_chain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha1": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha256": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "issuer": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "ja3": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "server_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subject": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "supported_ciphers": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "curve": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "established": {
+ "type": "boolean"
+ },
+ "next_protocol": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "resumed": {
+ "type": "boolean"
+ },
"rtt": {
"properties": {
"handshake": {
@@ -1901,6 +3046,138 @@
}
}
}
+ },
+ "server": {
+ "properties": {
+ "certificate": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "certificate_chain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "hash": {
+ "properties": {
+ "md5": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha1": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "sha256": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "issuer": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "ja3s": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "subject": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "issuer": {
+ "properties": {
+ "common_name": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
+ "ignore_above": 1024
+ },
+ "distinguished_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "not_after": {
+ "type": "date"
+ },
+ "not_before": {
+ "type": "date"
+ },
+ "public_key_algorithm": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "public_key_curve": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "public_key_exponent": {
+ "type": "long"
+ },
+ "public_key_size": {
+ "type": "long"
+ },
+ "serial_number": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "signature_algorithm": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "subject": {
+ "properties": {
+ "common_name": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false,
+ "analyzer": "simple"
+ }
+ },
+ "ignore_above": 1024
+ },
+ "distinguished_name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "version_number": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ }
+ }
+ },
+ "version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "version_protocol": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
@@ -1909,16 +3186,16 @@
"trace": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"transaction": {
"properties": {
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
}
@@ -1927,83 +3204,123 @@
"url": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "extension": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"fragment": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"original": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"password": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"path": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"port": {
"type": "long"
},
"query": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "registered_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"scheme": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "top_level_domain": {
+ "type": "keyword",
+ "ignore_above": 1024
},
"username": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"user": {
"properties": {
"domain": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"email": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full_name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"group": {
"properties": {
+ "domain": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"hash": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"id": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
}
}
},
@@ -2012,50 +3329,147 @@
"device": {
"properties": {
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"original": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"os": {
"properties": {
"family": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"full": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"kernel": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"name": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
},
"platform": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
}
}
},
"version": {
- "ignore_above": 1024,
- "type": "keyword"
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "vlan": {
+ "properties": {
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "name": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "vulnerability": {
+ "properties": {
+ "category": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "classification": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "description": {
+ "type": "keyword",
+ "fields": {
+ "text": {
+ "type": "text",
+ "norms": false
+ }
+ },
+ "ignore_above": 1024
+ },
+ "enumeration": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "reference": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "report_id": {
+ "type": "keyword",
+ "ignore_above": 1024
+ },
+ "scanner": {
+ "properties": {
+ "vendor": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "score": {
+ "properties": {
+ "base": {
+ "type": "float"
+ },
+ "environmental": {
+ "type": "float"
+ },
+ "temporal": {
+ "type": "float"
+ },
+ "version": {
+ "type": "keyword",
+ "ignore_above": 1024
+ }
+ }
+ },
+ "severity": {
+ "type": "keyword",
+ "ignore_above": 1024
}
}
}
diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts
index 94ad393ead3a3..ce36385a2f9df 100644
--- a/x-pack/test/functional/page_objects/canvas_page.ts
+++ b/x-pack/test/functional/page_objects/canvas_page.ts
@@ -65,14 +65,12 @@ export function CanvasPageProvider({ getService }: FtrProviderContext) {
async expectNoAddElementButton() {
// Ensure page is fully loaded first by waiting for the refresh button
- const refreshPopoverExists = await find.existsByCssSelector('#auto-refresh-popover', 20000);
+ const refreshPopoverExists = await testSubjects.exists('canvas-refresh-control', {
+ timeout: 20000,
+ });
expect(refreshPopoverExists).to.be(true);
- const addElementButtonExists = await find.existsByCssSelector(
- 'button[data-test-subj=add-element-button]',
- 10 // don't need much of a wait at all here, because we already waited for refresh button above
- );
- expect(addElementButtonExists).to.be(false);
+ await testSubjects.missingOrFail('add-element-button');
},
};
}
diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts
index 0ebcb5c87deee..53c89eadeced7 100644
--- a/x-pack/test/functional/page_objects/uptime_page.ts
+++ b/x-pack/test/functional/page_objects/uptime_page.ts
@@ -13,8 +13,11 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo
const retry = getService('retry');
return new (class UptimePage {
- public async goToRoot() {
+ public async goToRoot(refresh?: boolean) {
await navigation.goToUptime();
+ if (refresh) {
+ await navigation.refreshApp();
+ }
}
public async setDateRange(start: string, end: string) {
diff --git a/x-pack/test/functional/services/uptime/certificates.ts b/x-pack/test/functional/services/uptime/certificates.ts
new file mode 100644
index 0000000000000..fb7cb6191b0ae
--- /dev/null
+++ b/x-pack/test/functional/services/uptime/certificates.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { FtrProviderContext } from '../../ftr_provider_context';
+
+export function UptimeCertProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+
+ const changeSearchField = async (text: string) => {
+ const input = await testSubjects.find('uptimeCertSearch');
+ await input.clearValueWithKeyboard();
+ await input.type(text);
+ };
+
+ return {
+ async hasViewCertButton() {
+ return retry.tryForTime(15000, async () => {
+ await testSubjects.existOrFail('uptimeCertificatesLink');
+ });
+ },
+ async certificateExists(cert: { certId: string; monitorId: string }) {
+ return retry.tryForTime(15000, async () => {
+ await testSubjects.existOrFail(cert.certId);
+ await testSubjects.existOrFail('monitor-page-link-' + cert.monitorId);
+ });
+ },
+ async hasCertificates(expectedTotal?: number) {
+ return retry.tryForTime(15000, async () => {
+ const totalCerts = await testSubjects.getVisibleText('uptimeCertTotal');
+ if (expectedTotal) {
+ expect(Number(totalCerts) === expectedTotal).to.eql(true);
+ } else {
+ expect(Number(totalCerts) > 0).to.eql(true);
+ }
+ });
+ },
+ async searchIsWorking(monId: string) {
+ const self = this;
+ return retry.tryForTime(15000, async () => {
+ await changeSearchField(monId);
+ await self.hasCertificates(1);
+ });
+ },
+ };
+}
diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts
index 36a5d7c9702f8..c17fa3a5f6339 100644
--- a/x-pack/test/functional/services/uptime/navigation.ts
+++ b/x-pack/test/functional/services/uptime/navigation.ts
@@ -25,9 +25,13 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv
});
};
+ const refreshApp = async () => {
+ await testSubjects.click('superDatePickerApplyTimeButton');
+ };
+
return {
async refreshApp() {
- await testSubjects.click('superDatePickerApplyTimeButton');
+ await refreshApp();
},
async goToUptime() {
@@ -60,6 +64,13 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv
}
},
+ goToCertificates: async () => {
+ return retry.tryForTime(30 * 1000, async () => {
+ await testSubjects.click('uptimeCertificatesLink');
+ await testSubjects.existOrFail('uptimeCertificatesPage');
+ });
+ },
+
async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) {
await PageObjects.timePicker.setAbsoluteRange(dateStart, dateEnd);
await this.goToMonitor(monitorId);
diff --git a/x-pack/test/functional/services/uptime/uptime.ts b/x-pack/test/functional/services/uptime/uptime.ts
index 601feb6b0646e..8d36ba4bf6cfd 100644
--- a/x-pack/test/functional/services/uptime/uptime.ts
+++ b/x-pack/test/functional/services/uptime/uptime.ts
@@ -12,6 +12,7 @@ import { UptimeMonitorProvider } from './monitor';
import { UptimeNavigationProvider } from './navigation';
import { UptimeAlertsProvider } from './alerts';
import { UptimeMLAnomalyProvider } from './ml_anomaly';
+import { UptimeCertProvider } from './certificates';
export function UptimeProvider(context: FtrProviderContext) {
const common = UptimeCommonProvider(context);
@@ -20,6 +21,7 @@ export function UptimeProvider(context: FtrProviderContext) {
const navigation = UptimeNavigationProvider(context);
const alerts = UptimeAlertsProvider(context);
const ml = UptimeMLAnomalyProvider(context);
+ const cert = UptimeCertProvider(context);
return {
common,
@@ -28,5 +30,6 @@ export function UptimeProvider(context: FtrProviderContext) {
navigation,
alerts,
ml,
+ cert,
};
}
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
index 562f64656319e..1facc05bc186d 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
@@ -21,10 +21,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('Connectors', function() {
before(async () => {
await alerting.actions.createAction({
- name: `server-log-${Date.now()}`,
- actionTypeId: '.server-log',
+ name: `slack-${Date.now()}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
});
await pageObjects.common.navigateToApp('triggersActions');
@@ -36,12 +38,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
- await testSubjects.click('.server-log-card');
+ await testSubjects.click('.slack-card');
+
+ await testSubjects.setValue('nameInput', connectorName);
- const nameInput = await testSubjects.find('nameInput');
- await nameInput.click();
- await nameInput.clearValue();
- await nameInput.type(connectorName);
+ await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)');
@@ -54,7 +55,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(searchResults).to.eql([
{
name: connectorName,
- actionType: 'Server log',
+ actionType: 'Slack',
referencedByCount: '0',
},
]);
@@ -66,12 +67,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
- await testSubjects.click('.server-log-card');
+ await testSubjects.click('.slack-card');
+
+ await testSubjects.setValue('nameInput', connectorName);
- const nameInput = await testSubjects.find('nameInput');
- await nameInput.click();
- await nameInput.clearValue();
- await nameInput.type(connectorName);
+ await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)');
@@ -84,10 +84,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await find.clickByCssSelector('[data-test-subj="connectorsTableCell-name"] button');
- const nameInputToUpdate = await testSubjects.find('nameInput');
- await nameInputToUpdate.click();
- await nameInputToUpdate.clearValue();
- await nameInputToUpdate.type(updatedConnectorName);
+ await testSubjects.setValue('nameInput', updatedConnectorName);
+
+ await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
await find.clickByCssSelector('[data-test-subj="saveEditedActionButton"]:not(disabled)');
@@ -100,7 +99,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(searchResultsAfterEdit).to.eql([
{
name: updatedConnectorName,
- actionType: 'Server log',
+ actionType: 'Slack',
referencedByCount: '0',
},
]);
@@ -110,12 +109,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
async function createConnector(connectorName: string) {
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
- await testSubjects.click('.server-log-card');
+ await testSubjects.click('.slack-card');
+
+ await testSubjects.setValue('nameInput', connectorName);
- const nameInput = await testSubjects.find('nameInput');
- await nameInput.click();
- await nameInput.clearValue();
- await nameInput.type(connectorName);
+ await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)');
await pageObjects.common.closeToast();
@@ -148,12 +146,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
async function createConnector(connectorName: string) {
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
- await testSubjects.click('.server-log-card');
+ await testSubjects.click('.slack-card');
+
+ await testSubjects.setValue('nameInput', connectorName);
- const nameInput = await testSubjects.find('nameInput');
- await nameInput.click();
- await nameInput.clearValue();
- await nameInput.type(connectorName);
+ await testSubjects.setValue('slackWebhookUrlInput', 'https://test');
await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)');
await pageObjects.common.closeToast();
@@ -186,7 +183,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('should not be able to delete a preconfigured connector', async () => {
- const preconfiguredConnectorName = 'xyz';
+ const preconfiguredConnectorName = 'Serverlog';
await pageObjects.triggersActionsUI.searchConnectors(preconfiguredConnectorName);
const searchResults = await pageObjects.triggersActionsUI.getConnectorsList();
@@ -197,7 +194,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('should not be able to edit a preconfigured connector', async () => {
- const preconfiguredConnectorName = 'xyz';
+ const preconfiguredConnectorName = 'xyztest';
await pageObjects.triggersActionsUI.searchConnectors(preconfiguredConnectorName);
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
index d0ce18bbc1c54..7970c9b24427e 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
@@ -27,16 +27,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const actions = await Promise.all([
alerting.actions.createAction({
- name: `server-log-${testRunUuid}-${0}`,
- actionTypeId: '.server-log',
+ name: `slack-${testRunUuid}-${0}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
}),
alerting.actions.createAction({
- name: `server-log-${testRunUuid}-${1}`,
- actionTypeId: '.server-log',
+ name: `slack-${testRunUuid}-${1}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
}),
]);
@@ -72,7 +76,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(alertType).to.be(`Always Firing`);
const { actionType, actionCount } = await pageObjects.alertDetailsUI.getActionsLabels();
- expect(actionType).to.be(`Server log`);
+ expect(actionType).to.be(`Slack`);
expect(actionCount).to.be(`+1`);
});
@@ -168,7 +172,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const alert = await alerting.alerts.createAlertWithActions(
testRunUuid,
'.index-threshold',
- params
+ params,
+ [
+ {
+ group: 'threshold met',
+ id: 'my-server-log',
+ params: { level: 'info', message: ' {{context.message}}' },
+ },
+ ]
);
// refresh to see alert
await browser.refresh();
@@ -183,6 +194,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const editButton = await testSubjects.find('openEditAlertFlyoutButton');
await editButton.click();
+ expect(await testSubjects.exists('hasActionsDisabled')).to.eql(false);
const updatedAlertName = `Changed Alert Name ${uuid.v4()}`;
await testSubjects.setValue('alertNameInput', updatedAlertName, {
@@ -197,6 +209,53 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const headingText = await pageObjects.alertDetailsUI.getHeadingText();
expect(headingText).to.be(updatedAlertName);
});
+
+ it('should reset alert when canceling an edit', async () => {
+ await pageObjects.common.navigateToApp('triggersActions');
+ const params = {
+ aggType: 'count',
+ termSize: 5,
+ thresholdComparator: '>',
+ timeWindowSize: 5,
+ timeWindowUnit: 'm',
+ groupBy: 'all',
+ threshold: [1000, 5000],
+ index: ['.kibana_1'],
+ timeField: 'alert',
+ };
+ const alert = await alerting.alerts.createAlertWithActions(
+ testRunUuid,
+ '.index-threshold',
+ params
+ );
+ // refresh to see alert
+ await browser.refresh();
+
+ await pageObjects.header.waitUntilLoadingHasFinished();
+
+ // Verify content
+ await testSubjects.existOrFail('alertsList');
+
+ // click on first alert
+ await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
+
+ const editButton = await testSubjects.find('openEditAlertFlyoutButton');
+ await editButton.click();
+
+ const updatedAlertName = `Changed Alert Name ${uuid.v4()}`;
+ await testSubjects.setValue('alertNameInput', updatedAlertName, {
+ clearWithKeyboard: true,
+ });
+
+ await testSubjects.click('cancelSaveEditedAlertButton');
+ await find.waitForDeletedByCssSelector('[data-test-subj="cancelSaveEditedAlertButton"]');
+
+ await editButton.click();
+
+ const nameInputAfterCancel = await testSubjects.find('alertNameInput');
+ const textAfterCancel = await nameInputAfterCancel.getAttribute('value');
+ expect(textAfterCancel).to.eql(alert.name);
+ });
});
describe('View In App', function() {
@@ -257,16 +316,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const actions = await Promise.all([
alerting.actions.createAction({
- name: `server-log-${testRunUuid}-${0}`,
- actionTypeId: '.server-log',
+ name: `slack-${testRunUuid}-${0}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
}),
alerting.actions.createAction({
- name: `server-log-${testRunUuid}-${1}`,
- actionTypeId: '.server-log',
+ name: `slack-${testRunUuid}-${1}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
}),
]);
@@ -469,16 +532,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const actions = await Promise.all([
alerting.actions.createAction({
- name: `server-log-${testRunUuid}-${0}`,
- actionTypeId: '.server-log',
+ name: `slack-${testRunUuid}-${0}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
}),
alerting.actions.createAction({
- name: `server-log-${testRunUuid}-${1}`,
- actionTypeId: '.server-log',
+ name: `slack-${testRunUuid}-${1}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
}),
]);
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
index f049406b639c7..2edab1b164a1b 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
@@ -59,10 +59,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('navigates to an alert details page', async () => {
const action = await alerting.actions.createAction({
- name: `server-log-${Date.now()}`,
- actionTypeId: '.server-log',
+ name: `Slack-${Date.now()}`,
+ actionTypeId: '.slack',
config: {},
- secrets: {},
+ secrets: {
+ webhookUrl: 'https://test',
+ },
});
const alert = await alerting.alerts.createAlwaysFiringWithAction(
diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts
index 71b22a336f6b9..ef2270fb97745 100644
--- a/x-pack/test/functional_with_es_ssl/config.ts
+++ b/x-pack/test/functional_with_es_ssl/config.ts
@@ -10,6 +10,21 @@ import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
import { services } from './services';
import { pageObjects } from './page_objects';
+// .server-log is specifically not enabled
+const enabledActionTypes = [
+ '.email',
+ '.index',
+ '.pagerduty',
+ '.servicenow',
+ '.slack',
+ '.webhook',
+ 'test.authorization',
+ 'test.failing',
+ 'test.index-record',
+ 'test.noop',
+ 'test.rate-limit',
+];
+
// eslint-disable-next-line import/no-default-export
export default async function({ readConfigFile }: FtrConfigProviderContext) {
const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js'));
@@ -50,15 +65,21 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
`--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
`--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`,
+ `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
`--xpack.actions.preconfigured=${JSON.stringify([
{
id: 'my-slack1',
actionTypeId: '.slack',
- name: 'Slack#xyz',
+ name: 'Slack#xyztest',
config: {
webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz',
},
},
+ {
+ id: 'my-server-log',
+ actionTypeId: '.server-log',
+ name: 'Serverlog#xyz',
+ },
])}`,
],
},