From b7bba213db0504cca89173535014d15c54dcdc50 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 27 Aug 2019 08:44:30 +0200 Subject: [PATCH 01/66] De-angularize DocViewer table layout (#43240) * Convert component to React * EUI-ficate buttons, warnings and collapse button * Add jest tests --- .../index_patterns/format_hit.ts | 19 +- .../kbn_doc_views/public/views/table.html | 102 ------- .../kbn_doc_views/public/views/table.js | 77 ------ .../trust_as_html.js => views/table.ts} | 16 +- .../public/views/table/table.test.tsx | 260 ++++++++++++++++++ .../public/views/table/table.tsx | 91 ++++++ .../public/views/table/table_helper.test.ts | 177 ++++++++++++ .../public/views/table/table_helper.tsx | 85 ++++++ .../public/views/table/table_row.tsx | 102 +++++++ .../views/table/table_row_btn_collapse.tsx | 44 +++ .../views/table/table_row_btn_filter_add.tsx | 57 ++++ .../table/table_row_btn_filter_exists.tsx | 69 +++++ .../table/table_row_btn_filter_remove.tsx | 57 ++++ .../table/table_row_btn_toggle_column.tsx | 67 +++++ .../views/table/table_row_icon_no_mapping.tsx | 44 +++ .../views/table/table_row_icon_underscore.tsx | 51 ++++ .../kibana/public/doc_viewer/_doc_viewer.scss | 29 +- .../public/doc_viewer/doc_viewer_tab.tsx | 8 +- .../directives/field_name/field_name.tsx | 4 +- .../ui/public/registry/doc_views_types.ts | 22 +- 20 files changed, 1176 insertions(+), 205 deletions(-) delete mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table.html delete mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table.js rename src/legacy/core_plugins/kbn_doc_views/public/{filters/trust_as_html.js => views/table.ts} (72%) create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_collapse.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_add.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_exists.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_remove.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_toggle_column.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_no_mapping.tsx create mode 100644 src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_underscore.tsx diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts index 28797c28db834..39101ef36ca8d 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/format_hit.ts @@ -26,17 +26,28 @@ const partialFormattedCache = new WeakMap(); // Takes a hit, merges it with any stored/scripted fields, and with the metaFields // returns a formatted version export function formatHitProvider(indexPattern: IndexPattern, defaultFormat: any) { - function convert(hit: Record, val: any, fieldName: string) { + function convert(hit: Record, val: any, fieldName: string, type: string = 'html') { const field = indexPattern.fields.byName[fieldName]; - if (!field) return defaultFormat.convert(val, 'html'); + if (!field) return defaultFormat.convert(val, type); const parsedUrl = { origin: window.location.origin, pathname: window.location.pathname, }; - return field.format.getConverterFor('html')(val, field, hit, parsedUrl); + return field.format.getConverterFor(type)(val, field, hit, parsedUrl); } - function formatHit(hit: Record) { + function formatHit(hit: Record, type: string = 'html') { + if (type === 'text') { + // formatHit of type text is for react components to get rid of + // since it's currently just used at the discover's doc view table, caching is not necessary + const flattened = indexPattern.flattenHit(hit); + const result: Record = {}; + for (const [key, value] of Object.entries(flattened)) { + result[key] = convert(hit, value, key, type); + } + return result; + } + const cached = formattedCache.get(hit); if (cached) { return cached; diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table.html b/src/legacy/core_plugins/kbn_doc_views/public/views/table.html deleted file mode 100644 index 48b66b233b3cf..0000000000000 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-
diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table.js b/src/legacy/core_plugins/kbn_doc_views/public/views/table.js deleted file mode 100644 index c467a9a3eba81..0000000000000 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table.js +++ /dev/null @@ -1,77 +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 _ from 'lodash'; -import { addDocView } from 'ui/registry/doc_views'; -import '../filters/trust_as_html'; -import tableHtml from './table.html'; -import { i18n } from '@kbn/i18n'; - -const MIN_LINE_LENGTH = 350; - -addDocView({ - title: i18n.translate('kbnDocViews.table.tableTitle', { - defaultMessage: 'Table', - }), - order: 10, - directive: { - template: tableHtml, - controller: $scope => { - $scope.mapping = $scope.indexPattern.fields.byName; - $scope.flattened = $scope.indexPattern.flattenHit($scope.hit); - $scope.formatted = $scope.indexPattern.formatHit($scope.hit); - $scope.fields = _.keys($scope.flattened).sort(); - $scope.fieldRowOpen = {}; - $scope.fields.forEach(field => ($scope.fieldRowOpen[field] = false)); - - $scope.canToggleColumns = function canToggleColumn() { - return _.isFunction($scope.onAddColumn) && _.isFunction($scope.onRemoveColumn); - }; - - $scope.toggleColumn = function toggleColumn(columnName) { - if ($scope.columns.includes(columnName)) { - $scope.onRemoveColumn(columnName); - } else { - $scope.onAddColumn(columnName); - } - }; - - $scope.isColumnActive = function isColumnActive(columnName) { - return $scope.columns.includes(columnName); - }; - - $scope.showArrayInObjectsWarning = function (row, field) { - const value = $scope.flattened[field]; - return Array.isArray(value) && typeof value[0] === 'object'; - }; - - $scope.enableDocValueCollapse = function (docValueField) { - const html = - typeof $scope.formatted[docValueField] === 'undefined' - ? $scope.hit[docValueField] - : $scope.formatted[docValueField]; - return html.length > MIN_LINE_LENGTH; - }; - - $scope.toggleViewer = function (field) { - $scope.fieldRowOpen[field] = !$scope.fieldRowOpen[field]; - }; - }, - }, -}); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/filters/trust_as_html.js b/src/legacy/core_plugins/kbn_doc_views/public/views/table.ts similarity index 72% rename from src/legacy/core_plugins/kbn_doc_views/public/filters/trust_as_html.js rename to src/legacy/core_plugins/kbn_doc_views/public/views/table.ts index ea952ca9f6874..f90840f195003 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/filters/trust_as_html.js +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table.ts @@ -17,11 +17,15 @@ * under the License. */ -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/doc_views'); +import _ from 'lodash'; +import { addDocView } from 'ui/registry/doc_views'; +import { i18n } from '@kbn/i18n'; +import { DocViewTable } from './table/table'; -// Simple filter to allow using ng-bind-html without explicitly calling $sce.trustAsHtml in a controller -// (See http://goo.gl/mpj9o2) -module.filter('trustAsHtml', function ($sce) { - return $sce.trustAsHtml; +addDocView({ + title: i18n.translate('kbnDocViews.table.tableTitle', { + defaultMessage: 'Table', + }), + order: 10, + component: DocViewTable, }); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx new file mode 100644 index 0000000000000..03437e7b1ac26 --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx @@ -0,0 +1,260 @@ +/* + * 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 React from 'react'; +import { mount } from 'enzyme'; +import { IndexPattern } from 'ui/index_patterns'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; +// @ts-ignore +import { flattenHitWrapper } from '../../../../data/public/index_patterns/index_patterns/flatten_hit'; +import { DocViewTable } from './table'; + +// @ts-ignore +const indexPattern = { + fields: { + byName: { + _index: { + name: '_index', + type: 'string', + scripted: false, + filterable: true, + }, + message: { + name: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + extension: { + name: 'extension', + type: 'string', + scripted: false, + filterable: true, + }, + bytes: { + name: 'bytes', + type: 'number', + scripted: false, + filterable: true, + }, + scripted: { + name: 'scripted', + type: 'number', + scripted: true, + filterable: false, + }, + }, + }, + metaFields: ['_index', '_score'], + flattenHit: undefined, + formatHit: jest.fn(hit => hit), +} as IndexPattern; + +indexPattern.flattenHit = flattenHitWrapper(indexPattern, indexPattern.metaFields); + +describe('DocViewTable at Discover', () => { + // At Discover's main view, all buttons are rendered + // check for existence of action buttons and warnings + + const hit = { + _index: 'logstash-2014.09.09', + _score: 1, + _source: { + message: + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. \ + Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus \ + et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, \ + ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. \ + Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, \ + rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. \ + Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. \ + Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut', + extension: 'html', + not_mapped: 'yes', + bytes: 100, + objectArray: [{ foo: true }], + relatedContent: { + test: 1, + }, + scripted: 123, + _underscore: 123, + }, + }; + + const props = { + hit, + columns: ['extension'], + indexPattern, + filter: jest.fn(), + onAddColumn: jest.fn(), + onRemoveColumn: jest.fn(), + }; + const component = mount(); + [ + { + _property: '_index', + addInclusiveFilterButton: true, + collapseBtn: false, + noMappingWarning: false, + toggleColumnButton: true, + underscoreWarning: false, + }, + { + _property: 'message', + addInclusiveFilterButton: false, + collapseBtn: true, + noMappingWarning: false, + toggleColumnButton: true, + underscoreWarning: false, + }, + { + _property: '_underscore', + addInclusiveFilterButton: false, + collapseBtn: false, + noMappingWarning: false, + toggleColumnButton: true, + underScoreWarning: true, + }, + { + _property: 'scripted', + addInclusiveFilterButton: false, + collapseBtn: false, + noMappingWarning: false, + toggleColumnButton: true, + underScoreWarning: false, + }, + { + _property: 'not_mapped', + addInclusiveFilterButton: false, + collapseBtn: false, + noMappingWarning: true, + toggleColumnButton: true, + underScoreWarning: false, + }, + ].forEach(check => { + const rowComponent = findTestSubject(component, `tableDocViewRow-${check._property}`); + + it(`renders row for ${check._property}`, () => { + expect(rowComponent.length).toBe(1); + }); + + ([ + 'addInclusiveFilterButton', + 'collapseBtn', + 'toggleColumnButton', + 'underscoreWarning', + ] as const).forEach(element => { + const elementExist = check[element]; + + if (typeof elementExist === 'boolean') { + const btn = findTestSubject(rowComponent, element); + + it(`renders ${element} for '${check._property}' correctly`, () => { + const disabled = btn.length ? btn.props().disabled : true; + const clickAble = btn.length && !disabled ? true : false; + expect(clickAble).toBe(elementExist); + }); + } + }); + + (['noMappingWarning'] as const).forEach(element => { + const elementExist = check[element]; + + if (typeof elementExist === 'boolean') { + const el = findTestSubject(rowComponent, element); + + it(`renders ${element} for '${check._property}' correctly`, () => { + expect(el.length).toBe(elementExist ? 1 : 0); + }); + } + }); + }); +}); + +describe('DocViewTable at Discover Doc', () => { + const hit = { + _index: 'logstash-2014.09.09', + _score: 1, + _source: { + extension: 'html', + not_mapped: 'yes', + }, + }; + // here no action buttons are rendered + const props = { + hit, + indexPattern, + }; + const component = mount(); + const foundLength = findTestSubject(component, 'addInclusiveFilterButton').length; + + it(`renders no action buttons`, () => { + expect(foundLength).toBe(0); + }); +}); + +describe('DocViewTable at Discover Context', () => { + // here no toggleColumnButtons are rendered + const hit = { + _index: 'logstash-2014.09.09', + _source: { + message: + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. \ + Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus \ + et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, \ + ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. \ + Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, \ + rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. \ + Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. \ + Phasellus ullamcorper ipsum rutrum nunc. Nunc nonummy metus. Vestibulum volutpat pretium libero. Cras id dui. Aenean ut', + }, + }; + const props = { + hit, + columns: ['extension'], + indexPattern, + filter: jest.fn(), + }; + + const component = mount(); + + it(`renders no toggleColumnButton`, () => { + const foundLength = findTestSubject(component, 'toggleColumnButtons').length; + expect(foundLength).toBe(0); + }); + + it(`renders addInclusiveFilterButton`, () => { + const row = findTestSubject(component, `tableDocViewRow-_index`); + const btn = findTestSubject(row, 'addInclusiveFilterButton'); + expect(btn.length).toBe(1); + btn.simulate('click'); + expect(props.filter).toBeCalled(); + }); + + it(`renders functional collapse button`, () => { + const btn = findTestSubject(component, `collapseBtn`); + const html = component.html(); + + expect(component.html()).toContain('truncate-by-height'); + + expect(btn.length).toBe(1); + btn.simulate('click'); + expect(component.html() !== html).toBeTruthy(); + }); +}); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx new file mode 100644 index 0000000000000..ff784f6159870 --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx @@ -0,0 +1,91 @@ +/* + * 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 React, { useState } from 'react'; +import { DocViewRenderProps } from 'ui/registry/doc_views'; +import { DocViewTableRow } from './table_row'; +import { formatValue, arrayContainsObjects } from './table_helper'; + +const COLLAPSE_LINE_LENGTH = 350; + +export function DocViewTable({ + hit, + indexPattern, + filter, + columns, + onAddColumn, + onRemoveColumn, +}: DocViewRenderProps) { + const mapping = indexPattern.fields.byName; + const flattened = indexPattern.flattenHit(hit); + const formatted = indexPattern.formatHit(hit, 'html'); + const [fieldRowOpen, setFieldRowOpen] = useState({} as Record); + + function toggleValueCollapse(field: string) { + fieldRowOpen[field] = fieldRowOpen[field] !== true; + setFieldRowOpen({ ...fieldRowOpen }); + } + + return ( + + + {Object.keys(flattened) + .sort() + .map(field => { + const valueRaw = flattened[field]; + const value = formatValue(valueRaw, formatted[field]); + const isCollapsible = typeof value === 'string' && value.length > COLLAPSE_LINE_LENGTH; + const isCollapsed = isCollapsible && !fieldRowOpen[field]; + const toggleColumn = + onRemoveColumn && onAddColumn && Array.isArray(columns) + ? () => { + if (columns.includes(field)) { + onRemoveColumn(field); + } else { + onAddColumn(field); + } + } + : undefined; + const isArrayOfObjects = + Array.isArray(flattened[field]) && arrayContainsObjects(flattened[field]); + const displayUnderscoreWarning = !mapping[field] && field.indexOf('_') === 0; + const displayNoMappingWarning = + !mapping[field] && !displayUnderscoreWarning && !isArrayOfObjects; + + return ( + toggleValueCollapse(field)} + onToggleColumn={toggleColumn} + value={value} + valueRaw={valueRaw} + /> + ); + })} + +
+ ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts new file mode 100644 index 0000000000000..f075e06c7651f --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts @@ -0,0 +1,177 @@ +/* + * 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 { + replaceMarkWithReactDom, + convertAngularHtml, + arrayContainsObjects, + formatValue, +} from './table_helper'; + +describe('replaceMarkWithReactDom', () => { + it(`converts test to react nodes`, () => { + const actual = replaceMarkWithReactDom( + 'marked1 blablabla marked2 end' + ); + expect(actual).toMatchInlineSnapshot(` + + + + + marked1 + + blablabla + + + + marked2 + + end + + + `); + }); + + it(`doesn't convert invalid markup to react dom nodes`, () => { + const actual = replaceMarkWithReactDom('test sdf sdf'); + expect(actual).toMatchInlineSnapshot(` + + + test sdf + + + sdf + + + + + `); + }); + + it(`returns strings without markup unchanged `, () => { + const actual = replaceMarkWithReactDom('blablabla'); + expect(actual).toMatchInlineSnapshot(` + + blablabla + + `); + }); +}); + +describe('convertAngularHtml', () => { + it(`converts html for usage in angular to usage in react`, () => { + const actual = convertAngularHtml('Good morning!'); + expect(actual).toMatchInlineSnapshot(`"Good morning!"`); + }); + it(`converts html containing for usage in react`, () => { + const actual = convertAngularHtml( + 'Good morningdear reviewer!' + ); + expect(actual).toMatchInlineSnapshot(` + + Good + + + morning + + dear + + + + reviewer + + ! + + + `); + }); +}); + +describe('arrayContainsObjects', () => { + it(`returns false for an array of primitives`, () => { + const actual = arrayContainsObjects(['test', 'test']); + expect(actual).toBeFalsy(); + }); + + it(`returns true for an array of objects`, () => { + const actual = arrayContainsObjects([{}, {}]); + expect(actual).toBeTruthy(); + }); + + it(`returns true for an array of objects and primitves`, () => { + const actual = arrayContainsObjects([{}, 'sdf']); + expect(actual).toBeTruthy(); + }); + + it(`returns false for an array of null values`, () => { + const actual = arrayContainsObjects([null, null]); + expect(actual).toBeFalsy(); + }); + + it(`returns false if no array is given`, () => { + const actual = arrayContainsObjects([null, null]); + expect(actual).toBeFalsy(); + }); +}); + +describe('formatValue', () => { + it(`formats an array of objects`, () => { + const actual = formatValue([{ test: '123' }, ''], ''); + expect(actual).toMatchInlineSnapshot(` + "{ + \\"test\\": \\"123\\" + } + \\"\\"" + `); + }); + it(`formats an array of primitives`, () => { + const actual = formatValue(['test1', 'test2'], ''); + expect(actual).toMatchInlineSnapshot(`"test1, test2"`); + }); + it(`formats an object`, () => { + const actual = formatValue({ test: 1 }, ''); + expect(actual).toMatchInlineSnapshot(` + "{ + \\"test\\": 1 + }" + `); + }); + it(`formats an angular formatted string `, () => { + const actual = formatValue( + '', + 'Good morningdear reviewer!' + ); + expect(actual).toMatchInlineSnapshot(` + + Good + + + morning + + dear + + + + reviewer + + ! + + + `); + }); +}); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx new file mode 100644 index 0000000000000..e959ec336bf3a --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx @@ -0,0 +1,85 @@ +/* + * 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 React from 'react'; +import { unescape } from 'lodash'; + +/** + * Convert markup of the given string to ReactNodes + * @param text + */ +export function replaceMarkWithReactDom(text: string): React.ReactNode { + return ( + <> + {text.split('').map((markedText, idx) => { + const sub = markedText.split(''); + if (sub.length === 1) { + return markedText; + } + return ( + + {sub[0]} + {sub[1]} + + ); + })} + + ); +} + +/** + * Current html of the formatter is angular flavored, this current workaround + * should be removed when all consumers of the formatHit function are react based + */ +export function convertAngularHtml(html: string): string | React.ReactNode { + if (typeof html === 'string') { + const cleaned = html.replace('', '').replace('', ''); + const unescaped = unescape(cleaned); + if (unescaped.indexOf('') !== -1) { + return replaceMarkWithReactDom(unescaped); + } + return unescaped; + } + return html; +} +/** + * Returns true if the given array contains at least 1 object + */ +export function arrayContainsObjects(value: unknown[]) { + return Array.isArray(value) && value.some(v => typeof v === 'object' && v !== null); +} + +/** + * The current field formatter provides html for angular usage + * This html is cleaned up and prepared for usage in the react world + * Furthermore test are converted to ReactNodes + */ +export function formatValue( + value: null | string | number | boolean | object | Array, + valueFormatted: string +): string | React.ReactNode { + if (Array.isArray(value) && arrayContainsObjects(value)) { + return value.map(v => JSON.stringify(v, null, 2)).join('\n'); + } else if (Array.isArray(value)) { + return value.join(', '); + } else if (typeof value === 'object' && value !== null) { + return JSON.stringify(value, null, 2); + } else { + return typeof valueFormatted === 'string' ? convertAngularHtml(valueFormatted) : String(value); + } +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx new file mode 100644 index 0000000000000..2059e35b2c42e --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React, { ReactNode } from 'react'; +import { FieldName } from 'ui/directives/field_name/field_name'; +import { FieldMapping, DocViewFilterFn } from 'ui/registry/doc_views_types'; +import classNames from 'classnames'; +import { DocViewTableRowBtnFilterAdd } from './table_row_btn_filter_add'; +import { DocViewTableRowBtnFilterRemove } from './table_row_btn_filter_remove'; +import { DocViewTableRowBtnToggleColumn } from './table_row_btn_toggle_column'; +import { DocViewTableRowBtnCollapse } from './table_row_btn_collapse'; +import { DocViewTableRowBtnFilterExists } from './table_row_btn_filter_exists'; +import { DocViewTableRowIconNoMapping } from './table_row_icon_no_mapping'; +import { DocViewTableRowIconUnderscore } from './table_row_icon_underscore'; + +export interface Props { + field: string; + fieldMapping?: FieldMapping; + displayNoMappingWarning: boolean; + displayUnderscoreWarning: boolean; + isCollapsible: boolean; + isColumnActive: boolean; + isCollapsed: boolean; + onToggleCollapse: () => void; + onFilter?: DocViewFilterFn; + onToggleColumn?: () => void; + value: string | ReactNode; + valueRaw: unknown; +} + +export function DocViewTableRow({ + field, + fieldMapping, + displayNoMappingWarning, + displayUnderscoreWarning, + isCollapsible, + isCollapsed, + isColumnActive, + onFilter, + onToggleCollapse, + onToggleColumn, + value, + valueRaw, +}: Props) { + const valueClassName = classNames({ + kbnDocViewer__value: true, + 'truncate-by-height': isCollapsible && isCollapsed, + }); + + return ( + + {typeof onFilter === 'function' && ( + + onFilter(fieldMapping, valueRaw, '+')} + /> + onFilter(fieldMapping, valueRaw, '-')} + /> + {typeof onToggleColumn === 'function' && ( + + )} + onFilter('_exists_', field, '+')} + scripted={fieldMapping && fieldMapping.scripted} + /> + + )} + + + + + {isCollapsible && ( + + )} + {displayUnderscoreWarning && } + {displayNoMappingWarning && } +
+ {value} +
+ + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_collapse.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_collapse.tsx new file mode 100644 index 0000000000000..cb6b522ba9559 --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_collapse.tsx @@ -0,0 +1,44 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; + +export interface Props { + onClick: () => void; + isCollapsed: boolean; +} + +export function DocViewTableRowBtnCollapse({ onClick, isCollapsed }: Props) { + const label = i18n.translate('kbnDocViews.table.toggleFieldDetails', { + defaultMessage: 'Toggle field details', + }); + return ( + + onClick()} + iconType={isCollapsed ? 'arrowRight' : 'arrowDown'} + iconSize={'s'} + /> + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_add.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_add.tsx new file mode 100644 index 0000000000000..df4572c5bb53b --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_add.tsx @@ -0,0 +1,57 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface Props { + onClick: () => void; + disabled: boolean; +} + +export function DocViewTableRowBtnFilterAdd({ onClick, disabled = false }: Props) { + const tooltipContent = disabled ? ( + + ) : ( + + ); + + return ( + + + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_exists.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_exists.tsx new file mode 100644 index 0000000000000..ae387ef1c4fa1 --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_exists.tsx @@ -0,0 +1,69 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface Props { + onClick: () => void; + disabled?: boolean; + scripted?: boolean; +} + +export function DocViewTableRowBtnFilterExists({ + onClick, + disabled = false, + scripted = false, +}: Props) { + const tooltipContent = disabled ? ( + scripted ? ( + + ) : ( + + ) + ) : ( + + ); + + return ( + + + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_remove.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_remove.tsx new file mode 100644 index 0000000000000..eda6636582977 --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_filter_remove.tsx @@ -0,0 +1,57 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface Props { + onClick: () => void; + disabled?: boolean; +} + +export function DocViewTableRowBtnFilterRemove({ onClick, disabled = false }: Props) { + const tooltipContent = disabled ? ( + + ) : ( + + ); + + return ( + + + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_toggle_column.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_toggle_column.tsx new file mode 100644 index 0000000000000..55864f6567fe9 --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_btn_toggle_column.tsx @@ -0,0 +1,67 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface Props { + active: boolean; + disabled?: boolean; + onClick: () => void; +} + +export function DocViewTableRowBtnToggleColumn({ onClick, active, disabled = false }: Props) { + if (disabled) { + return ( + + ); + } + return ( + + } + > + + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_no_mapping.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_no_mapping.tsx new file mode 100644 index 0000000000000..ab22ba7bea7bd --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_no_mapping.tsx @@ -0,0 +1,44 @@ +/* + * 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 React from 'react'; +import { EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function DocViewTableRowIconNoMapping() { + const ariaLabel = i18n.translate('kbnDocViews.table.noCachedMappingForThisFieldAriaLabel', { + defaultMessage: 'Warning', + }); + const tooltipContent = i18n.translate('kbnDocViews.table.noCachedMappingForThisFieldTooltip', { + defaultMessage: + 'No cached mapping for this field. Refresh field list from the Management > Index Patterns page', + }); + return ( + + ); +} diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_underscore.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_underscore.tsx new file mode 100644 index 0000000000000..b527714014dae --- /dev/null +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row_icon_underscore.tsx @@ -0,0 +1,51 @@ +/* + * 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 React from 'react'; +import { EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function DocViewTableRowIconUnderscore() { + const ariaLabel = i18n.translate( + 'kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel', + { + defaultMessage: 'Warning', + } + ); + const tooltipContent = i18n.translate( + 'kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip', + { + defaultMessage: 'Field names beginning with {underscoreSign} are not supported', + values: { underscoreSign: '_' }, + } + ); + + return ( + + ); +} diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/_doc_viewer.scss b/src/legacy/core_plugins/kibana/public/doc_viewer/_doc_viewer.scss index 539efbe592a51..25aa530976719 100644 --- a/src/legacy/core_plugins/kibana/public/doc_viewer/_doc_viewer.scss +++ b/src/legacy/core_plugins/kibana/public/doc_viewer/_doc_viewer.scss @@ -1,3 +1,7 @@ +.kbnDocViewerTable { + margin-top: $euiSizeS; +} + .kbnDocViewer { pre, .kbnDocViewer__value { @@ -6,6 +10,11 @@ word-wrap: break-word; white-space: pre-wrap; color: $euiColorFullShade; + vertical-align: top; + padding-top: 2px; + } + .kbnDocViewer__field { + padding-top: 8px; } .dscFieldName { @@ -28,20 +37,22 @@ } } -.kbnDocViewer__content { - background-color: $euiColorEmptyShade; - padding: $euiSizeS 0 0 0; -} - .kbnDocViewer__buttons, .kbnDocViewer__field { white-space: nowrap; } +.kbnDocViewer__buttons { + width: 60px; +} + +.kbnDocViewer__field { + width: 160px; +} .kbnDocViewer__actionButton { opacity: 0; - - &:focus { - opacity: 1; - } } +.kbnDocViewer__warning { + margin-right: $euiSizeS; +} + diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_tab.tsx b/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_tab.tsx index d0fa29c5344a9..0b25421d8aff3 100644 --- a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_tab.tsx @@ -17,6 +17,7 @@ * under the License. */ import React from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; import { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; @@ -77,7 +78,12 @@ export class DocViewerTab extends React.Component { } // doc view is provided by a react component + const Component = component as any; - return ; + return ( + + + + ); } } diff --git a/src/legacy/ui/public/directives/field_name/field_name.tsx b/src/legacy/ui/public/directives/field_name/field_name.tsx index 95c403ae938ed..b7b9d6920eef1 100644 --- a/src/legacy/ui/public/directives/field_name/field_name.tsx +++ b/src/legacy/ui/public/directives/field_name/field_name.tsx @@ -29,8 +29,8 @@ interface Props { field?: { type: string; name: string; - rowCount: number; - scripted: boolean; + rowCount?: number; + scripted?: boolean; }; fieldName?: string; fieldType?: string; diff --git a/src/legacy/ui/public/registry/doc_views_types.ts b/src/legacy/ui/public/registry/doc_views_types.ts index 9388764309646..f078f374b4579 100644 --- a/src/legacy/ui/public/registry/doc_views_types.ts +++ b/src/legacy/ui/public/registry/doc_views_types.ts @@ -31,13 +31,27 @@ export type AngularController = (scope: AngularScope) => void; export type ElasticSearchHit = Record>; +export interface FieldMapping { + filterable?: boolean; + scripted?: boolean; + rowCount?: number; + type: string; + name: string; +} + +export type DocViewFilterFn = ( + mapping: FieldMapping | string | undefined, + value: unknown, + mode: '+' | '-' +) => void; + export interface DocViewRenderProps { - columns: string[]; - filter: (field: string, value: string | number, operation: string) => void; + columns?: string[]; + filter?: DocViewFilterFn; hit: ElasticSearchHit; indexPattern: IndexPattern; - onAddColumn: (columnName: string) => void; - onRemoveColumn: (columnName: string) => void; + onAddColumn?: (columnName: string) => void; + onRemoveColumn?: (columnName: string) => void; } export type DocViewRenderFn = ( domeNode: HTMLDivElement, From 0772e1934646064f2be87f117ba636a361e09c8f Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 27 Aug 2019 02:22:53 -0700 Subject: [PATCH 02/66] [ML] Data frames: Fixes table sorting. (#43859) - EuiInMemoryTable will not correctly reflect prop updates like sorting. So for example, when the component gets mounted with sorting={false} it will never consider a later update to make sorting available after all data is loaded. This PR fixes it by mounting the component only once sorting was set properly. This affected all data frame analytics/transform tables. - This consolidates code where we had multiple custom type definitions for EuiInMemoryTable because it's not based on TypeScript itself yet. The PR adds TypeScript Prop definitions for the component in ml/common/types/eui/in_memory_table.ts based on React propTypes and exposes a MlInMemoryTable component that wraps EuiInMemoryTable. I'll be in contact with the EUI team so they can make use of this for EUI itself. --- .../ml/common/types/eui/in_memory_table.ts | 184 ++++++++++++++++++ .../source_index_preview.tsx | 26 +-- .../components/step_define/pivot_preview.tsx | 19 +- .../components/transform_list/columns.tsx | 21 +- .../expanded_row_preview_pane.tsx | 3 +- .../transform_list/transform_list.tsx | 3 +- .../transform_list/transform_table.tsx | 22 +-- .../components/exploration/exploration.tsx | 22 +-- .../analytics_list/analytics_list.tsx | 3 +- .../analytics_list/analytics_table.tsx | 22 +-- 10 files changed, 241 insertions(+), 84 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts diff --git a/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts b/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts new file mode 100644 index 0000000000000..b67a18562921b --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts @@ -0,0 +1,184 @@ +/* + * 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 { Component, HTMLAttributes, ReactElement, ReactNode } from 'react'; + +import { CommonProps, EuiInMemoryTable } from '@elastic/eui'; + +// At some point this could maybe solved with a generic . +type Item = any; + +// Not using an enum here because the original HorizontalAlignment is also a union type of string. +type HorizontalAlignment = 'left' | 'center' | 'right'; + +type SortableFunc = (item: Item) => any; +type Sortable = boolean | SortableFunc; +type DATA_TYPES = any; +type FooterFunc = (payload: { items: Item[]; pagination: any }) => ReactNode; +type RenderFunc = (value: any, record?: any) => ReactNode; +export interface FieldDataColumnType { + field: string; + name: ReactNode; + description?: string; + dataType?: DATA_TYPES; + width?: string; + sortable?: Sortable; + align?: HorizontalAlignment; + truncateText?: boolean; + render?: RenderFunc; + footer?: string | ReactElement | FooterFunc; +} + +export interface ComputedColumnType { + render: RenderFunc; + name?: ReactNode; + description?: string; + sortable?: (item: Item) => any; + width?: string; + truncateText?: boolean; +} + +type ICON_TYPES = any; +type IconTypesFunc = (item: Item) => ICON_TYPES; // (item) => oneOf(ICON_TYPES) +type BUTTON_ICON_COLORS = any; +type ButtonIconColorsFunc = (item: Item) => BUTTON_ICON_COLORS; // (item) => oneOf(ICON_BUTTON_COLORS) +interface DefaultItemActionType { + type?: 'icon' | 'button'; + name: string; + description: string; + onClick?(item: Item): void; + href?: string; + target?: string; + available?(item: Item): boolean; + enabled?(item: Item): boolean; + isPrimary?: boolean; + icon?: ICON_TYPES | IconTypesFunc; // required when type is 'icon' + color?: BUTTON_ICON_COLORS | ButtonIconColorsFunc; +} + +interface CustomItemActionType { + render(item: Item, enabled: boolean): ReactNode; + available?(item: Item): boolean; + enabled?(item: Item): boolean; + isPrimary?: boolean; +} + +export interface ExpanderColumnType { + align?: HorizontalAlignment; + width?: string; + isExpander: boolean; + render: RenderFunc; +} + +type SupportedItemActionType = DefaultItemActionType | CustomItemActionType; + +export interface ActionsColumnType { + actions: SupportedItemActionType[]; + name?: ReactNode; + description?: string; + width?: string; +} + +export type ColumnType = + | ActionsColumnType + | ComputedColumnType + | ExpanderColumnType + | FieldDataColumnType; + +type QueryType = any; + +interface Schema { + strict?: boolean; + fields?: Record; + flags?: string[]; +} + +interface SearchBoxConfigPropTypes { + placeholder?: string; + incremental?: boolean; + schema?: Schema; +} + +interface Box { + placeholder?: string; + incremental?: boolean; + // here we enable the user to just assign 'true' to the schema, in which case + // we will auto-generate it out of the columns configuration + schema?: boolean | SearchBoxConfigPropTypes['schema']; +} + +type SearchFiltersFiltersType = any; + +interface ExecuteQueryOptions { + defaultFields: string[]; + isClauseMatcher: () => void; + explain: boolean; +} + +type SearchType = + | boolean + | { + toolsLeft?: ReactNode; + toolsRight?: ReactNode; + defaultQuery?: QueryType; + box?: Box; + filters?: SearchFiltersFiltersType; + onChange?: (arg: any) => void; + executeQueryOptions?: ExecuteQueryOptions; + }; + +interface PageSizeOptions { + pageSizeOptions: number[]; +} +interface InitialPageOptions extends PageSizeOptions { + initialPageIndex: number; + initialPageSize: number; +} +type Pagination = boolean | PageSizeOptions | InitialPageOptions; + +type PropertySortType = any; +type Sorting = boolean | { sort: PropertySortType }; + +type SelectionType = any; + +type ItemIdTypeFunc = (item: Item) => string; +type ItemIdType = + | string // the name of the item id property + | ItemIdTypeFunc; + +export type EuiInMemoryTableProps = CommonProps & { + columns: ColumnType[]; + hasActions?: boolean; + isExpandable?: boolean; + isSelectable?: boolean; + items?: Item[]; + loading?: boolean; + message?: HTMLAttributes; + error?: string; + compressed?: boolean; + search?: SearchType; + pagination?: Pagination; + sorting?: Sorting; + // Set `allowNeutralSort` to false to force column sorting. Defaults to true. + allowNeutralSort?: boolean; + selection?: SelectionType; + itemId?: ItemIdType; + itemIdToExpandedRowMap?: Record; + rowProps?: () => void | Record; + cellProps?: () => void | Record; + onTableChange?: (arg: { + page: { index: number; size: number }; + sort: { field: string; direction: string }; + }) => void; +}; + +interface ComponentWithConstructor extends Component { + new (): Component; +} + +export const MlInMemoryTable = (EuiInMemoryTable as any) as ComponentWithConstructor< + EuiInMemoryTableProps +>; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx index 9bda576b8c260..a08f60c868b7b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FunctionComponent, useState } from 'react'; +import React, { useState } from 'react'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; @@ -18,8 +18,6 @@ import { EuiCopy, EuiFlexGroup, EuiFlexItem, - EuiInMemoryTable, - EuiInMemoryTableProps, EuiPanel, EuiPopover, EuiPopoverTitle, @@ -30,14 +28,7 @@ import { RIGHT_ALIGNMENT, } from '@elastic/eui'; -// TODO EUI's types for EuiInMemoryTable is missing these props -interface ExpandableTableProps extends EuiInMemoryTableProps { - compressed: boolean; - itemIdToExpandedRowMap: ItemIdToExpandedRowMap; - isExpandable: boolean; -} - -const ExpandableTable = (EuiInMemoryTable as any) as FunctionComponent; +import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; import { KBN_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import { Dictionary } from '../../../../../../common/types/common'; @@ -204,13 +195,13 @@ export const SourceIndexPreview: React.SFC = React.memo(({ cellClick, que docFieldsCount = docFields.length; } - const columns = selectedFields.map(k => { - const column = { + const columns: ColumnType[] = selectedFields.map(k => { + const column: ColumnType = { field: `_source["${k}"]`, name: k, sortable: true, truncateText: true, - } as Dictionary; + }; const field = indexPattern.fields.find(f => f.name === k); @@ -315,7 +306,7 @@ export const SourceIndexPreview: React.SFC = React.memo(({ cellClick, que if (columns.length > 0) { sorting = { sort: { - field: columns[0].field, + field: `_source["${selectedFields[0]}"]`, direction: SORT_DIRECTON.ASC, }, }; @@ -425,8 +416,9 @@ export const SourceIndexPreview: React.SFC = React.memo(({ cellClick, que {status !== SOURCE_INDEX_STATUS.LOADING && ( )} - {clearTable === false && ( - 0 && sorting !== false && ( + ; - function sortColumns(groupByArr: PivotGroupByConfig[]) { return (a: string, b: string) => { // make sure groupBy fields are always most left columns @@ -237,7 +229,7 @@ export const PivotPreview: SFC = React.memo(({ aggs, groupBy, const columns = columnKeys .filter(k => typeof dataFramePreviewMappings.properties[k] !== 'undefined') .map(k => { - const column: Dictionary = { + const column: ColumnType = { field: k, name: k, sortable: true, @@ -290,8 +282,9 @@ export const PivotPreview: SFC = React.memo(({ aggs, groupBy, {status !== PIVOT_PREVIEW_STATUS.LOADING && ( )} - {dataFramePreviewData.length > 0 && clearTable === false && ( - 0 && clearTable === false && columns.length > 0 && ( + = ({ transformConfig }) => { return ( { { items={filterActive ? filteredTransforms : transforms} itemId={DataFrameTransformListColumn.id} itemIdToExpandedRowMap={itemIdToExpandedRowMap} - onChange={onTableChange} + onTableChange={onTableChange} pagination={pagination} selection={selection} sorting={sorting} diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx index aa810105a6a54..bfb4e86abe8b5 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx @@ -7,11 +7,11 @@ // This component extends EuiInMemoryTable with some // fixes and TS specs until the changes become available upstream. -import React, { Component, Fragment } from 'react'; +import React, { Fragment } from 'react'; -import { EuiInMemoryTable, EuiInMemoryTableProps, EuiProgress } from '@elastic/eui'; +import { EuiProgress } from '@elastic/eui'; -import { ItemIdToExpandedRowMap } from './common'; +import { MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; // The built in loading progress bar of EuiInMemoryTable causes a full DOM replacement // of the table and doesn't play well with auto-refreshing. That's why we're displaying @@ -73,21 +73,7 @@ const getInitialSorting = (columns: any, sorting: any) => { }; }; -// TODO EUI's types for EuiInMemoryTable is missing these props -interface ExpandableTableProps extends EuiInMemoryTableProps { - itemIdToExpandedRowMap?: ItemIdToExpandedRowMap; - isExpandable?: boolean; - onChange({ page }: { page?: {} | undefined }): void; - loading?: boolean; - compressed?: boolean; - error?: string; -} -interface ComponentWithConstructor extends Component { - new (): Component; -} -const ExpandableTable = (EuiInMemoryTable as any) as ComponentWithConstructor; - -export class TransformTable extends ExpandableTable { +export class TransformTable extends MlInMemoryTable { static getDerivedStateFromProps(nextProps: any, prevState: any) { const derivedState = { ...prevState.prevProps, diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx index 0528baf19a011..48bd31c34dcfa 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, FunctionComponent, useEffect, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; @@ -18,8 +18,6 @@ import { EuiCheckbox, EuiFlexGroup, EuiFlexItem, - EuiInMemoryTable, - EuiInMemoryTableProps, EuiPanel, EuiPopover, EuiPopoverTitle, @@ -32,12 +30,7 @@ import { import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; -// TODO EUI's types for EuiInMemoryTable is missing these props -interface ExpandableTableProps extends EuiInMemoryTableProps { - compressed: boolean; -} - -const ExpandableTable = (EuiInMemoryTable as any) as FunctionComponent; +import { ColumnType, MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; import { useUiChromeContext } from '../../../../../contexts/ui/use_ui_chrome_context'; @@ -218,7 +211,7 @@ export const Exploration: FC = React.memo(({ jobId }) => { docFieldsCount = docFields.length; } - const columns = []; + const columns: ColumnType[] = []; if (selectedFields.length > 0 && tableItems.length > 0) { // table cell color coding takes into account: @@ -241,12 +234,12 @@ export const Exploration: FC = React.memo(({ jobId }) => { ...selectedFields .sort(sortColumns(tableItems[0]._source, jobConfig.dest.results_field)) .map(k => { - const column = { + const column: ColumnType = { field: `_source["${k}"]`, name: k, sortable: true, truncateText: true, - } as Record; + }; const render = (d: any, fullItem: EsDoc) => { if (Array.isArray(d) && d.every(item => typeof item === 'string')) { @@ -367,7 +360,7 @@ export const Exploration: FC = React.memo(({ jobId }) => { if (columns.length > 0) { sorting = { sort: { - field: columns[0].field, + field: `_source["${selectedFields[0]}"]`, direction: SORT_DIRECTON.ASC, }, }; @@ -443,7 +436,8 @@ export const Exploration: FC = React.memo(({ jobId }) => { )} {clearTable === false && columns.length > 0 && ( - = ({ = ({ items={filterActive ? filteredAnalytics : analytics} itemId={DataFrameAnalyticsListColumn.id} itemIdToExpandedRowMap={itemIdToExpandedRowMap} - onChange={onTableChange} + onTableChange={onTableChange} pagination={pagination} sorting={sorting} search={search} diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx index ca00191746f06..006aef70e5528 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx @@ -7,11 +7,9 @@ // This component extends EuiInMemoryTable with some // fixes and TS specs until the changes become available upstream. -import React, { Component, Fragment } from 'react'; +import React, { Fragment } from 'react'; -import { EuiInMemoryTable, EuiInMemoryTableProps, EuiProgress } from '@elastic/eui'; - -import { ItemIdToExpandedRowMap } from './common'; +import { EuiProgress } from '@elastic/eui'; // The built in loading progress bar of EuiInMemoryTable causes a full DOM replacement // of the table and doesn't play well with auto-refreshing. That's why we're displaying @@ -73,21 +71,9 @@ const getInitialSorting = (columns: any, sorting: any) => { }; }; -// TODO EUI's types for EuiInMemoryTable is missing these props -interface ExpandableTableProps extends EuiInMemoryTableProps { - itemIdToExpandedRowMap?: ItemIdToExpandedRowMap; - isExpandable?: boolean; - onChange({ page }: { page?: {} | undefined }): void; - loading?: boolean; - compressed?: boolean; - error?: string; -} -interface ComponentWithConstructor extends Component { - new (): Component; -} -const ExpandableTable = (EuiInMemoryTable as any) as ComponentWithConstructor; +import { MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; -export class AnalyticsTable extends ExpandableTable { +export class AnalyticsTable extends MlInMemoryTable { static getDerivedStateFromProps(nextProps: any, prevState: any) { const derivedState = { ...prevState.prevProps, From dce041c70bc9e5e5596e3e7272dfb60f477572c9 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 27 Aug 2019 10:43:59 +0100 Subject: [PATCH 03/66] [ML] Adding error reporting to new job wizard charts (#43857) --- .../messagebar/messagebar_service.d.ts | 20 +++++++++++++ .../transform_service/delete_transform.ts | 2 +- .../transform_service/start_transform.ts | 2 +- .../transform_service/stop_transform.ts | 2 +- .../estimate_bucket_span.ts | 18 ++---------- .../multi_metric_view/metric_selection.tsx | 29 ++++++++++++------- .../bucket_span_estimator.js | 6 ++-- .../single_series_checker.js | 6 ++-- 8 files changed, 51 insertions(+), 34 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts new file mode 100644 index 0000000000000..747ee928ac0c1 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +declare interface MlMessageBarService { + getMessages(): any[]; + addMessage(msg: any): void; + removeMessage(index: number): void; + clear(): void; + info(text: any): void; + warning(text: any): void; + error(text: any, resp?: any): void; + notify: { + error(text: any, resp?: any): void; + }; +} + +export const mlMessageBarService: MlMessageBarService; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts index 824bff8e5cb39..796af91d04d40 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/delete_transform.ts @@ -13,7 +13,7 @@ import { DataFrameTransformEndpointRequest, DataFrameTransformEndpointResult, } from '../../components/transform_list/common'; -// @ts-ignore no declaration file + import { mlMessageBarService } from '../../../../../../public/components/messagebar/messagebar_service'; export const deleteTransforms = async (dataFrames: DataFrameTransformListRow[]) => { diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts index 27e558000c000..7887607ee573e 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/start_transform.ts @@ -15,7 +15,7 @@ import { DataFrameTransformEndpointRequest, DataFrameTransformEndpointResult, } from '../../components/transform_list/common'; -// @ts-ignore no declaration file + import { mlMessageBarService } from '../../../../../../public/components/messagebar/messagebar_service'; export const startTransforms = async (dataFrames: DataFrameTransformListRow[]) => { diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts index 3fa6e9c7aea39..d7e5485d22656 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/services/transform_service/stop_transform.ts @@ -15,7 +15,7 @@ import { DataFrameTransformEndpointRequest, DataFrameTransformEndpointResult, } from '../../components/transform_list/common'; -// @ts-ignore no declaration file + import { mlMessageBarService } from '../../../../../../public/components/messagebar/messagebar_service'; export const stopTransforms = async (dataFrames: DataFrameTransformListRow[]) => { diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index bfe559e5edd21..f480e199e752a 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -6,13 +6,12 @@ import { useContext, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; import { JobCreatorContext } from '../../../job_creator_context'; import { EVENT_RATE_FIELD_ID } from '../../../../../../../../common/types/fields'; import { isMultiMetricJobCreator, isPopulationJobCreator } from '../../../../../common/job_creator'; import { ml } from '../../../../../../../services/ml_api_service'; import { useKibanaContext } from '../../../../../../../contexts/kibana'; +import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; export enum ESTIMATE_STATUS { NOT_RUNNING, @@ -47,20 +46,7 @@ export function useEstimateBucketSpan() { const { name, error, message } = await ml.estimateBucketSpan(data); setStatus(ESTIMATE_STATUS.NOT_RUNNING); if (error === true) { - let text = ''; - if (message !== undefined) { - if (typeof message === 'object') { - text = message.msg || JSON.stringify(message); - } else { - text = message; - } - } - toastNotifications.addDanger({ - title: i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.bucketSpanEstimatorError', { - defaultMessage: `Bucket span estimation error`, - }), - text, - }); + mlMessageBarService.notify.error(message); } else { jobCreator.bucketSpan = name; jobCreatorUpdate(); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index c14e2e59a3387..d0c673f9c952d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -16,6 +16,7 @@ import { AggFieldPair } from '../../../../../../../../common/types/fields'; import { defaultChartSettings, ChartSettings } from '../../../charts/common/settings'; import { MetricSelector } from './metric_selector'; import { ChartGrid } from './chart_grid'; +import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; interface Props { isActive: boolean; @@ -118,7 +119,9 @@ export const MultiMetricDetectors: FC = ({ isActive, setIsValid }) => { chartLoader .loadFieldExampleValues(splitField) .then(setFieldValues) - .catch(() => {}); + .catch(error => { + mlMessageBarService.notify.error(error); + }); } else { setFieldValues([]); } @@ -154,16 +157,20 @@ export const MultiMetricDetectors: FC = ({ isActive, setIsValid }) => { if (aggFieldPairList.length > 0) { setLoadingData(true); - const resp: LineChartData = await chartLoader.loadLineCharts( - jobCreator.start, - jobCreator.end, - aggFieldPairList, - jobCreator.splitField, - fieldValues.length > 0 ? fieldValues[0] : null, - cs.intervalMs - ); - - setLineChartsData(resp); + try { + const resp: LineChartData = await chartLoader.loadLineCharts( + jobCreator.start, + jobCreator.end, + aggFieldPairList, + jobCreator.splitField, + fieldValues.length > 0 ? fieldValues[0] : null, + cs.intervalMs + ); + setLineChartsData(resp); + } catch (error) { + mlMessageBarService.notify.error(error); + setLineChartsData([]); + } setLoadingData(false); } } diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js b/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js index 6f4d5abddad62..0d67ad6b8a5a0 100644 --- a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js +++ b/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js @@ -8,6 +8,8 @@ import _ from 'lodash'; +import { mlLog } from '../../client/log'; + import { INTERVALS } from './intervals'; import { singleSeriesCheckerFactory } from './single_series_checker'; import { polledDataCheckerFactory } from './polled_data_checker'; @@ -115,7 +117,7 @@ export function estimateBucketSpanFactory(callWithRequest, elasticsearchPlugin, run() { return new Promise((resolve, reject) => { if (this.checkers.length === 0) { - console.log('BucketSpanEstimator: run has stopped because no checks were created'); + mlLog.warn('BucketSpanEstimator: run has stopped because no checks were created'); reject('BucketSpanEstimator: run has stopped because no checks were created'); } @@ -136,7 +138,7 @@ export function estimateBucketSpanFactory(callWithRequest, elasticsearchPlugin, resolve(median); } else { // no results found - console.log('BucketSpanEstimator: run has stopped because no checks returned a valid interval'); + mlLog.warn('BucketSpanEstimator: run has stopped because no checks returned a valid interval'); reject('BucketSpanEstimator: run has stopped because no checks returned a valid interval'); } } diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js b/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js index b3fa7bdb1496f..a0286b8e34b8c 100644 --- a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js +++ b/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js @@ -13,6 +13,8 @@ * Bucket spans: 5m, 10m, 30m, 1h, 3h */ + +import { mlLog } from '../../client/log'; import { INTERVALS, LONG_INTERVALS } from './intervals'; export function singleSeriesCheckerFactory(callWithRequest) { @@ -59,7 +61,7 @@ export function singleSeriesCheckerFactory(callWithRequest) { start(); }) .catch((resp) => { - console.log('SingleSeriesChecker: Could not load metric reference data', this); + mlLog.warn('SingleSeriesChecker: Could not load metric reference data'); reject(resp); }); } @@ -145,7 +147,7 @@ export function singleSeriesCheckerFactory(callWithRequest) { } } } else { - console.log('SingleSeriesChecker: runTest stopped because fullBuckets is empty', this); + mlLog.warn('SingleSeriesChecker: runTest stopped because fullBuckets is empty'); reject('runTest stopped because fullBuckets is empty'); } }) From 1d0c1c2196418a169d694c3df3b75615d71900b5 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Tue, 27 Aug 2019 06:03:56 -0400 Subject: [PATCH 04/66] Security - allow for custom cluster privileges (#43817) * allow for custom cluster privileges * improve prop/state names * update snapshot * remove unnecessary code * removing state altogether --- .../cluster_privileges.test.tsx.snap | 2 ++ .../elasticsearch_privileges.test.tsx.snap | 2 +- .../privileges/es/cluster_privileges.test.tsx | 32 ++++++++++++++++++- .../privileges/es/cluster_privileges.tsx | 21 ++++++++++-- .../es/elasticsearch_privileges.tsx | 2 +- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap index 457473172c2e6..b38b7e6634ada 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap @@ -7,10 +7,12 @@ exports[`it renders without crashing 1`] = ` > { const role: Role = { @@ -24,8 +25,37 @@ test('it renders without crashing', () => { ); expect(wrapper).toMatchSnapshot(); }); + +test('it allows for custom cluster privileges', () => { + const role: Role = { + name: '', + elasticsearch: { + cluster: ['existing-custom', 'monitor'], + indices: [], + run_as: [], + }, + kibana: [], + }; + + const onChange = jest.fn(); + const wrapper = mountWithIntl( + + ); + + const clusterPrivsSelect = wrapper.find( + 'EuiComboBox[data-test-subj="cluster-privileges-combobox"]' + ); + + (clusterPrivsSelect.props() as any).onCreateOption('custom-cluster-privilege'); + + expect(onChange).toHaveBeenCalledWith(['existing-custom', 'monitor', 'custom-cluster-privilege']); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/cluster_privileges.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/cluster_privileges.tsx index b6bd1930a1d27..7d9dab6bebffd 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/cluster_privileges.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/cluster_privileges.tsx @@ -6,18 +6,20 @@ import { EuiComboBox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { Component } from 'react'; +import _ from 'lodash'; import { Role } from '../../../../../../../common/model'; import { isReadOnlyRole } from '../../../../../../lib/role_utils'; interface Props { role: Role; - availableClusterPrivileges: string[]; + builtinClusterPrivileges: string[]; onChange: (privs: string[]) => void; } export class ClusterPrivileges extends Component { public render() { - return {this.buildComboBox(this.props.availableClusterPrivileges)}; + const availableClusterPrivileges = this.getAvailableClusterPrivileges(); + return {this.buildComboBox(availableClusterPrivileges)}; } public buildComboBox = (items: string[]) => { @@ -32,9 +34,11 @@ export class ClusterPrivileges extends Component { return ( @@ -44,4 +48,17 @@ export class ClusterPrivileges extends Component { public onClusterPrivilegesChange = (selectedPrivileges: any) => { this.props.onChange(selectedPrivileges.map((priv: any) => priv.label)); }; + + private onCreateCustomPrivilege = (customPrivilege: string) => { + this.props.onChange([...this.props.role.elasticsearch.cluster, customPrivilege]); + }; + + private getAvailableClusterPrivileges = () => { + const availableClusterPrivileges = [ + ...this.props.builtinClusterPrivileges, + ...this.props.role.elasticsearch.cluster, + ]; + + return _.uniq(availableClusterPrivileges); + }; } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx index 7540cf2cedab7..c0e6db3fef21c 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/elasticsearch_privileges.tsx @@ -99,7 +99,7 @@ export class ElasticsearchPrivileges extends Component { From 5917cd375f7e66a1a24582b64ee20c41b2a2619e Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Tue, 27 Aug 2019 13:34:56 +0300 Subject: [PATCH 05/66] Telemetry/opt in welcome screen (#42110) * refactor opt_in_message * welcome optin card * finish optin in welcome screen * add steps * disable current step * remove checkbox option * add metrics * fix typescript checks * hide in oss * update snapshots * only require TelemetryOptInProvider if telemetry is enabled * pass telemetry description from service * remove x-pack import * remove x-pack import * update image * update per design feedback * update props * fix in oss * await before moving to next screen * utlize bannerId in telemtry provider * keep export in 1 line * ts-ignore banner import * add test * update tests * update translations * update home tests * remove extra license header * showTelemetryOptIn -> shouldShowTelemetryOptIn * remote extra EuiTexts * update jest snapshot * unencrypted telemetry example --- .../kibana/public/assets/illo_telemetry.png | Bin 0 -> 559051 bytes .../__snapshots__/home.test.js.snap | 3 + .../public/home/components/_welcome.scss | 3 +- .../kibana/public/home/components/home.js | 8 + .../public/home/components/home.test.js | 24 +- .../public/home/components/home.test.mocks.ts | 45 +++ .../kibana/public/home/components/home_app.js | 10 +- .../home/components/sample_data/index.tsx | 71 +++++ .../home/components/telemetry_opt_in/index.ts | 23 ++ .../opt_in_details_component.tsx | 163 +++++++++++ .../telemetry_opt_in/opt_in_message.tsx | 106 +++++++ .../telemetry_opt_in_card.tsx | 81 ++++++ .../kibana/public/home/components/welcome.js | 130 --------- .../kibana/public/home/components/welcome.tsx | 161 +++++++++++ .../kibana/public/home/kibana_services.js | 14 + .../kibana/public/home}/telemetry_opt_in.js | 31 +- src/legacy/ui/public/styles/_mixins.scss | 4 +- .../telemetry_opt_in.test.js.snap | 272 +----------------- .../telemetry_opt_in/telemetry_opt_in.js | 4 +- .../public/lib/telemetry.js | 4 +- ...=> opt_in_details_component.test.tsx.snap} | 0 .../public/components/{index.js => index.ts} | 3 + .../components/opt_in_banner_component.tsx | 53 ++++ ...t.js => opt_in_details_component.test.tsx} | 9 +- ...ponent.js => opt_in_details_component.tsx} | 96 +++---- .../public/components/opt_in_message.tsx | 97 +++++++ .../public/components/telemetry_form.test.js | 1 + .../welcome_banner/__tests__/render_banner.js | 33 --- .../hacks/welcome_banner/click_banner.js | 4 +- .../click_banner.js => click_banner.test.js} | 49 ++-- ...ettings.js => handle_old_settings.test.js} | 101 ++++--- .../welcome_banner/opt_in_banner_component.js | 147 ---------- .../hacks/welcome_banner/render_banner.js | 6 +- .../welcome_banner/render_banner.test.js | 30 ++ ...w_banner.js => should_show_banner.test.js} | 36 +-- .../public/services/telemetry_opt_in.test.js | 13 +- .../services/telemetry_opt_in.test.mocks.js | 27 ++ .../public/services/telemetry_opt_in.ts | 8 + .../translations/translations/ja-JP.json | 4 +- .../translations/translations/zh-CN.json | 4 +- 40 files changed, 1099 insertions(+), 779 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/assets/illo_telemetry.png create mode 100644 src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts create mode 100644 src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx create mode 100644 src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx create mode 100644 src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx create mode 100644 src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/home/components/welcome.js create mode 100644 src/legacy/core_plugins/kibana/public/home/components/welcome.tsx rename {x-pack/legacy/plugins/telemetry/public/services => src/legacy/core_plugins/kibana/public/home}/telemetry_opt_in.js (52%) rename x-pack/legacy/plugins/telemetry/public/components/__snapshots__/{opt_in_details_component.test.js.snap => opt_in_details_component.test.tsx.snap} (100%) rename x-pack/legacy/plugins/telemetry/public/components/{index.js => index.ts} (74%) create mode 100644 x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx rename x-pack/legacy/plugins/telemetry/public/components/{opt_in_details_component.test.js => opt_in_details_component.test.tsx} (74%) rename x-pack/legacy/plugins/telemetry/public/components/{opt_in_details_component.js => opt_in_details_component.tsx} (65%) create mode 100644 x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js rename x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/{__tests__/click_banner.js => click_banner.test.js} (64%) rename x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/{__tests__/handle_old_settings.js => handle_old_settings.test.js} (61%) delete mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js create mode 100644 x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js rename x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/{__tests__/should_show_banner.js => should_show_banner.test.js} (69%) create mode 100644 x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js create mode 100644 x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts diff --git a/src/legacy/core_plugins/kibana/public/assets/illo_telemetry.png b/src/legacy/core_plugins/kibana/public/assets/illo_telemetry.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d6dac59ab24ad76705ec6ecb09e6e6600f3689 GIT binary patch literal 559051 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 z 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

TOC2e@Vt1Yif76p0+2bCEth8b-AbdF49y==w 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%0A} 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

  • )tbN@b|1~QIE?_g_#7b3qf!cex!QI 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

    2h%BHX+Xk7t(lU)mWU89gjve4wFU=u$ORnLfg5Y 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 z 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

    fMjWgQcO}=nM~`?BcpD3#sQF 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 literal 0 HcmV?d00001 diff --git a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap index 2331fdd559c91..0e73c0cbc93a5 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/home/components/__snapshots__/home.test.js.snap @@ -1135,7 +1135,10 @@ exports[`home welcome should show the normal home page if welcome screen is disa exports[`home welcome should show the welcome screen if enabled, and there are no index patterns defined 1`] = ` `; diff --git a/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss b/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss index 821fbe46b1404..d8c777534647b 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss +++ b/src/legacy/core_plugins/kibana/public/home/components/_welcome.scss @@ -1,6 +1,5 @@ - .homWelcome { - @include kibanaFullScreenGraphics; + @include kibanaFullScreenGraphics($euiZLevel6); } .homWelcome__header { diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 3b7e2c0990c6f..76f15f797ed6c 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -225,6 +225,10 @@ export class Home extends Component { ); } @@ -247,6 +251,10 @@ export class Home extends Component { Home.propTypes = { addBasePath: PropTypes.func.isRequired, + fetchTelemetry: PropTypes.func.isRequired, + getTelemetryBannerId: PropTypes.func.isRequired, + setOptIn: PropTypes.func.isRequired, + shouldShowTelemetryOptIn: PropTypes.bool.isRequired, directories: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/components/home.test.js index c10aa3f0b1e32..aa520ba2ed5f9 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.js @@ -17,30 +17,14 @@ * under the License. */ +import './home.test.mocks'; + import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { Home } from './home'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -jest.mock( - 'ui/chrome', - () => ({ - getBasePath: jest.fn(() => 'path'), - getInjected: jest.fn(() => ''), - }), - { virtual: true } -); - -jest.mock( - 'ui/capabilities', - () => ({ - catalogue: {}, - management: {}, - navLinks: {} - }) -); - describe('home', () => { let defaultProps; @@ -50,6 +34,10 @@ describe('home', () => { apmUiEnabled: true, mlEnabled: true, kibanaVersion: '99.2.1', + fetchTelemetry: jest.fn(), + getTelemetryBannerId: jest.fn(), + setOptIn: jest.fn(), + showTelemetryOptIn: false, addBasePath(url) { return `base_path/${url}`; }, diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts new file mode 100644 index 0000000000000..1eed597a90a4b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts @@ -0,0 +1,45 @@ +/* + * 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 { notificationServiceMock } from '../../../../../../core/public/mocks'; + +jest.doMock('ui/new_platform', () => { + return { + npSetup: { + core: { + notifications: notificationServiceMock.createSetupContract(), + }, + }, + }; +}); + +jest.doMock( + 'ui/chrome', + () => ({ + getBasePath: jest.fn(() => 'path'), + getInjected: jest.fn(() => ''), + }), + { virtual: true } +); + +jest.doMock('ui/capabilities', () => ({ + catalogue: {}, + management: {}, + navLinks: {}, +})); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index fc24eeaa0f451..9aa44863f6d70 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -30,12 +30,10 @@ import { } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; +import { telemetryOptInProvider, shouldShowTelemetryOptIn } from '../kibana_services'; import chrome from 'ui/chrome'; -export function HomeApp({ - directories, -}) { - +export function HomeApp({ directories }) { const isCloudEnabled = chrome.getInjected('isCloudEnabled', false); const apmUiEnabled = chrome.getInjected('apmUiEnabled', true); const mlEnabled = chrome.getInjected('mlEnabled', false); @@ -94,6 +92,10 @@ export function HomeApp({ find={savedObjectsClient.find} localStorage={localStorage} urlBasePath={chrome.getBasePath()} + shouldShowTelemetryOptIn={shouldShowTelemetryOptIn} + setOptIn={telemetryOptInProvider.setOptIn} + fetchTelemetry={telemetryOptInProvider.fetchExample} + getTelemetryBannerId={telemetryOptInProvider.getBannerId} /> diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx b/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx new file mode 100644 index 0000000000000..1bb8bd214d2cb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data/index.tsx @@ -0,0 +1,71 @@ +/* + * 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. + */ + +/* + * The UI and related logic for the welcome screen that *should* show only + * when it is enabled (the default) and there is no Kibana-consumed data + * in Elasticsearch. + */ + +import React from 'react'; +import { + // @ts-ignore + EuiCard, + EuiButton, + EuiButtonEmpty, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + urlBasePath: string; + onDecline: () => void; + onConfirm: () => void; +} + +export function SampleDataCard({ urlBasePath, onDecline, onConfirm }: Props) { + return ( + } + description={ + + } + footer={ +

    + + + + + + +
    + } + /> + ); +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts new file mode 100644 index 0000000000000..63636433bc00b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { renderTelemetryOptInCard, Props } from './telemetry_opt_in_card'; + +export const TelemetryOptInCard = (props: Props) => { + return renderTelemetryOptInCard(props); +}; diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx new file mode 100644 index 0000000000000..d90f54b2bcb54 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_details_component.tsx @@ -0,0 +1,163 @@ +/* + * 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 * as React from 'react'; + +import { + EuiCallOut, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiLoadingSpinner, + EuiPortal, // EuiPortal is a temporary requirement to use EuiFlyout with "ownFocus" + EuiText, + EuiTextColor, + EuiTitle, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + fetchTelemetry: () => Promise; + onClose: () => void; +} + +interface State { + isLoading: boolean; + hasPrivilegeToRead: boolean; + data: any[] | null; +} + +/** + * React component for displaying the example data associated with the Telemetry opt-in banner. + */ +export class OptInExampleFlyout extends React.PureComponent { + public readonly state: State = { + data: null, + isLoading: true, + hasPrivilegeToRead: false, + }; + + componentDidMount() { + this.props + .fetchTelemetry() + .then(response => + this.setState({ + data: Array.isArray(response.data) ? response.data : null, + isLoading: false, + hasPrivilegeToRead: true, + }) + ) + .catch(err => { + this.setState({ + isLoading: false, + hasPrivilegeToRead: err.status !== 403, + }); + }); + } + + renderBody({ data, isLoading, hasPrivilegeToRead }: State) { + if (isLoading) { + return ( + + + + + + ); + } + + if (!hasPrivilegeToRead) { + return ( + + } + color="danger" + iconType="cross" + > + + + ); + } + + if (data === null) { + return ( + + } + color="danger" + iconType="cross" + > + + + ); + } + + return {JSON.stringify(data, null, 2)}; + } + + render() { + return ( + + + + +

    + +

    +
    + + + + + +
    + {this.renderBody(this.state)} +
    +
    + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx new file mode 100644 index 0000000000000..0f581e819b052 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/opt_in_message.tsx @@ -0,0 +1,106 @@ +/* + * 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 * as React from 'react'; +import { EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { OptInExampleFlyout } from './opt_in_details_component'; + +interface Props { + fetchTelemetry: () => Promise; +} + +interface State { + showDetails: boolean; + showExample: boolean; +} + +export class OptInMessage extends React.PureComponent { + public readonly state: State = { + showDetails: false, + showExample: false, + }; + + toggleShowExample = () => { + this.setState(prevState => ({ + showExample: !prevState.showExample, + })); + }; + + render() { + const { fetchTelemetry } = this.props; + const { showDetails, showExample } = this.state; + + const getDetails = () => ( + + + + ), + telemetryPrivacyStatementLink: ( + + + + ), + }} + /> + ); + + const getFlyoutDetails = () => ( + this.setState({ showExample: false })} + fetchTelemetry={fetchTelemetry} + /> + ); + + const getReadMore = () => ( + this.setState({ showDetails: true })}> + + + ); + + return ( + + {' '} + {!showDetails && getReadMore()} + {showDetails && getDetails()} + {showDetails && showExample && getFlyoutDetails()} + + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx new file mode 100644 index 0000000000000..5fa842291b028 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/telemetry_opt_in/telemetry_opt_in_card.tsx @@ -0,0 +1,81 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + // @ts-ignore + EuiCard, + EuiButton, +} from '@elastic/eui'; +import { OptInMessage } from './opt_in_message'; + +export interface Props { + urlBasePath: string; + onConfirm: () => void; + onDecline: () => void; + fetchTelemetry: () => Promise; +} + +export function renderTelemetryOptInCard({ + urlBasePath, + fetchTelemetry, + onConfirm, + onDecline, +}: Props) { + return ( + + } + description={} + footer={ +
    + + + + + + +
    + } + /> + ); +} diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.js b/src/legacy/core_plugins/kibana/public/home/components/welcome.js deleted file mode 100644 index 98560b748ec0d..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.js +++ /dev/null @@ -1,130 +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. - */ - -/* - * The UI and related logic for the welcome screen that *should* show only - * when it is enabled (the default) and there is no Kibana-consumed data - * in Elasticsearch. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { - EuiCard, - EuiTitle, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - EuiText, - EuiIcon, - EuiButton, - EuiButtonEmpty, - EuiPortal, -} from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -/** - * Shows a full-screen welcome page that gives helpful quick links to beginners. - */ -export class Welcome extends React.Component { - hideOnEsc = e => { - if (e.key === 'Escape') { - this.props.onSkip(); - } - }; - - componentDidMount() { - document.addEventListener('keydown', this.hideOnEsc); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.hideOnEsc); - } - - render() { - const { urlBasePath, onSkip } = this.props; - - return ( - -
    -
    -
    - - - - - -

    - -

    -
    - -

    - -

    -
    - -
    -
    -
    - - - } - description={ - } - footer={ -
    - - - - - - -
    - } - /> -
    -
    -
    -
    -
    - ); - } -} - -Welcome.propTypes = { - urlBasePath: PropTypes.string.isRequired, - onSkip: PropTypes.func.isRequired, -}; diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx new file mode 100644 index 0000000000000..8869819290263 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -0,0 +1,161 @@ +/* + * 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. + */ + +/* + * The UI and related logic for the welcome screen that *should* show only + * when it is enabled (the default) and there is no Kibana-consumed data + * in Elasticsearch. + */ + +import React from 'react'; +import { + EuiTitle, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiIcon, + EuiPortal, +} from '@elastic/eui'; +// @ts-ignore +import { banners } from 'ui/notify'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import chrome from 'ui/chrome'; +import { SampleDataCard } from './sample_data'; +import { TelemetryOptInCard } from './telemetry_opt_in'; +// @ts-ignore +import { trackUiMetric, METRIC_TYPE } from '../kibana_services'; + +interface Props { + urlBasePath: string; + onSkip: () => {}; + fetchTelemetry: () => Promise; + setOptIn: (enabled: boolean) => Promise; + getTelemetryBannerId: () => string; + shouldShowTelemetryOptIn: boolean; +} +interface State { + step: number; +} + +/** + * Shows a full-screen welcome page that gives helpful quick links to beginners. + */ +export class Welcome extends React.PureComponent { + public readonly state: State = { + step: 0, + }; + + private hideOnEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + this.props.onSkip(); + } + }; + + private redirecToSampleData() { + const path = chrome.addBasePath('#/home/tutorial_directory/sampleData'); + window.location.href = path; + } + private async handleTelemetrySelection(confirm: boolean) { + const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`; + trackUiMetric(METRIC_TYPE.CLICK, metricName); + await this.props.setOptIn(confirm); + const bannerId = this.props.getTelemetryBannerId(); + banners.remove(bannerId); + this.setState(() => ({ step: 1 })); + } + + private onSampleDataDecline = () => { + trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.props.onSkip(); + }; + private onSampleDataConfirm = () => { + trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.redirecToSampleData(); + }; + + componentDidMount() { + trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); + if (this.props.shouldShowTelemetryOptIn) { + trackUiMetric(METRIC_TYPE.COUNT, 'welcomeScreenWithTelemetryOptIn'); + } + document.addEventListener('keydown', this.hideOnEsc); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.hideOnEsc); + } + + render() { + const { urlBasePath, shouldShowTelemetryOptIn, fetchTelemetry } = this.props; + const { step } = this.state; + + return ( + +
    +
    +
    + + + + + +

    + +

    +
    + +

    + +

    +
    + +
    +
    +
    + + + {shouldShowTelemetryOptIn && step === 0 && ( + + )} + {(!shouldShowTelemetryOptIn || step === 1) && ( + + )} + + + +
    +
    +
    + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.js b/src/legacy/core_plugins/kibana/public/home/kibana_services.js index fc06a61ae343d..c5480e16491a9 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.js +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.js @@ -18,9 +18,23 @@ */ import { uiModules } from 'ui/modules'; +import { npStart } from 'ui/new_platform'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { TelemetryOptInProvider } from './telemetry_opt_in'; export let indexPatternService; +export let shouldShowTelemetryOptIn; +export let telemetryOptInProvider; + +export const trackUiMetric = createUiStatsReporter('Kibana_home'); +export { METRIC_TYPE }; uiModules.get('kibana').run(($injector) => { + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + const Private = $injector.get('Private'); + + telemetryOptInProvider = Private(TelemetryOptInProvider); + shouldShowTelemetryOptIn = telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); indexPatternService = $injector.get('indexPatterns'); }); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js b/src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js similarity index 52% rename from x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js rename to src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js index 2fcd2012a1528..274820844da45 100644 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.js +++ b/src/legacy/core_plugins/kibana/public/home/telemetry_opt_in.js @@ -1,20 +1,37 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 moment from 'moment'; import { setCanTrackUiMetrics } from 'ui/ui_metric'; import { toastNotifications } from 'ui/notify'; +import { npStart } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; export function TelemetryOptInProvider($injector, chrome) { - let currentOptInStatus = $injector.get('telemetryOptedIn'); - setCanTrackUiMetrics(currentOptInStatus); + let currentOptInStatus = npStart.core.injectedMetadata.getInjectedVar('telemetryOptedIn'); + let bannerId = null; + setCanTrackUiMetrics(currentOptInStatus); const provider = { + getBannerId: () => bannerId, getOptIn: () => currentOptInStatus, + setBannerId(id) { bannerId = id; }, setOptIn: async (enabled) => { setCanTrackUiMetrics(enabled); const $http = $injector.get('$http'); @@ -24,10 +41,10 @@ export function TelemetryOptInProvider($injector, chrome) { currentOptInStatus = enabled; } catch (error) { toastNotifications.addError(error, { - title: i18n.translate('xpack.telemetry.optInErrorToastTitle', { + title: i18n.translate('kbn.home.telemetry.optInErrorToastTitle', { defaultMessage: 'Error', }), - toastMessage: i18n.translate('xpack.telemetry.optInErrorToastText', { + toastMessage: i18n.translate('kbn.home.telemetry.optInErrorToastText', { defaultMessage: 'An error occured while trying to set the usage statistics preference.', }), }); diff --git a/src/legacy/ui/public/styles/_mixins.scss b/src/legacy/ui/public/styles/_mixins.scss index cb027ee684a17..ae529a4678d5d 100644 --- a/src/legacy/ui/public/styles/_mixins.scss +++ b/src/legacy/ui/public/styles/_mixins.scss @@ -55,13 +55,13 @@ } } -@mixin kibanaFullScreenGraphics() { +@mixin kibanaFullScreenGraphics($euiZLevel: $euiZLevel9) { position: fixed; top: 0; left: 0; right: 0; bottom: 0; - z-index: $euiZLevel9 + 1000; + z-index: $euiZLevel + 1000; background: inherit; background-image: linear-gradient(0deg, $euiColorLightestShade 0%, $euiColorEmptyShade 100%); opacity: 0; diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap index e07d9144c01c5..642b8399ff6d1 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/telemetry_opt_in.test.js.snap @@ -104,277 +104,7 @@ exports[`TelemetryOptIn should display when telemetry not opted in 1`] = ` "timeZone": null, } } -> - -
    - - -

    - - Help Elastic support provide better service - -

    -
    - -
    - - - - - - } - className="eui-AlignBaseline" - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="readMorePopover" - isOpen={false} - ownFocus={true} - panelPaddingSize="m" - > - -

    - - - , - "telemetryPrivacyStatementLink": - - , - } - } - /> -

    -
    - , - } - } - /> - - } - onChange={[Function]} - > -
    - -
    - -
    - - +/> `; exports[`TelemetryOptIn should not display when telemetry is opted in 1`] = ` diff --git a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js b/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js index 1bd5f075449e2..35403ccb672f3 100644 --- a/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js +++ b/x-pack/legacy/plugins/license_management/public/components/telemetry_opt_in/telemetry_opt_in.js @@ -13,7 +13,7 @@ import { EuiTitle, EuiPopover } from '@elastic/eui'; -import { showTelemetryOptIn, getTelemetryFetcher, PRIVACY_STATEMENT_URL, OptInExampleFlyout } from '../../lib/telemetry'; +import { shouldShowTelemetryOptIn, getTelemetryFetcher, PRIVACY_STATEMENT_URL, OptInExampleFlyout } from '../../lib/telemetry'; import { FormattedMessage } from '@kbn/i18n/react'; export class TelemetryOptIn extends React.Component { @@ -127,7 +127,7 @@ export class TelemetryOptIn extends React.Component { ); - return showTelemetryOptIn() ? ( + return shouldShowTelemetryOptIn() ? ( {example} {toCurrentCustomers} diff --git a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js b/x-pack/legacy/plugins/license_management/public/lib/telemetry.js index 79c65618cb1a1..08928549916eb 100644 --- a/x-pack/legacy/plugins/license_management/public/lib/telemetry.js +++ b/x-pack/legacy/plugins/license_management/public/lib/telemetry.js @@ -24,9 +24,9 @@ export const setTelemetryOptInService = (aTelemetryOptInService) => { export const optInToTelemetry = async (enableTelemetry) => { await telemetryOptInService.setOptIn(enableTelemetry); }; -export const showTelemetryOptIn = () => { +export const shouldShowTelemetryOptIn = () => { return telemetryEnabled && !telemetryOptInService.getOptIn(); }; export const getTelemetryFetcher = () => { - return fetchTelemetry(httpClient); + return fetchTelemetry(httpClient, { unencrypted: true }); }; diff --git a/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.js.snap b/x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.js.snap rename to x-pack/legacy/plugins/telemetry/public/components/__snapshots__/opt_in_details_component.test.tsx.snap diff --git a/x-pack/legacy/plugins/telemetry/public/components/index.js b/x-pack/legacy/plugins/telemetry/public/components/index.ts similarity index 74% rename from x-pack/legacy/plugins/telemetry/public/components/index.js rename to x-pack/legacy/plugins/telemetry/public/components/index.ts index 3461da4fdcaca..9675bd997b183 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/index.js +++ b/x-pack/legacy/plugins/telemetry/public/components/index.ts @@ -4,5 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore export { TelemetryForm } from './telemetry_form'; export { OptInExampleFlyout } from './opt_in_details_component'; +export { OptInBanner } from './opt_in_banner_component'; +export { OptInMessage } from './opt_in_message'; diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx new file mode 100644 index 0000000000000..19754504c081e --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_banner_component.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { OptInMessage } from './opt_in_message'; + +interface Props { + fetchTelemetry: () => Promise; + optInClick: (optIn: boolean) => void; +} + +/** + * React component for displaying the Telemetry opt-in banner. + */ +export class OptInBanner extends React.PureComponent { + render() { + const title = ( + + ); + return ( + + + + + + this.props.optInClick(true)}> + + + + + this.props.optInClick(false)}> + + + + + + ); + } +} diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.js b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx similarity index 74% rename from x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.js rename to x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx index 358735ff95c8f..c58927c66756b 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.js +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.test.tsx @@ -9,8 +9,13 @@ import { OptInExampleFlyout } from './opt_in_details_component'; describe('OptInDetailsComponent', () => { it('renders as expected', () => { - expect(shallowWithIntl( - ({ data: [] }))} onClose={jest.fn()} />) + expect( + shallowWithIntl( + ({ data: [] }))} + onClose={jest.fn()} + /> + ) ).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.js b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx similarity index 65% rename from x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.js rename to x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx index aac7ebf1b625b..9cfb55af1dab3 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.js +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_details_component.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { EuiCallOut, @@ -24,39 +23,37 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; +interface Props { + fetchTelemetry: () => Promise; + onClose: () => void; +} + +interface State { + isLoading: boolean; + hasPrivilegeToRead: boolean; + data: any[] | null; +} + /** * React component for displaying the example data associated with the Telemetry opt-in banner. */ -export class OptInExampleFlyout extends Component { - - static propTypes = { - /** - * Callback function with no parameters that returns a {@code Promise} containing the - * telemetry data (expected to be an array). - */ - fetchTelemetry: PropTypes.func.isRequired, - /** - * Callback function with no parameters that closes this flyout. - */ - onClose: PropTypes.func.isRequired, - } - - constructor(props) { - super(props); - this.state = { - data: null, - isLoading: true, - hasPrivilegeToRead: false, - }; - } +export class OptInExampleFlyout extends React.PureComponent { + public readonly state: State = { + data: null, + isLoading: true, + hasPrivilegeToRead: false, + }; componentDidMount() { - this.props.fetchTelemetry() - .then(response => this.setState({ - data: Array.isArray(response.data) ? response.data : null, - isLoading: false, - hasPrivilegeToRead: true, - })) + this.props + .fetchTelemetry() + .then(response => + this.setState({ + data: Array.isArray(response.data) ? response.data : null, + isLoading: false, + hasPrivilegeToRead: true, + }) + ) .catch(err => { this.setState({ isLoading: false, @@ -65,7 +62,7 @@ export class OptInExampleFlyout extends Component { }); } - renderBody({ data, isLoading, hasPrivilegeToRead }) { + renderBody({ data, isLoading, hasPrivilegeToRead }: State) { if (isLoading) { return ( @@ -79,10 +76,12 @@ export class OptInExampleFlyout extends Component { if (!hasPrivilegeToRead) { return ( } + title={ + + } color="danger" iconType="cross" > @@ -97,10 +96,12 @@ export class OptInExampleFlyout extends Component { if (data === null) { return ( } + title={ + + } color="danger" iconType="cross" > @@ -114,21 +115,13 @@ export class OptInExampleFlyout extends Component { ); } - return ( - - {JSON.stringify(data, null, 2)} - - ); + return {JSON.stringify(data, null, 2)}; } render() { return ( - +

    @@ -149,12 +142,9 @@ export class OptInExampleFlyout extends Component { - - {this.renderBody(this.state)} - + {this.renderBody(this.state)} ); } - } diff --git a/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx b/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx new file mode 100644 index 0000000000000..5be20f8de32c1 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/components/opt_in_message.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { OptInExampleFlyout } from './opt_in_details_component'; + +interface Props { + fetchTelemetry: () => Promise; +} + +interface State { + showDetails: boolean; + showExample: boolean; +} + +export class OptInMessage extends React.PureComponent { + public readonly state: State = { + showDetails: false, + showExample: false, + }; + + toggleShowExample = () => { + this.setState(prevState => ({ + showExample: !prevState.showExample, + })); + }; + + render() { + const { showDetails, showExample } = this.state; + + const getDetails = () => ( + +

    + + + + ), + telemetryPrivacyStatementLink: ( + + + + ), + }} + /> +

    +
    + ); + + const getFlyoutDetails = () => ( + this.setState({ showExample: false })} + fetchTelemetry={this.props.fetchTelemetry} + /> + ); + + const getReadMore = () => ( + this.setState({ showDetails: true })}> + + + ); + + return ( + + +

    + {getConfigTelemetryDesc()} {!showDetails && getReadMore()} +

    +
    + {showDetails && getDetails()} + {showDetails && showExample && getFlyoutDetails()} +
    + ); + } +} diff --git a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js b/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js index ee9418f8da2db..4df9cc0da1695 100644 --- a/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js +++ b/x-pack/legacy/plugins/telemetry/public/components/telemetry_form.test.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import '../services/telemetry_opt_in.test.mocks'; import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { TelemetryForm } from './telemetry_form'; diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js deleted file mode 100644 index 0ba0a21b6413b..0000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/render_banner.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { renderBanner } from '../render_banner'; - -describe('render_banner', () => { - - it('adds a banner to banners with priority of 10000', () => { - const config = { }; - const banners = { - add: sinon.stub() - }; - const fetchTelemetry = sinon.stub(); - banners.add.returns('brucer-banner'); - - renderBanner(config, fetchTelemetry, { _banners: banners }); - - expect(banners.add.calledOnce).to.be(true); - expect(fetchTelemetry.called).to.be(false); - - const bannerConfig = banners.add.getCall(0).args[0]; - - expect(bannerConfig.component).not.to.be(undefined); - expect(bannerConfig.priority).to.be(10000); - }); - -}); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js index 979eb635c5c5e..f337a8025e01d 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.js @@ -16,18 +16,16 @@ import { FormattedMessage } from '@kbn/i18n/react'; /** * Handle clicks from the user on the opt-in banner. * - * @param {String} bannerId Banner ID to close upon success. * @param {Object} telemetryOptInProvider the telemetry opt-in provider * @param {Boolean} optIn {@code true} to opt into telemetry. * @param {Object} _banners Singleton banners. Can be overridden for tests. * @param {Object} _toastNotifications Singleton toast notifications. Can be overridden for tests. */ export async function clickBanner( - bannerId, telemetryOptInProvider, optIn, { _banners = banners, _toastNotifications = toastNotifications } = {}) { - + const bannerId = telemetryOptInProvider.getBannerId(); let set = false; try { diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/click_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js similarity index 64% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/click_banner.js rename to x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js index ddc953b710658..751a8f5498ee5 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/click_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/click_banner.test.js @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; +import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; + import sinon from 'sinon'; import { uiModules } from 'ui/modules'; @@ -13,16 +14,12 @@ uiModules.get('kibana') // MockInjector used in these tests is not impacted .constant('telemetryOptedIn', null); -import { - clickBanner, -} from '../click_banner'; -import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in'; +import { clickBanner } from './click_banner'; +import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; const getMockInjector = ({ simulateFailure }) => { const get = sinon.stub(); - get.withArgs('telemetryOptedIn').returns(null); - const mockHttp = { post: sinon.stub() }; @@ -60,16 +57,18 @@ describe('click_banner', () => { remove: sinon.spy() }; + const optIn = true; + const bannerId = 'bruce-banner'; + mockInjectedMetadata({ telemetryOptedIn: optIn }); const telemetryOptInProvider = getTelemetryOptInProvider(); - const bannerId = 'bruce-banner'; - const optIn = true; + telemetryOptInProvider.setBannerId(bannerId); - await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners }); + await clickBanner(telemetryOptInProvider, optIn, { _banners: banners }); - expect(telemetryOptInProvider.getOptIn()).to.be(optIn); - expect(banners.remove.calledOnce).to.be(true); - expect(banners.remove.calledWith(bannerId)).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(optIn); + expect(banners.remove.calledOnce).toBe(true); + expect(banners.remove.calledWith(bannerId)).toBe(true); }); it('sets setting unsuccessfully, adds toast, and does not touch banner', async () => { @@ -79,15 +78,15 @@ describe('click_banner', () => { const banners = { remove: sinon.spy() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ simulateFailure: true }); - const bannerId = 'bruce-banner'; const optIn = true; + mockInjectedMetadata({ telemetryOptedIn: null }); + const telemetryOptInProvider = getTelemetryOptInProvider({ simulateFailure: true }); - await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); + await clickBanner(telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); - expect(telemetryOptInProvider.getOptIn()).to.be(null); - expect(toastNotifications.addDanger.calledOnce).to.be(true); - expect(banners.remove.notCalled).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(null); + expect(toastNotifications.addDanger.calledOnce).toBe(true); + expect(banners.remove.notCalled).toBe(true); }); it('sets setting unsuccessfully with error, adds toast, and does not touch banner', async () => { @@ -97,15 +96,15 @@ describe('click_banner', () => { const banners = { remove: sinon.spy() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ simulateError: true }); - const bannerId = 'bruce-banner'; const optIn = false; + mockInjectedMetadata({ telemetryOptedIn: null }); + const telemetryOptInProvider = getTelemetryOptInProvider({ simulateError: true }); - await clickBanner(bannerId, telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); + await clickBanner(telemetryOptInProvider, optIn, { _banners: banners, _toastNotifications: toastNotifications }); - expect(telemetryOptInProvider.getOptIn()).to.be(null); - expect(toastNotifications.addDanger.calledOnce).to.be(true); - expect(banners.remove.notCalled).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(null); + expect(toastNotifications.addDanger.calledOnce).toBe(true); + expect(banners.remove.notCalled).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/handle_old_settings.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js similarity index 61% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/handle_old_settings.js rename to x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js index 9e61e08cc1b26..40e3bb042fa88 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/handle_old_settings.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/handle_old_settings.test.js @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; +import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; + import sinon from 'sinon'; -import { CONFIG_TELEMETRY } from '../../../../common/constants'; -import { handleOldSettings } from '../handle_old_settings'; -import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in'; +import { CONFIG_TELEMETRY } from '../../../common/constants'; +import { handleOldSettings } from './handle_old_settings'; +import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) => { const $http = { @@ -24,15 +25,13 @@ const getTelemetryOptInProvider = (enabled, { simulateFailure = false } = {}) => const chrome = { addBasePath: url => url }; + mockInjectedMetadata({ telemetryOptedIn: enabled }); const $injector = { get: (key) => { if (key === '$http') { return $http; } - if (key === 'telemetryOptedIn') { - return enabled; - } throw new Error(`unexpected mock injector usage for ${key}`); } }; @@ -50,22 +49,22 @@ describe('handle_old_settings', () => { }; const telemetryOptInProvider = getTelemetryOptInProvider(null); - expect(telemetryOptInProvider.getOptIn()).to.be(null); + expect(telemetryOptInProvider.getOptIn()).toBe(null); config.get.withArgs('xPackMonitoring:allowReport', null).returns(true); config.set.withArgs(CONFIG_TELEMETRY, true).returns(Promise.resolve(true)); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(true); }); it('re-uses old "telemetry:optIn" setting and stays opted in', async () => { @@ -76,22 +75,22 @@ describe('handle_old_settings', () => { }; const telemetryOptInProvider = getTelemetryOptInProvider(null); - expect(telemetryOptInProvider.getOptIn()).to.be(null); + expect(telemetryOptInProvider.getOptIn()).toBe(null); config.get.withArgs('xPackMonitoring:allowReport', null).returns(false); config.get.withArgs(CONFIG_TELEMETRY, null).returns(true); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(true); + expect(telemetryOptInProvider.getOptIn()).toBe(true); }); it('re-uses old "allowReport" setting and stays opted out', async () => { @@ -102,21 +101,21 @@ describe('handle_old_settings', () => { }; const telemetryOptInProvider = getTelemetryOptInProvider(null); - expect(telemetryOptInProvider.getOptIn()).to.be(null); + expect(telemetryOptInProvider.getOptIn()).toBe(null); config.get.withArgs('xPackMonitoring:allowReport', null).returns(false); config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(true)); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(false); + expect(telemetryOptInProvider.getOptIn()).toBe(false); }); it('re-uses old "telemetry:optIn" setting and stays opted out', async () => { @@ -131,16 +130,16 @@ describe('handle_old_settings', () => { config.get.withArgs(CONFIG_TELEMETRY, null).returns(false); config.get.withArgs('xPackMonitoring:allowReport', null).returns(true); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); - expect(config.remove.calledThrice).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:allowReport'); - expect(config.remove.getCall(1).args[0]).to.be('xPackMonitoring:showBanner'); - expect(config.remove.getCall(2).args[0]).to.be(CONFIG_TELEMETRY); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); + expect(config.remove.calledThrice).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:allowReport'); + expect(config.remove.getCall(1).args[0]).toBe('xPackMonitoring:showBanner'); + expect(config.remove.getCall(2).args[0]).toBe(CONFIG_TELEMETRY); - expect(telemetryOptInProvider.getOptIn()).to.be(false); + expect(telemetryOptInProvider.getOptIn()).toBe(false); }); it('acknowledges users old setting even if re-setting fails', async () => { @@ -156,10 +155,10 @@ describe('handle_old_settings', () => { config.set.withArgs(CONFIG_TELEMETRY, false).returns(Promise.resolve(false)); // note: because it doesn't remove the old settings _and_ returns false, there's no risk of suddenly being opted in - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(false); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(false); - expect(config.get.calledTwice).to.be(true); - expect(config.set.called).to.be(false); + expect(config.get.calledTwice).toBe(true); + expect(config.set.called).toBe(false); }); it('removes show banner setting and presents user with choice', async () => { @@ -173,11 +172,11 @@ describe('handle_old_settings', () => { config.get.withArgs('xPackMonitoring:allowReport', null).returns(null); config.get.withArgs('xPackMonitoring:showBanner', null).returns(false); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(true); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(true); - expect(config.get.calledThrice).to.be(true); - expect(config.remove.calledOnce).to.be(true); - expect(config.remove.getCall(0).args[0]).to.be('xPackMonitoring:showBanner'); + expect(config.get.calledThrice).toBe(true); + expect(config.remove.calledOnce).toBe(true); + expect(config.remove.getCall(0).args[0]).toBe('xPackMonitoring:showBanner'); }); it('is effectively ignored on fresh installs', async () => { @@ -190,9 +189,9 @@ describe('handle_old_settings', () => { config.get.withArgs('xPackMonitoring:allowReport', null).returns(null); config.get.withArgs('xPackMonitoring:showBanner', null).returns(null); - expect(await handleOldSettings(config, telemetryOptInProvider)).to.be(true); + expect(await handleOldSettings(config, telemetryOptInProvider)).toBe(true); - expect(config.get.calledThrice).to.be(true); + expect(config.get.calledThrice).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js deleted file mode 100644 index 75ec89b309dec..0000000000000 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/opt_in_banner_component.js +++ /dev/null @@ -1,147 +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, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; - -import { - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { getConfigTelemetryDesc, PRIVACY_STATEMENT_URL } from '../../../common/constants'; -import { OptInExampleFlyout } from '../../components'; - -/** - * React component for displaying the Telemetry opt-in banner. - * - * TODO: When Jest tests become available in X-Pack, we should add one for this component. - */ -export class OptInBanner extends Component { - static propTypes = { - /** - * Callback function with no parameters that returns a {@code Promise} containing the - * telemetry data (expected to be an array). - */ - fetchTelemetry: PropTypes.func.isRequired, - /** - * Callback function passed a boolean to opt in ({@code true}) or out ({@code false}). - */ - optInClick: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - - this.state = { - showDetails: false, - showExample: false, - }; - } - - render() { - let title = getConfigTelemetryDesc(); - let details; - let flyoutDetails; - - if (this.state.showDetails) { - details = ( - -

    - this.setState({ showExample: !this.state.showExample })}> - - - ), - telemetryPrivacyStatementLink: ( - - - - ) - }} - /> -

    -
    - ); - - if (this.state.showExample) { - flyoutDetails = ( - this.setState({ showExample: false })} - fetchTelemetry={this.props.fetchTelemetry} - /> - ); - } - } else { - title = ( - - {getConfigTelemetryDesc()} {( - this.setState({ showDetails: true })}> - - - )} - - ); - } - - const titleNode = ( - {title} - ); - - return ( - - { details } - { flyoutDetails } - - - - this.props.optInClick(true)} - > - - - - - this.props.optInClick(false)} - > - - - - - - ); - } -} diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js index 1fa1287cc2d98..9143d13069316 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.js @@ -9,7 +9,7 @@ import React from 'react'; import { banners } from 'ui/notify'; import { clickBanner } from './click_banner'; -import { OptInBanner } from './opt_in_banner_component'; +import { OptInBanner } from '../../components/opt_in_banner_component'; /** * Render the Telemetry Opt-in banner. @@ -22,10 +22,12 @@ export function renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners const bannerId = _banners.add({ component: ( clickBanner(bannerId, telemetryOptInProvider, optIn)} + optInClick={optIn => clickBanner(telemetryOptInProvider, optIn)} fetchTelemetry={fetchTelemetry} /> ), priority: 10000 }); + + telemetryOptInProvider.setBannerId(bannerId); } diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js new file mode 100644 index 0000000000000..a55027703f951 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/render_banner.test.js @@ -0,0 +1,30 @@ +/* + * 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 { renderBanner } from './render_banner'; + +describe('render_banner', () => { + + it('adds a banner to banners with priority of 10000', () => { + const bannerID = 'brucer-banner'; + + const telemetryOptInProvider = { setBannerId: jest.fn() }; + const banners = { add: jest.fn().mockReturnValue(bannerID) }; + const fetchTelemetry = jest.fn(); + + renderBanner(telemetryOptInProvider, fetchTelemetry, { _banners: banners }); + + expect(banners.add).toBeCalledTimes(1); + expect(fetchTelemetry).toBeCalledTimes(0); + expect(telemetryOptInProvider.setBannerId).toBeCalledWith(bannerID); + + const bannerConfig = banners.add.mock.calls[0][0]; + + expect(bannerConfig.component).not.toBe(undefined); + expect(bannerConfig.priority).toBe(10000); + }); + +}); diff --git a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/should_show_banner.js b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js similarity index 69% rename from x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/should_show_banner.js rename to x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js index 8712fa55d3d79..1bfe7e954738e 100644 --- a/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/__tests__/should_show_banner.js +++ b/x-pack/legacy/plugins/telemetry/public/hacks/welcome_banner/should_show_banner.test.js @@ -4,18 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; +import { mockInjectedMetadata } from '../../services/telemetry_opt_in.test.mocks'; + import sinon from 'sinon'; -import { CONFIG_TELEMETRY } from '../../../../common/constants'; -import { shouldShowBanner } from '../should_show_banner'; -import { TelemetryOptInProvider } from '../../../services/telemetry_opt_in'; +import { CONFIG_TELEMETRY } from '../../../common/constants'; +import { shouldShowBanner } from './should_show_banner'; +import { TelemetryOptInProvider } from '../../services/telemetry_opt_in'; -const getMockInjector = ({ telemetryEnabled }) => { +const getMockInjector = () => { const get = sinon.stub(); - get.withArgs('telemetryOptedIn').returns(telemetryEnabled); - const mockHttp = { post: sinon.stub() }; @@ -25,8 +24,9 @@ const getMockInjector = ({ telemetryEnabled }) => { return { get }; }; -const getTelemetryOptInProvider = ({ telemetryEnabled = null } = {}) => { - const injector = getMockInjector({ telemetryEnabled }); +const getTelemetryOptInProvider = ({ telemetryOptedIn = null } = {}) => { + mockInjectedMetadata({ telemetryOptedIn }); + const injector = getMockInjector(); const chrome = { addBasePath: (url) => url }; @@ -49,28 +49,28 @@ describe('should_show_banner', () => { const showBannerTrue = await shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings: handleOldSettingsTrue }); const showBannerFalse = await shouldShowBanner(telemetryOptInProvider, config, { _handleOldSettings: handleOldSettingsFalse }); - expect(showBannerTrue).to.be(true); - expect(showBannerFalse).to.be(false); + expect(showBannerTrue).toBe(true); + expect(showBannerFalse).toBe(false); - expect(config.get.callCount).to.be(0); - expect(handleOldSettingsTrue.calledOnce).to.be(true); - expect(handleOldSettingsFalse.calledOnce).to.be(true); + expect(config.get.callCount).toBe(0); + expect(handleOldSettingsTrue.calledOnce).toBe(true); + expect(handleOldSettingsFalse.calledOnce).toBe(true); }); it('returns false if telemetry opt-in setting is set to true', async () => { const config = { get: sinon.stub() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryEnabled: true }); + const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryOptedIn: true }); - expect(await shouldShowBanner(telemetryOptInProvider, config)).to.be(false); + expect(await shouldShowBanner(telemetryOptInProvider, config)).toBe(false); }); it('returns false if telemetry opt-in setting is set to false', async () => { const config = { get: sinon.stub() }; - const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryEnabled: false }); + const telemetryOptInProvider = getTelemetryOptInProvider({ telemetryOptedIn: false }); - expect(await shouldShowBanner(telemetryOptInProvider, config)).to.be(false); + expect(await shouldShowBanner(telemetryOptInProvider, config)).toBe(false); }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js index 9e032c27baa14..5b93f84eabf4a 100644 --- a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.js @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { mockInjectedMetadata } from './telemetry_opt_in.test.mocks'; import { TelemetryOptInProvider } from './telemetry_opt_in'; describe('TelemetryOptInProvider', () => { @@ -19,12 +20,11 @@ describe('TelemetryOptInProvider', () => { addBasePath: (url) => url }; + mockInjectedMetadata({ telemetryOptedIn: optedIn }); + const mockInjector = { get: (key) => { switch (key) { - case 'telemetryOptedIn': { - return optedIn; - } case '$http': { return mockHttp; } @@ -77,4 +77,11 @@ describe('TelemetryOptInProvider', () => { // opt-in change should not be reflected expect(provider.getOptIn()).toEqual(false); }); + + it('should return the current bannerId', () => { + const { provider } = setup({}); + const bannerId = 'bruce-banner'; + provider.setBannerId(bannerId); + expect(provider.getBannerId()).toEqual(bannerId); + }); }); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js new file mode 100644 index 0000000000000..63bfcb4b2a327 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.test.mocks.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { injectedMetadataServiceMock } from '../../../../../../src/core/public/mocks'; +const injectedMetadataMock = injectedMetadataServiceMock.createStartContract(); + +export function mockInjectedMetadata({ telemetryOptedIn }) { + const mockGetInjectedVar = jest.fn().mockImplementation((key) => { + switch (key) { + case 'telemetryOptedIn': return telemetryOptedIn; + default: throw new Error(`unexpected injectedVar ${key}`); + } + }); + + injectedMetadataMock.getInjectedVar = mockGetInjectedVar; +} + +jest.doMock('ui/new_platform', () => ({ + npStart: { + core: { + injectedMetadata: injectedMetadataMock + }, + }, +})); diff --git a/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts new file mode 100644 index 0000000000000..9c1bfde1d27b9 --- /dev/null +++ b/x-pack/legacy/plugins/telemetry/public/services/telemetry_opt_in.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +export { TelemetryOptInProvider } from '../../../../../../src/legacy/core_plugins/kibana/public/home/telemetry_opt_in'; // eslint-disable-line diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1334fd4c4df01..0ad30e7701d11 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2432,6 +2432,8 @@ "kbn.visualize.wizard.step1Breadcrumb": "作成", "kbn.visualize.wizard.step2Breadcrumb": "作成", "kbn.visualizeTitle": "可視化", + "kbn.home.telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", + "kbn.home.telemetry.optInErrorToastTitle": "エラー", "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされていません", "kbnDocViews.table.filterForFieldPresentButtonAriaLabel": "現在のフィールドのフィルター", "kbnDocViews.table.filterForFieldPresentButtonTooltip": "現在のフィールドのフィルター", @@ -10125,8 +10127,6 @@ "xpack.telemetry.callout.errorLoadingClusterStatisticsTitle": "クラスター統計の読み込みエラー", "xpack.telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", "xpack.telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", - "xpack.telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", - "xpack.telemetry.optInErrorToastTitle": "エラー", "xpack.telemetry.readOurUsageDataPrivacyStatementLinkText": "使用データのプライバシーステートメントをお読みください", "xpack.telemetry.seeExampleOfWhatWeCollectLinkText": "収集されるデータの例を見る", "xpack.telemetry.telemetryConfigDescription": "基本的な機能利用に関する統計情報を提供して Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0ea7732c6bcc2..4508f98b00b1c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2432,6 +2432,8 @@ "kbn.visualize.wizard.step1Breadcrumb": "创建", "kbn.visualize.wizard.step2Breadcrumb": "创建", "kbn.visualizeTitle": "可视化", + "kbn.home.telemetry.optInErrorToastText": "尝试设置使用统计信息首选项时发生错误。", + "kbn.home.telemetry.optInErrorToastTitle": "错误", "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "不支持以 {underscoreSign} 开头的字段名称", "kbnDocViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", "kbnDocViews.table.filterForFieldPresentButtonTooltip": "筛留存在的字段", @@ -10267,8 +10269,6 @@ "xpack.telemetry.callout.errorLoadingClusterStatisticsTitle": "加载集群统计信息时出错", "xpack.telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", "xpack.telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", - "xpack.telemetry.optInErrorToastText": "尝试设置使用统计信息首选项时发生错误。", - "xpack.telemetry.optInErrorToastTitle": "错误", "xpack.telemetry.readOurUsageDataPrivacyStatementLinkText": "阅读我们的使用情况数据隐私声明", "xpack.telemetry.seeExampleOfWhatWeCollectLinkText": "查看我们收集的内容示例", "xpack.telemetry.telemetryConfigDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。", From 8cdd469ce71dcb162889a12cd45f706c6b87d1fb Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 27 Aug 2019 15:04:36 +0300 Subject: [PATCH 06/66] Timefilter - replace SimpleEmitter with observables (#43748) * Replaced 'timeUpdate' and 'enabledUpdated' timefilter events with observables. * Change enabledUpdated$ to a BehaviorSubject * refreshIntervalUpdate + fixes in monitoring * autoRefreshFetch * getFetch + delete listenAndDigestAsync * Removed SimpleEmitter parent * Updated timefilter tests * Post merge code updates in ML + type fixes * visual editor unsubscribe * removed unused import * timefilter mock * Import only from top level of timefilter * Fixed typo in discover * unsubscribe in monitoring * Deleted two tests relying on timefilter implementing EventEmitter * Renamed subscribtion var name * import path for fixing jest test ? * Removed unused row --- .../query/query_bar/components/query_bar.tsx | 2 +- .../data/public/search/search_bar/index.tsx | 2 +- .../kibana/public/dashboard/dashboard_app.tsx | 6 +- .../dashboard/dashboard_app_controller.tsx | 46 ++++++++---- .../public/dashboard/dashboard_state.test.ts | 7 +- .../dashboard/lib/update_saved_dashboard.ts | 3 +- .../saved_dashboard/saved_dashboard.d.ts | 2 +- .../public/discover/controllers/discover.js | 32 +++++--- .../discover/embeddable/search_embeddable.ts | 3 +- .../embeddable/search_embeddable_factory.ts | 2 +- .../public/discover/embeddable/types.ts | 2 +- .../kibana/public/visualize/editor/editor.js | 26 ++++--- .../embeddable/visualize_embeddable.ts | 2 +- .../core_plugins/timelion/public/app.js | 2 +- src/legacy/ui/public/courier/courier.js | 4 +- .../public/courier/search_poll/search_poll.js | 2 +- .../ui/public/directives/listen/listen.js | 17 ----- src/legacy/ui/public/timefilter/index.js | 20 ----- .../timefilter/{index.d.ts => index.ts} | 8 +- .../ui/public/timefilter/timefilter.d.ts | 8 +- src/legacy/ui/public/timefilter/timefilter.js | 73 +++++++++++++++---- .../ui/public/timefilter/timefilter.test.js | 37 ++++++++-- .../request_handlers/request_handlers.d.ts | 2 +- .../embedded_visualize_handler.test.mocks.ts | 13 +++- .../loader/embedded_visualize_handler.test.ts | 4 +- .../loader/embedded_visualize_handler.ts | 5 +- .../ui/public/visualize/loader/types.ts | 2 +- .../functions/common/saved_map.ts | 2 +- .../server/lib/build_embeddable_filters.ts | 2 +- .../public/layers/joins/inner_join.test.js | 2 +- .../maps/public/layers/sources/es_source.js | 2 +- .../layers/sources/es_term_source.test.js | 2 +- .../navigation_menu/top_nav/top_nav.tsx | 14 ++-- .../ml/public/contexts/ui/ui_context.tsx | 3 +- .../transform_list/use_refresh_interval.ts | 15 ++-- .../analytics_list/use_refresh_interval.ts | 12 +-- .../datavisualizer/index_based/page.tsx | 4 +- .../ml/public/explorer/explorer_controller.js | 4 +- .../jobs_list_view/jobs_list_view.js | 7 +- .../create_job/create_job_controller.js | 12 ++- .../create_job/create_job_controller.js | 6 +- .../create_job/create_job_controller.js | 6 +- .../timeseriesexplorer/timeseriesexplorer.js | 4 +- .../ml/public/util/chart_utils.test.js | 10 --- .../services/__tests__/executor_provider.js | 18 ----- .../public/services/executor_provider.js | 12 ++- .../public/views/no_data/controller.js | 15 +++- 47 files changed, 279 insertions(+), 205 deletions(-) delete mode 100644 src/legacy/ui/public/timefilter/index.js rename src/legacy/ui/public/timefilter/{index.d.ts => index.ts} (75%) diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx index a61b487c3e446..e41ef5d422b84 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar.tsx @@ -22,7 +22,7 @@ import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query'; import classNames from 'classnames'; import React, { Component } from 'react'; import { Storage } from 'ui/storage'; -import { timeHistory } from 'ui/timefilter/time_history'; +import { timeHistory } from 'ui/timefilter'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSuperDatePicker } from '@elastic/eui'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx index d110c420cdc07..8d98e4d2f9b7a 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx @@ -18,7 +18,7 @@ */ import { Filter } from '@kbn/es-query'; -import { RefreshInterval, TimeRange } from 'ui/timefilter/timefilter'; +import { RefreshInterval, TimeRange } from 'ui/timefilter'; import { Query } from '../../query/query_bar'; export * from './components'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 52a4b9d6e803e..a0fe853484eee 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -36,11 +36,12 @@ import { import { KbnUrl } from 'ui/url/kbn_url'; import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; import moment from 'moment'; +import { Subscription } from 'rxjs'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; @@ -81,7 +82,6 @@ export interface DashboardAppScope extends ng.IScope { refreshInterval: any; }) => void; onFiltersUpdated: (filters: Filter[]) => void; - $listenAndDigestAsync: any; onCancelApplyFilters: () => void; onApplyFilters: (filters: Filter[]) => void; onQuerySaved: (savedQuery: SavedQuery) => void; @@ -93,7 +93,7 @@ export interface DashboardAppScope extends ng.IScope { showSaveQuery: boolean; kbnTopNav: any; enterEditMode: () => void; - $listen: any; + timefilterSubscriptions$: Subscription; } const app = uiModules.get('app/dashboard', [ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 6c10cb1cdd5cf..31906fcbfc0de 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -24,6 +24,7 @@ import angular from 'angular'; import { uniq } from 'lodash'; import chrome from 'ui/chrome'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import { toastNotifications } from 'ui/notify'; // @ts-ignore @@ -515,23 +516,36 @@ export class DashboardAppController { } ); - $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { - // The only reason this is here is so that search embeddables work on a dashboard with - // a refresh interval turned on. This kicks off the search poller. It should be - // refactored so no embeddables need to listen to the timefilter directly but instead - // the container tells it when to reload. - courier.fetch(); - }); + $scope.timefilterSubscriptions$ = new Subscription(); + // The only reason this is here is so that search embeddables work on a dashboard with + // a refresh interval turned on. This kicks off the search poller. It should be + // refactored so no embeddables need to listen to the timefilter directly but instead + // the container tells it when to reload. + $scope.timefilterSubscriptions$.add( + subscribeWithScope($scope, timefilter.getFetch$(), { + next: () => { + courier.fetch(); + }, + }) + ); - $scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', () => { - updateState(); - refreshDashboardContainer(); - }); + $scope.timefilterSubscriptions$.add( + subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { + next: () => { + updateState(); + refreshDashboardContainer(); + }, + }) + ); - $scope.$listenAndDigestAsync(timefilter, 'timeUpdate', () => { - updateState(); - refreshDashboardContainer(); - }); + $scope.timefilterSubscriptions$.add( + subscribeWithScope($scope, timefilter.getTimeUpdate$(), { + next: () => { + updateState(); + refreshDashboardContainer(); + }, + }) + ); function updateViewMode(newMode: ViewMode) { $scope.topNavMenu = getTopNavConfig( @@ -799,6 +813,8 @@ export class DashboardAppController { $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); + $scope.timefilterSubscriptions$.unsubscribe(); + dashboardStateManager.destroy(); if (inputSubscription) { inputSubscription.unsubscribe(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index a713e13679816..9e9f5c3fe0b37 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -42,12 +42,15 @@ describe('DashboardState', function() { getRefreshInterval: jest.fn(), disableTimeRangeSelector: jest.fn(), enableAutoRefreshSelector: jest.fn(), - off: jest.fn(), - on: jest.fn(), getActiveBounds: () => {}, enableTimeRangeSelector: () => {}, isAutoRefreshSelectorEnabled: true, isTimeRangeSelectorEnabled: true, + getAutoRefreshFetch$: jest.fn(), + getEnabledUpdated$: jest.fn(), + getRefreshIntervalUpdate$: jest.fn(), + getFetch$: jest.fn(), + getTimeUpdate$: jest.fn(), }; function initDashboardState() { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts index a43bee881c631..93f487ba6d502 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts @@ -19,8 +19,7 @@ import _ from 'lodash'; import { AppState } from 'ui/state_management/app_state'; -import { Timefilter } from 'ui/timefilter'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; +import { Timefilter, RefreshInterval } from 'ui/timefilter'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts index 1f752833f45a8..68fd8f0a5a976 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts @@ -20,7 +20,7 @@ import { SearchSource } from 'ui/courier'; import { SavedObject } from 'ui/saved_objects/saved_object'; import moment from 'moment'; -import { RefreshInterval } from 'ui/timefilter/timefilter'; +import { RefreshInterval } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index 35fbd05ad925f..b0f58829daf87 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -21,6 +21,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; import angular from 'angular'; +import { Subscription } from 'rxjs'; import moment from 'moment'; import chrome from 'ui/chrome'; import dateMath from '@elastic/datemath'; @@ -209,8 +210,7 @@ function discoverController( requests: new RequestAdapter() }; - let filterUpdateSubscription; - let filterFetchSubscription; + const subscriptions = new Subscription(); timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); @@ -236,8 +236,7 @@ function discoverController( const savedSearch = $route.current.locals.savedSearch; $scope.$on('$destroy', () => { savedSearch.destroy(); - if (filterFetchSubscription) filterFetchSubscription.unsubscribe(); - if (filterUpdateSubscription) filterUpdateSubscription.unsubscribe(); + subscriptions.unsubscribe(); }); const $appStatus = $scope.appStatus = this.appStatus = { @@ -564,10 +563,19 @@ function discoverController( $scope.updateDataSource() .then(function () { - $scope.$listen(timefilter, 'autoRefreshFetch', $scope.fetch); - $scope.$listen(timefilter, 'refreshIntervalUpdate', $scope.updateRefreshInterval); - $scope.$listen(timefilter, 'timeUpdate', $scope.updateTime); - $scope.$listen(timefilter, 'fetch', $scope.fetch); + subscriptions.add(subscribeWithScope($scope, timefilter.getAutoRefreshFetch$(), { + next: $scope.fetch + })); + + subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { + next: $scope.updateRefreshInterval + })); + subscriptions.add(subscribeWithScope($scope, timefilter.getTimeUpdate$(), { + next: $scope.updateTime + })); + subscriptions.add(subscribeWithScope($scope, timefilter.getFetch$(), { + next: $scope.fetch + })); $scope.$watchCollection('state.sort', function (sort) { if (!sort) return; @@ -580,19 +588,19 @@ function discoverController( }); // update data source when filters update - filterUpdateSubscription = subscribeWithScope($scope, queryFilter.getUpdates$(), { + subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { next: () => { $scope.filters = queryFilter.getFilters(); $scope.updateDataSource().then(function () { $state.save(); }); } - }); + })); // fetch data when filters fire fetch event - filterFetchSubscription = subscribeWithScope($scope, queryFilter.getUpdates$(), { + subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { next: $scope.fetch - }); + })); // update data source when hitting forward/back and the query changes $scope.$listen($state, 'fetch_with_changes', function (diff) { diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index 8288e3d1d1d7a..614bbd8b12a64 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -25,11 +25,10 @@ import { SearchSource } from 'ui/courier'; import { StaticIndexPattern } from 'ui/index_patterns'; import { RequestAdapter } from 'ui/inspector/adapters'; import { Adapters } from 'ui/inspector/types'; -import { getTime } from 'ui/timefilter/get_time'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter, FilterStateStore } from '@kbn/es-query'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { getTime, TimeRange } from 'ui/timefilter'; import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; import { APPLY_FILTER_TRIGGER, diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index c76b3c7fbc5fd..9cbf1f2dace29 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -22,7 +22,7 @@ import { capabilities } from 'ui/capabilities'; import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { EmbeddableFactory, diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts index 43648d575014e..104d298f50c33 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -18,7 +18,7 @@ */ import { StaticIndexPattern } from 'ui/index_patterns'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; import { SavedSearch } from '../types'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 85911160f1e62..d81bc05048fbf 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -18,6 +18,7 @@ */ import _ from 'lodash'; +import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import '../saved_visualizations/saved_visualizations'; import './visualization_editor'; @@ -404,12 +405,16 @@ function VisEditor( } }; - const updateRefreshInterval = () => { - $scope.refreshInterval = timefilter.getRefreshInterval(); - }; + const subscriptions = new Subscription(); - $scope.$listenAndDigestAsync(timefilter, 'timeUpdate', updateTimeRange); - $scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', updateRefreshInterval); + subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { + next: () => { + $scope.refreshInterval = timefilter.getRefreshInterval(); + } + })); + subscriptions.add(subscribeWithScope($scope, timefilter.getTimeUpdate$(), { + next: updateTimeRange + })); // update the searchSource when query updates $scope.fetch = function () { @@ -420,15 +425,15 @@ function VisEditor( }; // update the searchSource when filters update - const filterUpdateSubscription = subscribeWithScope($scope, queryFilter.getUpdates$(), { + subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { next: () => { $scope.filters = queryFilter.getFilters(); $scope.globalFilters = queryFilter.getGlobalFilters(); } - }); - const filterFetchSubscription = subscribeWithScope($scope, queryFilter.getFetches$(), { + })); + subscriptions.add(subscribeWithScope($scope, queryFilter.getFetches$(), { next: $scope.fetch - }); + })); $scope.$on('$destroy', function () { if ($scope._handler) { @@ -436,8 +441,7 @@ function VisEditor( } savedVis.destroy(); stateMonitor.destroy(); - filterUpdateSubscription.unsubscribe(); - filterFetchSubscription.unsubscribe(); + subscriptions.unsubscribe(); }); if (!$scope.chrome.getVisible()) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index bbe610e5e7755..d750010a132d6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -29,7 +29,7 @@ import { } from 'ui/visualize/loader/types'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { Filter } from '@kbn/es-query'; import { EmbeddableInput, diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 01a9d8dc57b9b..29e56f77fd837 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -326,7 +326,7 @@ app.controller('timelion', function ( }; $scope.$listen($scope.state, 'fetch_with_changes', $scope.search); - $scope.$listen(timefilter, 'fetch', $scope.search); + timefilter.getFetch$().subscribe($scope.search); $scope.opts = { saveExpression: saveExpression, diff --git a/src/legacy/ui/public/courier/courier.js b/src/legacy/ui/public/courier/courier.js index bd242aefc8f42..a317932e51118 100644 --- a/src/legacy/ui/public/courier/courier.js +++ b/src/legacy/ui/public/courier/courier.js @@ -54,7 +54,7 @@ uiModules.get('kibana/courier').service('courier', ($rootScope, Private) => { } }; - $rootScope.$listen(timefilter, 'refreshIntervalUpdate', updateRefreshInterval); + const refreshIntervalSubscription = timefilter.getRefreshIntervalUpdate$().subscribe(updateRefreshInterval); const closeOnFatal = _.once(() => { // If there was a fatal error, then stop future searches. We want to use pause instead of @@ -68,6 +68,8 @@ uiModules.get('kibana/courier').service('courier', ($rootScope, Private) => { if (searchRequestQueue.getCount()) { throw new Error('Aborting all pending requests failed.'); } + + refreshIntervalSubscription.unsubscribe(); }); addFatalErrorCallback(closeOnFatal); diff --git a/src/legacy/ui/public/courier/search_poll/search_poll.js b/src/legacy/ui/public/courier/search_poll/search_poll.js index ac921a9a2cf93..91c866c14aa49 100644 --- a/src/legacy/ui/public/courier/search_poll/search_poll.js +++ b/src/legacy/ui/public/courier/search_poll/search_poll.js @@ -82,7 +82,7 @@ export function SearchPollProvider(Private, Promise) { // We use resolve() here instead of try() because the latter won't trigger a $digest // when the promise resolves. this._searchPromise = Promise.resolve().then(() => { - timefilter.emit('autoRefreshFetch'); + timefilter.notifyShouldFetch(); const requests = searchRequestQueue.getInactive(); // The promise returned from fetchSearchRequests() only resolves when the requests complete. diff --git a/src/legacy/ui/public/directives/listen/listen.js b/src/legacy/ui/public/directives/listen/listen.js index 3c2e3ddb50021..b877e8ee6b08e 100644 --- a/src/legacy/ui/public/directives/listen/listen.js +++ b/src/legacy/ui/public/directives/listen/listen.js @@ -37,21 +37,4 @@ uiModules.get('kibana') emitter.off(eventName, handler); }); }; - - /** - * Helper that registers an event listener, and removes that listener when - * the $scope is destroyed. Handler is executed inside $evalAsync, ensuring digest cycle is run after the handler - * - * @param {SimpleEmitter} emitter - the event emitter to listen to - * @param {string} eventName - the event name - * @param {Function} handler - the event handler - * @return {undefined} - */ - $rootScope.constructor.prototype.$listenAndDigestAsync = function (emitter, eventName, handler) { - const evalAsyncWrappedHandler = (...args) => { - this.$evalAsync(() => handler(args)); - }; - this.$listen(emitter, eventName, evalAsyncWrappedHandler); - }; - }); diff --git a/src/legacy/ui/public/timefilter/index.js b/src/legacy/ui/public/timefilter/index.js deleted file mode 100644 index bf4227aecf442..0000000000000 --- a/src/legacy/ui/public/timefilter/index.js +++ /dev/null @@ -1,20 +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. - */ - -export { timefilter, registerTimefilterWithGlobalState } from './timefilter'; diff --git a/src/legacy/ui/public/timefilter/index.d.ts b/src/legacy/ui/public/timefilter/index.ts similarity index 75% rename from src/legacy/ui/public/timefilter/index.d.ts rename to src/legacy/ui/public/timefilter/index.ts index ce1ae86bb93ba..234ee50cece1a 100644 --- a/src/legacy/ui/public/timefilter/index.d.ts +++ b/src/legacy/ui/public/timefilter/index.ts @@ -17,5 +17,9 @@ * under the License. */ -export { timefilter, Timefilter } from './timefilter'; -export { timeHistory, TimeRange } from './time_history'; +// @ts-ignore +export { registerTimefilterWithGlobalState } from './timefilter'; +export { timefilter, Timefilter, RefreshInterval } from './timefilter'; +export { timeHistory, TimeRange, TimeHistory } from './time_history'; + +export { getTime } from './get_time'; diff --git a/src/legacy/ui/public/timefilter/timefilter.d.ts b/src/legacy/ui/public/timefilter/timefilter.d.ts index 4d56b9885db4b..fb9808298c6d0 100644 --- a/src/legacy/ui/public/timefilter/timefilter.d.ts +++ b/src/legacy/ui/public/timefilter/timefilter.d.ts @@ -18,6 +18,7 @@ */ import { Moment } from 'moment'; +import { Observable } from 'rxjs'; import { TimeRange } from './time_history'; import { RefreshInterval } from '../../../../plugins/data/public'; @@ -27,6 +28,11 @@ export { RefreshInterval, TimeRange }; export interface Timefilter { time: TimeRange; + getEnabledUpdated$: () => Observable; + getTimeUpdate$: () => Observable; + getRefreshIntervalUpdate$: () => Observable; + getAutoRefreshFetch$: () => Observable; + getFetch$: () => Observable; getTime: () => TimeRange; setTime: (timeRange: TimeRange) => void; setRefreshInterval: (refreshInterval: RefreshInterval) => void; @@ -36,8 +42,6 @@ export interface Timefilter { disableTimeRangeSelector: () => void; enableAutoRefreshSelector: () => void; enableTimeRangeSelector: () => void; - off: (event: string, reload: () => void) => void; - on: (event: string, reload: () => void) => void; isAutoRefreshSelectorEnabled: boolean; isTimeRangeSelectorEnabled: boolean; } diff --git a/src/legacy/ui/public/timefilter/timefilter.js b/src/legacy/ui/public/timefilter/timefilter.js index f2d2f4dea5c6b..fa85d175690a9 100644 --- a/src/legacy/ui/public/timefilter/timefilter.js +++ b/src/legacy/ui/public/timefilter/timefilter.js @@ -18,24 +18,60 @@ */ import _ from 'lodash'; +import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; import { calculateBounds, getTime } from './get_time'; -import { parseQueryString } from 'ui/timefilter/lib/parse_querystring'; -import { SimpleEmitter } from 'ui/utils/simple_emitter'; +import { parseQueryString } from './lib/parse_querystring'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import uiRoutes from '../routes'; import chrome from 'ui/chrome'; import { areTimePickerValsDifferent } from './lib/diff_time_picker_vals'; import { timeHistory } from './time_history'; -class Timefilter extends SimpleEmitter { +class Timefilter { constructor() { - super(); + + // Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled + this.enabledUpdated$ = new BehaviorSubject(); + + // Fired when a user changes the timerange + this.timeUpdate$ = new Subject(); + + // Fired when a user changes the the autorefresh settings + this.refreshIntervalUpdate$ = new Subject(); + + // Used when search poll triggers an auto refresh + this.autoRefreshFetch$ = new Subject(); + + this.fetch$ = new Subject(); + this.isTimeRangeSelectorEnabled = false; this.isAutoRefreshSelectorEnabled = false; this._time = chrome.getUiSettingsClient().get('timepicker:timeDefaults'); this.setRefreshInterval(chrome.getUiSettingsClient().get('timepicker:refreshIntervalDefaults')); } + getEnabledUpdated$ = () => { + return this.enabledUpdated$.asObservable(); + } + + getTimeUpdate$ = () => { + return this.timeUpdate$.asObservable(); + } + + getRefreshIntervalUpdate$ = () => { + return this.refreshIntervalUpdate$.asObservable(); + } + + getAutoRefreshFetch$ = () => { + return this.autoRefreshFetch$.asObservable(); + } + + getFetch$ = () => { + return this.fetch$.asObservable(); + } + + getTime = () => { const { from, to } = this._time; return { @@ -61,8 +97,8 @@ class Timefilter extends SimpleEmitter { to: newTime.to, }; timeHistory.add(this._time); - this.emit('timeUpdate'); - this.emit('fetch'); + this.timeUpdate$.next(); + this.fetch$.next(); } } @@ -91,9 +127,9 @@ class Timefilter extends SimpleEmitter { // Only send out an event if we already had a previous refresh interval (not for the initial set) // and the old and new refresh interval are actually different. if (prevRefreshInterval && areTimePickerValsDifferent(prevRefreshInterval, newRefreshInterval)) { - this.emit('refreshIntervalUpdate'); + this.refreshIntervalUpdate$.next(); if (!newRefreshInterval.pause && newRefreshInterval.value !== 0) { - this.emit('fetch'); + this.fetch$.next(); } } } @@ -138,7 +174,7 @@ class Timefilter extends SimpleEmitter { */ enableTimeRangeSelector = () => { this.isTimeRangeSelectorEnabled = true; - this.emit('enabledUpdated'); + this.enabledUpdated$.next(); } /** @@ -146,7 +182,7 @@ class Timefilter extends SimpleEmitter { */ disableTimeRangeSelector = () => { this.isTimeRangeSelectorEnabled = false; - this.emit('enabledUpdated'); + this.enabledUpdated$.next(); } /** @@ -154,7 +190,7 @@ class Timefilter extends SimpleEmitter { */ enableAutoRefreshSelector = () => { this.isAutoRefreshSelectorEnabled = true; - this.emit('enabledUpdated'); + this.enabledUpdated$.next(); } /** @@ -162,7 +198,11 @@ class Timefilter extends SimpleEmitter { */ disableAutoRefreshSelector = () => { this.isAutoRefreshSelectorEnabled = false; - this.emit('enabledUpdated'); + this.enabledUpdated$.next(); + } + + notifyShouldFetch = () => { + this.autoRefreshFetch$.next(); } } @@ -209,9 +249,14 @@ export const registerTimefilterWithGlobalState = _.once((globalState, $rootScope globalState.save(); }; - $rootScope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', updateGlobalStateWithTime); + subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { + next: updateGlobalStateWithTime + }); + + subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { + next: updateGlobalStateWithTime + }); - $rootScope.$listenAndDigestAsync(timefilter, 'timeUpdate', updateGlobalStateWithTime); }); uiRoutes diff --git a/src/legacy/ui/public/timefilter/timefilter.test.js b/src/legacy/ui/public/timefilter/timefilter.test.js index 084d03b129339..64a43b259dd3f 100644 --- a/src/legacy/ui/public/timefilter/timefilter.test.js +++ b/src/legacy/ui/public/timefilter/timefilter.test.js @@ -64,6 +64,8 @@ function clearNowTimeStub() { describe('setTime', () => { let update; let fetch; + let updateSub; + let fetchSub; beforeEach(() => { update = sinon.spy(); @@ -72,8 +74,13 @@ describe('setTime', () => { from: 0, to: 1, }); - timefilter.on('timeUpdate', update); - timefilter.on('fetch', fetch); + updateSub = timefilter.getTimeUpdate$().subscribe(update); + fetchSub = timefilter.getFetch$().subscribe(fetch); + }); + + afterEach(() => { + updateSub.unsubscribe(); + fetchSub.unsubscribe(); }); test('should update time', () => { @@ -116,9 +123,10 @@ describe('setTime', () => { }); describe('setRefreshInterval', () => { - let update; let fetch; + let fetchSub; + let refreshSub; beforeEach(() => { update = sinon.spy(); @@ -127,8 +135,13 @@ describe('setRefreshInterval', () => { pause: false, value: 0 }); - timefilter.on('refreshIntervalUpdate', update); - timefilter.on('fetch', fetch); + refreshSub = timefilter.getRefreshIntervalUpdate$().subscribe(update); + fetchSub = timefilter.getFetch$().subscribe(fetch); + }); + + afterEach(() => { + refreshSub.unsubscribe(); + fetchSub.unsubscribe(); }); test('should update refresh interval', () => { @@ -203,10 +216,15 @@ describe('setRefreshInterval', () => { describe('isTimeRangeSelectorEnabled', () => { let update; + let updateSub; beforeEach(() => { update = sinon.spy(); - timefilter.on('enabledUpdated', update); + updateSub = timefilter.getEnabledUpdated$().subscribe(update); + }); + + afterEach(() => { + updateSub.unsubscribe(); }); test('should emit updated when disabled', () => { @@ -224,10 +242,15 @@ describe('isTimeRangeSelectorEnabled', () => { describe('isAutoRefreshSelectorEnabled', () => { let update; + let updateSub; beforeEach(() => { update = sinon.spy(); - timefilter.on('enabledUpdated', update); + updateSub = timefilter.getEnabledUpdated$().subscribe(update); + }); + + afterEach(() => { + updateSub.unsubscribe(); }); test('should emit updated when disabled', () => { diff --git a/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts b/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts index 27d57e65a1289..476b3fa68473e 100644 --- a/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts +++ b/src/legacy/ui/public/vis/request_handlers/request_handlers.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; import { SearchSource } from '../../courier'; diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts index 6adea30c94fb4..a7414865d7433 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.mocks.ts @@ -19,7 +19,7 @@ jest.useFakeTimers(); -import { EventEmitter } from 'events'; +import { Subject } from 'rxjs'; jest.mock('ui/notify', () => ({ toastNotifications: jest.fn(), @@ -34,7 +34,16 @@ jest.mock('./pipeline_helpers/utilities', () => ({ getTableAggs: jest.fn(), })); -export const timefilter = new EventEmitter(); +const autoRefreshFetchSub = new Subject(); + +export const timefilter = { + _triggerAutoRefresh: () => { + autoRefreshFetchSub.next(); + }, + getAutoRefreshFetch$: () => { + return autoRefreshFetchSub.asObservable(); + }, +}; jest.doMock('../../timefilter', () => ({ timefilter })); jest.mock('../../inspector', () => ({ diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts index d6a5ede713c6c..9a16405398efb 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts @@ -86,7 +86,7 @@ describe('EmbeddedVisualizeHandler', () => { describe('autoFetch', () => { it('should trigger a reload when autoFetch=true and auto refresh happens', () => { const spy = jest.spyOn(handler, 'fetchAndRender'); - timefilter.emit('autoRefreshFetch'); + timefilter._triggerAutoRefresh(); jest.runAllTimers(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith(true); @@ -110,7 +110,7 @@ describe('EmbeddedVisualizeHandler', () => { } ); const spy = jest.spyOn(handler, 'fetchAndRender'); - timefilter.emit('autoRefreshFetch'); + timefilter._triggerAutoRefresh(); jest.runAllTimers(); expect(spy).not.toHaveBeenCalled(); }); diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts index 774ccb4d2a069..1a0155246e87f 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -105,6 +105,7 @@ export class EmbeddedVisualizeHandler { private actions: any = {}; private events$: Rx.Observable; private autoFetch: boolean; + private autoRefreshFetchSubscription: Rx.Subscription | undefined; constructor( private readonly element: HTMLElement, @@ -167,7 +168,7 @@ export class EmbeddedVisualizeHandler { this.vis.on('reload', this.reload); this.uiState.on('change', this.onUiStateChange); if (autoFetch) { - timefilter.on('autoRefreshFetch', this.reload); + this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.reload); } // This is a hack to give maps visualizations access to data in the @@ -269,7 +270,7 @@ export class EmbeddedVisualizeHandler { this.destroyed = true; this.debouncedFetchAndRender.cancel(); if (this.autoFetch) { - timefilter.off('autoRefreshFetch', this.reload); + if (this.autoRefreshFetchSubscription) this.autoRefreshFetchSubscription.unsubscribe(); } this.vis.removeListener('reload', this.reload); this.vis.removeListener('update', this.handleVisUpdate); diff --git a/src/legacy/ui/public/visualize/loader/types.ts b/src/legacy/ui/public/visualize/loader/types.ts index 77e86ae063082..595a0649ec5ef 100644 --- a/src/legacy/ui/public/visualize/loader/types.ts +++ b/src/legacy/ui/public/visualize/loader/types.ts @@ -18,7 +18,7 @@ */ import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { Query } from 'src/legacy/core_plugins/data/public'; import { SavedObject } from 'ui/saved_objects/saved_object'; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts index 6778c43010956..a461e4c305bed 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_map.ts @@ -5,7 +5,7 @@ */ import { Filter as ESFilterType } from '@kbn/es-query'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/public'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { buildEmbeddableFilters } from '../../../server/lib/build_embeddable_filters'; import { Filter } from '../../../types'; diff --git a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts index b97d520de5c5a..9e49d07969dde 100644 --- a/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts +++ b/x-pack/legacy/plugins/canvas/server/lib/build_embeddable_filters.ts @@ -5,7 +5,7 @@ */ import { buildQueryFilter, Filter as ESFilterType } from '@kbn/es-query'; -import { TimeRange } from 'ui/timefilter/time_history'; +import { TimeRange } from 'ui/timefilter'; import { Filter } from '../../types'; // @ts-ignore Untyped Local import { buildBoolArray } from './build_bool_array'; diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js index 0a5a885739f86..e6fb1957fec41 100644 --- a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js @@ -14,7 +14,7 @@ jest.mock('ui/vis/editors/default/schemas', () => { }); jest.mock('../../kibana_services', () => {}); jest.mock('ui/vis/agg_configs', () => {}); -jest.mock('ui/timefilter/timefilter', () => {}); +jest.mock('ui/timefilter', () => {}); jest.mock('../vector_layer', () => {}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index 6d64b468d14f7..afb7314908c4c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -11,7 +11,7 @@ import { SearchSource } from '../../kibana_services'; import { createExtentFilter } from '../../elasticsearch_geo_utils'; -import { timefilter } from 'ui/timefilter/timefilter'; +import { timefilter } from 'ui/timefilter'; import _ from 'lodash'; import { AggConfigs } from 'ui/vis/agg_configs'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js index 65bfd993c871a..67a25769ee5c3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js @@ -12,7 +12,7 @@ jest.mock('ui/vis/editors/default/schemas', () => ({ })); jest.mock('../../kibana_services', () => {}); jest.mock('ui/vis/agg_configs', () => {}); -jest.mock('ui/timefilter/timefilter', () => {}); +jest.mock('ui/timefilter', () => {}); const indexPatternTitle = 'myIndex'; const termFieldName = 'myTermField'; diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx index 61b4594ae008b..6e38b37c33a24 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/top_nav/top_nav.tsx @@ -5,8 +5,9 @@ */ import React, { FC, Fragment, useState, useEffect } from 'react'; +import { Subscription } from 'rxjs'; import { EuiSuperDatePicker } from '@elastic/eui'; -import { TimeHistory, TimeRange } from 'ui/timefilter/time_history'; +import { TimeHistory, TimeRange } from 'ui/timefilter'; import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; import { useUiContext } from '../../../contexts/ui/use_ui_context'; @@ -44,14 +45,13 @@ export const TopNav: FC = () => { const dateFormat = chrome.getUiSettingsClient().get('dateFormat'); useEffect(() => { - timefilter.on('refreshIntervalUpdate', timefilterUpdateListener); - timefilter.on('timeUpdate', timefilterUpdateListener); - timefilter.on('enabledUpdated', timefilterUpdateListener); + const subscriptions = new Subscription(); + subscriptions.add(timefilter.getRefreshIntervalUpdate$().subscribe(timefilterUpdateListener)); + subscriptions.add(timefilter.getTimeUpdate$().subscribe(timefilterUpdateListener)); + subscriptions.add(timefilter.getEnabledUpdated$().subscribe(timefilterUpdateListener)); return function cleanup() { - timefilter.off('refreshIntervalUpdate', timefilterUpdateListener); - timefilter.off('timeUpdate', timefilterUpdateListener); - timefilter.off('enabledUpdated', timefilterUpdateListener); + subscriptions.unsubscribe(); }; }, []); diff --git a/x-pack/legacy/plugins/ml/public/contexts/ui/ui_context.tsx b/x-pack/legacy/plugins/ml/public/contexts/ui/ui_context.tsx index 52a90f795e5bd..4cb97cf5639fe 100644 --- a/x-pack/legacy/plugins/ml/public/contexts/ui/ui_context.tsx +++ b/x-pack/legacy/plugins/ml/public/contexts/ui/ui_context.tsx @@ -7,8 +7,7 @@ import React from 'react'; import chrome from 'ui/chrome'; -import { timefilter } from 'ui/timefilter'; -import { timeHistory } from 'ui/timefilter/time_history'; +import { timefilter, timeHistory } from 'ui/timefilter'; // This provides ui/* based imports via React Context. // Because these dependencies can use regular imports, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/use_refresh_interval.ts b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/use_refresh_interval.ts index c2acf63eb407e..c4086ba6225bb 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/use_refresh_interval.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/use_refresh_interval.ts @@ -21,12 +21,14 @@ export const useRefreshInterval = ( const { refresh } = useRefreshTransformList(); useEffect(() => { let transformRefreshInterval: null | number = null; + const refreshIntervalSubscription = timefilter + .getRefreshIntervalUpdate$() + .subscribe(setAutoRefresh); timefilter.disableTimeRangeSelector(); timefilter.enableAutoRefreshSelector(); initAutoRefresh(); - initAutoRefreshUpdate(); function initAutoRefresh() { const { value } = timefilter.getRefreshInterval(); @@ -42,13 +44,6 @@ export const useRefreshInterval = ( setAutoRefresh(); } - function initAutoRefreshUpdate() { - // update the interval if it changes - timefilter.on('refreshIntervalUpdate', () => { - setAutoRefresh(); - }); - } - function setAutoRefresh() { const { value, pause } = timefilter.getRefreshInterval(); if (pause) { @@ -71,6 +66,9 @@ export const useRefreshInterval = ( } function clearRefreshInterval() { + if (refreshIntervalSubscription) { + refreshIntervalSubscription.unsubscribe(); + } setBlockRefresh(true); if (transformRefreshInterval !== null) { window.clearInterval(transformRefreshInterval); @@ -79,6 +77,7 @@ export const useRefreshInterval = ( // useEffect cleanup return () => { + refreshIntervalSubscription.unsubscribe(); clearRefreshInterval(); }; }, []); // [] as comparator makes sure this only runs once diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts index 09e689dfd2a27..cfd900b303aa3 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts @@ -22,11 +22,13 @@ export const useRefreshInterval = ( useEffect(() => { let analyticsRefreshInterval: null | number = null; + const refreshIntervalSubscription = timefilter + .getRefreshIntervalUpdate$() + .subscribe(setAutoRefresh); timefilter.disableTimeRangeSelector(); timefilter.enableAutoRefreshSelector(); initAutoRefresh(); - initAutoRefreshUpdate(); function initAutoRefresh() { const { value } = timefilter.getRefreshInterval(); @@ -42,13 +44,6 @@ export const useRefreshInterval = ( setAutoRefresh(); } - function initAutoRefreshUpdate() { - // update the interval if it changes - timefilter.on('refreshIntervalUpdate', () => { - setAutoRefresh(); - }); - } - function setAutoRefresh() { const { value, pause } = timefilter.getRefreshInterval(); if (pause) { @@ -79,6 +74,7 @@ export const useRefreshInterval = ( // useEffect cleanup return () => { + refreshIntervalSubscription.unsubscribe(); clearRefreshInterval(); }; }, []); // [] as comparator makes sure this only runs once diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx index 82f1d985d41b7..c2929df8b3e9b 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx @@ -166,9 +166,9 @@ export const Page: FC = () => { const [nonMetricFieldQuery, setNonMetricFieldQuery] = useState(defaults.nonMetricFieldQuery); useEffect(() => { - timefilter.on('timeUpdate', loadOverallStats); + const timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe(loadOverallStats); return () => { - timefilter.off('timeUpdate', loadOverallStats); + timeUpdateSubscription.unsubscribe(); }; }, []); diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer_controller.js b/x-pack/legacy/plugins/ml/public/explorer/explorer_controller.js index b1dcd55aed599..693c884a7bb90 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer_controller.js +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer_controller.js @@ -203,11 +203,11 @@ module.controller('MlExplorerController', function ( })); // Refresh all the data when the time range is altered. - $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { + subscriptions.add(timefilter.getFetch$().subscribe(() => { if ($scope.jobSelectionUpdateInProgress === false) { explorer$.next({ action: EXPLORER_ACTION.RELOAD }); } - }); + })); subscriptions.add(subscribeAppStateToObservable(AppState, 'mlShowCharts', showCharts$, () => $rootScope.$applyAsync())); subscriptions.add(subscribeAppStateToObservable(AppState, 'mlSelectInterval', interval$, () => $rootScope.$applyAsync())); diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index fa55ef0fdda0c..fe8c1342f5ef0 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -69,6 +69,7 @@ export class JobsListView extends Component { this.showCreateWatchFlyout = () => {}; this.blockRefresh = false; + this.refreshIntervalSubscription = null; } componentDidMount() { @@ -100,7 +101,7 @@ export class JobsListView extends Component { componentWillUnmount() { if (this.props.isManagementTable === undefined) { - timefilter.off('refreshIntervalUpdate'); + if (this.refreshIntervalSubscription) this.refreshIntervalSubscription.unsubscribe(); deletingJobsRefreshTimeout = null; this.clearRefreshInterval(); } @@ -122,8 +123,8 @@ export class JobsListView extends Component { initAutoRefreshUpdate() { // update the interval if it changes - timefilter.on('refreshIntervalUpdate', () => { - this.setAutoRefresh(); + this.refreshIntervalSubscription = timefilter.getRefreshIntervalUpdate$().subscribe({ + next: this.setAutoRefresh }); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js index e0df234a5139c..fc83d8b1390f9 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/multi_metric/create_job/create_job_controller.js @@ -18,6 +18,7 @@ import dateMath from '@elastic/datemath'; import angular from 'angular'; import uiRoutes from 'ui/routes'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import { checkLicenseExpired } from 'plugins/ml/license/check_license'; import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; import { MlTimeBuckets } from 'plugins/ml/util/ml_time_buckets'; @@ -757,15 +758,18 @@ module preLoadJob($scope, appState); }); - $scope.$listenAndDigestAsync(timefilter, 'fetch', () => { - $scope.loadVis(); - if ($scope.formConfig.splitField !== undefined) { - $scope.setModelMemoryLimit(); + const fetchSub = subscribeWithScope($scope, timefilter.getFetch$(), { + next: () => { + $scope.loadVis(); + if ($scope.formConfig.splitField !== undefined) { + $scope.setModelMemoryLimit(); + } } }); $scope.$on('$destroy', () => { globalForceStop = true; angular.element(window).off('resize'); + fetchSub.unsubscribe(); }); }); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js index b3235ee6b374e..6f2770730db19 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/population/create_job/create_job_controller.js @@ -43,6 +43,7 @@ import { PopulationJobServiceProvider } from './create_job_service'; import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; import template from './create_job.html'; import { timefilter } from 'ui/timefilter'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; uiRoutes .when('/jobs/new_job/simple/population', { @@ -737,10 +738,13 @@ module preLoadJob($scope, appState); }); - $scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.loadVis); + const fetchSub = subscribeWithScope($scope, timefilter.getFetch$(), { + next: $scope.loadVis + }); $scope.$on('$destroy', () => { globalForceStop = true; angular.element(window).off('resize'); + fetchSub.unsubscribe(); }); }); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js index de4a0a2e6cdb7..7e1c77c8ee403 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job/simple/single_metric/create_job/create_job_controller.js @@ -18,6 +18,7 @@ import dateMath from '@elastic/datemath'; import angular from 'angular'; import uiRoutes from 'ui/routes'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import { getSafeAggregationName } from 'plugins/ml/../common/util/job_utils'; import { checkLicenseExpired } from 'plugins/ml/license/check_license'; import { checkCreateJobsPrivilege } from 'plugins/ml/privilege/check_privilege'; @@ -621,10 +622,13 @@ module moveToAdvancedJobCreation(job); }; - $scope.$listenAndDigestAsync(timefilter, 'fetch', $scope.loadVis); + const fetchSub = subscribeWithScope($scope, timefilter.getFetch$(), { + next: $scope.loadVis + }); $scope.$on('$destroy', () => { globalForceStop = true; + fetchSub.unsubscribe(); }); $scope.$evalAsync(() => { diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js index 9a5437199f5b2..5441d6ccd74c0 100644 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js @@ -882,7 +882,8 @@ export class TimeSeriesExplorer extends React.Component { timefilter.enableTimeRangeSelector(); timefilter.enableAutoRefreshSelector(); - timefilter.on('timeUpdate', this.refresh); + + this.subscriptions.add(timefilter.getTimeUpdate$().subscribe(this.refresh)); // Required to redraw the time series chart when the container is resized. this.resizeChecker = new ResizeChecker(this.resizeRef.current); @@ -894,7 +895,6 @@ export class TimeSeriesExplorer extends React.Component { componentWillUnmount() { this.subscriptions.unsubscribe(); - this.props.timefilter.off('timeUpdate', this.refresh); this.resizeChecker.destroy(); this.unsubscribeFromGlobalState(); } diff --git a/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js b/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js index d61b4f3edcfe0..eea6691c944b8 100644 --- a/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js +++ b/x-pack/legacy/plugins/ml/public/util/chart_utils.test.js @@ -31,16 +31,6 @@ jest.mock('ui/chrome', }, }), { virtual: true }); -jest.mock('ui/timefilter/lib/parse_querystring', - () => ({ - parseQueryString: () => { - return { - // Can not access local variable from within a mock - forceNow: global.nowTime - }; - }, - }), { virtual: true }); - import d3 from 'd3'; import moment from 'moment'; import { mount } from 'enzyme'; diff --git a/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js b/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js index 36ab9431058eb..4c72902c294bc 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js +++ b/x-pack/legacy/plugins/monitoring/public/services/__tests__/executor_provider.js @@ -37,24 +37,6 @@ describe('$executor service', () => { afterEach(() => executor.destroy()); - it('should register listener for fetch upon start', () => { - executor.start(scope); - const listeners = timefilter.listeners('fetch'); - const handlerFunc = listeners.find(listener => { - return listener.name === 'reFetch'; - }); - expect(handlerFunc).to.not.be.null; - }); - - it('should register listener for refreshIntervalUpdate upon start', () => { - executor.start(scope); - const listeners = timefilter.listeners('refreshIntervalUpdate'); - const handlerFunc = listeners.find(listener => { - return listener.name === 'killIfPaused'; - }); - expect(handlerFunc).to.not.be.null; - }); - it('should not call $timeout if the timefilter is not paused and set to zero', () => { executor.start(scope); expect($timeout.callCount).to.equal(0); diff --git a/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js b/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js index 7dd54e2a6ec27..70e0bbf6b0f80 100644 --- a/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js +++ b/x-pack/legacy/plugins/monitoring/public/services/executor_provider.js @@ -5,9 +5,12 @@ */ import { timefilter } from 'ui/timefilter'; +import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +import { Subscription } from 'rxjs'; export function executorProvider(Promise, $timeout) { const queue = []; + const subscriptions = new Subscription(); let executionTimer; let ignorePaused = false; @@ -46,6 +49,7 @@ export function executorProvider(Promise, $timeout) { * @returns {void} */ function destroy() { + subscriptions.unsubscribe(); cancel(); ignorePaused = false; queue.splice(0, queue.length); @@ -92,8 +96,12 @@ export function executorProvider(Promise, $timeout) { return { register, start($scope) { - $scope.$listenAndDigestAsync(timefilter, 'fetch', reFetch); - $scope.$listenAndDigestAsync(timefilter, 'refreshIntervalUpdate', killIfPaused); + subscriptions.add(subscribeWithScope($scope, timefilter.getFetch$(), { + next: reFetch + })); + subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { + next: killIfPaused + })); start(); }, run, diff --git a/x-pack/legacy/plugins/monitoring/public/views/no_data/controller.js b/x-pack/legacy/plugins/monitoring/public/views/no_data/controller.js index 371ef8e2bcc10..1576b80235c44 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/no_data/controller.js +++ b/x-pack/legacy/plugins/monitoring/public/views/no_data/controller.js @@ -16,15 +16,19 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { NoData } from 'plugins/monitoring/components'; import { timefilter } from 'ui/timefilter'; import { I18nContext } from 'ui/i18n'; -import 'ui/directives/listen'; import { CODE_PATH_LICENSE } from '../../../common/constants'; const REACT_NODE_ID_NO_DATA = 'noDataReact'; +// NoDataController watches all this attributes. +// After consulting with @Chris Roberson, decided to move this out of the class for now. +let timeUpdateSubscription; + export class NoDataController { + constructor($injector, $scope) { const $executor = $injector.get('$executor'); - this.enableTimefilter($executor, $scope); + this.enableTimefilter($executor); this.registerCleanup($scope, $executor); Object.assign(this, this.getDefaultModel()); @@ -103,10 +107,12 @@ export class NoDataController { $executor.start($scope); // start the executor to keep refreshing the search for data } - enableTimefilter($executor, $scope) { + enableTimefilter($executor) { timefilter.enableTimeRangeSelector(); timefilter.enableAutoRefreshSelector(); - $scope.$listen(timefilter, 'timeUpdate', () => $executor.run()); // re-fetch if they change the time filter + + // re-fetch if they change the time filter + timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe(() => $executor.run()); } registerCleanup($scope, $executor) { @@ -114,6 +120,7 @@ export class NoDataController { $scope.$on('$destroy', () => { $executor.destroy(); unmountComponentAtNode(document.getElementById(REACT_NODE_ID_NO_DATA)); + if (timeUpdateSubscription) { timeUpdateSubscription.unsubscribe(); } }); } } From 77e95eacdd5043778a1d866cf2fe5ff87afef4ac Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Tue, 27 Aug 2019 15:12:58 +0200 Subject: [PATCH 07/66] [APM] Instantly display local UI filter names (#44063) Closes #43339. --- .../apm/public/hooks/useLocalUIFilters.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/hooks/useLocalUIFilters.ts b/x-pack/legacy/plugins/apm/public/hooks/useLocalUIFilters.ts index 4d9f68b7d7b3a..6f0ccf0e99925 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useLocalUIFilters.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useLocalUIFilters.ts @@ -9,14 +9,24 @@ import { useFetcher } from './useFetcher'; import { callApi } from '../services/rest/callApi'; import { LocalUIFiltersAPIResponse } from '../../server/lib/ui_filters/local_ui_filters'; import { useUrlParams } from './useUrlParams'; -import { LocalUIFilterName } from '../../server/lib/ui_filters/local_ui_filters/config'; +import { + LocalUIFilterName, + localUIFilters +} from '../../server/lib/ui_filters/local_ui_filters/config'; import { history } from '../utils/history'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { removeUndefinedProps } from '../context/UrlParamsContext/helpers'; import { PROJECTION } from '../../common/projections/typings'; import { pickKeys } from '../utils/pickKeys'; -const initialData = [] as LocalUIFiltersAPIResponse; +const getInitialData = ( + filterNames: LocalUIFilterName[] +): LocalUIFiltersAPIResponse => { + return filterNames.map(filterName => ({ + options: [], + ...localUIFilters[filterName] + })); +}; export function useLocalUIFilters({ projection, @@ -53,7 +63,7 @@ export function useLocalUIFilters({ }); }; - const { data = initialData, status } = useFetcher(() => { + const { data = getInitialData(filterNames), status } = useFetcher(() => { return callApi({ method: 'GET', pathname: `/api/apm/ui_filters/local_filters/${projection}`, From e3499ed8d2d32a97fe67fe8dd6f552f6c324bf49 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 22 Aug 2019 15:10:50 +0300 Subject: [PATCH 08/66] Typescriptifying --- ....test.js => diff_time_picker_vals.test.ts} | 41 ++--- ...icker_vals.js => diff_time_picker_vals.ts} | 29 +++- ...se_querystring.js => parse_querystring.ts} | 0 .../{timefilter.js => timefilter.ts} | 157 ++++++++++-------- 4 files changed, 124 insertions(+), 103 deletions(-) rename src/legacy/ui/public/timefilter/lib/{diff_time_picker_vals.test.js => diff_time_picker_vals.test.ts} (74%) rename src/legacy/ui/public/timefilter/lib/{diff_time_picker_vals.js => diff_time_picker_vals.ts} (59%) rename src/legacy/ui/public/timefilter/lib/{parse_querystring.js => parse_querystring.ts} (100%) rename src/legacy/ui/public/timefilter/{timefilter.js => timefilter.ts} (69%) diff --git a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.js b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts similarity index 74% rename from src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.js rename to src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts index 12d729dd0136b..a30de37cc641d 100644 --- a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.js +++ b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts @@ -17,42 +17,40 @@ * under the License. */ - import moment from 'moment'; import expect from '@kbn/expect'; -import { areTimePickerValsDifferent } from './diff_time_picker_vals'; +import { areTimeRangesDifferent } from './diff_time_picker_vals'; describe('Diff Time Picker Values', () => { - test('accepts two undefined values', () => { - const diff = areTimePickerValsDifferent(undefined, undefined); + const diff = areTimeRangesDifferent(undefined, undefined); expect(diff).to.be(false); }); describe('dateMath ranges', () => { test('knows a match', () => { - const diff = areTimePickerValsDifferent( + const diff = areTimeRangesDifferent( { to: 'now', - from: 'now-7d' + from: 'now-7d', }, { to: 'now', - from: 'now-7d' + from: 'now-7d', } ); expect(diff).to.be(false); }); test('knows a difference', () => { - const diff = areTimePickerValsDifferent( + const diff = areTimeRangesDifferent( { to: 'now', - from: 'now-7d' + from: 'now-7d', }, { to: 'now', - from: 'now-1h' + from: 'now-1h', } ); @@ -62,14 +60,14 @@ describe('Diff Time Picker Values', () => { describe('a dateMath range, and a moment range', () => { test('is always different', () => { - const diff = areTimePickerValsDifferent( + const diff = areTimeRangesDifferent( { to: moment(), - from: moment() + from: moment(), }, { to: 'now', - from: 'now-1h' + from: 'now-1h', } ); @@ -82,14 +80,14 @@ describe('Diff Time Picker Values', () => { const to = moment(); const from = moment().add(1, 'day'); - const diff = areTimePickerValsDifferent( + const diff = areTimeRangesDifferent( { to: to.clone(), - from: from.clone() + from: from.clone(), }, { to: to.clone(), - from: from.clone() + from: from.clone(), } ); @@ -101,23 +99,18 @@ describe('Diff Time Picker Values', () => { const from = moment().add(1, 'day'); const from2 = moment().add(2, 'day'); - const diff = areTimePickerValsDifferent( + const diff = areTimeRangesDifferent( { to: to.clone(), - from: from.clone() + from: from.clone(), }, { to: to.clone(), - from: from2.clone() + from: from2.clone(), } ); expect(diff).to.be(true); }); }); - - test('does not fall apart with unusual values', () => { - const diff = areTimePickerValsDifferent({}, {}); - expect(diff).to.be(false); - }); }); diff --git a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.js b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts similarity index 59% rename from src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.js rename to src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts index aedee5c8fda9c..304986788f9b2 100644 --- a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.js +++ b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts @@ -18,18 +18,35 @@ */ import _ from 'lodash'; +import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -const valueOf = function (o) { +const valueOf = function(o) { if (o) return o.valueOf(); }; -export function areTimePickerValsDifferent(rangeA, rangeB) { +export function areRefreshIntervalsDifferent(rangeA: RefreshInterval, rangeB: RefreshInterval) { if (_.isObject(rangeA) && _.isObject(rangeB)) { if ( - valueOf(rangeA.to) !== valueOf(rangeB.to) - || valueOf(rangeA.from) !== valueOf(rangeB.from) - || valueOf(rangeA.value) !== valueOf(rangeB.value) - || valueOf(rangeA.pause) !== valueOf(rangeB.pause) + valueOf(rangeA.value) !== valueOf(rangeB.value) || + valueOf(rangeA.pause) !== valueOf(rangeB.pause) + ) { + return true; + } + } else { + return !_.isEqual(rangeA, rangeB); + } + + return false; +} + +export function areTimeRangesDifferent( + rangeA: TimeRange | undefined, + rangeB: TimeRange | undefined +) { + if (rangeA && rangeB && _.isObject(rangeA) && _.isObject(rangeB)) { + if ( + valueOf(rangeA.to) !== valueOf(rangeB.to) || + valueOf(rangeA.from) !== valueOf(rangeB.from) ) { return true; } diff --git a/src/legacy/ui/public/timefilter/lib/parse_querystring.js b/src/legacy/ui/public/timefilter/lib/parse_querystring.ts similarity index 100% rename from src/legacy/ui/public/timefilter/lib/parse_querystring.js rename to src/legacy/ui/public/timefilter/lib/parse_querystring.ts diff --git a/src/legacy/ui/public/timefilter/timefilter.js b/src/legacy/ui/public/timefilter/timefilter.ts similarity index 69% rename from src/legacy/ui/public/timefilter/timefilter.js rename to src/legacy/ui/public/timefilter/timefilter.ts index fa85d175690a9..5c309bb705db6 100644 --- a/src/legacy/ui/public/timefilter/timefilter.js +++ b/src/legacy/ui/public/timefilter/timefilter.ts @@ -20,66 +20,68 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; -import { calculateBounds, getTime } from './get_time'; -import { parseQueryString } from './lib/parse_querystring'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import uiRoutes from '../routes'; import chrome from 'ui/chrome'; -import { areTimePickerValsDifferent } from './lib/diff_time_picker_vals'; +import { UiSettingsClientContract } from 'kibana/public'; +import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; +import { IndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns'; +import { IScope } from 'angular'; import { timeHistory } from './time_history'; +import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; +import uiRoutes from '../routes'; +import { parseQueryString } from './lib/parse_querystring'; +import { calculateBounds, getTime } from './get_time'; class Timefilter { - constructor() { - - // Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled - this.enabledUpdated$ = new BehaviorSubject(); - - // Fired when a user changes the timerange - this.timeUpdate$ = new Subject(); - - // Fired when a user changes the the autorefresh settings - this.refreshIntervalUpdate$ = new Subject(); - - // Used when search poll triggers an auto refresh - this.autoRefreshFetch$ = new Subject(); - - this.fetch$ = new Subject(); - - this.isTimeRangeSelectorEnabled = false; - this.isAutoRefreshSelectorEnabled = false; - this._time = chrome.getUiSettingsClient().get('timepicker:timeDefaults'); - this.setRefreshInterval(chrome.getUiSettingsClient().get('timepicker:refreshIntervalDefaults')); + // Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled + private enabledUpdated$ = new BehaviorSubject(false); + // Fired when a user changes the timerange + private timeUpdate$ = new Subject(); + // Fired when a user changes the the autorefresh settings + private refreshIntervalUpdate$ = new Subject(); + // Used when search poll triggers an auto refresh + private autoRefreshFetch$ = new Subject(); + private fetch$ = new Subject(); + + private _time: TimeRange; + private _refreshInterval!: RefreshInterval; + + public isTimeRangeSelectorEnabled: boolean = false; + public isAutoRefreshSelectorEnabled: boolean = false; + + constructor(uiSettings: UiSettingsClientContract) { + this._time = uiSettings.get('timepicker:timeDefaults'); + this.setRefreshInterval(uiSettings.get('timepicker:refreshIntervalDefaults')); } getEnabledUpdated$ = () => { return this.enabledUpdated$.asObservable(); - } + }; getTimeUpdate$ = () => { return this.timeUpdate$.asObservable(); - } + }; getRefreshIntervalUpdate$ = () => { return this.refreshIntervalUpdate$.asObservable(); - } + }; getAutoRefreshFetch$ = () => { return this.autoRefreshFetch$.asObservable(); - } + }; getFetch$ = () => { return this.fetch$.asObservable(); - } - + }; getTime = () => { const { from, to } = this._time; return { ...this._time, from: moment.isMoment(from) ? from.toISOString() : from, - to: moment.isMoment(to) ? to.toISOString() : to + to: moment.isMoment(to) ? to.toISOString() : to, }; - } + }; /** * Updates timefilter time. @@ -88,10 +90,10 @@ class Timefilter { * @property {string|moment} time.from * @property {string|moment} time.to */ - setTime = (time) => { + setTime = (time: TimeRange) => { // Object.assign used for partially composed updates const newTime = Object.assign(this.getTime(), time); - if (areTimePickerValsDifferent(this.getTime(), newTime)) { + if (areTimeRangesDifferent(this.getTime(), newTime)) { this._time = { from: newTime.from, to: newTime.to, @@ -100,11 +102,11 @@ class Timefilter { this.timeUpdate$.next(); this.fetch$.next(); } - } + }; getRefreshInterval = () => { return _.clone(this._refreshInterval); - } + }; /** * Set timefilter refresh interval. @@ -112,7 +114,7 @@ class Timefilter { * @property {number} time.value Refresh interval in milliseconds. Positive integer * @property {boolean} time.pause */ - setRefreshInterval = (refreshInterval) => { + setRefreshInterval = (refreshInterval: RefreshInterval) => { const prevRefreshInterval = this.getRefreshInterval(); const newRefreshInterval = { ...prevRefreshInterval, ...refreshInterval }; // If the refresh interval is <= 0 handle that as a paused refresh @@ -122,29 +124,35 @@ class Timefilter { } this._refreshInterval = { value: newRefreshInterval.value, - pause: newRefreshInterval.pause + pause: newRefreshInterval.pause, }; // Only send out an event if we already had a previous refresh interval (not for the initial set) // and the old and new refresh interval are actually different. - if (prevRefreshInterval && areTimePickerValsDifferent(prevRefreshInterval, newRefreshInterval)) { + if ( + prevRefreshInterval && + areRefreshIntervalsDifferent(prevRefreshInterval, newRefreshInterval) + ) { this.refreshIntervalUpdate$.next(); if (!newRefreshInterval.pause && newRefreshInterval.value !== 0) { this.fetch$.next(); } } - } + }; toggleRefresh = () => { - this.setRefreshInterval({ pause: !this._refreshInterval.pause }); - } + this.setRefreshInterval({ + pause: !this._refreshInterval.pause, + value: this._refreshInterval.value, + }); + }; - createFilter = (indexPattern, timeRange) => { + createFilter = (indexPattern: IndexPattern, timeRange: TimeRange) => { return getTime(indexPattern, timeRange ? timeRange : this._time, this.getForceNow()); - } + }; getBounds = () => { return this.calculateBounds(this._time); - } + }; getForceNow = () => { const forceNow = parseQueryString().forceNow; @@ -157,62 +165,61 @@ class Timefilter { throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`); } return new Date(ticks); - } + }; - calculateBounds = (timeRange) => { + calculateBounds = (timeRange: TimeRange) => { return calculateBounds(timeRange, { forceNow: this.getForceNow() }); - } + }; getActiveBounds = () => { if (this.isTimeRangeSelectorEnabled) { return this.getBounds(); } - } + }; /** * Show the time bounds selector part of the time filter */ enableTimeRangeSelector = () => { this.isTimeRangeSelectorEnabled = true; - this.enabledUpdated$.next(); - } + this.enabledUpdated$.next(true); + }; /** * Hide the time bounds selector part of the time filter */ disableTimeRangeSelector = () => { this.isTimeRangeSelectorEnabled = false; - this.enabledUpdated$.next(); - } + this.enabledUpdated$.next(false); + }; /** * Show the auto refresh part of the time filter */ enableAutoRefreshSelector = () => { this.isAutoRefreshSelectorEnabled = true; - this.enabledUpdated$.next(); - } + this.enabledUpdated$.next(true); + }; /** * Hide the auto refresh part of the time filter */ disableAutoRefreshSelector = () => { this.isAutoRefreshSelectorEnabled = false; - this.enabledUpdated$.next(); - } + this.enabledUpdated$.next(false); + }; notifyShouldFetch = () => { this.autoRefreshFetch$.next(); - } - + }; } -export const timefilter = new Timefilter(); +export const timefilter = new Timefilter(chrome.getUiSettingsClient()); // TODO // remove everything underneath once globalState is no longer an angular service // and listener can be registered without angular. -function convertISO8601(stringTime) { +function convertISO8601(stringTime: string) { const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); return obj.isValid() ? obj : stringTime; } @@ -221,18 +228,24 @@ function convertISO8601(stringTime) { // and require it to be executed to properly function. // This function is exposed for applications that do not use uiRoutes like APM // Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter -export const registerTimefilterWithGlobalState = _.once((globalState, $rootScope) => { +export const registerTimefilterWithGlobalState = _.once((globalState: any, $rootScope: IScope) => { const uiSettings = chrome.getUiSettingsClient(); const timeDefaults = uiSettings.get('timepicker:timeDefaults'); const refreshIntervalDefaults = uiSettings.get('timepicker:refreshIntervalDefaults'); timefilter.setTime(_.defaults(globalState.time || {}, timeDefaults)); - timefilter.setRefreshInterval(_.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults)); + timefilter.setRefreshInterval( + _.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults) + ); globalState.on('fetch_with_changes', () => { - // clone and default to {} in one - const newTime = _.defaults({}, globalState.time, timeDefaults); - const newRefreshInterval = _.defaults({}, globalState.refreshInterval, refreshIntervalDefaults); + // clone and default to {} in one + const newTime: TimeRange = _.defaults({}, globalState.time, timeDefaults); + const newRefreshInterval: RefreshInterval = _.defaults( + {}, + globalState.refreshInterval, + refreshIntervalDefaults + ); if (newTime) { if (newTime.to) newTime.to = convertISO8601(newTime.to); @@ -250,16 +263,14 @@ export const registerTimefilterWithGlobalState = _.once((globalState, $rootScope }; subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { - next: updateGlobalStateWithTime + next: updateGlobalStateWithTime, }); subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { - next: updateGlobalStateWithTime + next: updateGlobalStateWithTime, }); - }); -uiRoutes - .addSetupWork((globalState, $rootScope) => { - return registerTimefilterWithGlobalState(globalState, $rootScope); - }); +uiRoutes.addSetupWork((globalState, $rootScope) => { + return registerTimefilterWithGlobalState(globalState, $rootScope); +}); From 8b9d86a13f97c83730f84d7fffa62f8f88d0dc80 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 22 Aug 2019 16:00:59 +0300 Subject: [PATCH 09/66] More TS --- .../public/dashboard/dashboard_state.test.ts | 43 +++++---- src/legacy/ui/public/timefilter/get_time.ts | 2 +- src/legacy/ui/public/timefilter/index.ts | 7 +- .../timefilter/lib/diff_time_picker_vals.ts | 2 +- .../ui/public/timefilter/time_history.d.ts | 29 ------ .../{time_history.js => time_history.ts} | 13 ++- .../ui/public/timefilter/timefilter.d.ts | 49 ---------- ...{timefilter.test.js => timefilter.test.ts} | 96 +++++++++++-------- src/legacy/ui/public/timefilter/timefilter.ts | 23 +++-- 9 files changed, 106 insertions(+), 158 deletions(-) delete mode 100644 src/legacy/ui/public/timefilter/time_history.d.ts rename src/legacy/ui/public/timefilter/{time_history.js => time_history.ts} (88%) delete mode 100644 src/legacy/ui/public/timefilter/timefilter.d.ts rename src/legacy/ui/public/timefilter/{timefilter.test.js => timefilter.test.ts} (82%) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index 9e9f5c3fe0b37..40e65f5683ee2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -23,26 +23,27 @@ import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; import { AppStateClass } from 'ui/state_management/app_state'; import { DashboardAppState } from './types'; -import { Timefilter } from 'ui/timefilter'; +import { Timefilter, TimeRange } from 'ui/timefilter'; import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; describe('DashboardState', function() { let dashboardState: DashboardStateManager; const savedDashboard = getSavedDashboardMock(); - const mockTimefilter: Timefilter = { - time: { to: 'now', from: 'now-15m' }, + + let mockTime: TimeRange = { to: 'now', from: 'now-15m' }; + const mockTimefilter: Partial = { setTime(time) { - this.time = time; + mockTime = time as TimeRange; }, getTime() { - return this.time; + return mockTime; }, disableAutoRefreshSelector: jest.fn(), setRefreshInterval: jest.fn(), getRefreshInterval: jest.fn(), disableTimeRangeSelector: jest.fn(), enableAutoRefreshSelector: jest.fn(), - getActiveBounds: () => {}, + getActiveBounds: jest.fn(), enableTimeRangeSelector: () => {}, isAutoRefreshSelectorEnabled: true, isTimeRangeSelectorEnabled: true, @@ -67,14 +68,14 @@ describe('DashboardState', function() { savedDashboard.timeFrom = 'now/w'; savedDashboard.timeTo = 'now/w'; - mockTimefilter.time.from = '2015-09-19 06:31:44.000'; - mockTimefilter.time.to = '2015-09-29 06:31:44.000'; + mockTime.from = '2015-09-19 06:31:44.000'; + mockTime.to = '2015-09-29 06:31:44.000'; initDashboardState(); - dashboardState.syncTimefilterWithDashboard(mockTimefilter); + dashboardState.syncTimefilterWithDashboard(mockTimefilter as Timefilter); - expect(mockTimefilter.time.to).toBe('now/w'); - expect(mockTimefilter.time.from).toBe('now/w'); + expect(mockTime.to).toBe('now/w'); + expect(mockTime.from).toBe('now/w'); }); test('syncs relative time', function() { @@ -82,14 +83,14 @@ describe('DashboardState', function() { savedDashboard.timeFrom = 'now-13d'; savedDashboard.timeTo = 'now'; - mockTimefilter.time.from = '2015-09-19 06:31:44.000'; - mockTimefilter.time.to = '2015-09-29 06:31:44.000'; + mockTime.from = '2015-09-19 06:31:44.000'; + mockTime.to = '2015-09-29 06:31:44.000'; initDashboardState(); - dashboardState.syncTimefilterWithDashboard(mockTimefilter); + dashboardState.syncTimefilterWithDashboard(mockTimefilter as Timefilter); - expect(mockTimefilter.time.to).toBe('now'); - expect(mockTimefilter.time.from).toBe('now-13d'); + expect(mockTime.to).toBe('now'); + expect(mockTime.from).toBe('now-13d'); }); test('syncs absolute time', function() { @@ -97,14 +98,14 @@ describe('DashboardState', function() { savedDashboard.timeFrom = '2015-09-19 06:31:44.000'; savedDashboard.timeTo = '2015-09-29 06:31:44.000'; - mockTimefilter.time.from = 'now/w'; - mockTimefilter.time.to = 'now/w'; + mockTime.from = 'now/w'; + mockTime.to = 'now/w'; initDashboardState(); - dashboardState.syncTimefilterWithDashboard(mockTimefilter); + dashboardState.syncTimefilterWithDashboard(mockTimefilter as Timefilter); - expect(mockTimefilter.time.to).toBe(savedDashboard.timeTo); - expect(mockTimefilter.time.from).toBe(savedDashboard.timeFrom); + expect(mockTime.to).toBe(savedDashboard.timeTo); + expect(mockTime.from).toBe(savedDashboard.timeFrom); }); }); diff --git a/src/legacy/ui/public/timefilter/get_time.ts b/src/legacy/ui/public/timefilter/get_time.ts index eeb09cdfe3743..e54725dd9ba48 100644 --- a/src/legacy/ui/public/timefilter/get_time.ts +++ b/src/legacy/ui/public/timefilter/get_time.ts @@ -19,7 +19,7 @@ import dateMath from '@elastic/datemath'; import { Field, IndexPattern } from 'ui/index_patterns'; -import { TimeRange } from './time_history'; +import { TimeRange } from 'src/plugins/data/public'; interface CalculateBoundsOptions { forceNow?: Date; diff --git a/src/legacy/ui/public/timefilter/index.ts b/src/legacy/ui/public/timefilter/index.ts index 234ee50cece1a..5f5fcf6b19c7f 100644 --- a/src/legacy/ui/public/timefilter/index.ts +++ b/src/legacy/ui/public/timefilter/index.ts @@ -17,9 +17,8 @@ * under the License. */ -// @ts-ignore -export { registerTimefilterWithGlobalState } from './timefilter'; -export { timefilter, Timefilter, RefreshInterval } from './timefilter'; -export { timeHistory, TimeRange, TimeHistory } from './time_history'; +export { TimeRange, RefreshInterval } from '../../../../plugins/data/public'; +export { timefilter, Timefilter, registerTimefilterWithGlobalState } from './timefilter'; +export { timeHistory, TimeHistory } from './time_history'; export { getTime } from './get_time'; diff --git a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts index 304986788f9b2..e7d800bbb1e0c 100644 --- a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts +++ b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -const valueOf = function(o) { +const valueOf = function(o: any) { if (o) return o.valueOf(); }; diff --git a/src/legacy/ui/public/timefilter/time_history.d.ts b/src/legacy/ui/public/timefilter/time_history.d.ts deleted file mode 100644 index ef4a9bfbe4ccf..0000000000000 --- a/src/legacy/ui/public/timefilter/time_history.d.ts +++ /dev/null @@ -1,29 +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 { TimeRange } from '../../../../plugins/data/public'; - -export { TimeRange }; - -export interface TimeHistory { - add: (options: TimeRange) => void; - get: () => TimeRange[]; -} - -export const timeHistory: TimeHistory; diff --git a/src/legacy/ui/public/timefilter/time_history.js b/src/legacy/ui/public/timefilter/time_history.ts similarity index 88% rename from src/legacy/ui/public/timefilter/time_history.js rename to src/legacy/ui/public/timefilter/time_history.ts index 1ae908d174f97..66957ec5987c9 100644 --- a/src/legacy/ui/public/timefilter/time_history.js +++ b/src/legacy/ui/public/timefilter/time_history.ts @@ -18,21 +18,24 @@ */ import moment from 'moment'; +import { TimeRange } from 'src/plugins/data/public'; import { PersistedLog } from '../persisted_log'; -class TimeHistory { +export class TimeHistory { + private history: PersistedLog; + constructor() { const historyOptions = { maxLength: 10, filterDuplicates: true, - isDuplicate: (oldItem, newItem) => { + isDuplicate: (oldItem: TimeRange, newItem: TimeRange) => { return oldItem.from === newItem.from && oldItem.to === newItem.to; - } + }, }; this.history = new PersistedLog('kibana.timepicker.timeHistory', historyOptions); } - add(time) { + add(time: TimeRange) { if (!time) { return; } @@ -40,7 +43,7 @@ class TimeHistory { // time from/to can be strings or moment objects - convert to strings so always dealing with same types const justStringsTime = { from: moment.isMoment(time.from) ? time.from.toISOString() : time.from, - to: moment.isMoment(time.to) ? time.to.toISOString() : time.to + to: moment.isMoment(time.to) ? time.to.toISOString() : time.to, }; this.history.add(justStringsTime); } diff --git a/src/legacy/ui/public/timefilter/timefilter.d.ts b/src/legacy/ui/public/timefilter/timefilter.d.ts deleted file mode 100644 index fb9808298c6d0..0000000000000 --- a/src/legacy/ui/public/timefilter/timefilter.d.ts +++ /dev/null @@ -1,49 +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 { Moment } from 'moment'; -import { Observable } from 'rxjs'; -import { TimeRange } from './time_history'; -import { RefreshInterval } from '../../../../plugins/data/public'; - -// NOTE: These types are somewhat guessed, they may be incorrect. - -export { RefreshInterval, TimeRange }; - -export interface Timefilter { - time: TimeRange; - getEnabledUpdated$: () => Observable; - getTimeUpdate$: () => Observable; - getRefreshIntervalUpdate$: () => Observable; - getAutoRefreshFetch$: () => Observable; - getFetch$: () => Observable; - getTime: () => TimeRange; - setTime: (timeRange: TimeRange) => void; - setRefreshInterval: (refreshInterval: RefreshInterval) => void; - getRefreshInterval: () => RefreshInterval; - getActiveBounds: () => void; - disableAutoRefreshSelector: () => void; - disableTimeRangeSelector: () => void; - enableAutoRefreshSelector: () => void; - enableTimeRangeSelector: () => void; - isAutoRefreshSelectorEnabled: boolean; - isTimeRangeSelectorEnabled: boolean; -} - -export const timefilter: Timefilter; diff --git a/src/legacy/ui/public/timefilter/timefilter.test.js b/src/legacy/ui/public/timefilter/timefilter.test.ts similarity index 82% rename from src/legacy/ui/public/timefilter/timefilter.test.js rename to src/legacy/ui/public/timefilter/timefilter.test.ts index 64a43b259dd3f..454cd640a41b6 100644 --- a/src/legacy/ui/public/timefilter/timefilter.test.js +++ b/src/legacy/ui/public/timefilter/timefilter.test.ts @@ -19,12 +19,13 @@ import './timefilter.test.mocks'; -jest.mock('ui/chrome', +jest.mock( + 'ui/chrome', () => ({ getBasePath: () => `/some/base/path`, getUiSettingsClient: () => { return { - get: (key) => { + get: (key: string) => { switch (key) { case 'timepicker:timeDefaults': return { from: 'now-15m', to: 'now' }; @@ -33,46 +34,56 @@ jest.mock('ui/chrome', default: throw new Error(`Unexpected config key: ${key}`); } - } + }, }; }, - }), { virtual: true }); + }), + { virtual: true } +); -jest.mock('ui/timefilter/lib/parse_querystring', +jest.mock( + './lib/parse_querystring', () => ({ parseQueryString: () => { return { // Can not access local variable from within a mock - forceNow: global.nowTime + // @ts-ignore + forceNow: global.nowTime, }; }, - }), { virtual: true }); + }), + { virtual: true } +); import sinon from 'sinon'; import expect from '@kbn/expect'; import moment from 'moment'; import { timefilter } from './timefilter'; +import { Subscription } from 'rxjs'; +import { TimeRange, RefreshInterval } from 'src/plugins/data/public'; -function stubNowTime(nowTime) { +function stubNowTime(nowTime: any) { + // @ts-ignore global.nowTime = nowTime; } function clearNowTimeStub() { + // @ts-ignore delete global.nowTime; } describe('setTime', () => { - let update; - let fetch; - let updateSub; - let fetchSub; + let update: sinon.SinonSpy; + let fetch: sinon.SinonSpy; + let updateSub: Subscription; + let fetchSub: Subscription; beforeEach(() => { update = sinon.spy(); fetch = sinon.spy(); timefilter.setTime({ - from: 0, - to: 1, + from: '0', + to: '1', }); updateSub = timefilter.getTimeUpdate$().subscribe(update); fetchSub = timefilter.getFetch$().subscribe(fetch); @@ -84,29 +95,33 @@ describe('setTime', () => { }); test('should update time', () => { - timefilter.setTime({ from: 5, to: 10 }); + timefilter.setTime({ from: '5', to: '10' }); expect(timefilter.getTime()).to.eql({ from: 5, to: 10 }); }); test('should not add unexpected object keys to time state', () => { const unexpectedKey = 'unexpectedKey'; - timefilter.setTime({ from: 5, to: 10, [unexpectedKey]: 'I should not be added to time state' }); + timefilter.setTime({ + from: '5', + to: '10', + [unexpectedKey]: 'I should not be added to time state', + } as TimeRange); expect(timefilter.getTime()).not.to.have.property(unexpectedKey); }); test('should allow partial updates to time', () => { - timefilter.setTime({ from: 5, to: 10 }); + timefilter.setTime({ from: '5', to: '10' }); expect(timefilter.getTime()).to.eql({ from: 5, to: 10 }); }); test('not emit anything if the time has not changed', () => { - timefilter.setTime({ from: 0, to: 1 }); + timefilter.setTime({ from: '0', to: '1' }); expect(update.called).to.be(false); expect(fetch.called).to.be(false); }); test('emit update and fetch if the time has changed', () => { - timefilter.setTime({ from: 5, to: 10 }); + timefilter.setTime({ from: '5', to: '10' }); expect(update.called).to.be(true); expect(fetch.called).to.be(true); }); @@ -123,17 +138,17 @@ describe('setTime', () => { }); describe('setRefreshInterval', () => { - let update; - let fetch; - let fetchSub; - let refreshSub; + let update: sinon.SinonSpy; + let fetch: sinon.SinonSpy; + let fetchSub: Subscription; + let refreshSub: Subscription; beforeEach(() => { update = sinon.spy(); fetch = sinon.spy(); timefilter.setRefreshInterval({ pause: false, - value: 0 + value: 0, }); refreshSub = timefilter.getRefreshIntervalUpdate$().subscribe(update); fetchSub = timefilter.getFetch$().subscribe(fetch); @@ -151,7 +166,11 @@ describe('setRefreshInterval', () => { test('should not add unexpected object keys to refreshInterval state', () => { const unexpectedKey = 'unexpectedKey'; - timefilter.setRefreshInterval({ pause: true, value: 10, [unexpectedKey]: 'I should not be added to refreshInterval state' }); + timefilter.setRefreshInterval({ + pause: true, + value: 10, + [unexpectedKey]: 'I should not be added to refreshInterval state', + } as RefreshInterval); expect(timefilter.getRefreshInterval()).not.to.have.property(unexpectedKey); }); @@ -211,12 +230,11 @@ describe('setRefreshInterval', () => { expect(update.calledTwice).to.be(true); expect(fetch.calledOnce).to.be(true); }); - }); describe('isTimeRangeSelectorEnabled', () => { - let update; - let updateSub; + let update: sinon.SinonSpy; + let updateSub: Subscription; beforeEach(() => { update = sinon.spy(); @@ -241,8 +259,8 @@ describe('isTimeRangeSelectorEnabled', () => { }); describe('isAutoRefreshSelectorEnabled', () => { - let update; - let updateSub; + let update: sinon.SinonSpy; + let updateSub: Subscription; beforeEach(() => { update = sinon.spy(); @@ -267,11 +285,10 @@ describe('isAutoRefreshSelectorEnabled', () => { }); describe('calculateBounds', () => { - const fifteenMinutesInMilliseconds = 15 * 60 * 1000; const clockNowTicks = new Date(2000, 1, 1, 0, 0, 0, 0).valueOf(); - let clock; + let clock: sinon.SinonFakeTimers; beforeEach(() => { clock = sinon.useFakeTimers(clockNowTicks); @@ -285,19 +302,19 @@ describe('calculateBounds', () => { test('uses clock time by default', () => { const timeRange = { from: 'now-15m', - to: 'now' + to: 'now', }; stubNowTime(undefined); const result = timefilter.calculateBounds(timeRange); - expect(result.min.valueOf()).to.eql(clockNowTicks - fifteenMinutesInMilliseconds); - expect(result.max.valueOf()).to.eql(clockNowTicks); + expect(result.min && result.min.valueOf()).to.eql(clockNowTicks - fifteenMinutesInMilliseconds); + expect(result.max && result.max.valueOf()).to.eql(clockNowTicks); }); test('uses forceNow string', () => { const timeRange = { from: 'now-15m', - to: 'now' + to: 'now', }; const forceNowString = '1999-01-01T00:00:00.000Z'; @@ -305,18 +322,17 @@ describe('calculateBounds', () => { const result = timefilter.calculateBounds(timeRange); const forceNowTicks = Date.parse(forceNowString); - expect(result.min.valueOf()).to.eql(forceNowTicks - fifteenMinutesInMilliseconds); - expect(result.max.valueOf()).to.eql(forceNowTicks); + expect(result.min && result.min.valueOf()).to.eql(forceNowTicks - fifteenMinutesInMilliseconds); + expect(result.max && result.max.valueOf()).to.eql(forceNowTicks); }); test(`throws Error if forceNow can't be parsed`, () => { const timeRange = { from: 'now-15m', - to: 'now' + to: 'now', }; stubNowTime('not_a_parsable_date'); expect(() => timefilter.calculateBounds(timeRange)).to.throwError(); }); }); - diff --git a/src/legacy/ui/public/timefilter/timefilter.ts b/src/legacy/ui/public/timefilter/timefilter.ts index 5c309bb705db6..d8019a6001e9b 100644 --- a/src/legacy/ui/public/timefilter/timefilter.ts +++ b/src/legacy/ui/public/timefilter/timefilter.ts @@ -19,10 +19,10 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; -import moment from 'moment'; +import moment, { Moment } from 'moment'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import chrome from 'ui/chrome'; -import { UiSettingsClientContract } from 'kibana/public'; +import { UiSettingsClientContract } from 'src/core/public'; import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; import { IndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns'; import { IScope } from 'angular'; @@ -32,7 +32,7 @@ import uiRoutes from '../routes'; import { parseQueryString } from './lib/parse_querystring'; import { calculateBounds, getTime } from './get_time'; -class Timefilter { +export class Timefilter { // Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled private enabledUpdated$ = new BehaviorSubject(false); // Fired when a user changes the timerange @@ -90,7 +90,14 @@ class Timefilter { * @property {string|moment} time.from * @property {string|moment} time.to */ - setTime = (time: TimeRange) => { + setTime = ( + time: + | TimeRange + | { + from: Moment; + to: Moment; + } + ) => { // Object.assign used for partially composed updates const newTime = Object.assign(this.getTime(), time); if (areTimeRangesDifferent(this.getTime(), newTime)) { @@ -114,7 +121,7 @@ class Timefilter { * @property {number} time.value Refresh interval in milliseconds. Positive integer * @property {boolean} time.pause */ - setRefreshInterval = (refreshInterval: RefreshInterval) => { + setRefreshInterval = (refreshInterval: Partial) => { const prevRefreshInterval = this.getRefreshInterval(); const newRefreshInterval = { ...prevRefreshInterval, ...refreshInterval }; // If the refresh interval is <= 0 handle that as a paused refresh @@ -155,7 +162,7 @@ class Timefilter { }; getForceNow = () => { - const forceNow = parseQueryString().forceNow; + const forceNow = parseQueryString().forceNow as string; if (!forceNow) { return; } @@ -219,9 +226,9 @@ export const timefilter = new Timefilter(chrome.getUiSettingsClient()); // TODO // remove everything underneath once globalState is no longer an angular service // and listener can be registered without angular. -function convertISO8601(stringTime: string) { +function convertISO8601(stringTime: string): string { const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); - return obj.isValid() ? obj : stringTime; + return obj.isValid() ? obj.toString() : stringTime; } // Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter From a7ecf490f2939f56b115ae4e6f3e32e38b43ef2f Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 22 Aug 2019 16:12:36 +0300 Subject: [PATCH 10/66] define InputTimeRange type --- .../lib/diff_time_picker_vals.test.ts | 5 ----- .../timefilter/lib/diff_time_picker_vals.ts | 9 ++++----- .../ui/public/timefilter/timefilter.test.ts | 7 +++++-- src/legacy/ui/public/timefilter/timefilter.ts | 19 ++++++++++--------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts index a30de37cc641d..659e4d69f1ea0 100644 --- a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts +++ b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.test.ts @@ -22,11 +22,6 @@ import expect from '@kbn/expect'; import { areTimeRangesDifferent } from './diff_time_picker_vals'; describe('Diff Time Picker Values', () => { - test('accepts two undefined values', () => { - const diff = areTimeRangesDifferent(undefined, undefined); - expect(diff).to.be(false); - }); - describe('dateMath ranges', () => { test('knows a match', () => { const diff = areTimeRangesDifferent( diff --git a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts index e7d800bbb1e0c..23a05bc29a15b 100644 --- a/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts +++ b/src/legacy/ui/public/timefilter/lib/diff_time_picker_vals.ts @@ -18,7 +18,9 @@ */ import _ from 'lodash'; -import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; + +import { RefreshInterval } from 'src/plugins/data/public'; +import { InputTimeRange } from '../timefilter'; const valueOf = function(o: any) { if (o) return o.valueOf(); @@ -39,10 +41,7 @@ export function areRefreshIntervalsDifferent(rangeA: RefreshInterval, rangeB: Re return false; } -export function areTimeRangesDifferent( - rangeA: TimeRange | undefined, - rangeB: TimeRange | undefined -) { +export function areTimeRangesDifferent(rangeA: InputTimeRange, rangeB: InputTimeRange) { if (rangeA && rangeB && _.isObject(rangeA) && _.isObject(rangeB)) { if ( valueOf(rangeA.to) !== valueOf(rangeB.to) || diff --git a/src/legacy/ui/public/timefilter/timefilter.test.ts b/src/legacy/ui/public/timefilter/timefilter.test.ts index 454cd640a41b6..e1ece3a55a6a1 100644 --- a/src/legacy/ui/public/timefilter/timefilter.test.ts +++ b/src/legacy/ui/public/timefilter/timefilter.test.ts @@ -96,7 +96,10 @@ describe('setTime', () => { test('should update time', () => { timefilter.setTime({ from: '5', to: '10' }); - expect(timefilter.getTime()).to.eql({ from: 5, to: 10 }); + expect(timefilter.getTime()).to.eql({ + from: '5', + to: '10', + }); }); test('should not add unexpected object keys to time state', () => { @@ -111,7 +114,7 @@ describe('setTime', () => { test('should allow partial updates to time', () => { timefilter.setTime({ from: '5', to: '10' }); - expect(timefilter.getTime()).to.eql({ from: 5, to: 10 }); + expect(timefilter.getTime()).to.eql({ from: '5', to: '10' }); }); test('not emit anything if the time has not changed', () => { diff --git a/src/legacy/ui/public/timefilter/timefilter.ts b/src/legacy/ui/public/timefilter/timefilter.ts index d8019a6001e9b..c5a1aea330a8f 100644 --- a/src/legacy/ui/public/timefilter/timefilter.ts +++ b/src/legacy/ui/public/timefilter/timefilter.ts @@ -32,6 +32,14 @@ import uiRoutes from '../routes'; import { parseQueryString } from './lib/parse_querystring'; import { calculateBounds, getTime } from './get_time'; +// Timefilter accepts moment input but always returns string output +export type InputTimeRange = + | TimeRange + | { + from: Moment; + to: Moment; + }; + export class Timefilter { // Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled private enabledUpdated$ = new BehaviorSubject(false); @@ -74,7 +82,7 @@ export class Timefilter { return this.fetch$.asObservable(); }; - getTime = () => { + getTime = (): TimeRange => { const { from, to } = this._time; return { ...this._time, @@ -90,14 +98,7 @@ export class Timefilter { * @property {string|moment} time.from * @property {string|moment} time.to */ - setTime = ( - time: - | TimeRange - | { - from: Moment; - to: Moment; - } - ) => { + setTime = (time: InputTimeRange) => { // Object.assign used for partially composed updates const newTime = Object.assign(this.getTime(), time); if (areTimeRangesDifferent(this.getTime(), newTime)) { From f9739ea044ab4841a0bc0da0bc49c684b210b8ff Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 22 Aug 2019 16:56:01 +0300 Subject: [PATCH 11/66] fix import path --- src/legacy/ui/public/timefilter/timefilter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/ui/public/timefilter/timefilter.ts b/src/legacy/ui/public/timefilter/timefilter.ts index c5a1aea330a8f..729575a833e37 100644 --- a/src/legacy/ui/public/timefilter/timefilter.ts +++ b/src/legacy/ui/public/timefilter/timefilter.ts @@ -24,7 +24,7 @@ import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import chrome from 'ui/chrome'; import { UiSettingsClientContract } from 'src/core/public'; import { RefreshInterval, TimeRange } from 'src/plugins/data/public'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns'; +import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { IScope } from 'angular'; import { timeHistory } from './time_history'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; From 0f2324e44566ce2cf083d89082841e57d2db6ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Tue, 27 Aug 2019 10:21:27 -0400 Subject: [PATCH 12/66] Add invalidateAPIKey support to security plugin (#43707) * Initial work * Fix failing jest test * Use APIKeys class * Only use id to invalidate * Log all errors in invalidate function * Cleanup * Apply PR feedback --- .../legacy/server/lib/esjs_shield_plugin.js | 20 +++ .../server/authentication/api_keys.test.ts | 157 +++++++++++++----- .../server/authentication/api_keys.ts | 147 +++++++++++++--- .../server/authentication/index.test.ts | 49 ++++-- .../security/server/authentication/index.ts | 18 +- x-pack/plugins/security/server/plugin.test.ts | 1 + x-pack/plugins/security/server/plugin.ts | 13 +- 7 files changed, 321 insertions(+), 84 deletions(-) diff --git a/x-pack/legacy/server/lib/esjs_shield_plugin.js b/x-pack/legacy/server/lib/esjs_shield_plugin.js index ef1807b22daa4..83f050ee5ce59 100644 --- a/x-pack/legacy/server/lib/esjs_shield_plugin.js +++ b/x-pack/legacy/server/lib/esjs_shield_plugin.js @@ -516,5 +516,25 @@ fmt: '/_security/api_key', }, }); + + /** + * Invalidates an API key in Elasticsearch. + * + * @param {string} [id] An API key id. + * @param {string} [name] An API key name. + * @param {string} [realm_name] The name of an authentication realm. + * @param {string} [username] The username of a user. + * + * NOTE: While all parameters are optional, at least one of them is required. + * + * @returns {{invalidated_api_keys: string[], previously_invalidated_api_keys: string[], error_count: number, error_details?: object[]}} + */ + shield.invalidateAPIKey = ca({ + method: 'DELETE', + needBody: true, + url: { + fmt: '/_security/api_key', + }, + }); }; })); diff --git a/x-pack/plugins/security/server/authentication/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys.test.ts index e4c909843bb79..fcfa1fe4f4b60 100644 --- a/x-pack/plugins/security/server/authentication/api_keys.test.ts +++ b/x-pack/plugins/security/server/authentication/api_keys.test.ts @@ -4,57 +4,136 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createAPIKey } from './api_keys'; -import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { APIKeys } from './api_keys'; +import { ClusterClient, ScopedClusterClient } from '../../../../../src/core/server'; +import { + httpServerMock, + loggingServiceMock, + elasticsearchServiceMock, +} from '../../../../../src/core/server/mocks'; -const mockCallAsCurrentUser = jest.fn(); +describe('API Keys', () => { + let apiKeys: APIKeys; + let mockClusterClient: jest.Mocked>; + let mockScopedClusterClient: jest.Mocked>; + const mockIsSecurityFeatureDisabled = jest.fn(); -beforeAll(() => jest.resetAllMocks()); + beforeEach(() => { + mockClusterClient = elasticsearchServiceMock.createClusterClient(); + mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockClusterClient.asScoped.mockReturnValue((mockScopedClusterClient as unknown) as jest.Mocked< + ScopedClusterClient + >); + mockIsSecurityFeatureDisabled.mockReturnValue(false); + apiKeys = new APIKeys({ + clusterClient: mockClusterClient, + logger: loggingServiceMock.create().get('api-keys'), + isSecurityFeatureDisabled: mockIsSecurityFeatureDisabled, + }); + }); -describe('createAPIKey()', () => { - it('returns null when security feature is disabled', async () => { - const result = await createAPIKey({ - body: { + describe('create()', () => { + it('returns null when security feature is disabled', async () => { + mockIsSecurityFeatureDisabled.mockReturnValue(true); + const result = await apiKeys.create(httpServerMock.createKibanaRequest(), { name: '', role_descriptors: {}, - }, - loggers: loggingServiceMock.create(), - callAsCurrentUser: mockCallAsCurrentUser, - isSecurityFeatureDisabled: () => true, + }); + expect(result).toBeNull(); + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); }); - expect(result).toBeNull(); - expect(mockCallAsCurrentUser).not.toHaveBeenCalled(); - }); - it('calls callCluster with proper body arguments', async () => { - mockCallAsCurrentUser.mockResolvedValueOnce({ - id: '123', - name: 'key-name', - expiration: '1d', - api_key: 'abc123', - }); - const result = await createAPIKey({ - body: { + it('calls callCluster with proper parameters', async () => { + mockIsSecurityFeatureDisabled.mockReturnValue(false); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValueOnce({ + id: '123', name: 'key-name', - role_descriptors: { foo: true }, expiration: '1d', - }, - loggers: loggingServiceMock.create(), - callAsCurrentUser: mockCallAsCurrentUser, - isSecurityFeatureDisabled: () => false, - }); - expect(result).toEqual({ - api_key: 'abc123', - expiration: '1d', - id: '123', - name: 'key-name', - }); - expect(mockCallAsCurrentUser).toHaveBeenCalledWith('shield.createAPIKey', { - body: { + api_key: 'abc123', + }); + const result = await apiKeys.create(httpServerMock.createKibanaRequest(), { name: 'key-name', role_descriptors: { foo: true }, expiration: '1d', - }, + }); + expect(result).toEqual({ + api_key: 'abc123', + expiration: '1d', + id: '123', + name: 'key-name', + }); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.createAPIKey', + { + body: { + name: 'key-name', + role_descriptors: { foo: true }, + expiration: '1d', + }, + } + ); + }); + }); + + describe('invalidate()', () => { + it('returns null when security feature is disabled', async () => { + mockIsSecurityFeatureDisabled.mockReturnValue(true); + const result = await apiKeys.invalidate(httpServerMock.createKibanaRequest(), { + id: '123', + }); + expect(result).toBeNull(); + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + }); + + it('calls callCluster with proper parameters', async () => { + mockIsSecurityFeatureDisabled.mockReturnValue(false); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValueOnce({ + invalidated_api_keys: ['api-key-id-1'], + previously_invalidated_api_keys: [], + error_count: 0, + }); + const result = await apiKeys.invalidate(httpServerMock.createKibanaRequest(), { + id: '123', + }); + expect(result).toEqual({ + invalidated_api_keys: ['api-key-id-1'], + previously_invalidated_api_keys: [], + error_count: 0, + }); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.invalidateAPIKey', + { + body: { + id: '123', + }, + } + ); + }); + + it(`Only passes id as a parameter`, async () => { + mockIsSecurityFeatureDisabled.mockReturnValue(false); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValueOnce({ + invalidated_api_keys: ['api-key-id-1'], + previously_invalidated_api_keys: [], + error_count: 0, + }); + const result = await apiKeys.invalidate(httpServerMock.createKibanaRequest(), { + id: '123', + name: 'abc', + } as any); + expect(result).toEqual({ + invalidated_api_keys: ['api-key-id-1'], + previously_invalidated_api_keys: [], + error_count: 0, + }); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.invalidateAPIKey', + { + body: { + id: '123', + }, + } + ); }); }); }); diff --git a/x-pack/plugins/security/server/authentication/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys.ts index 9d684624c2971..688a3f5d944b6 100644 --- a/x-pack/plugins/security/server/authentication/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys.ts @@ -4,17 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LoggerFactory, ScopedClusterClient } from '../../../../../src/core/server'; +import { ClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server'; -export interface CreateAPIKeyOptions { - loggers: LoggerFactory; - callAsCurrentUser: ScopedClusterClient['callAsCurrentUser']; +/** + * Represents the options to create an APIKey class instance that will be + * shared between functions (create, invalidate, etc). + */ +export interface ConstructorOptions { + logger: Logger; + clusterClient: PublicMethodsOf; isSecurityFeatureDisabled: () => boolean; - body: { - name: string; - role_descriptors: Record; - expiration?: string; - }; +} + +/** + * Represents the params for creating an API key + */ +export interface CreateAPIKeyParams { + name: string; + role_descriptors: Record; + expiration?: string; +} + +/** + * Represents the params for invalidating an API key + */ +export interface InvalidateAPIKeyParams { + id: string; } /** @@ -42,24 +57,110 @@ export interface CreateAPIKeyResult { api_key: string; } -export async function createAPIKey({ - body, - loggers, - callAsCurrentUser, - isSecurityFeatureDisabled, -}: CreateAPIKeyOptions): Promise { - const logger = loggers.get('api-keys'); +/** + * The return value when invalidating an API key in Elasticsearch. + */ +export interface InvalidateAPIKeyResult { + /** + * The IDs of the API keys that were invalidated as part of the request. + */ + invalidated_api_keys: string[]; + /** + * The IDs of the API keys that were already invalidated. + */ + previously_invalidated_api_keys: string[]; + /** + * The number of errors that were encountered when invalidating the API keys. + */ + error_count: number; + /** + * Details about these errors. This field is not present in the response when error_count is 0. + */ + error_details?: Array<{ + type: string; + reason: string; + caused_by: { + type: string; + reason: string; + }; + }>; +} + +/** + * Class responsible for managing Elasticsearch API keys. + */ +export class APIKeys { + private readonly logger: Logger; + private readonly clusterClient: PublicMethodsOf; + private readonly isSecurityFeatureDisabled: () => boolean; + + constructor({ logger, clusterClient, isSecurityFeatureDisabled }: ConstructorOptions) { + this.logger = logger; + this.clusterClient = clusterClient; + this.isSecurityFeatureDisabled = isSecurityFeatureDisabled; + } + + /** + * Tries to create an API key for the current user. + * @param request Request instance. + * @param params The params to create an API key + */ + async create( + request: KibanaRequest, + params: CreateAPIKeyParams + ): Promise { + if (this.isSecurityFeatureDisabled()) { + return null; + } + + this.logger.debug('Trying to create an API key'); - if (isSecurityFeatureDisabled()) { - return null; + // User needs `manage_api_key` privilege to use this API + let result: CreateAPIKeyResult; + try { + result = (await this.clusterClient + .asScoped(request) + .callAsCurrentUser('shield.createAPIKey', { body: params })) as CreateAPIKeyResult; + this.logger.debug('API key was created successfully'); + } catch (e) { + this.logger.error(`Failed to create API key: ${e.message}`); + throw e; + } + + return result; } - logger.debug('Trying to create an API key'); + /** + * Tries to invalidate an API key. + * @param request Request instance. + * @param params The params to invalidate an API key. + */ + async invalidate( + request: KibanaRequest, + params: InvalidateAPIKeyParams + ): Promise { + if (this.isSecurityFeatureDisabled()) { + return null; + } - // User needs `manage_api_key` privilege to use this API - const key = (await callAsCurrentUser('shield.createAPIKey', { body })) as CreateAPIKeyResult; + this.logger.debug('Trying to invalidate an API key'); - logger.debug('API key was created successfully'); + // User needs `manage_api_key` privilege to use this API + let result: InvalidateAPIKeyResult; + try { + result = (await this.clusterClient + .asScoped(request) + .callAsCurrentUser('shield.invalidateAPIKey', { + body: { + id: params.id, + }, + })) as InvalidateAPIKeyResult; + this.logger.debug('API key was invalidated successfully'); + } catch (e) { + this.logger.error(`Failed to invalidate API key: ${e.message}`); + throw e; + } - return key; + return result; + } } diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts index 116fcf268e828..db8eded100e46 100644 --- a/x-pack/plugins/security/server/authentication/index.test.ts +++ b/x-pack/plugins/security/server/authentication/index.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('./api_keys'); jest.mock('./authenticator'); -jest.mock('./api_keys', () => ({ createAPIKey: jest.fn() })); import Boom from 'boom'; import { errors } from 'elasticsearch'; @@ -35,7 +35,12 @@ import { ConfigType, createConfig$ } from '../config'; import { LegacyAPI } from '../plugin'; import { AuthenticationResult } from './authentication_result'; import { setupAuthentication } from '.'; -import { CreateAPIKeyResult, CreateAPIKeyOptions } from './api_keys'; +import { + CreateAPIKeyResult, + CreateAPIKeyParams, + InvalidateAPIKeyResult, + InvalidateAPIKeyParams, +} from './api_keys'; function mockXPackFeature({ isEnabled = true }: Partial<{ isEnabled: boolean }> = {}) { return { @@ -358,29 +363,49 @@ describe('setupAuthentication()', () => { describe('createAPIKey()', () => { let createAPIKey: ( request: KibanaRequest, - body: CreateAPIKeyOptions['body'] + params: CreateAPIKeyParams ) => Promise; beforeEach(async () => { createAPIKey = (await setupAuthentication(mockSetupAuthenticationParams)).createAPIKey; }); it('calls createAPIKey with given arguments', async () => { - const { createAPIKey: createAPIKeyMock } = jest.requireMock('./api_keys'); - const options = { + const request = httpServerMock.createKibanaRequest(); + const apiKeysInstance = jest.requireMock('./api_keys').APIKeys.mock.instances[0]; + const params = { name: 'my-key', role_descriptors: {}, expiration: '1d', }; - createAPIKeyMock.mockResolvedValueOnce({ success: true }); - await expect(createAPIKey(httpServerMock.createKibanaRequest(), options)).resolves.toEqual({ + apiKeysInstance.create.mockResolvedValueOnce({ success: true }); + await expect(createAPIKey(request, params)).resolves.toEqual({ success: true, }); - expect(createAPIKeyMock).toHaveBeenCalledWith({ - body: options, - loggers: mockSetupAuthenticationParams.loggers, - callAsCurrentUser: mockScopedClusterClient.callAsCurrentUser, - isSecurityFeatureDisabled: expect.any(Function), + expect(apiKeysInstance.create).toHaveBeenCalledWith(request, params); + }); + }); + + describe('invalidateAPIKey()', () => { + let invalidateAPIKey: ( + request: KibanaRequest, + params: InvalidateAPIKeyParams + ) => Promise; + beforeEach(async () => { + invalidateAPIKey = (await setupAuthentication(mockSetupAuthenticationParams)) + .invalidateAPIKey; + }); + + it('calls invalidateAPIKey with given arguments', async () => { + const request = httpServerMock.createKibanaRequest(); + const apiKeysInstance = jest.requireMock('./api_keys').APIKeys.mock.instances[0]; + const params = { + id: '123', + }; + apiKeysInstance.invalidate.mockResolvedValueOnce({ success: true }); + await expect(invalidateAPIKey(request, params)).resolves.toEqual({ + success: true, }); + expect(apiKeysInstance.invalidate).toHaveBeenCalledWith(request, params); }); }); }); diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index 45bcff43770ed..6a88c5cddc441 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -14,7 +14,7 @@ import { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; import { Authenticator, ProviderSession } from './authenticator'; import { LegacyAPI } from '../plugin'; -import { createAPIKey, CreateAPIKeyOptions } from './api_keys'; +import { APIKeys, CreateAPIKeyParams, InvalidateAPIKeyParams } from './api_keys'; export { canRedirectRequest } from './can_redirect_request'; export { Authenticator, ProviderLoginAttempt } from './authenticator'; @@ -137,17 +137,19 @@ export async function setupAuthentication({ authLogger.debug('Successfully registered core authentication handler.'); + const apiKeys = new APIKeys({ + clusterClient, + logger: loggers.get('api-key'), + isSecurityFeatureDisabled, + }); return { login: authenticator.login.bind(authenticator), logout: authenticator.logout.bind(authenticator), getCurrentUser, - createAPIKey: (request: KibanaRequest, body: CreateAPIKeyOptions['body']) => - createAPIKey({ - body, - loggers, - isSecurityFeatureDisabled, - callAsCurrentUser: clusterClient.asScoped(request).callAsCurrentUser, - }), + createAPIKey: (request: KibanaRequest, params: CreateAPIKeyParams) => + apiKeys.create(request, params), + invalidateAPIKey: (request: KibanaRequest, params: InvalidateAPIKeyParams) => + apiKeys.invalidate(request, params), isAuthenticated: async (request: KibanaRequest) => { try { await getCurrentUser(request); diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 772019c54a2fc..6fe28fe56c417 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -38,6 +38,7 @@ describe('Security Plugin', () => { "authc": Object { "createAPIKey": [Function], "getCurrentUser": [Function], + "invalidateAPIKey": [Function], "isAuthenticated": [Function], "login": [Function], "logout": [Function], diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index ef02ac5aadbbd..353c64836a939 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -18,7 +18,12 @@ import { XPackInfo } from '../../../legacy/plugins/xpack_main/server/lib/xpack_i import { AuthenticatedUser } from '../common/model'; import { Authenticator, setupAuthentication } from './authentication'; import { createConfig$ } from './config'; -import { CreateAPIKeyOptions, CreateAPIKeyResult } from './authentication/api_keys'; +import { + CreateAPIKeyParams, + CreateAPIKeyResult, + InvalidateAPIKeyParams, + InvalidateAPIKeyResult, +} from './authentication/api_keys'; /** * Describes a set of APIs that is available in the legacy platform only and required by this plugin @@ -40,8 +45,12 @@ export interface PluginSetupContract { isAuthenticated: (request: KibanaRequest) => Promise; createAPIKey: ( request: KibanaRequest, - body: CreateAPIKeyOptions['body'] + params: CreateAPIKeyParams ) => Promise; + invalidateAPIKey: ( + request: KibanaRequest, + params: InvalidateAPIKeyParams + ) => Promise; }; config: RecursiveReadonly<{ From f188b292c856ebe18586b66c7f80ed409d1351f8 Mon Sep 17 00:00:00 2001 From: Chris Davies Date: Tue, 27 Aug 2019 10:32:20 -0400 Subject: [PATCH 13/66] Modify I18nProvider so that it does not generate new React components (#43556) This fixes some edge-cases that caused infinite loops: React thinks the tree has changed because of a new root component, effects fire off which change the state and cause a re-render, React thinks the tree has changed because of a new root component... --- .../__snapshots__/provider.test.tsx.snap | 6 +- packages/kbn-i18n/src/react/provider.test.tsx | 3 +- packages/kbn-i18n/src/react/provider.tsx | 46 +- .../src/react/pseudo_locale_wrapper.tsx | 75 +++ .../components/open_timeline/index.test.tsx | 23 +- .../open_timeline_modal/index.test.tsx | 22 +- .../__snapshots__/index.test.tsx.snap | 534 +++++++++--------- 7 files changed, 374 insertions(+), 335 deletions(-) create mode 100644 packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx diff --git a/packages/kbn-i18n/src/react/__snapshots__/provider.test.tsx.snap b/packages/kbn-i18n/src/react/__snapshots__/provider.test.tsx.snap index b584044979f06..c8a60fa069449 100644 --- a/packages/kbn-i18n/src/react/__snapshots__/provider.test.tsx.snap +++ b/packages/kbn-i18n/src/react/__snapshots__/provider.test.tsx.snap @@ -103,4 +103,8 @@ Object { } `; -exports[`I18nProvider renders children 1`] = ``; +exports[`I18nProvider renders children 1`] = ` + + + +`; diff --git a/packages/kbn-i18n/src/react/provider.test.tsx b/packages/kbn-i18n/src/react/provider.test.tsx index 6dabc806541cb..027b9242c4bc1 100644 --- a/packages/kbn-i18n/src/react/provider.test.tsx +++ b/packages/kbn-i18n/src/react/provider.test.tsx @@ -19,7 +19,6 @@ import { mount, shallow } from 'enzyme'; import * as React from 'react'; -import { intlShape } from 'react-intl'; import { injectI18n } from './inject'; import { I18nProvider } from './provider'; @@ -46,7 +45,7 @@ describe('I18nProvider', () => { , { childContextTypes: { - intl: intlShape, + intl: { formatMessage: jest.fn() }, }, } ); diff --git a/packages/kbn-i18n/src/react/provider.tsx b/packages/kbn-i18n/src/react/provider.tsx index a6bbfdb4d29b9..f5b237a88c161 100644 --- a/packages/kbn-i18n/src/react/provider.tsx +++ b/packages/kbn-i18n/src/react/provider.tsx @@ -22,47 +22,7 @@ import * as React from 'react'; import { IntlProvider } from 'react-intl'; import * as i18n from '../core'; -import { isPseudoLocale, translateUsingPseudoLocale } from '../core/pseudo_locale'; -import { injectI18n } from './inject'; - -/** - * To translate label that includes nested `FormattedMessage` instances React Intl - * replaces them with special placeholders (@__uid__@ELEMENT-uid-counter@__uid__@) - * and maps them back with nested translations after `formatMessage` processes - * original string, so we shouldn't modify these special placeholders with pseudo - * translations otherwise React Intl won't be able to properly replace placeholders. - * It's implementation detail of the React Intl, but since pseudo localization is dev - * only feature we should be fine here. - * @param message - */ -function translateFormattedMessageUsingPseudoLocale(message: string) { - const formattedMessageDelimiter = message.match(/@__.{10}__@/); - if (formattedMessageDelimiter !== null) { - return message - .split(formattedMessageDelimiter[0]) - .map(part => (part.startsWith('ELEMENT-') ? part : translateUsingPseudoLocale(part))) - .join(formattedMessageDelimiter[0]); - } - - return translateUsingPseudoLocale(message); -} - -/** - * If pseudo locale is detected, default intl.formatMessage should be decorated - * with the pseudo localization function. - * @param child I18nProvider child component. - */ -function wrapIntlFormatMessage(child: React.ReactElement) { - return React.createElement( - injectI18n(({ intl }) => { - const formatMessage = intl.formatMessage; - intl.formatMessage = (...args) => - translateFormattedMessageUsingPseudoLocale(formatMessage(...args)); - - return React.Children.only(child); - }) - ); -} +import { PseudoLocaleWrapper } from './pseudo_locale_wrapper'; /** * The library uses the provider pattern to scope an i18n context to a tree @@ -81,9 +41,7 @@ export class I18nProvider extends React.PureComponent { formats={i18n.getFormats()} textComponent={React.Fragment} > - {isPseudoLocale(i18n.getLocale()) && React.isValidElement(this.props.children) - ? wrapIntlFormatMessage(this.props.children) - : this.props.children} + {this.props.children} ); } diff --git a/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx b/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx new file mode 100644 index 0000000000000..db879fbae6ff1 --- /dev/null +++ b/packages/kbn-i18n/src/react/pseudo_locale_wrapper.tsx @@ -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 * as PropTypes from 'prop-types'; +import * as React from 'react'; +import * as i18n from '../core'; +import { isPseudoLocale, translateUsingPseudoLocale } from '../core/pseudo_locale'; + +/** + * To translate label that includes nested `FormattedMessage` instances React Intl + * replaces them with special placeholders (@__uid__@ELEMENT-uid-counter@__uid__@) + * and maps them back with nested translations after `formatMessage` processes + * original string, so we shouldn't modify these special placeholders with pseudo + * translations otherwise React Intl won't be able to properly replace placeholders. + * It's implementation detail of the React Intl, but since pseudo localization is dev + * only feature we should be fine here. + * @param message + */ +function translateFormattedMessageUsingPseudoLocale(message: string) { + const formattedMessageDelimiter = message.match(/@__.{10}__@/); + if (formattedMessageDelimiter !== null) { + return message + .split(formattedMessageDelimiter[0]) + .map(part => (part.startsWith('ELEMENT-') ? part : translateUsingPseudoLocale(part))) + .join(formattedMessageDelimiter[0]); + } + + return translateUsingPseudoLocale(message); +} + +/** + * If the locale is our pseudo locale (e.g. en-xa), we override the + * intl.formatMessage function to display scrambled characters. We are + * overriding the context rather than using injectI18n, because the + * latter creates a new React component, which causes React diffs to + * be inefficient in some cases, and can cause React hooks to lose + * their state. + */ +export class PseudoLocaleWrapper extends React.PureComponent { + public static propTypes = { children: PropTypes.element.isRequired }; + + public static contextTypes = { + intl: PropTypes.object.isRequired, + }; + + constructor(props: { children: React.ReactNode }, context: any) { + super(props, context); + + if (isPseudoLocale(i18n.getLocale())) { + const formatMessage = context.intl.formatMessage; + context.intl.formatMessage = (...args: any[]) => + translateFormattedMessageUsingPseudoLocale(formatMessage(...args)); + } + } + + public render() { + return this.props.children; + } +} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx index 056ffa2b9d1b7..beeb26b424c0c 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx @@ -28,15 +28,8 @@ const getStateChildComponent = ( ): // eslint-disable-next-line @typescript-eslint/no-explicit-any React.Component<{}, {}, any> => wrapper - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) + .find('[data-test-subj="stateful-timeline"]') + .last() .instance(); describe('StatefulOpenTimeline', () => { @@ -49,6 +42,7 @@ describe('StatefulOpenTimeline', () => { { { { { { { { { { { { , React.Component<{}, {}, any>> ): // eslint-disable-next-line @typescript-eslint/no-explicit-any -React.Component<{}, {}, any> => - wrapper - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .childAt(0) - .instance(); +React.Component<{}, {}, any> => wrapper.find('[data-test-subj="state-child-component"]').instance(); describe('OpenTimelineModalButton', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); @@ -66,7 +56,10 @@ describe('OpenTimelineModalButton', () => { - + @@ -158,7 +151,10 @@ describe('OpenTimelineModalButton', () => { - + diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap index 27725782d7ff8..93dc308ecc26c 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/url_state/__snapshots__/index.test.tsx.snap @@ -198,55 +198,11 @@ exports[`UrlStateContainer mounts and renders 1`] = ` messages={Object {}} textComponent={Symbol(react.fragment)} > - + - - - - + - - - - + + + - - - - - - - - - - + > + + + + + + + + + + + From b68091207311d33f119429e5f4c8da3e58bf955c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Tue, 27 Aug 2019 11:11:59 -0400 Subject: [PATCH 14/66] Disable alerting and actions plugin by default (#44083) * Disable alerting and actions plugin by default * Fix test failures --- x-pack/legacy/plugins/actions/index.ts | 2 +- x-pack/legacy/plugins/alerting/index.ts | 2 +- x-pack/test/alerting_api_integration/common/config.ts | 2 ++ .../test/api_integration/apis/xpack_main/features/features.ts | 2 -- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/actions/index.ts b/x-pack/legacy/plugins/actions/index.ts index ee46431b4ec57..61d3aa7273e7a 100644 --- a/x-pack/legacy/plugins/actions/index.ts +++ b/x-pack/legacy/plugins/actions/index.ts @@ -26,7 +26,7 @@ export function actions(kibana: any) { config(Joi: Root) { return Joi.object() .keys({ - enabled: Joi.boolean().default(true), + enabled: Joi.boolean().default(false), }) .default(); }, diff --git a/x-pack/legacy/plugins/alerting/index.ts b/x-pack/legacy/plugins/alerting/index.ts index 64af7fa671a71..10ccbdbf4cd55 100644 --- a/x-pack/legacy/plugins/alerting/index.ts +++ b/x-pack/legacy/plugins/alerting/index.ts @@ -27,7 +27,7 @@ export function alerting(kibana: any) { config(Joi: Root) { return Joi.object() .keys({ - enabled: Joi.boolean().default(true), + enabled: Joi.boolean().default(false), }) .default(); }, diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index ccc17da22d099..4bd207e51a941 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -53,6 +53,8 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ...xPackApiIntegrationTestsConfig.get('kbnTestServer'), serverArgs: [ ...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'), + '--xpack.actions.enabled=true', + '--xpack.alerting.enabled=true', ...disabledPlugins.map(key => `--xpack.${key}.enabled=false`), `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`, `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`, diff --git a/x-pack/test/api_integration/apis/xpack_main/features/features.ts b/x-pack/test/api_integration/apis/xpack_main/features/features.ts index d803dcad90ac1..4a65088681596 100644 --- a/x-pack/test/api_integration/apis/xpack_main/features/features.ts +++ b/x-pack/test/api_integration/apis/xpack_main/features/features.ts @@ -134,8 +134,6 @@ export default function({ getService }: FtrProviderContext) { 'maps', 'uptime', 'siem', - 'alerting', - 'actions', ].sort() ); }); From 243a095073f559310cb972cdb6f3472bf7c6a9e2 Mon Sep 17 00:00:00 2001 From: Miqayel Ohanjanyan Date: Tue, 27 Aug 2019 19:19:28 +0400 Subject: [PATCH 15/66] Add commonly used ranges to apm date-picker (#44082) --- .../components/shared/DatePicker/index.tsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/index.tsx index 911e63c69734f..2bb43f7fa44ec 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/index.tsx @@ -5,6 +5,7 @@ */ import { EuiSuperDatePicker } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { history } from '../../../utils/history'; @@ -14,6 +15,64 @@ import { useUrlParams } from '../../../hooks/useUrlParams'; export function DatePicker() { const location = useLocation(); const { urlParams, refreshTimeRange } = useUrlParams(); + const commonlyUsedRanges = [ + { + start: 'now-15m', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last15MinutesLabel', { + defaultMessage: 'Last 15 minutes' + }) + }, + { + start: 'now-30m', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last30MinutesLabel', { + defaultMessage: 'Last 30 minutes' + }) + }, + { + start: 'now-1h', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last1HourLabel', { + defaultMessage: 'Last 1 hour' + }) + }, + { + start: 'now-24h', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last24HoursLabel', { + defaultMessage: 'Last 24 hours' + }) + }, + { + start: 'now-7d', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last7DaysLabel', { + defaultMessage: 'Last 7 days' + }) + }, + { + start: 'now-30d', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last30DaysLabel', { + defaultMessage: 'Last 30 days' + }) + }, + { + start: 'now-90d', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last90DaysLabel', { + defaultMessage: 'Last 90 days' + }) + }, + { + start: 'now-1y', + end: 'now', + label: i18n.translate('xpack.apm.datePicker.last1YearLabel', { + defaultMessage: 'Last 1 year' + }) + } + ]; function updateUrl(nextQuery: { rangeFrom?: string; @@ -58,6 +117,7 @@ export function DatePicker() { }} onRefreshChange={onRefreshChange} showUpdateButton={true} + commonlyUsedRanges={commonlyUsedRanges} /> ); } From 3cc4653c967edfa5b034f8646dc8843f89573787 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 27 Aug 2019 10:57:12 -0500 Subject: [PATCH 16/66] [ML] Data Frames Summary Stats Bar (#43986) * move stat component out of jobStatsBar for reuse * create transformStatsBar component * add transformStatsBar to DF page * update tests * move create statsBar component for reuse * move stat component into statsBar component * move statsBar related types to stats_bar dir * rename scss file. remove unnecessary import --- .../public/components/stats_bar/_index.scss | 2 + .../ml/public/components/stats_bar/_stat.scss | 7 ++ .../components/stats_bar/_stats_bar.scss | 6 ++ .../ml/public/components/stats_bar/index.ts | 7 ++ .../ml/public/components/stats_bar/stat.tsx | 24 +++++ .../public/components/stats_bar/stats_bar.tsx | 46 ++++++++++ .../__snapshots__/page.test.tsx.snap | 9 +- .../transform_list.test.tsx.snap | 30 +++++- .../transform_list/transform_list.test.tsx | 9 +- .../transform_list/transform_list.tsx | 48 ++++------ .../transform_list/transforms_stats_bar.tsx | 91 +++++++++++++++++++ .../pages/transform_management/page.tsx | 32 ++++++- x-pack/legacy/plugins/ml/public/index.scss | 1 + .../ml/public/jobs/jobs_list/_index.scss | 3 +- .../components/jobs_stats_bar/_index.scss | 1 - .../jobs_stats_bar/_jobs_stats_bar.scss | 14 --- .../jobs_stats_bar/jobs_stats_bar.js | 19 +--- 17 files changed, 279 insertions(+), 70 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/components/stats_bar/_index.scss create mode 100644 x-pack/legacy/plugins/ml/public/components/stats_bar/_stat.scss create mode 100644 x-pack/legacy/plugins/ml/public/components/stats_bar/_stats_bar.scss create mode 100644 x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts create mode 100644 x-pack/legacy/plugins/ml/public/components/stats_bar/stat.tsx create mode 100644 x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx create mode 100644 x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transforms_stats_bar.tsx delete mode 100644 x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_index.scss delete mode 100644 x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_jobs_stats_bar.scss diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/_index.scss b/x-pack/legacy/plugins/ml/public/components/stats_bar/_index.scss new file mode 100644 index 0000000000000..e8d8e85763eff --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/_index.scss @@ -0,0 +1,2 @@ +@import 'stat'; +@import 'stats_bar'; diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/_stat.scss b/x-pack/legacy/plugins/ml/public/components/stats_bar/_stat.scss new file mode 100644 index 0000000000000..d05c1f7195587 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/_stat.scss @@ -0,0 +1,7 @@ +.stat { + margin-right: $euiSizeS; + + .stat-value { + font-weight: bold + } +} diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/_stats_bar.scss b/x-pack/legacy/plugins/ml/public/components/stats_bar/_stats_bar.scss new file mode 100644 index 0000000000000..c433b53789573 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/_stats_bar.scss @@ -0,0 +1,6 @@ +.mlStatsBar { + // SASSTODO: proper calcs + height: 42px; + padding: 14px; + background-color: $euiColorLightestShade; +} diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts b/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts new file mode 100644 index 0000000000000..4c781afe0a64c --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { StatsBar, TransformStatsBarStats } from './stats_bar'; diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/stat.tsx b/x-pack/legacy/plugins/ml/public/components/stats_bar/stat.tsx new file mode 100644 index 0000000000000..55fa902fe41ed --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/stat.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, { FC } from 'react'; + +export interface StatsBarStat { + label: string; + value: string | number; + show?: boolean; +} +interface StatProps { + stat: StatsBarStat; +} + +export const Stat: FC = ({ stat }) => { + return ( + + {stat.label}: {stat.value} + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx b/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx new file mode 100644 index 0000000000000..0995b1e70df5d --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.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, { FC } from 'react'; +import { Stat, StatsBarStat } from './stat'; + +interface JobStatsBarStats { + activeNodes: StatsBarStat; + total: StatsBarStat; + open: StatsBarStat; + failed: StatsBarStat; + closed: StatsBarStat; + activeDatafeeds: StatsBarStat; +} + +export interface TransformStatsBarStats { + total: StatsBarStat; + batch: StatsBarStat; + continuous: StatsBarStat; + failed: StatsBarStat; + started: StatsBarStat; +} + +type StatsBarStats = TransformStatsBarStats | JobStatsBarStats; +type StatsKey = keyof StatsBarStats; + +interface StatsBarProps { + stats: StatsBarStats; + dataTestSub: string; +} + +export const StatsBar: FC = ({ stats, dataTestSub }) => { + const statsList = Object.keys(stats).map(k => stats[k as StatsKey]); + return ( +
    + {statsList + .filter((s: StatsBarStat) => s.show) + .map((s: StatsBarStat) => ( + + ))} +
    + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap index af70dccfad236..dc16e6843ba91 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/__snapshots__/page.test.tsx.snap @@ -5,6 +5,9 @@ exports[`Data Frame: Job List Minimal initialization 1`] = ` + Minimal initialization 1`] = ` hasShadow={false} paddingSize="m" > - + diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap index d7ccbb57382f4..dd3763d054b14 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/__snapshots__/transform_list.test.tsx.snap @@ -1,7 +1,31 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Data Frame: Transform List Minimal initialization 1`] = ` - + + + + Create your first data frame transform + , + ] + } + data-test-subj="mlNoDataFrameTransformsFound" + iconColor="subdued" + title={ +

    + No data frame transforms found +

    + } + /> +
    `; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.test.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.test.tsx index e4e7537aae6ea..522f17c3382d2 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.test.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.test.tsx @@ -12,7 +12,14 @@ import { DataFrameTransformList } from './transform_list'; describe('Data Frame: Transform List ', () => { test('Minimal initialization', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx index 1a84a6df64da9..a17a782452456 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx @@ -19,11 +19,7 @@ import { SortDirection, } from '@elastic/eui'; -import { - DataFrameTransformId, - moveToDataFrameWizard, - useRefreshTransformList, -} from '../../../../common'; +import { DataFrameTransformId, moveToDataFrameWizard } from '../../../../common'; import { checkPermission } from '../../../../../privilege/check_privilege'; import { getTaskStateBadge } from './columns'; import { DeleteAction } from './action_delete'; @@ -39,11 +35,9 @@ import { Query, Clause, } from './common'; -import { getTransformsFactory } from '../../services/transform_service'; import { getColumns } from './columns'; import { ExpandedRow } from './expanded_row'; import { ProgressBar, TransformTable } from './transform_table'; -import { useRefreshInterval } from './use_refresh_interval'; function getItemIdToExpandedRowMap( itemIds: DataFrameTransformId[], @@ -69,20 +63,28 @@ function stringMatch(str: string | undefined, substr: string) { ); } -export const DataFrameTransformList: SFC = () => { - const [isInitialized, setIsInitialized] = useState(false); +interface Props { + isInitialized: boolean; + transforms: DataFrameTransformListRow[]; + errorMessage: any; + transformsLoading: boolean; +} + +export const DataFrameTransformList: SFC = ({ + isInitialized, + transforms, + errorMessage, + transformsLoading, +}) => { const [isLoading, setIsLoading] = useState(false); - const [blockRefresh, setBlockRefresh] = useState(false); const [filterActive, setFilterActive] = useState(false); - const [transforms, setTransforms] = useState([]); const [filteredTransforms, setFilteredTransforms] = useState([]); const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); const [transformSelection, setTransformSelection] = useState([]); const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false); - const [errorMessage, setErrorMessage] = useState(undefined); const [searchError, setSearchError] = useState(undefined); const [pageIndex, setPageIndex] = useState(0); @@ -96,20 +98,6 @@ export const DataFrameTransformList: SFC = () => { !checkPermission('canPreviewDataFrame') || !checkPermission('canStartStopDataFrame'); - const getTransforms = getTransformsFactory( - setTransforms, - setErrorMessage, - setIsInitialized, - blockRefresh - ); - // Subscribe to the refresh observable to trigger reloading the transform list. - useRefreshTransformList({ - isLoading: setIsLoading, - onRefresh: () => getTransforms(true), - }); - // Call useRefreshInterval() after the subscription above is set up. - useRefreshInterval(setBlockRefresh); - const onQueryChange = ({ query, error }: { query: Query; error: any }) => { if (error) { setSearchError(error.message); @@ -188,13 +176,13 @@ export const DataFrameTransformList: SFC = () => { // Before the transforms have been loaded for the first time, display the loading indicator only. // Otherwise a user would see 'No data frame transforms found' during the initial loading. if (!isInitialized) { - return ; + return ; } if (typeof errorMessage !== 'undefined') { return ( - + { if (transforms.length === 0) { return ( - + @@ -368,7 +356,7 @@ export const DataFrameTransformList: SFC = () => { return ( - + { + if (transform.mode === DATA_FRAME_MODE.CONTINUOUS) { + transformStats.continuous.value++; + } else if (transform.mode === DATA_FRAME_MODE.BATCH) { + transformStats.batch.value++; + } else if (transform.stats.state === DATA_FRAME_TRANSFORM_STATE.FAILED) { + failedTransforms++; + } else if (transform.stats.state === DATA_FRAME_TRANSFORM_STATE.STARTED) { + startedTransforms++; + } + }); + + transformStats.total.value = transformsList.length; + transformStats.started.value = startedTransforms; + + if (failedTransforms !== 0) { + transformStats.failed.value = failedTransforms; + transformStats.failed.show = true; + } else { + transformStats.failed.show = false; + } + + return transformStats; +} + +interface Props { + transformsList: DataFrameTransformListRow[]; +} + +export const TransformStatsBar: FC = ({ transformsList }) => { + const transformStats: TransformStatsBarStats = createTranformStats(transformsList); + + return ; +}; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx index 576e5f9a42637..3f00a37484808 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/page.tsx @@ -25,17 +25,42 @@ import { import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; import { useRefreshTransformList } from '../../common'; +import { DataFrameTransformListRow } from './components/transform_list/common'; import { CreateTransformButton } from './components/create_transform_button'; import { DataFrameTransformList } from './components/transform_list'; import { RefreshTransformListButton } from './components/refresh_transform_list_button'; +import { TransformStatsBar } from '../transform_management/components/transform_list/transforms_stats_bar'; +import { getTransformsFactory } from './services/transform_service'; +import { useRefreshInterval } from './components/transform_list/use_refresh_interval'; export const Page: FC = () => { const [isLoading, setIsLoading] = useState(false); + const [transformsLoading, setTransformsLoading] = useState(false); + const [isInitialized, setIsInitialized] = useState(false); + const [blockRefresh, setBlockRefresh] = useState(false); + const [transforms, setTransforms] = useState([]); + const [errorMessage, setErrorMessage] = useState(undefined); const { refresh } = useRefreshTransformList({ isLoading: setIsLoading }); + const getTransforms = getTransformsFactory( + setTransforms, + setErrorMessage, + setIsInitialized, + blockRefresh + ); + + // Subscribe to the refresh observable to trigger reloading the transform list. + useRefreshTransformList({ + isLoading: setTransformsLoading, + onRefresh: () => getTransforms(true), + }); + // Call useRefreshInterval() after the subscription above is set up. + useRefreshInterval(setBlockRefresh); + return ( + @@ -77,7 +102,12 @@ export const Page: FC = () => { - + diff --git a/x-pack/legacy/plugins/ml/public/index.scss b/x-pack/legacy/plugins/ml/public/index.scss index 0b7811cb0504b..2a751aea1d831 100644 --- a/x-pack/legacy/plugins/ml/public/index.scss +++ b/x-pack/legacy/plugins/ml/public/index.scss @@ -50,6 +50,7 @@ @import 'components/messagebar/index'; @import 'components/navigation_menu/index'; @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly + @import 'components/stats_bar/index'; // Hacks are last so they can overwrite anything above if needed @import 'hacks'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/_index.scss b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/_index.scss index a215ff2d1a835..2d26cd644eca2 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/_index.scss +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/_index.scss @@ -6,6 +6,5 @@ @import 'components/job_group/index'; @import 'components/jobs_list/index'; // SASSTODO: Dangerous EUI overwrites @import 'components/jobs_list_view/index'; -@import 'components/jobs_stats_bar/index'; @import 'components/multi_job_actions/index'; // SASSTODO: Dangerous EUI overwrites -@import 'components/start_datafeed_modal/index'; // SASSTODO: Needs a rewrite \ No newline at end of file +@import 'components/start_datafeed_modal/index'; // SASSTODO: Needs a rewrite diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_index.scss b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_index.scss deleted file mode 100644 index 995478bc0966c..0000000000000 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'jobs_stats_bar'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_jobs_stats_bar.scss b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_jobs_stats_bar.scss deleted file mode 100644 index 63a1bc01c94ae..0000000000000 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/_jobs_stats_bar.scss +++ /dev/null @@ -1,14 +0,0 @@ -.jobs-stats-bar { - // SASSTODO: proper calcs - height: 42px; - padding: 14px; - background-color: $euiColorLightestShade; - - .stat { - margin-right: $euiSizeS; - - .stat-value { - font-weight: bold - } - } -} diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js index f82b3f5b07b05..83116579a2adb 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/jobs_stats_bar/jobs_stats_bar.js @@ -6,6 +6,7 @@ import { JOB_STATE, DATAFEED_STATE } from 'plugins/ml/../common/constants/states'; +import { StatsBar } from '../../../../components/stats_bar'; import PropTypes from 'prop-types'; import React from 'react'; @@ -99,27 +100,11 @@ function createJobStats(jobsSummaryList) { return jobStats; } -function Stat({ stat }) { - return ( - - {stat.label}: {stat.value} - - ); -} -Stat.propTypes = { - stat: PropTypes.object.isRequired, -}; - export const JobStatsBar = ({ jobsSummaryList }) => { const jobStats = createJobStats(jobsSummaryList); - const stats = Object.keys(jobStats).map(k => jobStats[k]); return ( -
    - { - stats.filter(s => (s.show)).map(s => ) - } -
    + ); }; From 17106e8a78aa5fe5f6304537339f51f82348d28a Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Tue, 27 Aug 2019 18:28:54 +0200 Subject: [PATCH 17/66] Introduce PKI authentication provider. (#42606) --- src/core/server/http/http_server.mocks.ts | 5 +- .../legacy/server/lib/esjs_shield_plugin.js | 16 + .../server/authentication/authenticator.ts | 2 + .../authentication/providers/base.mock.ts | 23 +- .../server/authentication/providers/index.ts | 1 + .../authentication/providers/pki.test.ts | 589 ++++++++++++++++++ .../server/authentication/providers/pki.ts | 277 ++++++++ x-pack/scripts/functional_tests.js | 1 + x-pack/test/pki_api_integration/apis/index.ts | 14 + .../apis/security/index.ts | 13 + .../apis/security/pki_auth.ts | 370 +++++++++++ x-pack/test/pki_api_integration/config.ts | 70 +++ .../pki_api_integration/fixtures/README.md | 7 + .../pki_api_integration/fixtures/es_ca.key | 27 + .../fixtures/first_client.p12 | Bin 0 -> 3467 bytes .../fixtures/kibana_ca.crt | 19 + .../fixtures/kibana_ca.key | 27 + .../fixtures/second_client.p12 | Bin 0 -> 3469 bytes .../fixtures/untrusted_client.p12 | Bin 0 -> 3387 bytes .../ftr_provider_context.d.ts | 11 + x-pack/test/pki_api_integration/services.ts | 12 + 21 files changed, 1482 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/security/server/authentication/providers/pki.test.ts create mode 100644 x-pack/plugins/security/server/authentication/providers/pki.ts create mode 100644 x-pack/test/pki_api_integration/apis/index.ts create mode 100644 x-pack/test/pki_api_integration/apis/security/index.ts create mode 100644 x-pack/test/pki_api_integration/apis/security/pki_auth.ts create mode 100644 x-pack/test/pki_api_integration/config.ts create mode 100644 x-pack/test/pki_api_integration/fixtures/README.md create mode 100644 x-pack/test/pki_api_integration/fixtures/es_ca.key create mode 100644 x-pack/test/pki_api_integration/fixtures/first_client.p12 create mode 100644 x-pack/test/pki_api_integration/fixtures/kibana_ca.crt create mode 100644 x-pack/test/pki_api_integration/fixtures/kibana_ca.key create mode 100644 x-pack/test/pki_api_integration/fixtures/second_client.p12 create mode 100644 x-pack/test/pki_api_integration/fixtures/untrusted_client.p12 create mode 100644 x-pack/test/pki_api_integration/ftr_provider_context.d.ts create mode 100644 x-pack/test/pki_api_integration/services.ts diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 33a98127aa630..fcc232345a802 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -18,6 +18,7 @@ */ import { Request } from 'hapi'; import { merge } from 'lodash'; +import { Socket } from 'net'; import querystring from 'querystring'; @@ -37,6 +38,7 @@ interface RequestFixtureOptions { query?: Record; path?: string; method?: RouteMethod; + socket?: Socket; } function createKibanaRequestMock({ @@ -46,6 +48,7 @@ function createKibanaRequestMock({ body = {}, query = {}, method = 'get', + socket = new Socket(), }: RequestFixtureOptions = {}) { const queryString = querystring.stringify(query); return KibanaRequest.from( @@ -63,7 +66,7 @@ function createKibanaRequestMock({ }, route: { settings: {} }, raw: { - req: {}, + req: { socket }, }, } as any, { diff --git a/x-pack/legacy/server/lib/esjs_shield_plugin.js b/x-pack/legacy/server/lib/esjs_shield_plugin.js index 83f050ee5ce59..e0a69b5ce538e 100644 --- a/x-pack/legacy/server/lib/esjs_shield_plugin.js +++ b/x-pack/legacy/server/lib/esjs_shield_plugin.js @@ -536,5 +536,21 @@ fmt: '/_security/api_key', }, }); + + /** + * Gets an access token in exchange to the certificate chain for the target subject distinguished name. + * + * @param {string[]} x509_certificate_chain An ordered array of base64-encoded (Section 4 of RFC4648 - not + * base64url-encoded) DER PKIX certificate values. + * + * @returns {{access_token: string, type: string, expires_in: number}} + */ + shield.delegatePKI = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/delegate_pki', + }, + }); }; })); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index f01047eff7aa3..b3f34b023d8ee 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -25,6 +25,7 @@ import { SAMLAuthenticationProvider, TokenAuthenticationProvider, OIDCAuthenticationProvider, + PKIAuthenticationProvider, isSAMLRequestQuery, } from './providers'; import { AuthenticationResult } from './authentication_result'; @@ -98,6 +99,7 @@ const providerMap = new Map< ['saml', SAMLAuthenticationProvider], ['token', TokenAuthenticationProvider], ['oidc', OIDCAuthenticationProvider], + ['pki', PKIAuthenticationProvider], ]); function assertRequest(request: KibanaRequest) { diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index 8e7410ddec077..a659786f4aeff 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -7,12 +7,20 @@ import sinon from 'sinon'; import { ScopedClusterClient } from '../../../../../../src/core/server'; import { Tokens } from '../tokens'; -import { loggingServiceMock, httpServiceMock } from '../../../../../../src/core/server/mocks'; +import { + loggingServiceMock, + httpServiceMock, + elasticsearchServiceMock, +} from '../../../../../../src/core/server/mocks'; export type MockAuthenticationProviderOptions = ReturnType< typeof mockAuthenticationProviderOptions >; +export type MockAuthenticationProviderOptionsWithJest = ReturnType< + typeof mockAuthenticationProviderOptionsWithJest +>; + export function mockScopedClusterClient( client: MockAuthenticationProviderOptions['client'], requestMatcher: sinon.SinonMatcher = sinon.match.any @@ -34,3 +42,16 @@ export function mockAuthenticationProviderOptions() { tokens: sinon.createStubInstance(Tokens), }; } + +// Will be renamed to mockAuthenticationProviderOptions as soon as we migrate all providers tests to Jest. +export function mockAuthenticationProviderOptionsWithJest() { + const basePath = httpServiceMock.createSetupContract().basePath; + basePath.get.mockReturnValue('/base-path'); + + return { + client: elasticsearchServiceMock.createClusterClient(), + logger: loggingServiceMock.create().get(), + basePath, + tokens: { refresh: jest.fn(), invalidate: jest.fn() }, + }; +} diff --git a/x-pack/plugins/security/server/authentication/providers/index.ts b/x-pack/plugins/security/server/authentication/providers/index.ts index fef3be16c8d91..af0b90766e859 100644 --- a/x-pack/plugins/security/server/authentication/providers/index.ts +++ b/x-pack/plugins/security/server/authentication/providers/index.ts @@ -14,3 +14,4 @@ export { KerberosAuthenticationProvider } from './kerberos'; export { SAMLAuthenticationProvider, isSAMLRequestQuery } from './saml'; export { TokenAuthenticationProvider } from './token'; export { OIDCAuthenticationProvider, OIDCAuthenticationFlow } from './oidc'; +export { PKIAuthenticationProvider } from './pki'; diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts new file mode 100644 index 0000000000000..35d827c3a9bd1 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -0,0 +1,589 @@ +/* + * 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. + */ + +jest.mock('net'); +jest.mock('tls'); + +import { PeerCertificate, TLSSocket } from 'tls'; +import { errors } from 'elasticsearch'; + +import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { + MockAuthenticationProviderOptionsWithJest, + mockAuthenticationProviderOptionsWithJest, +} from './base.mock'; + +import { PKIAuthenticationProvider } from './pki'; +import { + ElasticsearchErrorHelpers, + ScopedClusterClient, +} from '../../../../../../src/core/server/elasticsearch'; +import { Socket } from 'net'; +import { getErrorStatusCode } from '../../errors'; + +interface MockPeerCertificate extends Partial { + issuerCertificate: MockPeerCertificate; + fingerprint256: string; +} + +function getMockPeerCertificate(chain: string[] | string) { + const mockPeerCertificate = {} as MockPeerCertificate; + + (Array.isArray(chain) ? chain : [chain]).reduce( + (certificate, fingerprint, index, fingerprintChain) => { + certificate.fingerprint256 = fingerprint; + certificate.raw = { toString: (enc: string) => `fingerprint:${fingerprint}:${enc}` }; + + // Imitate self-signed certificate that is issuer for itself. + certificate.issuerCertificate = index === fingerprintChain.length - 1 ? certificate : {}; + + return certificate.issuerCertificate; + }, + mockPeerCertificate as Record + ); + + return mockPeerCertificate; +} + +function getMockSocket({ + authorized = false, + peerCertificate = null, +}: { + authorized?: boolean; + peerCertificate?: MockPeerCertificate | null; +} = {}) { + const socket = new TLSSocket(new Socket()); + socket.authorized = authorized; + socket.getPeerCertificate = jest.fn().mockReturnValue(peerCertificate); + return socket; +} + +describe('PKIAuthenticationProvider', () => { + let provider: PKIAuthenticationProvider; + let mockOptions: MockAuthenticationProviderOptionsWithJest; + beforeEach(() => { + mockOptions = mockAuthenticationProviderOptionsWithJest(); + provider = new PKIAuthenticationProvider(mockOptions); + }); + + afterEach(() => jest.clearAllMocks()); + + describe('`authenticate` method', () => { + it('does not handle `authorization` header with unsupported schema even if state contains a valid token.', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: 'Basic some:credentials' }, + }); + const state = { + accessToken: 'some-valid-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }; + + const authenticationResult = await provider.authenticate(request, state); + + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + expect(request.headers.authorization).toBe('Basic some:credentials'); + expect(authenticationResult.notHandled()).toBe(true); + }); + + it('does not handle requests without certificate.', async () => { + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ authorized: true }), + }); + + const authenticationResult = await provider.authenticate(request, null); + + expect(authenticationResult.notHandled()).toBe(true); + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + + it('does not handle unauthorized requests.', async () => { + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ peerCertificate: getMockPeerCertificate('2A:7A:C2:DD') }), + }); + + const authenticationResult = await provider.authenticate(request, null); + + expect(authenticationResult.notHandled()).toBe(true); + expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + }); + + it('fails with non-401 error if state is available, peer is authorized, but certificate is not available.', async () => { + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ authorized: true }), + }); + + const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + const authenticationResult = await provider.authenticate(request, state); + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toMatchInlineSnapshot( + `[Error: Peer certificate is not available]` + ); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); + }); + + it('invalidates token and fails with 401 if state is present, but peer certificate is not.', async () => { + const request = httpServerMock.createKibanaRequest({ socket: getMockSocket() }); + const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + const authenticationResult = await provider.authenticate(request, state); + + expect(authenticationResult.failed()).toBe(true); + expect(getErrorStatusCode(authenticationResult.error)).toBe(401); + + expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ + accessToken: state.accessToken, + }); + }); + + it('invalidates token and fails with 401 if new certificate is present, but not authorized.', async () => { + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ peerCertificate: getMockPeerCertificate('2A:7A:C2:DD') }), + }); + const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + const authenticationResult = await provider.authenticate(request, state); + + expect(authenticationResult.failed()).toBe(true); + expect(getErrorStatusCode(authenticationResult.error)).toBe(401); + + expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ + accessToken: state.accessToken, + }); + }); + + it('gets an access token in exchange to peer certificate chain and stores it in the state.', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest({ + headers: {}, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + + const authenticationResult = await provider.authenticate(request); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:2A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + + expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ + headers: { authorization: `Bearer access-token` }, + }); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' }); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.state).toEqual({ + accessToken: 'access-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }); + }); + + it('gets an access token in exchange to a self-signed certificate and stores it in the state.', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest({ + headers: {}, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate('2A:7A:C2:DD'), + }), + }); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + + const authenticationResult = await provider.authenticate(request); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, + }); + + expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ + headers: { authorization: `Bearer access-token` }, + }); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' }); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.state).toEqual({ + accessToken: 'access-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }); + }); + + it('invalidates existing token and gets a new one if fingerprints do not match.', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '3A:9A:C5:DD' }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + + const authenticationResult = await provider.authenticate(request, state); + + expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ + accessToken: state.accessToken, + }); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:2A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' }); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.state).toEqual({ + accessToken: 'access-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }); + }); + + it('gets a new access token even if existing token is expired.', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(['2A:7A:C2:DD', '3B:8B:D3:EE']), + }), + }); + const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser + // In response to call with an expired token. + .mockRejectedValueOnce(ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())) + // In response to a call with a new token. + .mockResolvedValueOnce(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + + const authenticationResult = await provider.authenticate(request, state); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { + x509_certificate_chain: [ + 'fingerprint:2A:7A:C2:DD:base64', + 'fingerprint:3B:8B:D3:EE:base64', + ], + }, + }); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' }); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + expect(authenticationResult.state).toEqual({ + accessToken: 'access-token', + peerCertificateFingerprint256: '2A:7A:C2:DD', + }); + }); + + it('fails with 401 if existing token is expired, but certificate is not present.', async () => { + const request = httpServerMock.createKibanaRequest({ socket: getMockSocket() }); + const state = { accessToken: 'existing-token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()) + ); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + + const authenticationResult = await provider.authenticate(request, state); + + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.failed()).toBe(true); + expect(getErrorStatusCode(authenticationResult.error)).toBe(401); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + }); + + it('fails if could not retrieve an access token in exchange to peer certificate chain.', async () => { + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate('2A:7A:C2:DD'), + }), + }); + + const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); + mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason); + + const authenticationResult = await provider.authenticate(request); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, + }); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toBe(failureReason); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + }); + + it('fails if could not retrieve user using the new access token.', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: {}, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate('2A:7A:C2:DD'), + }), + }); + + const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' }); + + const authenticationResult = await provider.authenticate(request); + + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); + expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', { + body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] }, + }); + + expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1); + expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ + headers: { authorization: `Bearer access-token` }, + }); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toBe(failureReason); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + }); + + it('succeeds if state contains a valid token.', async () => { + const user = mockAuthenticatedUser(); + const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(state.peerCertificateFingerprint256), + }), + }); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + + const authenticationResult = await provider.authenticate(request, state); + + expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); + + expect(request.headers).not.toHaveProperty('authorization'); + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.authHeaders).toEqual({ + authorization: `Bearer ${state.accessToken}`, + }); + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.state).toBeUndefined(); + }); + + it('fails if token from the state is rejected because of unknown reason.', async () => { + const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + const request = httpServerMock.createKibanaRequest({ + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(state.peerCertificateFingerprint256), + }), + }); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(new errors.ServiceUnavailable()); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + + const authenticationResult = await provider.authenticate(request, state); + + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toHaveProperty('status', 503); + expect(authenticationResult.authResponseHeaders).toBeUndefined(); + }); + + it('succeeds if `authorization` contains a valid token.', async () => { + const user = mockAuthenticatedUser(); + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: 'Bearer some-valid-token' }, + }); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + + const authenticationResult = await provider.authenticate(request); + + expect(request.headers.authorization).toBe('Bearer some-valid-token'); + expect(authenticationResult.succeeded()).toBe(true); + expect(authenticationResult.authHeaders).toBeUndefined(); + expect(authenticationResult.user).toBe(user); + expect(authenticationResult.state).toBeUndefined(); + }); + + it('fails if token from `authorization` header is rejected.', async () => { + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: 'Bearer some-invalid-token' }, + }); + + const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + + const authenticationResult = await provider.authenticate(request); + + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toBe(failureReason); + }); + + it('fails if token from `authorization` header is rejected even if state contains a valid one.', async () => { + const user = mockAuthenticatedUser(); + const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: 'Bearer some-invalid-token' }, + socket: getMockSocket({ + authorized: true, + peerCertificate: getMockPeerCertificate(state.peerCertificateFingerprint256), + }), + }); + + const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockScopedClusterClient.callAsCurrentUser + // In response to call with a token from header. + .mockRejectedValueOnce(failureReason) + // In response to a call with a token from session (not expected to be called). + .mockResolvedValueOnce(user); + mockOptions.client.asScoped.mockReturnValue( + (mockScopedClusterClient as unknown) as jest.Mocked + ); + + const authenticationResult = await provider.authenticate(request, state); + + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toBe(failureReason); + }); + }); + + describe('`logout` method', () => { + it('returns `notHandled` if state is not presented.', async () => { + const request = httpServerMock.createKibanaRequest(); + + let deauthenticateResult = await provider.logout(request); + expect(deauthenticateResult.notHandled()).toBe(true); + + deauthenticateResult = await provider.logout(request, null); + expect(deauthenticateResult.notHandled()).toBe(true); + + expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); + }); + + it('fails if `tokens.invalidate` fails', async () => { + const request = httpServerMock.createKibanaRequest(); + const state = { accessToken: 'foo', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + const failureReason = new Error('failed to delete token'); + mockOptions.tokens.invalidate.mockRejectedValue(failureReason); + + const authenticationResult = await provider.logout(request, state); + + expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ accessToken: 'foo' }); + + expect(authenticationResult.failed()).toBe(true); + expect(authenticationResult.error).toBe(failureReason); + }); + + it('redirects to `/logged_out` page if access token is invalidated successfully.', async () => { + const request = httpServerMock.createKibanaRequest(); + const state = { accessToken: 'foo', peerCertificateFingerprint256: '2A:7A:C2:DD' }; + + mockOptions.tokens.invalidate.mockResolvedValue(undefined); + + const authenticationResult = await provider.logout(request, state); + + expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ accessToken: 'foo' }); + + expect(authenticationResult.redirected()).toBe(true); + expect(authenticationResult.redirectURL).toBe('/logged_out'); + }); + }); +}); diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts new file mode 100644 index 0000000000000..788395feae442 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -0,0 +1,277 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { DetailedPeerCertificate } from 'tls'; +import { KibanaRequest } from '../../../../../../src/core/server'; +import { AuthenticationResult } from '../authentication_result'; +import { DeauthenticationResult } from '../deauthentication_result'; +import { BaseAuthenticationProvider } from './base'; +import { Tokens } from '../tokens'; + +/** + * The state supported by the provider. + */ +interface ProviderState { + /** + * Access token we got in exchange to peer certificate chain. + */ + accessToken: string; + + /** + * The SHA-256 digest of the DER encoded peer leaf certificate. It is a `:` separated hexadecimal string. + */ + peerCertificateFingerprint256: string; +} + +/** + * Parses request's `Authorization` HTTP header if present and extracts authentication scheme. + * @param request Request instance to extract authentication scheme for. + */ +function getRequestAuthenticationScheme(request: KibanaRequest) { + const authorization = request.headers.authorization; + if (!authorization || typeof authorization !== 'string') { + return ''; + } + + return authorization.split(/\s+/)[0].toLowerCase(); +} + +/** + * Provider that supports PKI request authentication. + */ +export class PKIAuthenticationProvider extends BaseAuthenticationProvider { + /** + * Performs PKI request authentication. + * @param request Request instance. + * @param [state] Optional state object associated with the provider. + */ + public async authenticate(request: KibanaRequest, state?: ProviderState | null) { + this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`); + + const authenticationScheme = getRequestAuthenticationScheme(request); + if (authenticationScheme && authenticationScheme !== 'bearer') { + this.logger.debug(`Unsupported authentication scheme: ${authenticationScheme}`); + return AuthenticationResult.notHandled(); + } + + let authenticationResult = AuthenticationResult.notHandled(); + if (authenticationScheme) { + // We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore. + authenticationResult = await this.authenticateWithBearerScheme(request); + } + + if (state && authenticationResult.notHandled()) { + authenticationResult = await this.authenticateViaState(request, state); + + // If access token expired or doesn't match to the certificate fingerprint we should try to get + // a new one in exchange to peer certificate chain. + if ( + authenticationResult.notHandled() || + (authenticationResult.failed() && + Tokens.isAccessTokenExpiredError(authenticationResult.error)) + ) { + authenticationResult = await this.authenticateViaPeerCertificate(request); + // If we have an active session that we couldn't use to authenticate user and at the same time + // we couldn't use peer's certificate to establish a new one, then we should respond with 401 + // and force authenticator to clear the session. + if (authenticationResult.notHandled()) { + return AuthenticationResult.failed(Boom.unauthorized()); + } + } + } + + // If we couldn't authenticate by means of all methods above, let's try to check if we can authenticate + // request using its peer certificate chain, otherwise just return authentication result we have. + return authenticationResult.notHandled() + ? await this.authenticateViaPeerCertificate(request) + : authenticationResult; + } + + /** + * Invalidates access token retrieved in exchange for peer certificate chain if it exists. + * @param request Request instance. + * @param state State value previously stored by the provider. + */ + public async logout(request: KibanaRequest, state?: ProviderState | null) { + this.logger.debug(`Trying to log user out via ${request.url.path}.`); + + if (!state) { + this.logger.debug('There is no access token to invalidate.'); + return DeauthenticationResult.notHandled(); + } + + try { + await this.options.tokens.invalidate({ accessToken: state.accessToken }); + } catch (err) { + this.logger.debug(`Failed invalidating access token: ${err.message}`); + return DeauthenticationResult.failed(err); + } + + return DeauthenticationResult.redirectTo('/logged_out'); + } + + /** + * Tries to authenticate request with `Bearer ***` Authorization header by passing it to the Elasticsearch backend. + * @param request Request instance. + */ + private async authenticateWithBearerScheme(request: KibanaRequest) { + this.logger.debug('Trying to authenticate request using "Bearer" authentication scheme.'); + + try { + const user = await this.getUser(request); + + this.logger.debug('Request has been authenticated using "Bearer" authentication scheme.'); + return AuthenticationResult.succeeded(user); + } catch (err) { + this.logger.debug( + `Failed to authenticate request using "Bearer" authentication scheme: ${err.message}` + ); + return AuthenticationResult.failed(err); + } + } + + /** + * Tries to extract access token from state and adds it to the request before it's + * forwarded to Elasticsearch backend. + * @param request Request instance. + * @param state State value previously stored by the provider. + */ + private async authenticateViaState( + request: KibanaRequest, + { accessToken, peerCertificateFingerprint256 }: ProviderState + ) { + this.logger.debug('Trying to authenticate via state.'); + + // If peer is authorized, but its certificate isn't available, that likely means the connection + // with the peer is closed already. We shouldn't invalidate peer's access token in this case + // since we cannot guarantee that there is a mismatch in access token and peer certificate. + const peerCertificate = request.socket.getPeerCertificate(true); + if (peerCertificate === null && request.socket.authorized) { + this.logger.debug( + 'Cannot validate state access token with the peer certificate since it is not available.' + ); + return AuthenticationResult.failed(new Error('Peer certificate is not available')); + } + + if ( + !request.socket.authorized || + peerCertificate === null || + (peerCertificate as any).fingerprint256 !== peerCertificateFingerprint256 + ) { + this.logger.debug( + 'Peer certificate is not present or its fingerprint does not match to the one associated with the access token. Invalidating access token...' + ); + + try { + await this.options.tokens.invalidate({ accessToken }); + } catch (err) { + this.logger.debug(`Failed to invalidate access token: ${err.message}`); + return AuthenticationResult.failed(err); + } + + // Return "Not Handled" result to allow provider to try to exchange new peer certificate chain + // to the new access token down the line. + return AuthenticationResult.notHandled(); + } + + try { + const authHeaders = { authorization: `Bearer ${accessToken}` }; + const user = await this.getUser(request, authHeaders); + + this.logger.debug('Request has been authenticated via state.'); + return AuthenticationResult.succeeded(user, { authHeaders }); + } catch (err) { + this.logger.debug(`Failed to authenticate request via state: ${err.message}`); + return AuthenticationResult.failed(err); + } + } + + /** + * Tries to exchange peer certificate chain to access/refresh token pair. + * @param request Request instance. + */ + private async authenticateViaPeerCertificate(request: KibanaRequest) { + this.logger.debug('Trying to authenticate request via peer certificate chain.'); + + if (!request.socket.authorized) { + this.logger.debug( + `Authentication is not possible since peer certificate was not authorized: ${request.socket.authorizationError}.` + ); + return AuthenticationResult.notHandled(); + } + + const peerCertificate = request.socket.getPeerCertificate(true); + if (peerCertificate === null) { + this.logger.debug('Authentication is not possible due to missing peer certificate chain.'); + return AuthenticationResult.notHandled(); + } + + // We should collect entire certificate chain as an ordered array of certificates encoded as base64 strings. + const certificateChain = this.getCertificateChain(peerCertificate); + let accessToken: string; + try { + accessToken = (await this.options.client.callAsInternalUser('shield.delegatePKI', { + body: { x509_certificate_chain: certificateChain }, + })).access_token; + } catch (err) { + this.logger.debug( + `Failed to exchange peer certificate chain to an access token: ${err.message}` + ); + return AuthenticationResult.failed(err); + } + + this.logger.debug('Successfully retrieved access token in exchange to peer certificate chain.'); + + try { + // Then attempt to query for the user details using the new token + const authHeaders = { authorization: `Bearer ${accessToken}` }; + const user = await this.getUser(request, authHeaders); + + this.logger.debug('User has been authenticated with new access token'); + return AuthenticationResult.succeeded(user, { + authHeaders, + state: { + accessToken, + // NodeJS typings don't include `fingerprint256` yet. + peerCertificateFingerprint256: (peerCertificate as any).fingerprint256, + }, + }); + } catch (err) { + this.logger.debug(`Failed to authenticate request via access token: ${err.message}`); + return AuthenticationResult.failed(err); + } + } + + /** + * Starts from the leaf peer certificate and iterates up to the top-most available certificate + * authority using `issuerCertificate` certificate property. THe iteration is stopped only when + * we detect circular reference (root/self-signed certificate) or when `issuerCertificate` isn't + * available (null or empty object). + * @param peerCertificate Peer leaf certificate instance. + */ + private getCertificateChain(peerCertificate: DetailedPeerCertificate | null) { + const certificateChain = []; + let certificate: DetailedPeerCertificate | null = peerCertificate; + while (certificate !== null && Object.keys(certificate).length > 0) { + certificateChain.push(certificate.raw.toString('base64')); + + // For self-signed certificates, `issuerCertificate` may be a circular reference. + if (certificate === certificate.issuerCertificate) { + this.logger.debug('Self-signed certificate is detected in certificate chain'); + certificate = null; + } else { + certificate = certificate.issuerCertificate; + } + } + + this.logger.debug( + `Peer certificate chain consists of ${certificateChain.length} certificates.` + ); + + return certificateChain; + } +} diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 222647e000672..f513d117e08ae 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -21,6 +21,7 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/token_api_integration/config.js'), require.resolve('../test/oidc_api_integration/config.ts'), require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'), + // require.resolve('../test/pki_api_integration/config.ts'), require.resolve('../test/spaces_api_integration/spaces_only/config'), require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial'), require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic'), diff --git a/x-pack/test/pki_api_integration/apis/index.ts b/x-pack/test/pki_api_integration/apis/index.ts new file mode 100644 index 0000000000000..d859ed172ac69 --- /dev/null +++ b/x-pack/test/pki_api_integration/apis/index.ts @@ -0,0 +1,14 @@ +/* + * 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'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('apis PKI', function() { + this.tags('ciGroup6'); + loadTestFile(require.resolve('./security')); + }); +} diff --git a/x-pack/test/pki_api_integration/apis/security/index.ts b/x-pack/test/pki_api_integration/apis/security/index.ts new file mode 100644 index 0000000000000..d2bfe613ca7fa --- /dev/null +++ b/x-pack/test/pki_api_integration/apis/security/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('security', () => { + loadTestFile(require.resolve('./pki_auth')); + }); +} diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts new file mode 100644 index 0000000000000..8c29db674aaf3 --- /dev/null +++ b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts @@ -0,0 +1,370 @@ +/* + * 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 request, { Cookie } from 'request'; +import { delay } from 'bluebird'; +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +// @ts-ignore +import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const CA_CERT = readFileSync(CA_CERT_PATH); +const FIRST_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/first_client.p12')); +const SECOND_CLIENT_CERT = readFileSync(resolve(__dirname, '../../fixtures/second_client.p12')); +const UNTRUSTED_CLIENT_CERT = readFileSync( + resolve(__dirname, '../../fixtures/untrusted_client.p12') +); + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const config = getService('config'); + + function checkCookieIsSet(cookie: Cookie) { + expect(cookie.value).to.not.be.empty(); + + expect(cookie.key).to.be('sid'); + expect(cookie.path).to.be('/'); + expect(cookie.httpOnly).to.be(true); + expect(cookie.maxAge).to.be(null); + } + + function checkCookieIsCleared(cookie: Cookie) { + expect(cookie.value).to.be.empty(); + + expect(cookie.key).to.be('sid'); + expect(cookie.path).to.be('/'); + expect(cookie.httpOnly).to.be(true); + expect(cookie.maxAge).to.be(0); + } + + describe('PKI authentication', () => { + before(async () => { + await getService('esSupertest') + .post('/_security/role_mapping/first_client_pki') + .ca(CA_CERT) + .send({ + roles: ['kibana_user'], + enabled: true, + rules: { field: { dn: 'CN=first_client' } }, + }) + .expect(200); + }); + + it('should reject API requests that use untrusted certificate', async () => { + await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(UNTRUSTED_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .expect(401); + }); + + it('does not prevent basic login', async () => { + const [username, password] = config.get('servers.elasticsearch.auth').split(':'); + const response = await supertest + .post('/api/security/v1/login') + .ca(CA_CERT) + .pfx(UNTRUSTED_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .send({ username, password }) + .expect(204); + + const cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const cookie = request.cookie(cookies[0])!; + checkCookieIsSet(cookie); + + const { body: user } = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(UNTRUSTED_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .set('Cookie', cookie.cookieString()) + .expect(200); + + expect(user.username).to.eql(username); + expect(user.authentication_realm).to.eql({ name: 'reserved', type: 'reserved' }); + }); + + it('should properly set cookie and authenticate user', async () => { + const response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(200); + + const cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const sessionCookie = request.cookie(cookies[0])!; + checkCookieIsSet(sessionCookie); + + expect(response.body).to.eql({ + username: 'first_client', + roles: ['kibana_user'], + full_name: null, + email: null, + enabled: true, + metadata: { + pki_delegated_by_realm: 'reserved', + pki_delegated_by_user: 'elastic', + pki_dn: 'CN=first_client', + }, + authentication_realm: { name: 'pki1', type: 'pki' }, + lookup_realm: { name: 'pki1', type: 'pki' }, + }); + + // Cookie should be accepted. + await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + }); + + it('should update session if new certificate is provided', async () => { + let response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(200); + + const cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const sessionCookie = request.cookie(cookies[0])!; + checkCookieIsSet(sessionCookie); + + response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(SECOND_CLIENT_CERT) + .set('Cookie', sessionCookie.cookieString()) + .expect(200, { + username: 'second_client', + roles: [], + full_name: null, + email: null, + enabled: true, + metadata: { + pki_delegated_by_realm: 'reserved', + pki_delegated_by_user: 'elastic', + pki_dn: 'CN=second_client', + }, + authentication_realm: { name: 'pki1', type: 'pki' }, + lookup_realm: { name: 'pki1', type: 'pki' }, + }); + + checkCookieIsSet(request.cookie(response.headers['set-cookie'][0])!); + }); + + it('should reject valid cookie if used with untrusted certificate', async () => { + const response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(200); + + const cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const sessionCookie = request.cookie(cookies[0])!; + checkCookieIsSet(sessionCookie); + + await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(UNTRUSTED_CLIENT_CERT) + .set('Cookie', sessionCookie.cookieString()) + .expect(401); + }); + + describe('API access with active session', () => { + let sessionCookie: Cookie; + + beforeEach(async () => { + const response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(200); + + const cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + sessionCookie = request.cookie(cookies[0])!; + checkCookieIsSet(sessionCookie); + }); + + it('should extend cookie on every successful non-system API call', async () => { + const apiResponseOne = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + + expect(apiResponseOne.headers['set-cookie']).to.not.be(undefined); + const sessionCookieOne = request.cookie(apiResponseOne.headers['set-cookie'][0])!; + + checkCookieIsSet(sessionCookieOne); + expect(sessionCookieOne.value).to.not.equal(sessionCookie.value); + + const apiResponseTwo = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + + expect(apiResponseTwo.headers['set-cookie']).to.not.be(undefined); + const sessionCookieTwo = request.cookie(apiResponseTwo.headers['set-cookie'][0])!; + + checkCookieIsSet(sessionCookieTwo); + expect(sessionCookieTwo.value).to.not.equal(sessionCookieOne.value); + }); + + it('should not extend cookie for system API calls', async () => { + const systemAPIResponse = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .set('kbn-system-api', 'true') + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + + expect(systemAPIResponse.headers['set-cookie']).to.be(undefined); + }); + + it('should fail and preserve session cookie if unsupported authentication schema is used', async () => { + const apiResponse = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .set('Authorization', 'Basic a3JiNTprcmI1') + .set('Cookie', sessionCookie.cookieString()) + .expect(401); + + expect(apiResponse.headers['set-cookie']).to.be(undefined); + }); + }); + + describe('logging out', () => { + it('should redirect to `logged_out` page after successful logout', async () => { + // First authenticate user to retrieve session cookie. + const response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(200); + + let cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const sessionCookie = request.cookie(cookies[0])!; + checkCookieIsSet(sessionCookie); + + // And then log user out. + const logoutResponse = await supertest + .get('/api/security/v1/logout') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('Cookie', sessionCookie.cookieString()) + .expect(302); + + cookies = logoutResponse.headers['set-cookie']; + expect(cookies).to.have.length(1); + checkCookieIsCleared(request.cookie(cookies[0])!); + + expect(logoutResponse.headers.location).to.be('/logged_out'); + }); + + it('should redirect to home page if session cookie is not provided', async () => { + const logoutResponse = await supertest + .get('/api/security/v1/logout') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(302); + + expect(logoutResponse.headers['set-cookie']).to.be(undefined); + expect(logoutResponse.headers.location).to.be('/'); + }); + }); + + describe('API access with expired access token.', () => { + let sessionCookie: Cookie; + + beforeEach(async () => { + const response = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .expect(200); + + const cookies = response.headers['set-cookie']; + expect(cookies).to.have.length(1); + + sessionCookie = request.cookie(cookies[0])!; + checkCookieIsSet(sessionCookie); + }); + + it('AJAX call should re-acquire token and update existing cookie', async function() { + this.timeout(40000); + + // Access token expiration is set to 15s for API integration tests. + // Let's wait for 20s to make sure token expires. + await delay(20000); + + // This api call should succeed and automatically refresh token. Returned cookie will contain + // the new access token. + const apiResponse = await supertest + .get('/api/security/v1/me') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('kbn-xsrf', 'xxx') + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + + const cookies = apiResponse.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const refreshedCookie = request.cookie(cookies[0])!; + checkCookieIsSet(refreshedCookie); + }); + + it('non-AJAX call should re-acquire token and update existing cookie', async function() { + this.timeout(40000); + + // Access token expiration is set to 15s for API integration tests. + // Let's wait for 20s to make sure token expires. + await delay(20000); + + // This request should succeed and automatically refresh token. Returned cookie will contain + // the new access and refresh token pair. + const nonAjaxResponse = await supertest + .get('/app/kibana') + .ca(CA_CERT) + .pfx(FIRST_CLIENT_CERT) + .set('Cookie', sessionCookie.cookieString()) + .expect(200); + + const cookies = nonAjaxResponse.headers['set-cookie']; + expect(cookies).to.have.length(1); + + const refreshedCookie = request.cookie(cookies[0])!; + checkCookieIsSet(refreshedCookie); + }); + }); + }); +} diff --git a/x-pack/test/pki_api_integration/config.ts b/x-pack/test/pki_api_integration/config.ts new file mode 100644 index 0000000000000..50b41ad251827 --- /dev/null +++ b/x-pack/test/pki_api_integration/config.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 { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +// @ts-ignore +import { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from '@kbn/dev-utils'; +import { services } from './services'; + +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js')); + + const servers = { + ...xPackAPITestsConfig.get('servers'), + elasticsearch: { + ...xPackAPITestsConfig.get('servers.elasticsearch'), + protocol: 'https', + }, + kibana: { + ...xPackAPITestsConfig.get('servers.kibana'), + protocol: 'https', + }, + }; + + return { + testFiles: [require.resolve('./apis')], + servers, + services, + junit: { + reportName: 'X-Pack PKI API Integration Tests', + }, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + ssl: true, + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.token.enabled=true', + 'xpack.security.authc.token.timeout=15s', + 'xpack.security.http.ssl.client_authentication=optional', + 'xpack.security.http.ssl.verification_mode=certificate', + 'xpack.security.authc.realms.native.native1.order=0', + 'xpack.security.authc.realms.pki.pki1.order=1', + 'xpack.security.authc.realms.pki.pki1.delegation.enabled=true', + `xpack.security.authc.realms.pki.pki1.certificate_authorities=${CA_CERT_PATH}`, + ], + }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + '--server.ssl.enabled=true', + `--server.ssl.key=${ES_KEY_PATH}`, + `--server.ssl.certificate=${ES_CERT_PATH}`, + `--server.ssl.certificateAuthorities=${JSON.stringify([ + CA_CERT_PATH, + resolve(__dirname, './fixtures/kibana_ca.crt'), + ])}`, + `--server.ssl.clientAuthentication=required`, + `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + `--xpack.security.authc.providers=${JSON.stringify(['pki', 'basic'])}`, + ], + }, + }; +} diff --git a/x-pack/test/pki_api_integration/fixtures/README.md b/x-pack/test/pki_api_integration/fixtures/README.md new file mode 100644 index 0000000000000..0fcbc76183b48 --- /dev/null +++ b/x-pack/test/pki_api_integration/fixtures/README.md @@ -0,0 +1,7 @@ +# PKI Fixtures + +* `es_ca.key` - the CA key used to sign certificates from @kbn/dev-utils that are used and trusted by test Elasticsearch server. +* `first_client.p12` and `second_client.p12` - the client certificate bundles signed by `es_ca.key` and hence trusted by +both test Kibana and Elasticsearch servers. +* `untrusted_client.p12` - the client certificate bundle trusted by test Kibana server, but not test Elasticsearch test server. +* `kibana_ca.crt` and `kibana_ca.key` - the CA certificate and key trusted by test Kibana server only. \ No newline at end of file diff --git a/x-pack/test/pki_api_integration/fixtures/es_ca.key b/x-pack/test/pki_api_integration/fixtures/es_ca.key new file mode 100644 index 0000000000000..5428f86851e5a --- /dev/null +++ b/x-pack/test/pki_api_integration/fixtures/es_ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAjSJiqfwPZfvgHO1OZbxzgPn2EW/KewIHXygTAdL926Pm6R45 +G5H972B46NcSUoOZbOhDyvg6OKMJAICiXa85yOf3nyTo4APspR+K4AH60SEJohRF +mZwL/OryfiKvN5n5DxC2+Hb1wouwBUJM6DP62C24ve8YWuWwNkhJqWKe1YQUzPc1 +svqvU5uaHTzvLtp++RqSDNkcIqWl5S9Ip5PtOv6MHkCaIr2g4KQzplFwhT5qVd1Q +nYVBsQ0D8htLqUJBfjW0KHouEZpbjxJlc+EuyExS1o1+y3mVT+t2yZHAoIquh5ve +5A7a/RGJTyoR5u1DFs4Tcx2378kjA86gCQtClwIDAQABAoIBAFTOGKMzxrztQJmh +Lr6LIoyZpnaLygtoCK3xEprCAbB9KD9j3cTnUMMKIR0oPuY+FW8Pkczgo3ts2/fl +U6sfo4VJfc2vDA+vy/7cmUJJbkFDrNorfDb1QW7UbqnEhazPZIzc6lUahkpETZyb +XkMZGN3Ve3EFvojAA8ZaYYjarb52HRddLPZJ7c8ZiHfJ1jHNIvx6dIQ6CJVuovBJ +OGbbSAK8MjUtOI2XzWNHgUqGHcjVDFysuAac3ckK14TaN4KVNRl+usAMkZwqSM5u +j/ATFL9hx7nkzh3KWPsuOLMoLX7JN81z0YtT52wTxJoSiZKk/u91JHZ3NcrsOSPS +oLvVkyECgYEA16qtXvtmboAbqeuXf0nF+7QD0b+MdaRFIacqTG0LpEgY9Tjgs9Pn +6z44tHABWPVkRLNQZiky99MAq4Ci354Bk9dmylCw9ADH78VGmKWklbQEr1rw4dqm +DHTj9NQ79SyTdiasQjnnxCilWkrO6ZUqD8og4DT5MhzfxO/ZND8arGsCgYEAp4df +oI5lwlc1n9X/G9RQAKwNM5un8RmReleUVemjkcvWwvZVEjV0Gcc1WtjB+77Y5B9B +CM3laURDGrAgX5VS/I2jb0xqBNUr8XccSkDQAP9UuVPZgxpS+8d0R3fxVzniHWwR +WC2dW/Is40i/6+7AkFXhkiFiqxkvSg4pWHPazYUCgYB/gP7C+urSRZcVXJ3SuXD9 +oK3pYc/O9XGRtd0CFi4d0CpBQIFIj+27XKv1sYp6Z4oCO+k6nPzvG6Z3vrOMdUQF +fgHddttHRvbtwLo+ISAvCaEDc0aaoMQu9SSYaKmSB+qenbqV5NorVMR9n2C5JGEb +uKq7I1Z41C1Pp2XIx84jRQKBgQCjKvfZsjesZDJnfg9dtJlDPlARXt7gte16gkiI +sOnOfAGtnCzZclSlMuBlnk65enVXIpW+FIQH1iOhn7+4OQE92FpBceSk1ldZdJCK +RbwR7J5Bb0igJ4iBkA9R+KGIOmlgDLyL7MmiHyrXKCk9iynkqrDsGjY2vW3QrCBa +9WQ73QKBgQDAYZzplO4TPoPK9AnxoW/HpSwGEO7Fb8fLyPg94CvHn4QBCFJUKuTn +hBp/TJgF6CjQWQMr2FKVFF33Ow7+Qa96YGvmYlEjR/71D4Rlprj5JJpuO154DI3I +YIMNTjvwEQEI+YamMarKsz0Kq+I1EYSAf6bQ4H2PgxDxwTXaLkl0RA== +-----END RSA PRIVATE KEY----- diff --git a/x-pack/test/pki_api_integration/fixtures/first_client.p12 b/x-pack/test/pki_api_integration/fixtures/first_client.p12 new file mode 100644 index 0000000000000000000000000000000000000000..62da80d9ab80efbedd24edf322acbc1aecc38ef8 GIT binary patch literal 3467 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 diff --git a/x-pack/test/pki_api_integration/services.ts b/x-pack/test/pki_api_integration/services.ts new file mode 100644 index 0000000000000..31f58df081ddb --- /dev/null +++ b/x-pack/test/pki_api_integration/services.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 { services as apiIntegrationServices } from '../api_integration/services'; + +export const services = { + esSupertest: apiIntegrationServices.esSupertest, + supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, +}; From 37fa09962d13e41d9e0b17553086e7a1170ebddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Tue, 27 Aug 2019 18:29:59 +0200 Subject: [PATCH 18/66] Update console autocomplete for ML put data_frame analytics (#44101) --- .../ml.put_data_frame_analytics.json | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/console_extensions/spec/overrides/ml.put_data_frame_analytics.json b/x-pack/legacy/plugins/console_extensions/spec/overrides/ml.put_data_frame_analytics.json index ceeaa3487594d..cd2e5a8da7d06 100644 --- a/x-pack/legacy/plugins/console_extensions/spec/overrides/ml.put_data_frame_analytics.json +++ b/x-pack/legacy/plugins/console_extensions/spec/overrides/ml.put_data_frame_analytics.json @@ -10,11 +10,30 @@ "results_field": "" }, "analysis": { - "outlier_detection": { - "n_neighbors": 1, - "method": {"__one_of": ["lof", "ldof", "distance_knn_nn", "distance_knn"]}, - "feature_influence_threshold": 1.0 - } + "__one_of": [{ + "outlier_detection": { + "__template": { + "n_neighbors": "" + }, + "n_neighbors": 1, + "method": {"__one_of": ["lof", "ldof", "distance_knn_nn", "distance_knn"]}, + "feature_influence_threshold": 1.0 + } + }, { + "regression": { + "__template": { + "dependent_variable": "" + }, + "dependent_variable": "", + "lambda": 1.0, + "gamma": 1.0, + "eta": 1.0, + "maximum_number_trees": 1, + "feature_bag_fraction": 1.0, + "prediction_field_name": "", + "training_percent": 1.0 + } + }] }, "analyzed_fields": { "__one_of": [ @@ -30,6 +49,7 @@ } ] }, + "description": "", "model_memory_limit": "" } } From 2e279aa2bdb194897b7187c91de20b36d95708f6 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Tue, 27 Aug 2019 09:36:12 -0700 Subject: [PATCH 19/66] Feature/default enable csv dashboard (#44048) * Enable panel-action downloads by default * Updating snapshots --- .../plugins/reporting/__snapshots__/index.test.js.snap | 8 ++++---- x-pack/legacy/plugins/reporting/index.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap index 3397f0ae39b08..120114673b267 100644 --- a/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap +++ b/x-pack/legacy/plugins/reporting/__snapshots__/index.test.js.snap @@ -26,7 +26,7 @@ Object { }, "csv": Object { "checkForFormulas": true, - "enablePanelActionDownload": false, + "enablePanelActionDownload": true, "maxSizeBytes": 10485760, "scroll": Object { "duration": "30s", @@ -88,7 +88,7 @@ Object { }, "csv": Object { "checkForFormulas": true, - "enablePanelActionDownload": false, + "enablePanelActionDownload": true, "maxSizeBytes": 10485760, "scroll": Object { "duration": "30s", @@ -149,7 +149,7 @@ Object { }, "csv": Object { "checkForFormulas": true, - "enablePanelActionDownload": false, + "enablePanelActionDownload": true, "maxSizeBytes": 10485760, "scroll": Object { "duration": "30s", @@ -211,7 +211,7 @@ Object { }, "csv": Object { "checkForFormulas": true, - "enablePanelActionDownload": false, + "enablePanelActionDownload": true, "maxSizeBytes": 10485760, "scroll": Object { "duration": "30s", diff --git a/x-pack/legacy/plugins/reporting/index.js b/x-pack/legacy/plugins/reporting/index.js index 44cc7d229d1c5..6acf77f1dc726 100644 --- a/x-pack/legacy/plugins/reporting/index.js +++ b/x-pack/legacy/plugins/reporting/index.js @@ -134,7 +134,7 @@ export const reporting = (kibana) => { }).default(), csv: Joi.object({ checkForFormulas: Joi.boolean().default(true), - enablePanelActionDownload: Joi.boolean().default(false), + enablePanelActionDownload: Joi.boolean().default(true), maxSizeBytes: Joi.number().integer().default(1024 * 1024 * 10), // bytes in a kB * kB in a mB * 10 scroll: Joi.object({ duration: Joi.string().regex(/^[0-9]+(d|h|m|s|ms|micros|nanos)$/, { name: 'DurationString' }).default('30s'), From b98954a78f8267e714a1951b542f325666cf1ec8 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 27 Aug 2019 12:57:10 -0400 Subject: [PATCH 20/66] Fixes alert mustache templating with arrays (#44094) * Fixes alert mustache templating with arrays fixes https://github.com/elastic/kibana/issues/44057 Prior to this fix, values inside an array were not subject to the mustache transform. --- .../lib/transform_action_params.test.ts | 72 ++++++++++++------- .../server/lib/transform_action_params.ts | 29 ++++---- 2 files changed, 61 insertions(+), 40 deletions(-) diff --git a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts b/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts index 57498416b9612..824174db93d81 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts @@ -16,14 +16,14 @@ test('skips non string parameters', () => { }; const result = transformActionParams(params, {}, {}); expect(result).toMatchInlineSnapshot(` -Object { - "boolean": true, - "date": "2019-02-12T21:01:22.479Z", - "empty1": null, - "empty2": undefined, - "number": 1, -} -`); + Object { + "boolean": true, + "date": "2019-02-12T21:01:22.479Z", + "empty1": null, + "empty2": undefined, + "number": 1, + } + `); }); test('missing parameters get emptied out', () => { @@ -33,11 +33,11 @@ test('missing parameters get emptied out', () => { }; const result = transformActionParams(params, {}, {}); expect(result).toMatchInlineSnapshot(` -Object { - "message1": "", - "message2": "This message \\"\\" is missing", -} -`); + Object { + "message1": "", + "message2": "This message \\"\\" is missing", + } + `); }); test('context parameters are passed to templates', () => { @@ -46,10 +46,10 @@ test('context parameters are passed to templates', () => { }; const result = transformActionParams(params, {}, { foo: 'fooVal' }); expect(result).toMatchInlineSnapshot(` -Object { - "message": "Value \\"fooVal\\" exists", -} -`); + Object { + "message": "Value \\"fooVal\\" exists", + } + `); }); test('state parameters are passed to templates', () => { @@ -58,10 +58,10 @@ test('state parameters are passed to templates', () => { }; const result = transformActionParams(params, { bar: 'barVal' }, {}); expect(result).toMatchInlineSnapshot(` -Object { - "message": "Value \\"barVal\\" exists", -} -`); + Object { + "message": "Value \\"barVal\\" exists", + } + `); }); test('works recursively', () => { @@ -72,10 +72,28 @@ test('works recursively', () => { }; const result = transformActionParams(params, { value: 'state' }, { value: 'context' }); expect(result).toMatchInlineSnapshot(` -Object { - "body": Object { - "message": "State: \\"state\\", Context: \\"context\\"", - }, -} -`); + Object { + "body": Object { + "message": "State: \\"state\\", Context: \\"context\\"", + }, + } + `); +}); + +test('works recursively with arrays', () => { + const params = { + body: { + messages: ['State: "{{state.value}}", Context: "{{context.value}}"'], + }, + }; + const result = transformActionParams(params, { value: 'state' }, { value: 'context' }); + expect(result).toMatchInlineSnapshot(` + Object { + "body": Object { + "messages": Array [ + "State: \\"state\\", Context: \\"context\\"", + ], + }, + } + `); }); diff --git a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts b/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts index 985715146375d..83522bf93d784 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts @@ -5,19 +5,22 @@ */ import Mustache from 'mustache'; -import { isPlainObject } from 'lodash'; +import { isString, cloneDeep } from 'lodash'; import { AlertActionParams, State, Context } from '../types'; -export function transformActionParams(params: AlertActionParams, state: State, context: Context) { - const result: AlertActionParams = {}; - for (const [key, value] of Object.entries(params)) { - if (isPlainObject(value)) { - result[key] = transformActionParams(value as AlertActionParams, state, context); - } else if (typeof value !== 'string') { - result[key] = value; - } else { - result[key] = Mustache.render(value, { context, state }); - } - } - return result; +export function transformActionParams( + params: AlertActionParams, + state: State, + context: Context +): AlertActionParams { + const result = cloneDeep(params, (value: any) => { + if (!isString(value)) return; + + return Mustache.render(value, { context, state }); + }); + + // The return type signature for `cloneDeep()` ends up taking the return + // type signature for the customizer, but rather than pollute the customizer + // with casts, seemed better to just do it in one place, here. + return (result as unknown) as AlertActionParams; } From a5c63537a2031ed8cc922262b324e96e7829fe0f Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 27 Aug 2019 18:17:47 +0100 Subject: [PATCH 21/66] chore(NA): fix babel plugin discover for thread-loader-warmup (#44106) * chore(NA): fix babel plugin discover for thread-loader-warmup * chore(NA): apply changes accordingly the PR review --- packages/kbn-babel-preset/webpack_preset.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/kbn-babel-preset/webpack_preset.js b/packages/kbn-babel-preset/webpack_preset.js index 3f07d5aabd019..fcd7e76cfde98 100644 --- a/packages/kbn-babel-preset/webpack_preset.js +++ b/packages/kbn-babel-preset/webpack_preset.js @@ -30,8 +30,6 @@ module.exports = () => { ], require('./common_preset'), ], - plugins: [ - '@babel/plugin-syntax-dynamic-import' - ] + plugins: [require.resolve('@babel/plugin-syntax-dynamic-import')], }; }; From 3489274ce682ff2f3d8e9b814bfd4bb6bf7e7c31 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Tue, 27 Aug 2019 14:13:17 -0400 Subject: [PATCH 22/66] [Monitoring] Only do a single date_histogram agg for get_nodes calls (#43481) * I think this is working now * Add a way to uncovert, and then fix tests * Remove unnecessary export --- .../lib/elasticsearch/convert_metric_names.js | 76 + .../get_metric_aggs.test.js.snap | 152 +- .../handle_response.test.js.snap | 100 +- .../__test__/fixtures/cluster_data.json | 5176 +++++++++-------- .../nodes/get_nodes/get_metric_aggs.js | 15 +- .../nodes/get_nodes/get_nodes.js | 11 +- .../nodes/get_nodes/handle_response.js | 8 +- .../nodes/get_nodes/nodes_listing_metrics.js | 7 +- 8 files changed, 3068 insertions(+), 2477 deletions(-) create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js new file mode 100644 index 0000000000000..118c2610e0f48 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/convert_metric_names.js @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { cloneDeep } from 'lodash'; +import { LISTING_METRICS_NAMES } from './nodes/get_nodes/nodes_listing_metrics'; + +// We should use some explicit prefix for the converted aggregation name +// so we can easily strip them out later (see `convertMetricNames` and `uncovertMetricNames`) +const CONVERTED_TOKEN = `odh_`; + +/** + * This work stemmed from this issue: https://github.com/elastic/kibana/issues/43477 + * + * Historically, the `get_nodes` function created an aggregation with multiple sub `date_histogram` + * aggregations for each metric aggregation. From a top down view, the entire aggregations look liked: + * `terms` agg -> [`date_histogram` -> metric agg]x6 + * However, this is very inefficient, as each `date_histogram` will create a new set of search buckets + * unnecessarily and users will hit the `search.max_buckets` ceiling sooner. + * + * To solve this, we need to create a single `date_histogram`, then perform each metric agg as a sub aggregations + * of this single `date_histogram`. This is not straightforward though. The logic to build these aggregations + * is shared code between the rest of the monitoring code base and is not easily updated to accommodate the + * changes from above. To circumvent that, this function will adjust the existing aggregation names to work + * for a single date_histogram. + * + * @param string prefix - This is the aggregation name prefix where the rest of the name will be the type of aggregation + * @param object metricObj The metric aggregation itself + */ +export function convertMetricNames(prefix, metricObj) { + return Object.entries(metricObj).reduce((newObj, [key, value]) => { + const newValue = cloneDeep(value); + if (key.includes('_deriv') && newValue.derivative) { + newValue.derivative.buckets_path = `${CONVERTED_TOKEN}${prefix}__${newValue.derivative.buckets_path}`; + } + newObj[`${CONVERTED_TOKEN}${prefix}__${key}`] = newValue; + return newObj; + }, {}); +} + +/** + * Building upon the comment for `convertMetricNames`, we are dynamically changing the aggregation names to allow + * the single `date_histogram` to work properly. Unfortunately, the code that looks at the response also needs to + * understand the naming changes. And yet again, this code is shared amongst the rest of the monitoring code base. + * To circumvent this, we need to convert the changed aggregation names back to the original, expected names. + * This feels messy, but possible because we keep the original name in the converted aggregation name. + * + * @param object byDateBucketResponse - The response object from the single `date_histogram` bucket + */ +export function uncovertMetricNames(byDateBucketResponse) { + const unconverted = {}; + for (const metricName of LISTING_METRICS_NAMES) { + unconverted[metricName] = { + buckets: byDateBucketResponse.buckets.map(bucket => { + const { key_as_string, key, doc_count, ...rest } = bucket; /* eslint-disable-line camelcase */ + const metrics = Object.entries(rest).reduce((accum, [key, value]) => { + if (key.startsWith(`${CONVERTED_TOKEN}${metricName}`)) { + const name = key.split('__')[1]; + accum[name] = value; + } + return accum; + }, {}); + + return { + key_as_string, /* eslint-disable-line camelcase */ + key, + doc_count, /* eslint-disable-line camelcase */ + ...metrics, + }; + }) + }; + } + return unconverted; +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/get_metric_aggs.test.js.snap b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/get_metric_aggs.test.js.snap index 8b5b445b93111..df5db49cd40c6 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/get_metric_aggs.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/get_metric_aggs.test.js.snap @@ -2,44 +2,26 @@ exports[`get metric aggs should create aggregations for "basic" metrics 1`] = ` Object { - "node_cpu_utilization": Object { - "aggs": Object { - "metric": Object { - "max": Object { - "field": "node_stats.process.cpu.percent", - }, - }, - "metric_deriv": Object { - "derivative": Object { - "buckets_path": "metric", - "unit": "1s", - }, - }, + "odh_node_cpu_utilization__metric": Object { + "max": Object { + "field": "node_stats.process.cpu.percent", }, - "date_histogram": Object { - "field": "timestamp", - "fixed_interval": "30s", - "min_doc_count": 1, + }, + "odh_node_cpu_utilization__metric_deriv": Object { + "derivative": Object { + "buckets_path": "odh_node_cpu_utilization__metric", + "unit": "1s", }, }, - "node_jvm_mem_percent": Object { - "aggs": Object { - "metric": Object { - "max": Object { - "field": "node_stats.jvm.mem.heap_used_percent", - }, - }, - "metric_deriv": Object { - "derivative": Object { - "buckets_path": "metric", - "unit": "1s", - }, - }, + "odh_node_jvm_mem_percent__metric": Object { + "max": Object { + "field": "node_stats.jvm.mem.heap_used_percent", }, - "date_histogram": Object { - "field": "timestamp", - "fixed_interval": "30s", - "min_doc_count": 1, + }, + "odh_node_jvm_mem_percent__metric_deriv": Object { + "derivative": Object { + "buckets_path": "odh_node_jvm_mem_percent__metric", + "unit": "1s", }, }, } @@ -47,70 +29,52 @@ Object { exports[`get metric aggs should incorporate a metric custom aggs 1`] = ` Object { - "node_index_latency": Object { - "aggs": Object { - "event_time_in_millis": Object { - "max": Object { - "field": "node_stats.indices.indexing.index_time_in_millis", - }, - }, - "event_time_in_millis_deriv": Object { - "derivative": Object { - "buckets_path": "event_time_in_millis", - "gap_policy": "skip", - "unit": "1s", - }, - }, - "event_total": Object { - "max": Object { - "field": "node_stats.indices.indexing.index_total", - }, - }, - "event_total_deriv": Object { - "derivative": Object { - "buckets_path": "event_total", - "gap_policy": "skip", - "unit": "1s", - }, - }, + "odh_node_index_latency__event_time_in_millis": Object { + "max": Object { + "field": "node_stats.indices.indexing.index_time_in_millis", + }, + }, + "odh_node_index_latency__event_time_in_millis_deriv": Object { + "derivative": Object { + "buckets_path": "odh_node_index_latency__event_time_in_millis", + "gap_policy": "skip", + "unit": "1s", + }, + }, + "odh_node_index_latency__event_total": Object { + "max": Object { + "field": "node_stats.indices.indexing.index_total", }, - "date_histogram": Object { - "field": "timestamp", - "fixed_interval": "30s", - "min_doc_count": 1, + }, + "odh_node_index_latency__event_total_deriv": Object { + "derivative": Object { + "buckets_path": "odh_node_index_latency__event_total", + "gap_policy": "skip", + "unit": "1s", + }, + }, + "odh_node_query_latency__event_time_in_millis": Object { + "max": Object { + "field": "node_stats.indices.search.query_time_in_millis", }, }, - "node_query_latency": Object { - "aggs": Object { - "event_time_in_millis": Object { - "max": Object { - "field": "node_stats.indices.search.query_time_in_millis", - }, - }, - "event_time_in_millis_deriv": Object { - "derivative": Object { - "buckets_path": "event_time_in_millis", - "gap_policy": "skip", - "unit": "1s", - }, - }, - "event_total": Object { - "max": Object { - "field": "node_stats.indices.search.query_total", - }, - }, - "event_total_deriv": Object { - "derivative": Object { - "buckets_path": "event_total", - "gap_policy": "skip", - "unit": "1s", - }, - }, + "odh_node_query_latency__event_time_in_millis_deriv": Object { + "derivative": Object { + "buckets_path": "odh_node_query_latency__event_time_in_millis", + "gap_policy": "skip", + "unit": "1s", }, - "date_histogram": Object { - "field": "timestamp", - "fixed_interval": "30s", - "min_doc_count": 1, + }, + "odh_node_query_latency__event_total": Object { + "max": Object { + "field": "node_stats.indices.search.query_total", + }, + }, + "odh_node_query_latency__event_total_deriv": Object { + "derivative": Object { + "buckets_path": "odh_node_query_latency__event_total", + "gap_policy": "skip", + "unit": "1s", }, }, } diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap index c5fc350717a8b..1ab99c55b2beb 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap @@ -46,10 +46,10 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 37.3891333, - "maxVal": 41.948963750000004, - "minVal": 11.02918065, - "slope": 1, + "lastVal": 14.58673435, + "maxVal": 80.3369142, + "minVal": 11.0291808, + "slope": -1, }, }, "node_cgroup_throttled": Object { @@ -66,8 +66,8 @@ Array [ "units": "ns", }, "summary": Object { - "lastVal": 123012140, - "maxVal": 30063709491, + "lastVal": 0, + "maxVal": 23311083802, "minVal": 0, "slope": -1, }, @@ -85,10 +85,10 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 3, - "maxVal": 4, + "lastVal": 1, + "maxVal": 7, "minVal": 1, - "slope": 1, + "slope": -1, }, }, "node_free_space": Object { @@ -104,9 +104,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 3141324800, + "lastVal": 3140956160, "maxVal": 3195629568, - "minVal": 3141324800, + "minVal": 3140956160, "slope": -1, }, }, @@ -124,10 +124,10 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 40, + "lastVal": 42, "maxVal": 52, - "minVal": 25, - "slope": -1, + "minVal": 24, + "slope": 1, }, }, "node_load_average": Object { @@ -144,9 +144,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 1.0400390625, - "maxVal": 2.439453125, - "minVal": 1.0400390625, + "lastVal": 1.0302734375, + "maxVal": 3.0703125, + "minVal": 1.0302734375, "slope": -1, }, }, @@ -194,8 +194,8 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 0, - "maxVal": 8, + "lastVal": 1, + "maxVal": 39, "minVal": 0, "slope": -1, }, @@ -213,9 +213,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 3141402624, - "maxVal": 3148406784, - "minVal": 3141402624, + "lastVal": 3141033984, + "maxVal": 3162230784, + "minVal": 3141033984, "slope": -1, }, }, @@ -253,9 +253,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 1.0400390625, - "maxVal": 2.439453125, - "minVal": 1.0400390625, + "lastVal": 1.0302734375, + "maxVal": 3.0703125, + "minVal": 1.0302734375, "slope": -1, }, }, @@ -325,10 +325,10 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 37.3891333, - "maxVal": 41.948963750000004, - "minVal": 11.02918065, - "slope": 1, + "lastVal": 14.58673435, + "maxVal": 80.3369142, + "minVal": 11.0291808, + "slope": -1, }, }, "node_cgroup_throttled": Object { @@ -345,8 +345,8 @@ Array [ "units": "ns", }, "summary": Object { - "lastVal": 123012140, - "maxVal": 30063709491, + "lastVal": 0, + "maxVal": 23311083802, "minVal": 0, "slope": -1, }, @@ -364,10 +364,10 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 3, - "maxVal": 4, + "lastVal": 1, + "maxVal": 7, "minVal": 1, - "slope": 1, + "slope": -1, }, }, "node_free_space": Object { @@ -383,9 +383,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 3141324800, + "lastVal": 3140956160, "maxVal": 3195629568, - "minVal": 3141324800, + "minVal": 3140956160, "slope": -1, }, }, @@ -403,10 +403,10 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 40, + "lastVal": 42, "maxVal": 52, - "minVal": 25, - "slope": -1, + "minVal": 24, + "slope": 1, }, }, "node_load_average": Object { @@ -423,9 +423,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 1.0400390625, - "maxVal": 2.439453125, - "minVal": 1.0400390625, + "lastVal": 1.0302734375, + "maxVal": 3.0703125, + "minVal": 1.0302734375, "slope": -1, }, }, @@ -473,8 +473,8 @@ Array [ "units": "%", }, "summary": Object { - "lastVal": 0, - "maxVal": 8, + "lastVal": 1, + "maxVal": 39, "minVal": 0, "slope": -1, }, @@ -492,9 +492,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 3141402624, - "maxVal": 3148406784, - "minVal": 3141402624, + "lastVal": 3141033984, + "maxVal": 3162230784, + "minVal": 3141033984, "slope": -1, }, }, @@ -532,9 +532,9 @@ Array [ "units": "", }, "summary": Object { - "lastVal": 1.0400390625, - "maxVal": 2.439453125, - "minVal": 1.0400390625, + "lastVal": 1.0302734375, + "maxVal": 3.0703125, + "minVal": 1.0302734375, "slope": -1, }, }, diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/fixtures/cluster_data.json b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/fixtures/cluster_data.json index 27a8aaa18eab7..b8abe525b3fa6 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/fixtures/cluster_data.json +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/fixtures/cluster_data.json @@ -1,2358 +1,2912 @@ { "nodeStats": { "hits": { - "total": 26, - "hits": [ - { - "_source": { - "source_node": { - "uuid": "_x_V2YzPQU-a9KRRBxUxZQ", - "host": "127.0.0.1", - "transport_address": "127.0.0.1:9300", - "ip": "127.0.0.1", - "name": "hello01", - "timestamp": "2018-02-13T19:26:08.442Z" - } + "hits": [{ + "_source": { + "source_node": { + "host": "127.0.0.1", + "ip": "127.0.0.1", + "name": "hello01", + "timestamp": "2018-02-13T19:26:18.443Z", + "transport_address": "127.0.0.1:9300", + "uuid": "_x_V2YzPQU-a9KRRBxUxZQ" } - }, - { - "_source": { - "source_node": { - "uuid": "DAiX7fFjS3Wii7g2HYKrOg", - "host": "127.0.0.1", - "transport_address": "127.0.0.1:9301", - "ip": "127.0.0.1", - "name": "hello02", - "timestamp": "2018-02-13T19:26:07.288Z" - } + } + }, { + "_source": { + "source_node": { + "host": "127.0.0.1", + "ip": "127.0.0.1", + "name": "hello02", + "timestamp": "2018-02-13T19:26:17.290Z", + "transport_address": "127.0.0.1:9301", + "uuid": "DAiX7fFjS3Wii7g2HYKrOg" } } - ] + }] }, "aggregations": { "nodes": { - "buckets": [ - { - "key": "DAiX7fFjS3Wii7g2HYKrOg", - "node_cgroup_throttled": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - } - ] - }, - "node_free_space": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 3148406784 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 3146469376 - }, - "metric_deriv": { - "value": -1937408, - "normalized_value": -193740.8 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 3146575872 - }, - "metric_deriv": { - "value": 106496, - "normalized_value": 10649.6 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 3143720960 - }, - "metric_deriv": { - "value": -2854912, - "normalized_value": -285491.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 3145134080 - }, - "metric_deriv": { - "value": 1413120, - "normalized_value": 141312 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 3145375744 - }, - "metric_deriv": { - "value": 241664, - "normalized_value": 24166.4 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 3145003008 - }, - "metric_deriv": { - "value": -372736, - "normalized_value": -37273.6 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 3144859648 - }, - "metric_deriv": { - "value": -143360, - "normalized_value": -14336 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 3145433088 - }, - "metric_deriv": { - "value": 573440, - "normalized_value": 57344 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 3144224768 - }, - "metric_deriv": { - "value": -1208320, - "normalized_value": -120832 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 3143766016 - }, - "metric_deriv": { - "value": -458752, - "normalized_value": -45875.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 3141775360 - }, - "metric_deriv": { - "value": -1990656, - "normalized_value": -199065.6 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 3141402624 - }, - "metric_deriv": { - "value": -372736, - "normalized_value": -37273.6 - } - } - ] - }, - "node_load_average": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 2.1796875 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": -0.1796875, - "normalized_value": -0.01796875 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 2.140625 - }, - "metric_deriv": { - "value": 0.140625, - "normalized_value": 0.0140625 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 2.349609375 - }, - "metric_deriv": { - "value": 0.208984375, - "normalized_value": 0.0208984375 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 2.439453125 - }, - "metric_deriv": { - "value": 0.08984375, - "normalized_value": 0.008984375 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 2.140625 - }, - "metric_deriv": { - "value": -0.298828125, - "normalized_value": -0.0298828125 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 1.8798828125 - }, - "metric_deriv": { - "value": -0.2607421875, - "normalized_value": -0.02607421875 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 1.669921875 - }, - "metric_deriv": { - "value": -0.2099609375, - "normalized_value": -0.02099609375 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 1.41015625 - }, - "metric_deriv": { - "value": -0.259765625, - "normalized_value": -0.0259765625 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 1.4296875 - }, - "metric_deriv": { - "value": 0.01953125, - "normalized_value": 0.001953125 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 1.3701171875 - }, - "metric_deriv": { - "value": -0.0595703125, - "normalized_value": -0.00595703125 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 1.23046875 - }, - "metric_deriv": { - "value": -0.1396484375, - "normalized_value": -0.01396484375 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 1.0400390625 - }, - "metric_deriv": { - "value": -0.1904296875, - "normalized_value": -0.01904296875 - } - } - ] - }, - "node_cgroup_quota": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 941646282031 - }, - "periods": { - "value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 954414302394 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 12768020363, - "normalized_value": 12768020360 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 967670642606 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 13256340212, - "normalized_value": 13256340209 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 981871818195 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 14201175589, - "normalized_value": 14201175586 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 992156482002 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 10284663807, - "normalized_value": 10284663804 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 995592458443 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 3435976441, - "normalized_value": 3435976438 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 998276280550 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 2683822107, - "normalized_value": 2683822104 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 1000229207942 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 1952927392, - "normalized_value": 1952927389 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 1001857827909 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 1628619967, - "normalized_value": 1628619964 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 1009162784819 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 7304956910, - "normalized_value": 7304956907 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 1014294280510 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 5131495691, - "normalized_value": 5131495688 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 1022201061267 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 7906780757, - "normalized_value": 7906780754 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "quota": { - "value": -1 - }, - "usage": { - "value": 1024316448990 - }, - "periods": { - "value": 0 - }, - "usage_deriv": { - "value": 2115387723, - "normalized_value": 2115387720 - }, - "periods_deriv": { - "value": 0, - "normalized_value": 0 - } - } - ] - }, - "node_cpu_utilization": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 1 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": 1, - "normalized_value": 0.1 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 5 - }, - "metric_deriv": { - "value": 3, - "normalized_value": 0.3 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 3 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 8 - }, - "metric_deriv": { - "value": 5, - "normalized_value": 0.5 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 3 - }, - "metric_deriv": { - "value": -5, - "normalized_value": -0.5 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 4 - }, - "metric_deriv": { - "value": 1, - "normalized_value": 0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 1 - }, - "metric_deriv": { - "value": -3, - "normalized_value": -0.3 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 1 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": 1, - "normalized_value": 0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 4 - }, - "metric_deriv": { - "value": 2, - "normalized_value": 0.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 0 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - } - ] - }, - "node_jvm_mem_percent": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 22 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 22 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 34 - }, - "metric_deriv": { - "value": 12, - "normalized_value": 1.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 33 - }, - "metric_deriv": { - "value": -1, - "normalized_value": -0.1 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 39 - }, - "metric_deriv": { - "value": 6, - "normalized_value": 0.6 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 24 - }, - "metric_deriv": { - "value": -15, - "normalized_value": -1.5 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 24 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 23 - }, - "metric_deriv": { - "value": -1, - "normalized_value": -0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 41 - }, - "metric_deriv": { - "value": 18, - "normalized_value": 1.8 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 41 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 29 - }, - "metric_deriv": { - "value": -12, - "normalized_value": -1.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 43 - }, - "metric_deriv": { - "value": 14, - "normalized_value": 1.4 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 41 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - } - ] - } - }, - { - "key": "_x_V2YzPQU-a9KRRBxUxZQ", - "node_cgroup_throttled": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 30063709491 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 30063709491 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 31249323660 - }, - "metric_deriv": { - "value": 1185614169, - "normalized_value": 118561416.9 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 31273310919 - }, - "metric_deriv": { - "value": 23987259, - "normalized_value": 2398725.9 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 32079981364 - }, - "metric_deriv": { - "value": 806670445, - "normalized_value": 80667044.5 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 33670032364 - }, - "metric_deriv": { - "value": 1590051000, - "normalized_value": 159005100 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 33670032364 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 33670032364 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 34164622899 - }, - "metric_deriv": { - "value": 494590535, - "normalized_value": 49459053.5 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 34164622899 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 34564621879 - }, - "metric_deriv": { - "value": 399998980, - "normalized_value": 39999898 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 36485560063 - }, - "metric_deriv": { - "value": 1920938184, - "normalized_value": 192093818.4 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 37715681463 - }, - "metric_deriv": { - "value": 1230121400, - "normalized_value": 123012140 - } - } - ] - }, - "node_free_space": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 3148038144 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 3146174464 - }, - "metric_deriv": { - "value": -1863680, - "normalized_value": -186368 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 3195629568 - }, - "metric_deriv": { - "value": 49455104, - "normalized_value": 4945510.4 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 3143540736 - }, - "metric_deriv": { - "value": -52088832, - "normalized_value": -5208883.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 3144876032 - }, - "metric_deriv": { - "value": 1335296, - "normalized_value": 133529.6 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 3145293824 - }, - "metric_deriv": { - "value": 417792, - "normalized_value": 41779.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 3144929280 - }, - "metric_deriv": { - "value": -364544, - "normalized_value": -36454.4 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 3144785920 - }, - "metric_deriv": { - "value": -143360, - "normalized_value": -14336 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 3145347072 - }, - "metric_deriv": { - "value": 561152, - "normalized_value": 56115.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 3144146944 - }, - "metric_deriv": { - "value": -1200128, - "normalized_value": -120012.8 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 3143688192 - }, - "metric_deriv": { - "value": -458752, - "normalized_value": -45875.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 3141689344 - }, - "metric_deriv": { - "value": -1998848, - "normalized_value": -199884.8 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 3141324800 - }, - "metric_deriv": { - "value": -364544, - "normalized_value": -36454.4 - } - } - ] - }, - "node_load_average": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 2.1796875 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": -0.1796875, - "normalized_value": -0.01796875 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 2.140625 - }, - "metric_deriv": { - "value": 0.140625, - "normalized_value": 0.0140625 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 2.349609375 - }, - "metric_deriv": { - "value": 0.208984375, - "normalized_value": 0.0208984375 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 2.439453125 - }, - "metric_deriv": { - "value": 0.08984375, - "normalized_value": 0.008984375 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 2.140625 - }, - "metric_deriv": { - "value": -0.298828125, - "normalized_value": -0.0298828125 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 1.8798828125 - }, - "metric_deriv": { - "value": -0.2607421875, - "normalized_value": -0.02607421875 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 1.669921875 - }, - "metric_deriv": { - "value": -0.2099609375, - "normalized_value": -0.02099609375 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 1.41015625 - }, - "metric_deriv": { - "value": -0.259765625, - "normalized_value": -0.0259765625 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 1.4296875 - }, - "metric_deriv": { - "value": 0.01953125, - "normalized_value": 0.001953125 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 1.3701171875 - }, - "metric_deriv": { - "value": -0.0595703125, - "normalized_value": -0.00595703125 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 1.23046875 - }, - "metric_deriv": { - "value": -0.1396484375, - "normalized_value": -0.01396484375 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 1.0400390625 - }, - "metric_deriv": { - "value": -0.1904296875, - "normalized_value": -0.01904296875 - } - } - ] - }, - "node_cgroup_quota": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 85486240745 - }, - "periods": { - "value": 518 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 85706824361 - }, - "periods": { - "value": 528 - }, - "usage_deriv": { - "value": 220583616, - "normalized_value": 220583613 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 86545803639 - }, - "periods": { - "value": 538 - }, - "usage_deriv": { - "value": 838979278, - "normalized_value": 838979275 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 87109993704 - }, - "periods": { - "value": 548 - }, - "usage_deriv": { - "value": 564190065, - "normalized_value": 564190062 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 87940139081 - }, - "periods": { - "value": 558 - }, - "usage_deriv": { - "value": 830145377, - "normalized_value": 830145374 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 88333595089 - }, - "periods": { - "value": 568 - }, - "usage_deriv": { - "value": 393456008, - "normalized_value": 393456005 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 88712751708 - }, - "periods": { - "value": 578 - }, - "usage_deriv": { - "value": 379156619, - "normalized_value": 379156616 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 89174655252 - }, - "periods": { - "value": 588 - }, - "usage_deriv": { - "value": 461903544, - "normalized_value": 461903541 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 89535735322 - }, - "periods": { - "value": 598 - }, - "usage_deriv": { - "value": 361080070, - "normalized_value": 361080067 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 89818710046 - }, - "periods": { - "value": 608 - }, - "usage_deriv": { - "value": 282974724, - "normalized_value": 282974721 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 90586195724 - }, - "periods": { - "value": 618 - }, - "usage_deriv": { - "value": 767485678, - "normalized_value": 767485675 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 91244550402 - }, - "periods": { - "value": 628 - }, - "usage_deriv": { - "value": 658354678, - "normalized_value": 658354675 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "quota": { - "value": 200000 - }, - "usage": { - "value": 91992333071 - }, - "periods": { - "value": 638 - }, - "usage_deriv": { - "value": 747782669, - "normalized_value": 747782666 - }, - "periods_deriv": { - "value": 10, - "normalized_value": 10 - } - } - ] - }, - "node_cpu_utilization": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 1 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 1 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 4 - }, - "metric_deriv": { - "value": 3, - "normalized_value": 0.3 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 4 - }, - "metric_deriv": { - "value": 2, - "normalized_value": 0.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 1 - }, - "metric_deriv": { - "value": -1, - "normalized_value": -0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 2 - }, - "metric_deriv": { - "value": 1, - "normalized_value": 0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 1 - }, - "metric_deriv": { - "value": -1, - "normalized_value": -0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 1 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 3 - }, - "metric_deriv": { - "value": 2, - "normalized_value": 0.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 3 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 3 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - } - ] - }, - "node_jvm_mem_percent": { - "buckets": [ - { - "key_as_string": "2018-02-13T19:24:00.000Z", - "key": 1518549840000, - "doc_count": 1, - "metric": { - "value": 51 - } - }, - { - "key_as_string": "2018-02-13T19:24:10.000Z", - "key": 1518549850000, - "doc_count": 1, - "metric": { - "value": 51 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:24:20.000Z", - "key": 1518549860000, - "doc_count": 1, - "metric": { - "value": 37 - }, - "metric_deriv": { - "value": -14, - "normalized_value": -1.4 - } - }, - { - "key_as_string": "2018-02-13T19:24:30.000Z", - "key": 1518549870000, - "doc_count": 1, - "metric": { - "value": 39 - }, - "metric_deriv": { - "value": 2, - "normalized_value": 0.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:40.000Z", - "key": 1518549880000, - "doc_count": 1, - "metric": { - "value": 37 - }, - "metric_deriv": { - "value": -2, - "normalized_value": -0.2 - } - }, - { - "key_as_string": "2018-02-13T19:24:50.000Z", - "key": 1518549890000, - "doc_count": 1, - "metric": { - "value": 52 - }, - "metric_deriv": { - "value": 15, - "normalized_value": 1.5 - } - }, - { - "key_as_string": "2018-02-13T19:25:00.000Z", - "key": 1518549900000, - "doc_count": 1, - "metric": { - "value": 52 - }, - "metric_deriv": { - "value": 0, - "normalized_value": 0 - } - }, - { - "key_as_string": "2018-02-13T19:25:10.000Z", - "key": 1518549910000, - "doc_count": 1, - "metric": { - "value": 31 - }, - "metric_deriv": { - "value": -21, - "normalized_value": -2.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:20.000Z", - "key": 1518549920000, - "doc_count": 1, - "metric": { - "value": 46 - }, - "metric_deriv": { - "value": 15, - "normalized_value": 1.5 - } - }, - { - "key_as_string": "2018-02-13T19:25:30.000Z", - "key": 1518549930000, - "doc_count": 1, - "metric": { - "value": 47 - }, - "metric_deriv": { - "value": 1, - "normalized_value": 0.1 - } - }, - { - "key_as_string": "2018-02-13T19:25:40.000Z", - "key": 1518549940000, - "doc_count": 1, - "metric": { - "value": 25 - }, - "metric_deriv": { - "value": -22, - "normalized_value": -2.2 - } - }, - { - "key_as_string": "2018-02-13T19:25:50.000Z", - "key": 1518549950000, - "doc_count": 1, - "metric": { - "value": 39 - }, - "metric_deriv": { - "value": 14, - "normalized_value": 1.4 - } - }, - { - "key_as_string": "2018-02-13T19:26:00.000Z", - "key": 1518549960000, - "doc_count": 1, - "metric": { - "value": 40 - }, - "metric_deriv": { - "value": 1, - "normalized_value": 0.1 - } - } - ] - } + "buckets": [{ + "key": "_x_V2YzPQU-a9KRRBxUxZQ", + "by_date": { + "buckets": [{ + "key_as_string": "2018-02-13T19:22:10.000Z", + "key": 1518549730000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.5 + }, + "odh_node_cgroup_quota__periods": { + "value": 408 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 79244153296 + }, + "odh_node_free_space__metric": { + "value": 3164602368 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 24 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 23311083802 + } + }, { + "key_as_string": "2018-02-13T19:22:20.000Z", + "key": 1518549740000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.8896484375 + }, + "odh_node_cgroup_quota__periods": { + "value": 418 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 80850891580 + }, + "odh_node_free_space__metric": { + "value": 3162071040 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 36 + }, + "odh_node_cpu_utilization__metric": { + "value": 7 + }, + "odh_node_cgroup_throttled__metric": { + "value": 25723239654 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 1606738284, + "normalized_value": 160673828.4 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 2412155852, + "normalized_value": 241215585.2 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 5, + "normalized_value": 0.5 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.3896484375, + "normalized_value": 0.03896484375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 12, + "normalized_value": 1.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -2531328, + "normalized_value": -253132.8 + } + }, { + "key_as_string": "2018-02-13T19:22:30.000Z", + "key": 1518549750000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.220703125 + }, + "odh_node_cgroup_quota__periods": { + "value": 428 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 81159355284 + }, + "odh_node_free_space__metric": { + "value": 3160485888 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 37 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 25723239654 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 308463704, + "normalized_value": 30846370.4 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -6, + "normalized_value": -0.6 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.3310546875, + "normalized_value": 0.03310546875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -1585152, + "normalized_value": -158515.2 + } + }, { + "key_as_string": "2018-02-13T19:22:40.000Z", + "key": 1518549760000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 3.0703125 + }, + "odh_node_cgroup_quota__periods": { + "value": 438 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 82000164557 + }, + "odh_node_free_space__metric": { + "value": 3159011328 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 42 + }, + "odh_node_cpu_utilization__metric": { + "value": 4 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 840809273, + "normalized_value": 84080927.3 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 2214939241, + "normalized_value": 221493924.1 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 3, + "normalized_value": 0.3 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.849609375, + "normalized_value": 0.0849609375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 5, + "normalized_value": 0.5 + }, + "odh_node_free_space__metric_deriv": { + "value": -1474560, + "normalized_value": -147456 + } + }, { + "key_as_string": "2018-02-13T19:22:50.000Z", + "key": 1518549770000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.900390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 448 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 82269699529 + }, + "odh_node_free_space__metric": { + "value": 3158396928 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 28 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 269534972, + "normalized_value": 26953497.2 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -3, + "normalized_value": -0.3 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.169921875, + "normalized_value": -0.0169921875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -14, + "normalized_value": -1.4 + }, + "odh_node_free_space__metric_deriv": { + "value": -614400, + "normalized_value": -61440 + } + }, { + "key_as_string": "2018-02-13T19:23:00.000Z", + "key": 1518549780000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.609375 + }, + "odh_node_cgroup_quota__periods": { + "value": 458 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 82573932809 + }, + "odh_node_free_space__metric": { + "value": 3156910080 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 28 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 304233280, + "normalized_value": 30423328 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.291015625, + "normalized_value": -0.0291015625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -1486848, + "normalized_value": -148684.8 + } + }, { + "key_as_string": "2018-02-13T19:23:10.000Z", + "key": 1518549790000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.51953125 + }, + "odh_node_cgroup_quota__periods": { + "value": 468 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 82834253350 + }, + "odh_node_free_space__metric": { + "value": 3155001344 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 30 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 260320541, + "normalized_value": 26032054.1 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.08984375, + "normalized_value": -0.008984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -1908736, + "normalized_value": -190873.6 + } + }, { + "key_as_string": "2018-02-13T19:23:20.000Z", + "key": 1518549800000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.359375 + }, + "odh_node_cgroup_quota__periods": { + "value": 478 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 83226494668 + }, + "odh_node_free_space__metric": { + "value": 3153948672 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 42 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 392241318, + "normalized_value": 39224131.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.16015625, + "normalized_value": -0.016015625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 12, + "normalized_value": 1.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -1052672, + "normalized_value": -105267.2 + } + }, { + "key_as_string": "2018-02-13T19:23:30.000Z", + "key": 1518549810000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.150390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 488 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 83491765374 + }, + "odh_node_free_space__metric": { + "value": 3152474112 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 43 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 265270706, + "normalized_value": 26527070.6 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.208984375, + "normalized_value": -0.0208984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -1474560, + "normalized_value": -147456 + } + }, { + "key_as_string": "2018-02-13T19:23:40.000Z", + "key": 1518549820000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.0390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 498 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 84254865150 + }, + "odh_node_free_space__metric": { + "value": 3150368768 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 37 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 27938178895 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 763099776, + "normalized_value": 76309977.6 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.111328125, + "normalized_value": -0.0111328125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -6, + "normalized_value": -0.6 + }, + "odh_node_free_space__metric_deriv": { + "value": -2105344, + "normalized_value": -210534.4 + } + }, { + "key_as_string": "2018-02-13T19:23:50.000Z", + "key": 1518549830000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.9599609375 + }, + "odh_node_cgroup_quota__periods": { + "value": 508 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 85201025483 + }, + "odh_node_free_space__metric": { + "value": 3149758464 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 51 + }, + "odh_node_cpu_utilization__metric": { + "value": 4 + }, + "odh_node_cgroup_throttled__metric": { + "value": 30063709491 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 946160333, + "normalized_value": 94616033.3 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 2125530596, + "normalized_value": 212553059.6 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.0791015625, + "normalized_value": -0.00791015625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 14, + "normalized_value": 1.4 + }, + "odh_node_free_space__metric_deriv": { + "value": -610304, + "normalized_value": -61030.4 + } + }, { + "key_as_string": "2018-02-13T19:24:00.000Z", + "key": 1518549840000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.1796875 + }, + "odh_node_cgroup_quota__periods": { + "value": 518 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 85486240745 + }, + "odh_node_free_space__metric": { + "value": 3148038144 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 51 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 30063709491 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 285215262, + "normalized_value": 28521526.2 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -3, + "normalized_value": -0.3 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.2197265625, + "normalized_value": 0.02197265625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -1720320, + "normalized_value": -172032 + } + }, { + "key_as_string": "2018-02-13T19:24:10.000Z", + "key": 1518549850000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2 + }, + "odh_node_cgroup_quota__periods": { + "value": 528 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 85706824361 + }, + "odh_node_free_space__metric": { + "value": 3146174464 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 51 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 30063709491 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 220583616, + "normalized_value": 22058361.6 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.1796875, + "normalized_value": -0.01796875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -1863680, + "normalized_value": -186368 + } + }, { + "key_as_string": "2018-02-13T19:24:20.000Z", + "key": 1518549860000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.140625 + }, + "odh_node_cgroup_quota__periods": { + "value": 538 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 86545803639 + }, + "odh_node_free_space__metric": { + "value": 3195629568 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 37 + }, + "odh_node_cpu_utilization__metric": { + "value": 4 + }, + "odh_node_cgroup_throttled__metric": { + "value": 31249323660 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 838979278, + "normalized_value": 83897927.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 1185614169, + "normalized_value": 118561416.9 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 3, + "normalized_value": 0.3 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.140625, + "normalized_value": 0.0140625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -14, + "normalized_value": -1.4 + }, + "odh_node_free_space__metric_deriv": { + "value": 49455104, + "normalized_value": 4945510.4 + } + }, { + "key_as_string": "2018-02-13T19:24:30.000Z", + "key": 1518549870000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.349609375 + }, + "odh_node_cgroup_quota__periods": { + "value": 548 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 87109993704 + }, + "odh_node_free_space__metric": { + "value": 3143540736 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 39 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 31273310919 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 564190065, + "normalized_value": 56419006.5 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 23987259, + "normalized_value": 2398725.9 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.208984375, + "normalized_value": 0.0208984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -52088832, + "normalized_value": -5208883.2 + } + }, { + "key_as_string": "2018-02-13T19:24:40.000Z", + "key": 1518549880000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.439453125 + }, + "odh_node_cgroup_quota__periods": { + "value": 558 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 87940139081 + }, + "odh_node_free_space__metric": { + "value": 3144876032 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 37 + }, + "odh_node_cpu_utilization__metric": { + "value": 4 + }, + "odh_node_cgroup_throttled__metric": { + "value": 32079981364 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 830145377, + "normalized_value": 83014537.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 806670445, + "normalized_value": 80667044.5 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.08984375, + "normalized_value": 0.008984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": 1335296, + "normalized_value": 133529.6 + } + }, { + "key_as_string": "2018-02-13T19:24:50.000Z", + "key": 1518549890000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.140625 + }, + "odh_node_cgroup_quota__periods": { + "value": 568 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 88333595089 + }, + "odh_node_free_space__metric": { + "value": 3145293824 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 52 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 33670032364 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 393456008, + "normalized_value": 39345600.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 1590051000, + "normalized_value": 159005100 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.298828125, + "normalized_value": -0.0298828125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 15, + "normalized_value": 1.5 + }, + "odh_node_free_space__metric_deriv": { + "value": 417792, + "normalized_value": 41779.2 + } + }, { + "key_as_string": "2018-02-13T19:25:00.000Z", + "key": 1518549900000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.8798828125 + }, + "odh_node_cgroup_quota__periods": { + "value": 578 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 88712751708 + }, + "odh_node_free_space__metric": { + "value": 3144929280 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 52 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 33670032364 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 379156619, + "normalized_value": 37915661.9 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -1, + "normalized_value": -0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.2607421875, + "normalized_value": -0.02607421875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -364544, + "normalized_value": -36454.4 + } + }, { + "key_as_string": "2018-02-13T19:25:10.000Z", + "key": 1518549910000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.669921875 + }, + "odh_node_cgroup_quota__periods": { + "value": 588 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 89174655252 + }, + "odh_node_free_space__metric": { + "value": 3144785920 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 31 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 33670032364 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 461903544, + "normalized_value": 46190354.4 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.2099609375, + "normalized_value": -0.02099609375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -21, + "normalized_value": -2.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -143360, + "normalized_value": -14336 + } + }, { + "key_as_string": "2018-02-13T19:25:20.000Z", + "key": 1518549920000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.41015625 + }, + "odh_node_cgroup_quota__periods": { + "value": 598 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 89535735322 + }, + "odh_node_free_space__metric": { + "value": 3145347072 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 46 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 34164622899 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 361080070, + "normalized_value": 36108007 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 494590535, + "normalized_value": 49459053.5 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -1, + "normalized_value": -0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.259765625, + "normalized_value": -0.0259765625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 15, + "normalized_value": 1.5 + }, + "odh_node_free_space__metric_deriv": { + "value": 561152, + "normalized_value": 56115.2 + } + }, { + "key_as_string": "2018-02-13T19:25:30.000Z", + "key": 1518549930000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.4296875 + }, + "odh_node_cgroup_quota__periods": { + "value": 608 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 89818710046 + }, + "odh_node_free_space__metric": { + "value": 3144146944 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 47 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 34164622899 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 282974724, + "normalized_value": 28297472.4 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.01953125, + "normalized_value": 0.001953125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -1200128, + "normalized_value": -120012.8 + } + }, { + "key_as_string": "2018-02-13T19:25:40.000Z", + "key": 1518549940000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.3701171875 + }, + "odh_node_cgroup_quota__periods": { + "value": 618 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 90586195724 + }, + "odh_node_free_space__metric": { + "value": 3143688192 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 25 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 34564621879 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 767485678, + "normalized_value": 76748567.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 399998980, + "normalized_value": 39999898 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.0595703125, + "normalized_value": -0.00595703125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -22, + "normalized_value": -2.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -458752, + "normalized_value": -45875.2 + } + }, { + "key_as_string": "2018-02-13T19:25:50.000Z", + "key": 1518549950000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.23046875 + }, + "odh_node_cgroup_quota__periods": { + "value": 628 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 91244550402 + }, + "odh_node_free_space__metric": { + "value": 3141689344 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 39 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 36485560063 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 658354678, + "normalized_value": 65835467.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 1920938184, + "normalized_value": 192093818.4 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.1396484375, + "normalized_value": -0.01396484375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 14, + "normalized_value": 1.4 + }, + "odh_node_free_space__metric_deriv": { + "value": -1998848, + "normalized_value": -199884.8 + } + }, { + "key_as_string": "2018-02-13T19:26:00.000Z", + "key": 1518549960000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.0400390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 638 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 91992333071 + }, + "odh_node_free_space__metric": { + "value": 3141324800 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 40 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 37715681463 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 747782669, + "normalized_value": 74778266.9 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 1230121400, + "normalized_value": 123012140 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.1904296875, + "normalized_value": -0.01904296875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -364544, + "normalized_value": -36454.4 + } + }, { + "key_as_string": "2018-02-13T19:26:10.000Z", + "key": 1518549970000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.0302734375 + }, + "odh_node_cgroup_quota__periods": { + "value": 648 + }, + "odh_node_cgroup_quota__quota": { + "value": 200000 + }, + "odh_node_cgroup_quota__usage": { + "value": 92284067758 + }, + "odh_node_free_space__metric": { + "value": 3140956160 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 42 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 37715681463 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 291734687, + "normalized_value": 29173468.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 10, + "normalized_value": 1 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.009765625, + "normalized_value": -0.0009765625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -368640, + "normalized_value": -36864 + } + }] } - ] + }, { + "key": "DAiX7fFjS3Wii7g2HYKrOg", + "by_date": { + "buckets": [{ + "key_as_string": "2018-02-13T19:22:20.000Z", + "key": 1518549740000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.8896484375 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 810790936244 + }, + "odh_node_free_space__metric": { + "value": 3162230784 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 39 + }, + "odh_node_cpu_utilization__metric": { + "value": 39 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + } + }, { + "key_as_string": "2018-02-13T19:22:30.000Z", + "key": 1518549750000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.220703125 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 825262231049 + }, + "odh_node_free_space__metric": { + "value": 3160723456 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 42 + }, + "odh_node_cpu_utilization__metric": { + "value": 8 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 14471294805, + "normalized_value": 1447129480.5 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -31, + "normalized_value": -3.1 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.3310546875, + "normalized_value": 0.03310546875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 3, + "normalized_value": 0.3 + }, + "odh_node_free_space__metric_deriv": { + "value": -1507328, + "normalized_value": -150732.8 + } + }, { + "key_as_string": "2018-02-13T19:22:40.000Z", + "key": 1518549760000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 3.0703125 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 840142354488 + }, + "odh_node_free_space__metric": { + "value": 3159162880 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 40 + }, + "odh_node_cpu_utilization__metric": { + "value": 9 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 14880123439, + "normalized_value": 1488012343.9 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.849609375, + "normalized_value": 0.0849609375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -1560576, + "normalized_value": -156057.6 + } + }, { + "key_as_string": "2018-02-13T19:22:50.000Z", + "key": 1518549770000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.900390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 853176594348 + }, + "odh_node_free_space__metric": { + "value": 3158499328 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 28 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 13034239860, + "normalized_value": 1303423986 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -6, + "normalized_value": -0.6 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.169921875, + "normalized_value": -0.0169921875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -12, + "normalized_value": -1.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -663552, + "normalized_value": -66355.2 + } + }, { + "key_as_string": "2018-02-13T19:23:00.000Z", + "key": 1518549780000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.609375 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 865475051281 + }, + "odh_node_free_space__metric": { + "value": 3157045248 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 28 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 12298456933, + "normalized_value": 1229845693.3 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.291015625, + "normalized_value": -0.0291015625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -1454080, + "normalized_value": -145408 + } + }, { + "key_as_string": "2018-02-13T19:23:10.000Z", + "key": 1518549790000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.51953125 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 877571060666 + }, + "odh_node_free_space__metric": { + "value": 3155177472 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 26 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 12096009385, + "normalized_value": 1209600938.5 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.08984375, + "normalized_value": -0.008984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -1867776, + "normalized_value": -186777.6 + } + }, { + "key_as_string": "2018-02-13T19:23:20.000Z", + "key": 1518549800000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.359375 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 890240611377 + }, + "odh_node_free_space__metric": { + "value": 3154198528 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 40 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 12669550711, + "normalized_value": 1266955071.1 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.16015625, + "normalized_value": -0.016015625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 14, + "normalized_value": 1.4 + }, + "odh_node_free_space__metric_deriv": { + "value": -978944, + "normalized_value": -97894.4 + } + }, { + "key_as_string": "2018-02-13T19:23:30.000Z", + "key": 1518549810000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.150390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 902703393085 + }, + "odh_node_free_space__metric": { + "value": 3152666624 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 38 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 12462781708, + "normalized_value": 1246278170.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -1, + "normalized_value": -0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.208984375, + "normalized_value": -0.0208984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -1531904, + "normalized_value": -153190.4 + } + }, { + "key_as_string": "2018-02-13T19:23:40.000Z", + "key": 1518549820000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.0390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 915747539666 + }, + "odh_node_free_space__metric": { + "value": 3150692352 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 32 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 13044146581, + "normalized_value": 1304414658.1 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.111328125, + "normalized_value": -0.0111328125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -6, + "normalized_value": -0.6 + }, + "odh_node_free_space__metric_deriv": { + "value": -1974272, + "normalized_value": -197427.2 + } + }, { + "key_as_string": "2018-02-13T19:23:50.000Z", + "key": 1518549830000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.9599609375 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 928758926483 + }, + "odh_node_free_space__metric": { + "value": 3149934592 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 23 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 13011386817, + "normalized_value": 1301138681.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.0791015625, + "normalized_value": -0.00791015625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -9, + "normalized_value": -0.9 + }, + "odh_node_free_space__metric_deriv": { + "value": -757760, + "normalized_value": -75776 + } + }, { + "key_as_string": "2018-02-13T19:24:00.000Z", + "key": 1518549840000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.1796875 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 941646282031 + }, + "odh_node_free_space__metric": { + "value": 3148406784 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 22 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 12887355548, + "normalized_value": 1288735554.8 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.2197265625, + "normalized_value": 0.02197265625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -1, + "normalized_value": -0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -1527808, + "normalized_value": -152780.8 + } + }, { + "key_as_string": "2018-02-13T19:24:10.000Z", + "key": 1518549850000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 954414302394 + }, + "odh_node_free_space__metric": { + "value": 3146469376 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 22 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 12768020363, + "normalized_value": 1276802036.3 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.1796875, + "normalized_value": -0.01796875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -1937408, + "normalized_value": -193740.8 + } + }, { + "key_as_string": "2018-02-13T19:24:20.000Z", + "key": 1518549860000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.140625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 967670642606 + }, + "odh_node_free_space__metric": { + "value": 3146575872 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 34 + }, + "odh_node_cpu_utilization__metric": { + "value": 5 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 13256340212, + "normalized_value": 1325634021.2 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 3, + "normalized_value": 0.3 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.140625, + "normalized_value": 0.0140625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 12, + "normalized_value": 1.2 + }, + "odh_node_free_space__metric_deriv": { + "value": 106496, + "normalized_value": 10649.6 + } + }, { + "key_as_string": "2018-02-13T19:24:30.000Z", + "key": 1518549870000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.349609375 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 981871818195 + }, + "odh_node_free_space__metric": { + "value": 3143720960 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 33 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 14201175589, + "normalized_value": 1420117558.9 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.208984375, + "normalized_value": 0.0208984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -1, + "normalized_value": -0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -2854912, + "normalized_value": -285491.2 + } + }, { + "key_as_string": "2018-02-13T19:24:40.000Z", + "key": 1518549880000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.439453125 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 992156482002 + }, + "odh_node_free_space__metric": { + "value": 3145134080 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 39 + }, + "odh_node_cpu_utilization__metric": { + "value": 8 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 10284663807, + "normalized_value": 1028466380.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 5, + "normalized_value": 0.5 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.08984375, + "normalized_value": 0.008984375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 6, + "normalized_value": 0.6 + }, + "odh_node_free_space__metric_deriv": { + "value": 1413120, + "normalized_value": 141312 + } + }, { + "key_as_string": "2018-02-13T19:24:50.000Z", + "key": 1518549890000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 2.140625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 995592458443 + }, + "odh_node_free_space__metric": { + "value": 3145375744 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 24 + }, + "odh_node_cpu_utilization__metric": { + "value": 3 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 3435976441, + "normalized_value": 343597644.1 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -5, + "normalized_value": -0.5 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.298828125, + "normalized_value": -0.0298828125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -15, + "normalized_value": -1.5 + }, + "odh_node_free_space__metric_deriv": { + "value": 241664, + "normalized_value": 24166.4 + } + }, { + "key_as_string": "2018-02-13T19:25:00.000Z", + "key": 1518549900000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.8798828125 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 998276280550 + }, + "odh_node_free_space__metric": { + "value": 3145003008 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 24 + }, + "odh_node_cpu_utilization__metric": { + "value": 4 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 2683822107, + "normalized_value": 268382210.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.2607421875, + "normalized_value": -0.02607421875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -372736, + "normalized_value": -37273.6 + } + }, { + "key_as_string": "2018-02-13T19:25:10.000Z", + "key": 1518549910000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.669921875 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1000229207942 + }, + "odh_node_free_space__metric": { + "value": 3144859648 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 23 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 1952927392, + "normalized_value": 195292739.2 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -3, + "normalized_value": -0.3 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.2099609375, + "normalized_value": -0.02099609375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -1, + "normalized_value": -0.1 + }, + "odh_node_free_space__metric_deriv": { + "value": -143360, + "normalized_value": -14336 + } + }, { + "key_as_string": "2018-02-13T19:25:20.000Z", + "key": 1518549920000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.41015625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1001857827909 + }, + "odh_node_free_space__metric": { + "value": 3145433088 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 41 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 1628619967, + "normalized_value": 162861996.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.259765625, + "normalized_value": -0.0259765625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 18, + "normalized_value": 1.8 + }, + "odh_node_free_space__metric_deriv": { + "value": 573440, + "normalized_value": 57344 + } + }, { + "key_as_string": "2018-02-13T19:25:30.000Z", + "key": 1518549930000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.4296875 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1009162784819 + }, + "odh_node_free_space__metric": { + "value": 3144224768 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 41 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 7304956910, + "normalized_value": 730495691 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": 0.01953125, + "normalized_value": 0.001953125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -1208320, + "normalized_value": -120832 + } + }, { + "key_as_string": "2018-02-13T19:25:40.000Z", + "key": 1518549940000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.3701171875 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1014294280510 + }, + "odh_node_free_space__metric": { + "value": 3143766016 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 29 + }, + "odh_node_cpu_utilization__metric": { + "value": 4 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 5131495691, + "normalized_value": 513149569.1 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 2, + "normalized_value": 0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.0595703125, + "normalized_value": -0.00595703125 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -12, + "normalized_value": -1.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -458752, + "normalized_value": -45875.2 + } + }, { + "key_as_string": "2018-02-13T19:25:50.000Z", + "key": 1518549950000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.23046875 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1022201061267 + }, + "odh_node_free_space__metric": { + "value": 3141775360 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 43 + }, + "odh_node_cpu_utilization__metric": { + "value": 2 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 7906780757, + "normalized_value": 790678075.7 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.1396484375, + "normalized_value": -0.01396484375 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 14, + "normalized_value": 1.4 + }, + "odh_node_free_space__metric_deriv": { + "value": -1990656, + "normalized_value": -199065.6 + } + }, { + "key_as_string": "2018-02-13T19:26:00.000Z", + "key": 1518549960000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.0400390625 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1024316448990 + }, + "odh_node_free_space__metric": { + "value": 3141402624 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 41 + }, + "odh_node_cpu_utilization__metric": { + "value": 0 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 2115387723, + "normalized_value": 211538772.3 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.1904296875, + "normalized_value": -0.01904296875 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": -2, + "normalized_value": -0.2 + }, + "odh_node_free_space__metric_deriv": { + "value": -372736, + "normalized_value": -37273.6 + } + }, { + "key_as_string": "2018-02-13T19:26:10.000Z", + "key": 1518549970000, + "doc_count": 1, + "odh_node_load_average__metric": { + "value": 1.0302734375 + }, + "odh_node_cgroup_quota__periods": { + "value": 0 + }, + "odh_node_cgroup_quota__quota": { + "value": -1 + }, + "odh_node_cgroup_quota__usage": { + "value": 1026253543446 + }, + "odh_node_free_space__metric": { + "value": 3141033984 + }, + "odh_node_jvm_mem_percent__metric": { + "value": 41 + }, + "odh_node_cpu_utilization__metric": { + "value": 1 + }, + "odh_node_cgroup_throttled__metric": { + "value": 0 + }, + "odh_node_cgroup_quota__usage_deriv": { + "value": 1937094456, + "normalized_value": 193709445.6 + }, + "odh_node_cgroup_quota__periods_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cgroup_throttled__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_cpu_utilization__metric_deriv": { + "value": 1, + "normalized_value": 0.1 + }, + "odh_node_load_average__metric_deriv": { + "value": -0.009765625, + "normalized_value": -0.0009765625 + }, + "odh_node_jvm_mem_percent__metric_deriv": { + "value": 0, + "normalized_value": 0 + }, + "odh_node_free_space__metric_deriv": { + "value": -368640, + "normalized_value": -36864 + } + }] + } + }] } } }, "clusterStats": { - "cluster_uuid": "Cbo7k85ZRdy--yxmqeykog", "cluster_name": "hello", - "version": "7.0.0-alpha1", - "license": { - "status": "active", - "type": "trial", - "issue_date": "2014-09-29T00:00:00.000Z", - "expiry_date": "2030-09-29T23:59:59.999Z", - "expiry_date_in_millis": 1916956799999, - "cluster_needs_tls": true + "cluster_state": { + "master_node": "_x_V2YzPQU-a9KRRBxUxZQ", + "nodes": { + "DAiX7fFjS3Wii7g2HYKrOg": { + "attributes": { + "ml.enabled": "true", + "ml.machine_memory": "4143853568", + "ml.max_open_jobs": "20" + }, + "ephemeral_id": "gVSVcuUXRKqYZgY_eLjdQA", + "name": "hello02", + "transport_address": "127.0.0.1:9301" + }, + "_x_V2YzPQU-a9KRRBxUxZQ": { + "attributes": { + "ml.enabled": "true", + "ml.machine_memory": "4143853568", + "ml.max_open_jobs": "20" + }, + "ephemeral_id": "Q2RAn6QYQFyPNblvlm-C8g", + "name": "hello01", + "transport_address": "127.0.0.1:9300" + } + }, + "nodes_hash": 774278652, + "state_uuid": "xtTdpqmySg-IrwDDKgYk8w", + "status": "green", + "version": 55 }, "cluster_stats": { - "timestamp": 1518549968455, - "status": "green", "indices": { - "count": 6, - "shards": { - "total": 12, - "primaries": 6, - "replication": 1, - "index": { - "shards": { - "min": 2, - "max": 2, - "avg": 2 - }, - "primaries": { - "min": 1, - "max": 1, - "avg": 1 - }, - "replication": { - "min": 1, - "max": 1, - "avg": 1 - } - } + "completion": { + "size_in_bytes": 0 }, + "count": 6, "docs": { - "count": 402, - "deleted": 24 - }, - "store": { - "size_in_bytes": 1604573 + "count": 414, + "deleted": 36 }, "fielddata": { - "memory_size_in_bytes": 0, - "evictions": 0 + "evictions": 0, + "memory_size_in_bytes": 0 }, "query_cache": { - "memory_size_in_bytes": 0, - "total_count": 0, + "cache_count": 0, + "cache_size": 0, + "evictions": 0, "hit_count": 0, + "memory_size_in_bytes": 0, "miss_count": 0, - "cache_size": 0, - "cache_count": 0, - "evictions": 0 - }, - "completion": { - "size_in_bytes": 0 + "total_count": 0 }, "segments": { - "count": 38, - "memory_in_bytes": 268954, - "terms_memory_in_bytes": 185551, - "stored_fields_memory_in_bytes": 11960, - "term_vectors_memory_in_bytes": 0, - "norms_memory_in_bytes": 11520, - "points_memory_in_bytes": 2715, - "doc_values_memory_in_bytes": 57208, - "index_writer_memory_in_bytes": 287996, - "version_map_memory_in_bytes": 10008, + "count": 46, + "doc_values_memory_in_bytes": 61544, + "file_sizes": {}, "fixed_bit_set_memory_in_bytes": 400, + "index_writer_memory_in_bytes": 287996, "max_unsafe_auto_id_timestamp": 1518549739793, - "file_sizes": {} + "memory_in_bytes": 306088, + "norms_memory_in_bytes": 11520, + "points_memory_in_bytes": 3705, + "stored_fields_memory_in_bytes": 14456, + "term_vectors_memory_in_bytes": 0, + "terms_memory_in_bytes": 214863, + "version_map_memory_in_bytes": 10008 + }, + "shards": { + "index": { + "primaries": { + "avg": 1, + "max": 1, + "min": 1 + }, + "replication": { + "avg": 1, + "max": 1, + "min": 1 + }, + "shards": { + "avg": 2, + "max": 2, + "min": 2 + } + }, + "primaries": 6, + "replication": 1, + "total": 12 + }, + "store": { + "size_in_bytes": 1874096 } }, "nodes": { "count": { - "total": 2, - "data": 2, "coordinating_only": 0, + "data": 2, + "ingest": 2, "master": 2, - "ingest": 2 + "total": 2 + }, + "fs": { + "available_in_bytes": 3140956160, + "free_in_bytes": 4016672768, + "total_in_bytes": 16775028736 + }, + "jvm": { + "max_uptime_in_millis": 1136459, + "mem": { + "heap_max_in_bytes": 835321856, + "heap_used_in_bytes": 263910352 + }, + "threads": 78, + "versions": [{ + "count": 2, + "version": "1.8.0_131", + "vm_name": "OpenJDK 64-Bit Server VM", + "vm_vendor": "Oracle Corporation", + "vm_version": "25.131-b11" + }] + }, + "network_types": { + "http_types": { + "security4": 2 + }, + "transport_types": { + "security4": 2 + } }, - "versions": [ - "7.0.0-alpha1" - ], "os": { - "available_processors": 4, "allocated_processors": 2, - "names": [ - { - "name": "Linux", - "count": 2 - } - ], + "available_processors": 4, "mem": { - "total_in_bytes": 8287707136, - "free_in_bytes": 1106853888, - "used_in_bytes": 7180853248, + "free_in_bytes": 1061044224, "free_percent": 13, + "total_in_bytes": 8287707136, + "used_in_bytes": 7226662912, "used_percent": 87 - } + }, + "names": [{ + "count": 2, + "name": "Linux" + }] }, + "plugins": [{ + "classname": "org.elasticsearch.xpack.XPackPlugin", + "description": "Elasticsearch Expanded Pack Plugin", + "has_native_controller": true, + "name": "x-pack", + "requires_keystore": true, + "version": "7.0.0-alpha1" + }], "process": { "cpu": { - "percent": 16 + "percent": 4 }, "open_file_descriptors": { - "min": 208, - "max": 219, - "avg": 213 + "avg": 212, + "max": 217, + "min": 208 } }, - "jvm": { - "max_uptime_in_millis": 1126458, - "versions": [ - { - "version": "1.8.0_131", - "vm_name": "OpenJDK 64-Bit Server VM", - "vm_version": "25.131-b11", - "vm_vendor": "Oracle Corporation", - "count": 2 - } - ], - "mem": { - "heap_used_in_bytes": 259061752, - "heap_max_in_bytes": 835321856 - }, - "threads": 78 - }, - "fs": { - "total_in_bytes": 16775028736, - "free_in_bytes": 4017041408, - "available_in_bytes": 3141324800 - }, - "plugins": [ - { - "name": "x-pack", - "version": "7.0.0-alpha1", - "description": "Elasticsearch Expanded Pack Plugin", - "classname": "org.elasticsearch.xpack.XPackPlugin", - "has_native_controller": true, - "requires_keystore": true - } - ], - "network_types": { - "transport_types": { - "security4": 2 - }, - "http_types": { - "security4": 2 - } - } - } - }, - "cluster_state": { - "nodes_hash": 774278652, + "versions": ["7.0.0-alpha1"] + }, "status": "green", - "version": 55, - "state_uuid": "xtTdpqmySg-IrwDDKgYk8w", - "master_node": "_x_V2YzPQU-a9KRRBxUxZQ", - "nodes": { - "DAiX7fFjS3Wii7g2HYKrOg": { - "name": "hello02", - "ephemeral_id": "gVSVcuUXRKqYZgY_eLjdQA", - "transport_address": "127.0.0.1:9301", - "attributes": { - "ml.machine_memory": "4143853568", - "ml.max_open_jobs": "20", - "ml.enabled": "true" - } - }, - "_x_V2YzPQU-a9KRRBxUxZQ": { - "name": "hello01", - "ephemeral_id": "Q2RAn6QYQFyPNblvlm-C8g", - "transport_address": "127.0.0.1:9300", - "attributes": { - "ml.machine_memory": "4143853568", - "ml.max_open_jobs": "20", - "ml.enabled": "true" - } - } - } - } + "timestamp": 1518549978465 + }, + "cluster_uuid": "Cbo7k85ZRdy--yxmqeykog", + "license": { + "expiry_date": "2030-09-29T23:59:59.999Z", + "expiry_date_in_millis": 1916956799999, + "issue_date": "2014-09-29T00:00:00.000Z", + "status": "active", + "type": "trial" + }, + "version": "7.0.0-alpha1" }, "shardStats": { "indicesTotals": { @@ -2368,25 +2922,21 @@ "shardCount": 6, "indexCount": 6, "name": "hello02", - "node_ids": [ - "DAiX7fFjS3Wii7g2HYKrOg" - ], + "node_ids": ["DAiX7fFjS3Wii7g2HYKrOg"], "type": "node" }, "_x_V2YzPQU-a9KRRBxUxZQ": { "shardCount": 6, "indexCount": 6, "name": "hello01", - "node_ids": [ - "_x_V2YzPQU-a9KRRBxUxZQ" - ], + "node_ids": ["_x_V2YzPQU-a9KRRBxUxZQ"], "type": "master" } } }, "timeOptions": { - "min": 1518549844800, - "max": 1518549971312, + "min": 1518549720000, + "max": 1518550020000, "bucketSize": 10 } } diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js index ff2d0470968b1..611c9d0663daa 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.js @@ -6,6 +6,7 @@ import { metrics } from '../../../metrics'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../../common/constants'; +import { convertMetricNames } from '../../convert_metric_names'; /* * Create the DSL for date histogram aggregations based on an array of metric names @@ -16,8 +17,8 @@ import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../../common/constants'; * @param {Number} bucketSize: Bucket size in seconds for date histogram interval * @return {Object} Aggregation DSL */ -export function getMetricAggs(listingMetrics, bucketSize) { - const aggItems = {}; +export function getMetricAggs(listingMetrics) { + let aggItems = {}; listingMetrics.forEach(metricName => { const metric = metrics[metricName]; @@ -43,13 +44,9 @@ export function getMetricAggs(listingMetrics, bucketSize) { }; } - aggItems[metricName] = { - date_histogram: { - field: 'timestamp', - min_doc_count: 1, - fixed_interval: bucketSize + 's' - }, - aggs: metric.aggs || metricAgg + aggItems = { + ...aggItems, + ...convertMetricNames(metricName, metric.aggs || metricAgg) }; }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js index c1e070c624843..938cdbe055379 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js @@ -72,7 +72,16 @@ export async function getNodes(req, esIndexPattern, clusterStats, shardStats) { field: `source_node.uuid`, size: config.get('xpack.monitoring.max_bucket_size') }, - aggs: getMetricAggs(LISTING_METRICS_NAMES, bucketSize) + aggs: { + by_date: { + date_histogram: { + field: 'timestamp', + min_doc_count: 1, + fixed_interval: bucketSize + 's' + }, + aggs: getMetricAggs(LISTING_METRICS_NAMES, bucketSize) + } + } } }, sort: [ { timestamp: { order: 'desc' } } ] diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js index 1e626ca0591d3..448d6140e2585 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; import { mapNodesInfo } from './map_nodes_info'; import { mapNodesMetrics } from './map_nodes_metrics'; -import { LISTING_METRICS_NAMES } from './nodes_listing_metrics'; +import { uncovertMetricNames } from '../../convert_metric_names'; /* * Process the response from the get_nodes query @@ -31,10 +31,10 @@ export function handleResponse(response, clusterStats, shardStats, timeOptions = * with a sub-object for all the metrics buckets */ const nodeBuckets = get(response, 'aggregations.nodes.buckets', []); - const metricsForNodes = nodeBuckets.reduce((accum, { key: nodeId, ...allAggBuckets }) => { + const metricsForNodes = nodeBuckets.reduce((accum, { key: nodeId, by_date: byDate }) => { return { ...accum, - [nodeId]: pick(allAggBuckets, LISTING_METRICS_NAMES) // "metrics" are just the date histogram aggs + [nodeId]: uncovertMetricNames(byDate), }; }, {}); const nodesMetrics = mapNodesMetrics(metricsForNodes, nodesInfo, timeOptions); // summarize the metrics of online nodes diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics.js index 6b5f96069d390..87b3d6da30343 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics.js @@ -16,10 +16,5 @@ export const LISTING_METRICS_NAMES = [ ]; export const LISTING_METRICS_PATHS = [ - 'aggregations.nodes.buckets.node_cgroup_quota.buckets', - 'aggregations.nodes.buckets.node_cgroup_throttled.buckets', - 'aggregations.nodes.buckets.node_cpu_utilization.buckets', - 'aggregations.nodes.buckets.node_load_average.buckets', - 'aggregations.nodes.buckets.node_jvm_mem_percent.buckets', - 'aggregations.nodes.buckets.node_free_space.buckets', + `aggregations.nodes.buckets.by_date.buckets`, ]; From eeff5ef683031ad9857c63a1e7f5d1c803af8794 Mon Sep 17 00:00:00 2001 From: Jason Rhodes Date: Tue, 27 Aug 2019 14:28:14 -0400 Subject: [PATCH 23/66] [APM] Sets up APM with new shared Kibana core context (#43920) * Sets up APM with new shared Kibana core context * Removes unused core context and hook --- .../app/GlobalHelpExtension/index.tsx | 4 +-- .../components/app/Main/UpdateBreadcrumbs.tsx | 4 +-- .../Main/__test__/UpdateBreadcrumbs.test.js | 4 +-- .../app/Main/useUpdateBadgeEffect.ts | 4 +-- .../ServiceIntegrations/WatcherFlyout.tsx | 9 ++++--- .../ServiceIntegrations/index.tsx | 9 ++++--- .../__test__/ServiceOverview.test.tsx | 4 +-- .../components/app/ServiceOverview/index.tsx | 4 +-- .../components/shared/KueryBar/index.tsx | 4 +-- .../Links/DiscoverLinks/DiscoverLink.tsx | 4 +-- .../DiscoverLinks.integration.test.tsx | 4 +-- .../shared/Links/InfraLink.test.tsx | 4 +-- .../components/shared/Links/InfraLink.tsx | 4 +-- .../shared/Links/KibanaLink.test.tsx | 4 +-- .../components/shared/Links/KibanaLink.tsx | 4 +-- .../MachineLearningLinks/MLJobLink.test.tsx | 4 +-- .../MachineLearningLinks/MLLink.test.tsx | 4 +-- .../Links/MachineLearningLinks/MLLink.tsx | 4 +-- .../TransactionActionMenu.tsx | 4 +-- .../__test__/TransactionActionMenu.test.tsx | 4 +-- .../apm/public/context/CoreContext.tsx | 16 ------------ .../InvalidLicenseNotification.tsx | 4 +-- .../plugins/apm/public/hooks/useCore.tsx | 12 --------- x-pack/legacy/plugins/apm/public/index.tsx | 6 ++--- .../apm/public/new-platform/plugin.tsx | 6 ++--- .../public/context/kibana_core.tsx | 25 +++++++++++++++++++ .../plugins/observability/public/index.tsx | 4 ++- 27 files changed, 82 insertions(+), 81 deletions(-) delete mode 100644 x-pack/legacy/plugins/apm/public/context/CoreContext.tsx delete mode 100644 x-pack/legacy/plugins/apm/public/hooks/useCore.tsx create mode 100644 x-pack/legacy/plugins/observability/public/context/kibana_core.tsx diff --git a/x-pack/legacy/plugins/apm/public/components/app/GlobalHelpExtension/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/GlobalHelpExtension/index.tsx index fb10a65d975bf..57a0e5ad9ddc4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/GlobalHelpExtension/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/GlobalHelpExtension/index.tsx @@ -10,14 +10,14 @@ import React, { Fragment } from 'react'; import styled from 'styled-components'; import url from 'url'; import { px, units } from '../../../style/variables'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; const Container = styled.div` margin: ${px(units.minus)} 0; `; export const GlobalHelpExtension: React.SFC = () => { - const core = useCore(); + const core = useKibanaCore(); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx b/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx index 5eb2626f44872..b94860c8fdd8f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx @@ -8,7 +8,7 @@ import { Location } from 'history'; import { last } from 'lodash'; import React from 'react'; import { InternalCoreStart } from 'src/core/public'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { Breadcrumb, ProvideBreadcrumbs } from './ProvideBreadcrumbs'; import { routes } from './route_config'; @@ -45,7 +45,7 @@ class UpdateBreadcrumbsComponent extends React.Component { } export function UpdateBreadcrumbs() { - const core = useCore(); + const core = useKibanaCore(); return ( { - const { chrome } = useCore(); + const { chrome } = useKibanaCore(); useEffect(() => { const uiCapabilities = capabilities.get(); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx index 134934ff8425e..f3497d1235e81 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx @@ -32,11 +32,11 @@ import React, { Component } from 'react'; import styled from 'styled-components'; import { toastNotifications } from 'ui/notify'; import { InternalCoreStart } from 'src/core/public'; +import { KibanaCoreContext } from '../../../../../../observability/public'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { KibanaLink } from '../../../shared/Links/KibanaLink'; import { createErrorGroupWatch, Schedule } from './createErrorGroupWatch'; import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; -import { CoreContext } from '../../../../context/CoreContext'; type ScheduleKey = keyof Schedule; @@ -83,7 +83,8 @@ export class WatcherFlyout extends Component< WatcherFlyoutProps, WatcherFlyoutState > { - static contextType = CoreContext; + static contextType = KibanaCoreContext; + context!: React.ContextType; public state: WatcherFlyoutState = { schedule: 'daily', threshold: 10, @@ -156,7 +157,7 @@ export class WatcherFlyout extends Component< }; public createWatch = () => { - const core: InternalCoreStart = this.context; + const core = this.context; const { serviceName } = this.props.urlParams; if (!serviceName) { @@ -278,7 +279,7 @@ export class WatcherFlyout extends Component< return null; } - const core: InternalCoreStart = this.context; + const core = this.context; const userTimezoneSetting = getUserTimezone(core); const dailyTime = this.state.daily; const inputTime = `${dailyTime}Z`; // Add tz to make into UTC diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index 513c58d7a834a..8a5d7ad10f22b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -13,13 +13,12 @@ import { import { i18n } from '@kbn/i18n'; import { memoize } from 'lodash'; import React, { Fragment } from 'react'; -import { InternalCoreStart } from 'src/core/public'; import { idx } from '@kbn/elastic-idx'; +import { KibanaCoreContext } from '../../../../../../observability/public'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { LicenseContext } from '../../../../context/LicenseContext'; import { MachineLearningFlyout } from './MachineLearningFlyout'; import { WatcherFlyout } from './WatcherFlyout'; -import { CoreContext } from '../../../../context/CoreContext'; interface Props { urlParams: IUrlParams; @@ -31,7 +30,9 @@ interface State { type FlyoutName = null | 'ML' | 'Watcher'; export class ServiceIntegrations extends React.Component { - static contextType = CoreContext; + static contextType = KibanaCoreContext; + context!: React.ContextType; + public state: State = { isPopoverOpen: false, activeFlyout: null }; public getPanelItems = memoize((mlAvailable: boolean | undefined) => { @@ -67,7 +68,7 @@ export class ServiceIntegrations extends React.Component { }; public getWatcherPanelItems = () => { - const core: InternalCoreStart = this.context; + const core = this.context; return [ { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index b29428cc555ed..599f6c91e09e0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx @@ -11,7 +11,7 @@ import { toastNotifications } from 'ui/notify'; import * as callApmApi from '../../../../services/rest/callApmApi'; import { ServiceOverview } from '..'; import * as urlParamsHooks from '../../../../hooks/useUrlParams'; -import * as coreHooks from '../../../../hooks/useCore'; +import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; import { InternalCoreStart } from 'src/core/public'; import * as useLocalUIFilters from '../../../../hooks/useLocalUIFilters'; import { FETCH_STATUS } from '../../../../hooks/useFetcher'; @@ -39,7 +39,7 @@ describe('Service Overview -> View', () => { end: 'myEnd' } }); - spyOn(coreHooks, 'useCore').and.returnValue(coreMock); + spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock); jest.spyOn(useLocalUIFilters, 'useLocalUIFilters').mockReturnValue({ filters: [], diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx index 2fb81048a16f4..1e2fc98843d29 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx @@ -15,7 +15,7 @@ import { NoServicesMessage } from './NoServicesMessage'; import { ServiceList } from './ServiceList'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { useTrackPageview } from '../../../../../infra/public'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; import { PROJECTION } from '../../../../common/projections/typings'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { callApmApi } from '../../../services/rest/callApmApi'; @@ -29,7 +29,7 @@ const initalData = { let hasDisplayedToast = false; export function ServiceOverview() { - const core = useCore(); + const core = useKibanaCore(); const { urlParams: { start, end }, uiFilters diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 9fe8be856ece4..bc5d293220158 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -27,7 +27,7 @@ import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; import { useMatchedRoutes } from '../../../hooks/useMatchedRoutes'; import { RouteName } from '../../app/Main/route_config/route_names'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; import { getAPMIndexPattern } from '../../../services/rest/savedObjects'; const Container = styled.div` @@ -86,7 +86,7 @@ function getSuggestions( } export function KueryBar() { - const core = useCore(); + const core = useKibanaCore(); const [state, setState] = useState({ indexPattern: null, suggestions: [], diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx index 0673ab5e75cc6..17bb7373b6b9f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx @@ -11,7 +11,7 @@ import rison, { RisonValue } from 'rison-node'; import { useAPMIndexPattern } from '../../../../hooks/useAPMIndexPattern'; import { useLocation } from '../../../../hooks/useLocation'; import { getTimepickerRisonData } from '../rison_helpers'; -import { useCore } from '../../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../../observability/public'; interface Props { query: { @@ -31,7 +31,7 @@ interface Props { } export function DiscoverLink({ query = {}, ...rest }: Props) { - const core = useCore(); + const core = useKibanaCore(); const apmIndexPattern = useAPMIndexPattern(); const location = useLocation(); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx index 80cecd8ea7f4d..d9ca32f78f4dc 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx @@ -14,7 +14,7 @@ import { getRenderedHref } from '../../../../../utils/testHelpers'; import { DiscoverErrorLink } from '../DiscoverErrorLink'; import { DiscoverSpanLink } from '../DiscoverSpanLink'; import { DiscoverTransactionLink } from '../DiscoverTransactionLink'; -import * as hooks from '../../../../../hooks/useCore'; +import * as kibanaCore from '../../../../../../../observability/public/context/kibana_core'; import { InternalCoreStart } from 'src/core/public'; jest.mock('ui/kfetch'); @@ -34,7 +34,7 @@ beforeAll(() => { } } as unknown) as InternalCoreStart; - jest.spyOn(hooks, 'useCore').mockReturnValue(coreMock); + jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); }); afterAll(() => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx index 9925d87a159ca..d5518f6a96195 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx @@ -8,7 +8,7 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../utils/testHelpers'; import { InfraLink } from './InfraLink'; -import * as hooks from '../../../hooks/useCore'; +import * as kibanaCore from '../../../../../observability/public/context/kibana_core'; import { InternalCoreStart } from 'src/core/public'; const coreMock = ({ @@ -19,7 +19,7 @@ const coreMock = ({ } } as unknown) as InternalCoreStart; -jest.spyOn(hooks, 'useCore').mockReturnValue(coreMock); +jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); test('InfraLink produces the correct URL', async () => { const href = await getRenderedHref( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx index eda64b4bbedb2..192fafadba4c0 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx @@ -9,7 +9,7 @@ import { compact } from 'lodash'; import React from 'react'; import url from 'url'; import { fromQuery } from './url_helpers'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; interface InfraQueryParams { time?: number; @@ -24,7 +24,7 @@ interface Props extends EuiLinkAnchorProps { } export function InfraLink({ path, query = {}, ...rest }: Props) { - const core = useCore(); + const core = useKibanaCore(); const nextSearch = fromQuery(query); const href = url.format({ pathname: core.http.basePath.prepend('/app/infra'), diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx index 24637f971bf3c..c01d198b65b5a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx @@ -8,7 +8,7 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../utils/testHelpers'; import { KibanaLink } from './KibanaLink'; -import * as hooks from '../../../hooks/useCore'; +import * as kibanaCore from '../../../../../observability/public/context/kibana_core'; import { InternalCoreStart } from 'src/core/public'; describe('KibanaLink', () => { @@ -21,7 +21,7 @@ describe('KibanaLink', () => { } } as unknown) as InternalCoreStart; - jest.spyOn(hooks, 'useCore').mockReturnValue(coreMock); + jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); }); afterEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx index 53fe9da734644..de62d5e46070a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx @@ -7,7 +7,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; import url from 'url'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; interface Props extends EuiLinkAnchorProps { path?: string; @@ -15,7 +15,7 @@ interface Props extends EuiLinkAnchorProps { } export function KibanaLink({ path, ...rest }: Props) { - const core = useCore(); + const core = useKibanaCore(); const href = url.format({ pathname: core.http.basePath.prepend('/app/kibana'), hash: path diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx index c577a38029d29..524a2d225c84c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx @@ -8,7 +8,7 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLJobLink } from './MLJobLink'; -import * as hooks from '../../../../hooks/useCore'; +import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; import { InternalCoreStart } from 'src/core/public'; describe('MLJobLink', () => { @@ -21,7 +21,7 @@ describe('MLJobLink', () => { } } as unknown) as InternalCoreStart; - spyOn(hooks, 'useCore').and.returnValue(coreMock); + spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock); }); afterEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx index 5f66cf8563260..73f8bb2c7a213 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLLink } from './MLLink'; import * as savedObjects from '../../../../services/rest/savedObjects'; -import * as hooks from '../../../../hooks/useCore'; +import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; import { InternalCoreStart } from 'src/core/public'; jest.mock('ui/kfetch'); @@ -22,7 +22,7 @@ const coreMock = ({ } } as unknown) as InternalCoreStart; -jest.spyOn(hooks, 'useCore').mockReturnValue(coreMock); +jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); jest .spyOn(savedObjects, 'getAPMIndexPattern') diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx index e0b9331d28496..0fe80b729f010 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx @@ -10,7 +10,7 @@ import url from 'url'; import rison, { RisonValue } from 'rison-node'; import { useLocation } from '../../../../hooks/useLocation'; import { getTimepickerRisonData, TimepickerRisonData } from '../rison_helpers'; -import { useCore } from '../../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../../observability/public'; interface MlRisonData { ml?: { @@ -25,7 +25,7 @@ interface Props { } export function MLLink({ children, path = '', query = {} }: Props) { - const core = useCore(); + const core = useKibanaCore(); const location = useLocation(); const risonQuery: MlRisonData & TimepickerRisonData = getTimepickerRisonData( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 2e3382f71b204..317fac87eea5a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -24,7 +24,7 @@ import { DiscoverTransactionLink } from '../Links/DiscoverLinks/DiscoverTransact import { InfraLink } from '../Links/InfraLink'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { fromQuery } from '../Links/url_helpers'; -import { useCore } from '../../../hooks/useCore'; +import { useKibanaCore } from '../../../../../observability/public'; function getInfraMetricsQuery(transaction: Transaction) { const plus5 = new Date(transaction['@timestamp']); @@ -66,7 +66,7 @@ export const TransactionActionMenu: FunctionComponent = ( ) => { const { transaction } = props; - const core = useCore(); + const core = useKibanaCore(); const [isOpen, setIsOpen] = useState(false); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 89adbd5c0d832..8442f300aa0fc 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -11,7 +11,7 @@ import { TransactionActionMenu } from '../TransactionActionMenu'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import * as Transactions from './mockData'; import * as apmIndexPatternHooks from '../../../../hooks/useAPMIndexPattern'; -import * as coreHoooks from '../../../../hooks/useCore'; +import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; import { ISavedObject } from '../../../../services/rest/savedObjects'; import { InternalCoreStart } from 'src/core/public'; @@ -40,7 +40,7 @@ describe('TransactionActionMenu component', () => { jest .spyOn(apmIndexPatternHooks, 'useAPMIndexPattern') .mockReturnValue({ id: 'foo' } as ISavedObject); - jest.spyOn(coreHoooks, 'useCore').mockReturnValue(coreMock); + jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); }); afterEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/context/CoreContext.tsx b/x-pack/legacy/plugins/apm/public/context/CoreContext.tsx deleted file mode 100644 index 0bf39e4d9dadb..0000000000000 --- a/x-pack/legacy/plugins/apm/public/context/CoreContext.tsx +++ /dev/null @@ -1,16 +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, { createContext } from 'react'; -import { InternalCoreStart } from 'src/core/public'; - -const CoreContext = createContext({} as InternalCoreStart); -const CoreProvider: React.SFC<{ core: InternalCoreStart }> = props => { - const { core, ...restProps } = props; - return ; -}; - -export { CoreContext, CoreProvider }; diff --git a/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index ffd85412be0f4..1c340f4b4f3c7 100644 --- a/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -6,10 +6,10 @@ import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useCore } from '../../hooks/useCore'; +import { useKibanaCore } from '../../../../observability/public'; export function InvalidLicenseNotification() { - const core = useCore(); + const core = useKibanaCore(); const manageLicenseURL = core.http.basePath.prepend( '/app/kibana#/management/elasticsearch/license_management' ); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useCore.tsx b/x-pack/legacy/plugins/apm/public/hooks/useCore.tsx deleted file mode 100644 index 06942019d6530..0000000000000 --- a/x-pack/legacy/plugins/apm/public/hooks/useCore.tsx +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useContext } from 'react'; -import { CoreContext } from '../context/CoreContext'; - -export function useCore() { - return useContext(CoreContext); -} diff --git a/x-pack/legacy/plugins/apm/public/index.tsx b/x-pack/legacy/plugins/apm/public/index.tsx index 20d111333e4e1..bc1e6f9e714ac 100644 --- a/x-pack/legacy/plugins/apm/public/index.tsx +++ b/x-pack/legacy/plugins/apm/public/index.tsx @@ -19,16 +19,16 @@ import { plugin } from './new-platform'; import { REACT_APP_ROOT_ID } from './new-platform/plugin'; import './style/global_overrides.css'; import template from './templates/index.html'; -import { CoreProvider } from './context/CoreContext'; +import { KibanaCoreContextProvider } from '../../observability/public'; const { core } = npStart; // render APM feedback link in global help menu core.chrome.setHelpExtension(domElement => { ReactDOM.render( - + - , + , domElement ); return () => { diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index abd793245cbb6..c9a1e583a3cb0 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -9,8 +9,8 @@ import ReactDOM from 'react-dom'; import { Router, Route, Switch } from 'react-router-dom'; import styled from 'styled-components'; import { InternalCoreStart } from 'src/core/public'; +import { KibanaCoreContextProvider } from '../../../observability/public'; import { history } from '../utils/history'; -import { CoreProvider } from '../context/CoreContext'; import { LocationProvider } from '../context/LocationContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; import { px, unit, units } from '../style/variables'; @@ -57,7 +57,7 @@ export class Plugin { public start(core: InternalCoreStart) { const { i18n } = core; ReactDOM.render( - + @@ -65,7 +65,7 @@ export class Plugin { - , + , document.getElementById(REACT_APP_ROOT_ID) ); } diff --git a/x-pack/legacy/plugins/observability/public/context/kibana_core.tsx b/x-pack/legacy/plugins/observability/public/context/kibana_core.tsx new file mode 100644 index 0000000000000..778e138c5fa6c --- /dev/null +++ b/x-pack/legacy/plugins/observability/public/context/kibana_core.tsx @@ -0,0 +1,25 @@ +/* + * 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, { createContext, useContext } from 'react'; +import { InternalCoreStart } from '../../../../../../src/core/public'; + +interface AppMountContext { + core: InternalCoreStart; +} + +// TODO: Replace CoreStart/CoreSetup with AppMountContext +// see: https://github.com/elastic/kibana/pull/41007 + +export const KibanaCoreContext = createContext({} as AppMountContext['core']); + +export const KibanaCoreContextProvider: React.FC<{ core: AppMountContext['core'] }> = props => ( + +); + +export function useKibanaCore() { + return useContext(KibanaCoreContext); +} diff --git a/x-pack/legacy/plugins/observability/public/index.tsx b/x-pack/legacy/plugins/observability/public/index.tsx index 8052f4a9c02e8..49e5a6d787a55 100644 --- a/x-pack/legacy/plugins/observability/public/index.tsx +++ b/x-pack/legacy/plugins/observability/public/index.tsx @@ -3,5 +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 { KibanaCoreContext, KibanaCoreContextProvider, useKibanaCore } from './context/kibana_core'; +import { ExampleSharedComponent } from './components/example_shared_component'; -export { ExampleSharedComponent } from './components/example_shared_component'; +export { ExampleSharedComponent, KibanaCoreContext, KibanaCoreContextProvider, useKibanaCore }; From 43df3a78edb81fd98babb3b66667bb477826749d Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 27 Aug 2019 12:00:47 -0700 Subject: [PATCH 24/66] Reset dirty saved query on reload (#43927) * Clears changes a loaded saved query before loading another one * Adds general functional test to ensure changes to a saved query are discarded on reloading it * Moves resetting a dirty saved query to the app controllers in Discover, Visualize and Dashboard --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 2 +- .../kibana/public/discover/controllers/discover.js | 2 +- .../core_plugins/kibana/public/visualize/editor/editor.js | 2 +- test/functional/apps/discover/_saved_queries.js | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 31906fcbfc0de..cbb8a66301e35 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -432,7 +432,7 @@ export class DashboardAppController { }; $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = savedQuery; + $scope.savedQuery = { ...savedQuery }; }; $scope.onClearSavedQuery = () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index b0f58829daf87..dd1da717b4a82 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -932,7 +932,7 @@ function discoverController( }; $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = savedQuery; + $scope.savedQuery = { ...savedQuery }; }; $scope.onClearSavedQuery = () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index d81bc05048fbf..d4b0d39e2f18a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -481,7 +481,7 @@ function VisEditor( }; $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = savedQuery; + $scope.savedQuery = { ...savedQuery }; }; $scope.onClearSavedQuery = () => { diff --git a/test/functional/apps/discover/_saved_queries.js b/test/functional/apps/discover/_saved_queries.js index b744f7d9e224b..68a1e999b7ba8 100644 --- a/test/functional/apps/discover/_saved_queries.js +++ b/test/functional/apps/discover/_saved_queries.js @@ -123,6 +123,13 @@ export default function ({ getService, getPageObjects }) { await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); expect(await queryBar.getQueryString()).to.eql(''); }); + + it('resets any changes to a loaded query on reloading the same saved query', async () => { + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await queryBar.setQuery('response:503'); + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + expect(await queryBar.getQueryString()).to.eql('response:404'); + }); }); }); } From 61087ba40e3afa0c4d2d6252dc9ca9b988f72bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20=C3=81lvarez?= Date: Tue, 27 Aug 2019 21:25:24 +0200 Subject: [PATCH 25/66] update apm index pattern (#44107) --- .../kibana/server/tutorials/apm/index_pattern.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json index c437a6ab09f2c..46fb12392f2f6 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json +++ b/src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { - "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tracing.transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, From 084433fbee3c2ece12dd3cee1f50086a4ef614f3 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 27 Aug 2019 14:47:41 -0500 Subject: [PATCH 26/66] Upgrade EUI to 13.6.0 (#43916) * eui to 13.6.0 * euirange updates * euipage snapshot updates * add classname toggle for nav locking * new header wrapper component; removed observable * Add styles for locked nav And updated BEM naming of `header-global-wrapper` * move headerwrapper * isLocked localStorage * remove useEffect --- package.json | 2 +- src/core/public/chrome/chrome_service.tsx | 42 +++++++++-------- src/core/public/chrome/ui/header/_index.scss | 29 ++++++++---- src/core/public/chrome/ui/header/header.tsx | 11 ++++- .../chrome/ui/header/header_wrapper.tsx | 45 +++++++++++++++++++ src/core/public/chrome/ui/header/index.ts | 1 + src/core/public/chrome/ui/index.ts | 2 +- .../public/agg_types/controls/precision.tsx | 4 +- .../default/controls/radius_ratio_option.tsx | 4 +- .../plugins/kbn_tp_run_pipeline/package.json | 2 +- .../kbn_tp_custom_visualizations/package.json | 2 +- .../kbn_tp_embeddable_explorer/package.json | 2 +- .../kbn_tp_sample_panel_action/package.json | 2 +- .../kbn_tp_visualize_embedding/package.json | 2 +- .../components/fullscreen/fullscreen.scss | 7 +-- .../__snapshots__/no_data.test.js.snap | 4 +- .../__snapshots__/page_loading.test.js.snap | 2 +- x-pack/package.json | 2 +- yarn.lock | 8 ++-- 19 files changed, 121 insertions(+), 52 deletions(-) create mode 100644 src/core/public/chrome/ui/header/header_wrapper.tsx diff --git a/package.json b/package.json index 734d3c5576cf5..57d8a101b3478 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@babel/register": "^7.5.5", "@elastic/charts": "^10.2.0", "@elastic/datemath": "5.0.2", - "@elastic/eui": "13.3.0", + "@elastic/eui": "13.6.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 668bce522bf4e..2fa6206f0ea01 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -33,7 +33,7 @@ import { HttpStart } from '../http'; import { ChromeNavLinks, NavLinksService } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { NavControlsService, ChromeNavControls } from './nav_controls'; -import { LoadingIndicator, Header } from './ui'; +import { LoadingIndicator, HeaderWrapper as Header } from './ui'; import { DocLinksStart } from '../doc_links'; export { ChromeNavControls, ChromeRecentlyAccessed }; @@ -131,27 +131,25 @@ export class ChromeService { -
    -
    (FORCE_HIDDEN ? false : visibility)), - takeUntil(this.stop$) - )} - kibanaVersion={injectedMetadata.getKibanaVersion()} - navLinks$={navLinks.getNavLinks$()} - recentlyAccessed$={recentlyAccessed.get$()} - navControlsLeft$={navControls.getLeft$()} - navControlsRight$={navControls.getRight$()} - /> -
    +
    (FORCE_HIDDEN ? false : visibility)), + takeUntil(this.stop$) + )} + kibanaVersion={injectedMetadata.getKibanaVersion()} + navLinks$={navLinks.getNavLinks$()} + recentlyAccessed$={recentlyAccessed.get$()} + navControlsLeft$={navControls.getLeft$()} + navControlsRight$={navControls.getRight$()} + /> ), diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index 2b841eca6e01d..f19728a52dd70 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,13 +1,14 @@ @import '@elastic/eui/src/components/header/variables'; +@import '@elastic/eui/src/components/nav_drawer/variables'; -.header-global-wrapper { +.chrHeaderWrapper { width: 100%; position: fixed; top: 0; z-index: 10; } -.header-global-wrapper + .app-wrapper:not(.hidden-chrome) { +.chrHeaderWrapper ~ .app-wrapper:not(.hidden-chrome) { top: $euiHeaderChildSize; left: $euiHeaderChildSize; @@ -19,13 +20,6 @@ } } -// Mobile header is smaller -@include euiBreakpoint('xs', 's') { - .header-global-wrapper + .app-wrapper:not(.hidden-chrome) { - left: 0; - } -} - .chrHeaderHelpMenu__version { text-transform: none; } @@ -34,3 +28,20 @@ align-self: center; margin-right: $euiSize; } + +// Mobile header is smaller +@include euiBreakpoint('xs', 's') { + .chrHeaderWrapper ~ .app-wrapper:not(.hidden-chrome) { + left: 0; + } +} + +@include euiBreakpoint('xl') { + .chrHeaderWrapper--navIsLocked { + ~ .app-wrapper:not(.hidden-chrome) { + // Shrink the content from the left so it's no longer overlapped by the nav drawer (ALWAYS) + left: $euiNavDrawerWidthExpanded !important; // sass-lint:disable-line no-important + transition: left $euiAnimSpeedFast $euiAnimSlightResistance; + } + } +} diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 04c1a11824870..4dc64c57fa244 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -163,6 +163,8 @@ interface Props { navControlsRight$: Rx.Observable; intl: InjectedIntl; basePath: HttpStart['basePath']; + isLocked?: boolean; + onIsLockedUpdate?: (isLocked: boolean) => void; } interface State { @@ -266,8 +268,10 @@ class HeaderUI extends Component { breadcrumbs$, helpExtension$, intl, + isLocked, kibanaDocLink, kibanaVersion, + onIsLockedUpdate, } = this.props; const { appTitle, @@ -355,7 +359,12 @@ class HeaderUI extends Component { - + diff --git a/src/core/public/chrome/ui/header/header_wrapper.tsx b/src/core/public/chrome/ui/header/header_wrapper.tsx new file mode 100644 index 0000000000000..5306faa209586 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_wrapper.tsx @@ -0,0 +1,45 @@ +/* + * 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 React, { FunctionComponent, useState } from 'react'; +import classnames from 'classnames'; +import { Header, HeaderProps } from './'; + +const IS_LOCKED_KEY = 'core.chrome.isLocked'; + +export const HeaderWrapper: FunctionComponent = props => { + const initialIsLocked = localStorage.getItem(IS_LOCKED_KEY); + const [isLocked, setIsLocked] = useState(initialIsLocked === 'true'); + const setIsLockedStored = (locked: boolean) => { + localStorage.setItem(IS_LOCKED_KEY, `${locked}`); + setIsLocked(locked); + }; + const className = classnames( + 'chrHeaderWrapper', + { + 'chrHeaderWrapper--navIsLocked': isLocked, + }, + 'hide-for-sharing' + ); + return ( +
    +
    +
    + ); +}; diff --git a/src/core/public/chrome/ui/header/index.ts b/src/core/public/chrome/ui/header/index.ts index f4c7127b93bfb..f9c122b864dce 100644 --- a/src/core/public/chrome/ui/header/index.ts +++ b/src/core/public/chrome/ui/header/index.ts @@ -18,3 +18,4 @@ */ export { Header, HeaderProps } from './header'; +export { HeaderWrapper } from './header_wrapper'; diff --git a/src/core/public/chrome/ui/index.ts b/src/core/public/chrome/ui/index.ts index 0136f3e2b203d..69582f6f1ed52 100644 --- a/src/core/public/chrome/ui/index.ts +++ b/src/core/public/chrome/ui/index.ts @@ -18,4 +18,4 @@ */ export { LoadingIndicator } from './loading_indicator'; -export { Header } from './header'; +export { Header, HeaderWrapper } from './header'; diff --git a/src/legacy/ui/public/agg_types/controls/precision.tsx b/src/legacy/ui/public/agg_types/controls/precision.tsx index 2aa8566f67c04..a458c29933f02 100644 --- a/src/legacy/ui/public/agg_types/controls/precision.tsx +++ b/src/legacy/ui/public/agg_types/controls/precision.tsx @@ -41,7 +41,9 @@ function PrecisionParamEditor({ agg, value, setValue }: AggParamEditorProps setValue(Number(ev.target.value))} + onChange={(ev: React.ChangeEvent | React.MouseEvent) => + setValue(Number(ev.currentTarget.value)) + } data-test-subj={`visEditorMapPrecision${agg.id}`} showValue /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx index 141897e797504..f2625c628c3f1 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx @@ -57,7 +57,9 @@ function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlPro min={1} max={100} value={editorStateParams.radiusRatio || DEFAULT_VALUE} - onChange={e => setValue(editorStateParams, PARAM_NAME, parseFloat(e.target.value))} + onChange={(e: React.ChangeEvent | React.MouseEvent) => + setValue(editorStateParams, PARAM_NAME, parseFloat(e.currentTarget.value)) + } showRange showValue valueAppend="%" diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 4bd1eece12f87..f870e9518dcfd 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.3.0", + "@elastic/eui": "13.6.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index dfa3dacb4fb8b..f970f0d219b62 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.3.0", + "@elastic/eui": "13.6.0", "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 69f4cdaa895e2..cd1ca420f41d4 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.3.0", + "@elastic/eui": "13.6.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index 1d5f5f4202015..ae9ff2956c4bb 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.3.0", + "@elastic/eui": "13.6.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index fc80e03cc1f4a..47c8ac5a9df89 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.3.0", + "@elastic/eui": "13.6.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/x-pack/legacy/plugins/canvas/public/components/fullscreen/fullscreen.scss b/x-pack/legacy/plugins/canvas/public/components/fullscreen/fullscreen.scss index d366aac096bed..8dad4aaba5eb1 100644 --- a/x-pack/legacy/plugins/canvas/public/components/fullscreen/fullscreen.scss +++ b/x-pack/legacy/plugins/canvas/public/components/fullscreen/fullscreen.scss @@ -5,8 +5,9 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements } // remove space for global nav elements - .header-global-wrapper + .app-wrapper { - left: 0; + .chrHeaderWrapper ~ .app-wrapper { + // Override locked nav at all breakpoints + left: 0 !important; // sass-lint:disable-line no-important top: 0; } @@ -16,7 +17,7 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements } // hide all the interface parts - .header-global-wrapper, // K7 global top nav + .chrHeaderWrapper, // K7 global top nav .canvasLayout__stageHeader, .canvasLayout__sidebar, .canvasLayout__footer, diff --git a/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap index b7c4d4cd03812..7c663a26bc9df 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap @@ -9,7 +9,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = ` style="max-width:600px" >
    Date: Tue, 27 Aug 2019 12:55:57 -0700 Subject: [PATCH 27/66] Chore/bump chromium webgl+kerberos (#42751) * WIP: Adding libs for webgl * WIP Adding swiftshader libs to chromium * WIP: Adding missing binaries for webgl in chromium * Use pipes for communication with chrome to avoid networking snafus * Bumps puppeteer in prep for new chromium build + types and better @types package * Remove ignore * Removing of final @ts-ignore now that we have types * README updates * Fixing binding issues * Fixing maps integration wrt reporting + conditional pipes for puppeteer * Adding new deps to the windows build * New s3 builds * Checksums for updated linux build * Moving types out of puppeteer file and into core puppeteer module * launch => puppeteerLaunch * Maps comment about render loading in reporting * Clarify how reporting uses hooks and events for viz --- x-pack/build_chromium/README.md | 14 +++---- x-pack/build_chromium/build.py | 12 +++++- .../connected_components/gis_map/view.js | 25 +++++++++++- .../chromium/driver/chromium_driver.ts | 2 +- .../browsers/chromium/driver_factory/index.ts | 40 +++++++++---------- .../server/browsers/chromium/paths.ts | 18 ++++----- .../server/browsers/chromium/puppeteer.ts | 13 ++++++ x-pack/package.json | 4 +- yarn.lock | 16 ++++---- 9 files changed, 93 insertions(+), 51 deletions(-) create mode 100644 x-pack/legacy/plugins/reporting/server/browsers/chromium/puppeteer.ts diff --git a/x-pack/build_chromium/README.md b/x-pack/build_chromium/README.md index 0c147d003c92e..ad1fc484edfd8 100644 --- a/x-pack/build_chromium/README.md +++ b/x-pack/build_chromium/README.md @@ -86,13 +86,13 @@ In windows, at least, you will need to do a number of extra steps: Find the sha of the Chromium commit you wish to build. Most likely, you want to build the Chromium revision that is tied to the version of puppeteer that we're using. -Find the Chromium revision (modify the following command to be wherever you have the kibana source installed): +Find the Chromium revision (run in kibana's working directory): -- `cat ~/dev/elastic/kibana/x-pack/node_modules/puppeteer-core/package.json | grep chromium_revision` +- `cat node_modules/puppeteer-core/package.json | grep chromium_revision` - Take the revision number from that, and tack it to the end of this URL: https://crrev.com - - (For example: https://crrev.com/637110) + - (For example, puppeteer@1.19.0 has rev (674921): https://crrev.com/674921) - Grab the SHA from there - - (For example, rev 637110 has sha 2fac04abf6133ab2da2846a8fbd0e97690722699) + - (For example, rev 674921 has sha 312d84c8ce62810976feda0d3457108a6dfff9e6) Note: In Linux, you should run the build command in tmux so that if your ssh session disconnects, the build can keep going. To do this, just type `tmux` into your terminal to hop into a tmux session. If you get disconnected, you can hop back in like so: @@ -102,9 +102,9 @@ Note: In Linux, you should run the build command in tmux so that if your ssh ses To run the build, replace the sha in the following commands with the sha that you wish to build: -- Mac: `python ~/chromium/build_chromium/build.py 2fac04abf6133ab2da2846a8fbd0e97690722699` -- Linux: `python ~/chromium/build_chromium/build.py 2fac04abf6133ab2da2846a8fbd0e97690722699` -- Windows: `python c:\chromium\build_chromium\build.py 2fac04abf6133ab2da2846a8fbd0e97690722699` +- Mac: `python ~/chromium/build_chromium/build.py 312d84c8ce62810976feda0d3457108a6dfff9e6` +- Linux: `python ~/chromium/build_chromium/build.py 312d84c8ce62810976feda0d3457108a6dfff9e6` +- Windows: `python c:\chromium\build_chromium\build.py 312d84c8ce62810976feda0d3457108a6dfff9e6` ## Artifacts diff --git a/x-pack/build_chromium/build.py b/x-pack/build_chromium/build.py index 190a8dfc92d4a..82b0561fdcfe1 100644 --- a/x-pack/build_chromium/build.py +++ b/x-pack/build_chromium/build.py @@ -73,13 +73,23 @@ def archive_file(name): # must be bundled with the Chromium executable. if platform.system() == 'Linux': archive_file('headless_shell') + archive_file(os.path.join('swiftshader', 'libEGL.so')) + archive_file(os.path.join('swiftshader', 'libGLESv2.so')) + elif platform.system() == 'Windows': archive_file('headless_shell.exe') archive_file('dbghelp.dll') archive_file('icudtl.dat') + archive_file(os.path.join('swiftshader', 'libEGL.dll')) + archive_file(os.path.join('swiftshader', 'libEGL.dll.lib')) + archive_file(os.path.join('swiftshader', 'libGLESv2.dll')) + archive_file(os.path.join('swiftshader', 'libGLESv2.dll.lib')) + elif platform.system() == 'Darwin': archive_file('headless_shell') - archive_file('Helpers/chrome_crashpad_handler') + archive_file('libswiftshader_libEGL.dylib') + archive_file('libswiftshader_libGLESv2.dylib') + archive_file(path.join('Helpers', 'chrome_crashpad_handler')) archive.close() diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js index e7a0520f5874d..081f764978d78 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js @@ -16,11 +16,15 @@ import { ExitFullScreenButton } from 'ui/exit_full_screen'; import { getIndexPatternsFromIds } from '../../index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { i18n } from '@kbn/i18n'; +import uuid from 'uuid/v4'; + +const RENDER_COMPLETE_EVENT = 'renderComplete'; export class GisMap extends Component { state = { isInitialLoadRenderTimeoutComplete: false, + domId: uuid(), geoFields: [], } @@ -47,6 +51,21 @@ export class GisMap extends Component { this._clearRefreshTimer(); } + // Reporting uses both a `data-render-complete` attribute and a DOM event listener to determine + // if a visualization is done loading. The process roughly is: + // - See if the `data-render-complete` attribute is "true". If so we're done! + // - If it's not, then reporting injects a listener into the browser for a custom "renderComplete" event. + // - When that event is fired, we snapshot the viz and move on. + // Failure to not have the dom attribute, or custom event, will timeout the job. + // See x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts for more. + _onInitialLoadRenderComplete = () => { + const el = document.querySelector(`[data-dom-id="${this.state.domId}"]`); + + if (el) { + el.dispatchEvent(new CustomEvent(RENDER_COMPLETE_EVENT, { bubbles: true })); + } + } + _loadGeoFields = async (nextIndexPatternIds) => { if (_.isEqual(nextIndexPatternIds, this._prevIndexPatternIds)) { // all ready loaded index pattern ids @@ -118,9 +137,10 @@ export class GisMap extends Component { () => { if (this._isMounted) { this.setState({ isInitialLoadRenderTimeoutComplete: true }); + this._onInitialLoadRenderComplete(); } }, - 1000 + 5000 ); } @@ -135,6 +155,8 @@ export class GisMap extends Component { mapInitError, } = this.props; + const { domId } = this.state; + if (mapInitError) { return (
    @@ -179,6 +201,7 @@ export class GisMap extends Component { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 0ffed03c44de4..8b19f34ea3dd4 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -5,8 +5,8 @@ */ import open from 'opn'; -import { Page, SerializableOrJSHandle, EvaluateFn } from 'puppeteer'; import { parse as parseUrl } from 'url'; +import { Page, SerializableOrJSHandle, EvaluateFn } from 'puppeteer'; import { ViewZoomWidthHeight } from '../../../../export_types/common/layouts/layout'; import { LevelLogger } from '../../../../server/lib'; import { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 1a699d7e2c6fc..6df8b37dd1d30 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -6,14 +6,13 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -// @ts-ignore -import puppeteer from 'puppeteer-core'; import { Browser, Page, LaunchOptions } from 'puppeteer'; import rimraf from 'rimraf'; import * as Rx from 'rxjs'; import { map, share, mergeMap, filter, partition, ignoreElements, tap } from 'rxjs/operators'; import { InnerSubscriber } from 'rxjs/internal/InnerSubscriber'; +import { puppeteerLaunch } from '../puppeteer'; import { LevelLogger as Logger } from '../../../lib/level_logger'; import { HeadlessChromiumDriver } from '../driver'; import { args, IArgOptions } from './args'; @@ -63,23 +62,21 @@ export class HeadlessChromiumDriverFactory { proxyConfig: this.browserConfig.proxy, }); - return puppeteer - .launch({ - userDataDir, - executablePath: this.binaryPath, - ignoreHTTPSErrors: true, - args: chromiumArgs, - env: { - TZ: browserTimezone, - }, - } as LaunchOptions) - .catch((error: Error) => { - logger.warning( - `The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports: [${error}]` - ); - logger.warning(`See Chromium's log output at "${getChromeLogLocation(this.binaryPath)}"`); - return null; - }); + return puppeteerLaunch({ + userDataDir, + executablePath: this.binaryPath, + ignoreHTTPSErrors: true, + args: chromiumArgs, + env: { + TZ: browserTimezone, + }, + } as LaunchOptions).catch((error: Error) => { + logger.warning( + `The Reporting plugin encountered issues launching Chromium in a self-test. You may have trouble generating reports: [${error}]` + ); + logger.warning(`See Chromium's log output at "${getChromeLogLocation(this.binaryPath)}"`); + return null; + }); } create({ @@ -109,8 +106,8 @@ export class HeadlessChromiumDriverFactory { let browser: Browser; let page: Page; try { - browser = await puppeteer.launch({ - pipe: true, + browser = await puppeteerLaunch({ + pipe: !this.browserConfig.inspect, userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, @@ -126,7 +123,6 @@ export class HeadlessChromiumDriverFactory { // which can cause the job to fail even if we bump timeouts in // the config. Help alleviate errors like // "TimeoutError: waiting for selector ".application" failed: timeout 30000ms exceeded" - // @ts-ignore outdated typedefs for puppteer page.setDefaultTimeout(this.queueTimeout); this.logger.debug(`Browser driver factory created`); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts index 49e5228a96394..fee621d293c73 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/paths.ts @@ -12,23 +12,23 @@ export const paths = { packages: [ { platforms: ['darwin', 'freebsd', 'openbsd'], - archiveFilename: 'chromium-2fac04a-darwin.zip', - archiveChecksum: '36814b1629457aa178b4ecdf6cc1bc5f', - rawChecksum: '9b40e2efa7f4f1870835ee4cdaf1dd51', + archiveFilename: 'chromium-312d84c-darwin.zip', + archiveChecksum: '020303e829745fd332ae9b39442ce570', + rawChecksum: '101dfea297c5818a7a3f3317a99dde02', binaryRelativePath: 'headless_shell-darwin/headless_shell', }, { platforms: ['linux'], - archiveFilename: 'chromium-2fac04a-linux.zip', - archiveChecksum: '5cd6b898a35f9dc0ba6f49d821b8a2a3', - rawChecksum: 'b3fd218d3c3446c388da4e6c8a82754c', + archiveFilename: 'chromium-312d84c-linux.zip', + archiveChecksum: '15ba9166a42f93ee92e42217b737018d', + rawChecksum: '3455db62ea4bd2d6e891e9155313305a', binaryRelativePath: 'headless_shell-linux/headless_shell', }, { platforms: ['win32'], - archiveFilename: 'chromium-2fac04a-windows.zip', - archiveChecksum: '1499a4d5847792d59b9c1a8ab7dc8b94', - rawChecksum: '08b48d2f3d23c4bc8b58779ca4a7b627', + archiveFilename: 'chromium-312d84c-windows.zip', + archiveChecksum: '3e36adfb755dacacc226ed5fd6b43105', + rawChecksum: 'ec7aa6cfecb172129474b447311275ec', binaryRelativePath: 'headless_shell-windows\\headless_shell.exe', }, ], diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/puppeteer.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/puppeteer.ts new file mode 100644 index 0000000000000..caa25aab06287 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/puppeteer.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 puppeteer from 'puppeteer'; +// @ts-ignore lacking typedefs which this module fixes +import puppeteerCore from 'puppeteer-core'; + +export const puppeteerLaunch: ( + opts?: puppeteer.LaunchOptions +) => Promise = puppeteerCore.launch.bind(puppeteerCore); diff --git a/x-pack/package.json b/x-pack/package.json index 7ae1ddb459275..accf7d44443ba 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -81,7 +81,7 @@ "@types/pngjs": "^3.3.1", "@types/prop-types": "^15.5.3", "@types/proper-lockfile": "^3.0.1", - "@types/puppeteer": "^1.12.4", + "@types/puppeteer": "^1.19.0", "@types/react": "^16.8.0", "@types/react-dom": "^16.8.0", "@types/react-redux": "^6.0.6", @@ -305,7 +305,7 @@ "prop-types": "^15.6.0", "proper-lockfile": "^3.2.0", "puid": "1.0.7", - "puppeteer-core": "^1.13.0", + "puppeteer-core": "^1.19.0", "raw-loader": "3.1.0", "react": "^16.8.0", "react-apollo": "^2.1.4", diff --git a/yarn.lock b/yarn.lock index 283c8b2e8b7ca..3093184a22577 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3801,10 +3801,10 @@ resolved "https://registry.yarnpkg.com/@types/proper-lockfile/-/proper-lockfile-3.0.1.tgz#dd770a2abce3adbcce3bd1ed892ce2f5f17fbc86" integrity sha512-ODOjqxmaNs0Zkij+BJovsNJRSX7BJrr681o8ZnNTNIcTermvVFzLpz/XFtfg3vNrlPVTJY1l4e9h2LvHoxC1lg== -"@types/puppeteer@^1.12.4": - version "1.12.4" - resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.12.4.tgz#8388efdb0b30a54a7e7c4831ca0d709191d77ff1" - integrity sha512-aaGbJaJ9TuF9vZfTeoh876sBa+rYJWPwtsmHmYr28pGr42ewJnkDTq2aeSKEmS39SqUdkwLj73y/d7rBSp7mDQ== +"@types/puppeteer@^1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.19.0.tgz#59f0050bae019cee7c3af2bb840a25892a3078b6" + integrity sha512-Db9LWOuTm2bR/qgPE7PQCmnsCQ6flHdULuIDWTks8YdQ/SGHKg5WGWG54gl0734NDKCTF5MbqAp2qWuvBiyQ3Q== dependencies: "@types/node" "*" @@ -22510,10 +22510,10 @@ punycode@^1.2.4, punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -puppeteer-core@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-1.13.0.tgz#f8001851e924e6e9ef6e9fae1778c3ab87c3f307" - integrity sha512-8MypjWVHu2EEdtN2HxhCsTtIYdJgiCcbGpHoosv265fzanfOICC2/DadLZq6/Qc/OKsovQmjkO+2vKMrV3BRfA== +puppeteer-core@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-1.19.0.tgz#3c3f98edb5862583e3a9c19cbc0da57ccc63ba5c" + integrity sha512-ZPbbjUymorIJomHBvdZX5+2gciUmQtAdepCrkweHH6rMJr96xd/dXzHgmYEOBMatH44SmJrcMtWkgsLHJqT89g== dependencies: debug "^4.1.0" extract-zip "^1.6.6" From 4d1671bacab198cbd684663a88d5079211f714c4 Mon Sep 17 00:00:00 2001 From: Tre Date: Tue, 27 Aug 2019 14:04:29 -0600 Subject: [PATCH 28/66] List grunt tasks from terminal (#43798) * Add ability to run $ grunt tasks # Lists available tasks :) * Move conf and task to config per review --- package.json | 1 + tasks/config/availabletasks.js | 35 ++++++++++++++++++++++++++++++++++ yarn.lock | 10 +++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tasks/config/availabletasks.js diff --git a/package.json b/package.json index 57d8a101b3478..2bcd08377e4bf 100644 --- a/package.json +++ b/package.json @@ -381,6 +381,7 @@ "geckodriver": "1.16.2", "getopts": "^2.2.4", "grunt": "1.0.4", + "grunt-available-tasks": "^0.6.3", "grunt-cli": "^1.2.0", "grunt-contrib-watch": "^1.1.0", "grunt-karma": "2.0.0", diff --git a/tasks/config/availabletasks.js b/tasks/config/availabletasks.js new file mode 100644 index 0000000000000..e9392cec19504 --- /dev/null +++ b/tasks/config/availabletasks.js @@ -0,0 +1,35 @@ +/* + * 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. + */ + +module.exports = function (grunt) { + const config = { + availabletasks: { + tasks: { + options: { + filter: 'exclude', + tasks: ['availabletasks', 'tasks'] + } + } + } + }; + grunt.registerTask('tasks', ['availabletasks']); + grunt.config.merge(config); + + return config; +}; diff --git a/yarn.lock b/yarn.lock index 3093184a22577..c6cab76b655d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13963,6 +13963,14 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +grunt-available-tasks@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/grunt-available-tasks/-/grunt-available-tasks-0.6.3.tgz#5be7f6fdda776b80a7b272a21f68bd3050f82260" + integrity sha1-W+f2/dp3a4CnsnKiH2i9MFD4ImA= + dependencies: + chalk "^1.1.1" + lodash "^4.10.0" + grunt-babel@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/grunt-babel/-/grunt-babel-8.0.0.tgz#92ef63aafadf938c488dc2f926ac9846e0c93d1b" @@ -18535,7 +18543,7 @@ lodash@4.17.13, lodash@4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93" integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA== -lodash@>4.17.4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.4, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.5: +lodash@>4.17.4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.4, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.5: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== From 9d8f808063b617a55388831c63fe365382279e2d Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 27 Aug 2019 13:40:36 -0700 Subject: [PATCH 29/66] skip flaky test (#44132) --- test/functional/apps/visualize/_tsvb_chart.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index ad88bd5299b9e..cf7930603c20a 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -93,7 +93,8 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('switch index patterns', () => { + // FLAKY: https://github.com/elastic/kibana/issues/44132 + describe.skip('switch index patterns', () => { beforeEach(async () => { log.debug('Load kibana_sample_data_flights data'); await esArchiver.loadIfNeeded('kibana_sample_data_flights'); From fed58dfcd41c34087d9d5399efd033e29240d6e1 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 27 Aug 2019 13:43:11 -0700 Subject: [PATCH 30/66] skip flaky suite (#44086) --- x-pack/legacy/plugins/task_manager/lib/intervals.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts b/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts index a8e186c397d76..4ae98f39cdbd8 100644 --- a/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts +++ b/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts @@ -24,7 +24,8 @@ beforeAll(() => { afterAll(() => fakeTimer.restore()); -describe('taskIntervals', () => { +// FLAKY: https://github.com/elastic/kibana/issues/44086 +describe.skip('taskIntervals', () => { describe('assertValidInterval', () => { test('it accepts intervals in the form `Nm`', () => { expect(() => assertValidInterval(`${_.random(1000)}m`)).not.toThrow(); From 9a99e71f5c5b343b6f5c09f653d6dd5e33f5e598 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 13:48:38 -0700 Subject: [PATCH 31/66] Update dependency xml-crypto to v1 (#43227) * Update dependency xml-crypto to v1 * update license override for package with LGPL OR MIT license --- src/dev/license_checker/config.ts | 2 +- x-pack/package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 2380207572337..4fe50baaf83be 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -94,7 +94,7 @@ export const LICENSE_OVERRIDES = { 'sha.js@2.4.11': ['BSD-3-Clause AND MIT'], // TODO can be removed if the ISSUE#239 is accepted on the source - 'xmldom@0.1.19': ['MIT'], + 'xmldom@0.1.27': ['MIT'], // TODO can be removed if the PR#9 is accepted on the source 'pause-stream@0.0.11': ['MIT'], diff --git a/x-pack/package.json b/x-pack/package.json index accf7d44443ba..febf0fb5d37be 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -174,7 +174,7 @@ "ts-loader": "^6.0.4", "typescript": "3.5.3", "vinyl-fs": "^3.0.2", - "xml-crypto": "^0.10.1", + "xml-crypto": "^1.4.0", "yargs": "4.8.1" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index c6cab76b655d1..140c4fdbbecae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30230,13 +30230,13 @@ xhr@^2.0.1: parse-headers "^2.0.0" xtend "^4.0.0" -xml-crypto@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8" - integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg= +xml-crypto@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-1.4.0.tgz#de1cec8cd31cbd689cd90d3d6e8a27d4ae807de7" + integrity sha512-K8FRdRxICVulK4WhiTUcJrRyAIJFPVOqxfurA3x/JlmXBTxy+SkEENF6GeRt7p/rB6WSOUS9g0gXNQw5n+407g== dependencies: - xmldom "=0.1.19" - xpath.js ">=0.0.3" + xmldom "0.1.27" + xpath "0.0.27" xml-name-validator@^2.0.1: version "2.0.1" @@ -30281,10 +30281,10 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" integrity sha1-UZy0ymhtAFqEINNJbz8MruzKWA8= -xmldom@=0.1.19: - version "0.1.19" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc" - integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw= +xmldom@0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= xmlhttprequest-ssl@~1.5.4: version "1.5.5" @@ -30296,10 +30296,10 @@ xorshift@^0.2.0: resolved "https://registry.yarnpkg.com/xorshift/-/xorshift-0.2.1.tgz#fcd82267e9351c13f0fb9c73307f25331d29c63a" integrity sha1-/NgiZ+k1HBPw+5xzMH8lMx0pxjo= -xpath.js@>=0.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1" - integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== +xpath@0.0.27: + version "0.0.27" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" + integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== xregexp@4.0.0: version "4.0.0" From fea743e01a541180091659e7c0d8baea1d028e2a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 13:52:05 -0700 Subject: [PATCH 32/66] Update dependency abortcontroller-polyfill to ^1.3.0 (#44027) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2bcd08377e4bf..1829fe7798923 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "@types/react-grid-layout": "^0.16.7", "@types/recompose": "^0.30.5", "JSONStream": "1.3.5", - "abortcontroller-polyfill": "^1.1.9", + "abortcontroller-polyfill": "^1.3.0", "angular": "1.6.9", "angular-aria": "1.6.6", "angular-elastic": "2.5.1", diff --git a/yarn.lock b/yarn.lock index 140c4fdbbecae..69276a445c83c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4450,10 +4450,10 @@ abort-controller@^2.0.3: dependencies: event-target-shim "^5.0.0" -abortcontroller-polyfill@^1.1.9: - version "1.1.9" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.1.9.tgz#9fefe359fda2e9e0932dc85e6106453ac393b2da" - integrity sha512-omvG7zOHIs3BphdH62Kh3xy8nlftAsTyp7PDa9EmC3Jz9pa6sZFYk7UhNgu9Y4sIBhj6jF0RgeFZYvPnsP5sBw== +abortcontroller-polyfill@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.3.0.tgz#de69af32ae926c210b7efbcc29bf644ee4838b00" + integrity sha512-lbWQgf+eRvku3va8poBlDBO12FigTQr9Zb7NIjXrePrhxWVKdCP2wbDl1tLDaYa18PWTom3UEWwdH13S46I+yA== accept@3.x.x: version "3.0.2" From 075c68baf35ef0bed23fb440fa483849ba703f12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 13:57:15 -0700 Subject: [PATCH 33/66] Update dependency babel-eslint to ^10.0.3 (#44029) --- package.json | 2 +- packages/eslint-config-kibana/package.json | 2 +- .../kbn-eslint-plugin-eslint/package.json | 2 +- yarn.lock | 25 +++++++++---------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 1829fe7798923..142e40fd6a9c1 100644 --- a/package.json +++ b/package.json @@ -345,7 +345,7 @@ "@typescript-eslint/parser": "1.13.0", "angular-mocks": "1.4.7", "archiver": "^3.0.0", - "babel-eslint": "^10.0.2", + "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", "babel-plugin-dynamic-import-node": "^2.3.0", "backport": "4.6.4", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 0d4d9ca9904ae..22c6a1a955d57 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -17,7 +17,7 @@ "peerDependencies": { "@typescript-eslint/eslint-plugin": "1.13.0", "@typescript-eslint/parser": "1.13.0", - "babel-eslint": "^10.0.2", + "babel-eslint": "^10.0.3", "eslint": "5.16.0", "eslint-plugin-babel": "^5.3.0", "eslint-plugin-ban": "1.2.0", diff --git a/packages/kbn-eslint-plugin-eslint/package.json b/packages/kbn-eslint-plugin-eslint/package.json index ef8f5691bab05..2f8f18103e146 100644 --- a/packages/kbn-eslint-plugin-eslint/package.json +++ b/packages/kbn-eslint-plugin-eslint/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "peerDependencies": { "eslint": "5.16.0", - "babel-eslint": "^10.0.2" + "babel-eslint": "^10.0.3" }, "dependencies": { "micromatch": "3.1.10", diff --git a/yarn.lock b/yarn.lock index 69276a445c83c..8fd717f238c1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5756,17 +5756,17 @@ babel-code-frame@^6.26.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-eslint@^10.0.2: - version "10.0.2" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456" - integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q== +babel-eslint@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" + integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA== dependencies: "@babel/code-frame" "^7.0.0" "@babel/parser" "^7.0.0" "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" - eslint-scope "3.7.1" eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" babel-generator@^6.18.0: version "6.26.1" @@ -11247,14 +11247,6 @@ eslint-rule-composer@^0.3.0: resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== -eslint-scope@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" - integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" @@ -24664,6 +24656,13 @@ resolve@^1.11.1: dependencies: path-parse "^1.0.6" +resolve@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" + integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== + dependencies: + path-parse "^1.0.6" + resolve@^1.5.0, resolve@^1.7.1: version "1.7.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" From 6e5f6ffbd61780a03a4786acf5fcd6d82983ff4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 14:01:08 -0700 Subject: [PATCH 34/66] Update dependency base64-js to ^1.3.1 (#44031) --- x-pack/package.json | 2 +- yarn.lock | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/package.json b/x-pack/package.json index febf0fb5d37be..117222a7a154c 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -111,7 +111,7 @@ "babel-plugin-mock-imports": "^1.0.1", "babel-plugin-require-context-hook": "npm:babel-plugin-require-context-hook-babel7@1.0.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "base64-js": "^1.2.1", + "base64-js": "^1.3.1", "base64url": "^3.0.1", "chalk": "^2.4.1", "chance": "1.0.18", diff --git a/yarn.lock b/yarn.lock index 8fd717f238c1b..bd52563651511 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6352,6 +6352,11 @@ base64-js@^1.0.2, base64-js@^1.1.2: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" integrity sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw== +base64-js@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + base64id@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" From 697033f0764f25535a22cf839c6d4d7eaf9d4184 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 14:02:40 -0700 Subject: [PATCH 35/66] Update dependency chroma-js to ^1.4.1 (#44033) --- x-pack/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index 117222a7a154c..d02c62c8c27a8 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -221,7 +221,7 @@ "bluebird": "3.5.5", "boom": "^7.2.0", "brace": "0.11.1", - "chroma-js": "^1.3.6", + "chroma-js": "^1.4.1", "classnames": "2.2.6", "concat-stream": "1.6.2", "constate": "^0.9.0", diff --git a/yarn.lock b/yarn.lock index bd52563651511..e68d42e3cd157 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7678,10 +7678,10 @@ chownr@^1.0.1, chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== -chroma-js@^1.3.6: - version "1.3.7" - resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.3.7.tgz#38db1b46c99b002b77aa5e6b6744589388f28425" - integrity sha512-ARq0P94NObL8hdQbgc+E33X9OHiNzdHO7epe3nC/KgxNRxkQcFpzNqnGeFjvOY2GxfVhbia686NXD2jByb1o0g== +chroma-js@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.4.1.tgz#eb2d9c4d1ff24616be84b35119f4d26f8205f134" + integrity sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ== chrome-trace-event@^1.0.0: version "1.0.0" From 48c610968e040b1e1825e72d45e9da27c7d48e20 Mon Sep 17 00:00:00 2001 From: Dmitry Lemeshko Date: Tue, 27 Aug 2019 23:15:29 +0200 Subject: [PATCH 36/66] esArchiver: retry kibana config update (#43987) * [services/es_archiver] retry uiSettings update * run x-pack-ciGroup7 30x times * Revert "run x-pack-ciGroup7 30x times" This reverts commit 80e199c3aa52bbab3ff388f74eaa3146ee69bb15. * [saved_object_api_integration/common/services] add retry service to the set * add retry service for x-pack api tests --- test/common/services/es_archiver.ts | 1 + test/common/services/kibana_server/extend_es_archiver.js | 6 ++++-- .../saved_object_api_integration/common/services/index.ts | 1 + x-pack/test/spaces_api_integration/common/config.ts | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/common/services/es_archiver.ts b/test/common/services/es_archiver.ts index cf8474662306b..e72bb49a76c0d 100644 --- a/test/common/services/es_archiver.ts +++ b/test/common/services/es_archiver.ts @@ -46,6 +46,7 @@ export function EsArchiverProvider({ getService, hasService }: FtrProviderContex KibanaServer.extendEsArchiver({ esArchiver, kibanaServer: getService('kibanaServer'), + retry: getService('retry'), defaults: config.get('uiSettings.defaults'), }); } diff --git a/test/common/services/kibana_server/extend_es_archiver.js b/test/common/services/kibana_server/extend_es_archiver.js index 30ac65b81bc6a..d934e6725a9f5 100644 --- a/test/common/services/kibana_server/extend_es_archiver.js +++ b/test/common/services/kibana_server/extend_es_archiver.js @@ -20,7 +20,7 @@ const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload']; const KIBANA_INDEX = '.kibana'; -export function extendEsArchiver({ esArchiver, kibanaServer, defaults }) { +export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) { // only extend the esArchiver if there are default uiSettings to restore if (!defaults) { return; @@ -36,7 +36,9 @@ export function extendEsArchiver({ esArchiver, kibanaServer, defaults }) { // if the kibana index was created by the esArchiver then update the uiSettings // with the defaults to make sure that they are always in place initially if (stats[KIBANA_INDEX] && (stats[KIBANA_INDEX].created || stats[KIBANA_INDEX].deleted)) { - await kibanaServer.uiSettings.update(defaults); + await retry.try(async () => { + await kibanaServer.uiSettings.update(defaults); + }); } return stats; diff --git a/x-pack/test/saved_object_api_integration/common/services/index.ts b/x-pack/test/saved_object_api_integration/common/services/index.ts index 7c201af3ef469..dc2f023c0ba02 100644 --- a/x-pack/test/saved_object_api_integration/common/services/index.ts +++ b/x-pack/test/saved_object_api_integration/common/services/index.ts @@ -16,6 +16,7 @@ export const services = { esSupertestWithoutAuth: apiIntegrationServices.esSupertestWithoutAuth, supertest: kibanaApiIntegrationServices.supertest, supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, + retry: kibanaApiIntegrationServices.retry, esArchiver: kibanaFunctionalServices.esArchiver, kibanaServer: kibanaFunctionalServices.kibanaServer, }; diff --git a/x-pack/test/spaces_api_integration/common/config.ts b/x-pack/test/spaces_api_integration/common/config.ts index 4c8cc3a8234ee..daf3b45982a34 100644 --- a/x-pack/test/spaces_api_integration/common/config.ts +++ b/x-pack/test/spaces_api_integration/common/config.ts @@ -38,6 +38,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) esSupertestWithoutAuth: config.xpack.api.get('services.esSupertestWithoutAuth'), supertest: config.kibana.api.get('services.supertest'), supertestWithoutAuth: config.xpack.api.get('services.supertestWithoutAuth'), + retry: config.xpack.api.get('services.retry'), esArchiver: config.kibana.functional.get('services.esArchiver'), kibanaServer: config.kibana.functional.get('services.kibanaServer'), }, From f706eda5ec1300ef8d7453355f9f9381abc32d68 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2019 14:17:40 -0700 Subject: [PATCH 37/66] Update dependency @elastic/makelogs to ^4.5.0 (#43912) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 142e40fd6a9c1..a9be8d5f36b6c 100644 --- a/package.json +++ b/package.json @@ -266,7 +266,7 @@ "@elastic/eslint-config-kibana": "0.15.0", "@elastic/eslint-plugin-eui": "0.0.1", "@elastic/github-checks-reporter": "0.0.20b3", - "@elastic/makelogs": "^4.4.0", + "@elastic/makelogs": "^4.5.0", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", "@kbn/eslint-import-resolver-kibana": "2.0.0", diff --git a/yarn.lock b/yarn.lock index e68d42e3cd157..7c3c91023c576 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1200,10 +1200,10 @@ vscode-languageserver "^5.2.1" vscode-languageserver-types "^3.14.0" -"@elastic/makelogs@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@elastic/makelogs/-/makelogs-4.4.0.tgz#bfe9d774afdaa923583e436e3c8459ded5545247" - integrity sha512-m+nVI5wv212li2f4eShl/ZxjhkYz2lFQzqxDM5xWnhF/vL90tIbTAlLj+RkwXTT1cnmcEzXWNxFsZAfyyiux0A== +"@elastic/makelogs@^4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@elastic/makelogs/-/makelogs-4.5.0.tgz#d9256eb89fbfe7df9ff442cb31c598c5594370d7" + integrity sha512-xf8pG6vYoztTJUIjJDxHVRpwzMfsruZ33GjwbkzvKr4C/ZN1fjk5/iZYAnoGkknZgWVB1YEwTdFdUBR9h7ejcg== dependencies: async "^1.4.2" bluebird "^2.10.0" From 6f402b7382fb15e5c4f9380b9ea23b4c6ec1bc32 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Tue, 27 Aug 2019 14:25:48 -0700 Subject: [PATCH 38/66] [SR] SLM create and edit policies (#43390) * add buttons and links to create/edit policy * set up add policy form * start create policy form, including loading/error states and redirect for repository select field. add inline option to SectionLoading. add actions prop to SectionError * add snapshot name field * Change page title upon app navigation, improve breadcrumbs * Add on cancel to policy form, reorder fields * Add simple cron field * First pass at create/edit policy functionality * Adjust permissions for SLM tab * Adjust no snapshots prompt based on if policies exist or not * Add selectable indices to policy form * Move cron editor from rollup jobs to ES UI shared folder * Used shared cron editor for slm policy create/edit * Adjust copies; add duplicate schedule warning callout * Surface in progress information * Fix doc link for 7.x * Fix rollup tests * Copy edits from review * Add ES endpoint to request review * Remove unused imports * Fix i18n by cleaning up typo'd text * Remove unused import * Fix permissions and i18n * Revert change to Logistics copy * Fix bugs and PR feedback * Add cancel button to form and add comment for list * Adjust timeout comment * Fix bug with list of indices in detail panel when clicking through table * Add comment about EUI bug --- .i18nrc.json | 3 +- .../components/cron_editor/cron_daily.js | 29 +- .../components/cron_editor/cron_editor.js | 27 +- .../components/cron_editor/cron_hourly.js | 71 +++ .../components/cron_editor/cron_monthly.js | 37 +- .../components/cron_editor/cron_weekly.js | 37 +- .../components/cron_editor/cron_yearly.js | 45 +- .../public/components/cron_editor/index.d.ts | 26 + .../public/components/cron_editor/index.js | 21 + .../components/cron_editor}/services/cron.js | 19 +- .../cron_editor/services/humanized_numbers.js | 91 ++++ .../components/cron_editor/services/index.js | 21 + .../job_create_logistics.test.js | 70 +-- .../components/cron_editor/cron_hourly.js | 58 -- .../job_create/steps/components/index.js | 1 - .../job_create/steps/step_logistics.js | 4 +- .../sections/job_create/steps_config/index.js | 3 +- .../crud_app/services/humanized_numbers.js | 78 --- .../rollup/public/crud_app/services/index.js | 17 - .../snapshot_restore/common/constants.ts | 1 + .../snapshot_restore/common/lib/index.ts | 6 + .../lib/policy_serialization.test.ts | 0 .../lib/policy_serialization.ts | 33 +- .../lib/snapshot_serialization.test.ts | 0 .../lib/snapshot_serialization.ts | 26 +- .../snapshot_restore/common/types/policy.ts | 25 +- .../snapshot_restore/common/types/snapshot.ts | 4 +- .../snapshot_restore/public/app/app.tsx | 14 +- .../public/app/components/index.ts | 1 + .../components/policy_execute_provider.tsx | 13 +- .../components/policy_form/_policy_form.scss | 16 + .../app/components/policy_form/index.ts | 6 + .../app/components/policy_form/navigation.tsx | 55 ++ .../components/policy_form/policy_form.tsx | 217 ++++++++ .../app/components/policy_form/steps/index.ts | 22 + .../policy_form/steps/step_logistics.tsx | 507 ++++++++++++++++++ .../policy_form/steps/step_review.tsx | 329 ++++++++++++ .../policy_form/steps/step_settings.tsx | 454 ++++++++++++++++ .../components/repository_delete_provider.tsx | 6 +- .../steps/step_logistics.tsx | 8 +- .../public/app/components/section_error.tsx | 13 +- .../public/app/components/section_loading.tsx | 28 +- .../public/app/constants/index.ts | 7 + .../snapshot_restore/public/app/index.scss | 1 + .../public/app/sections/home/_home.scss | 10 + .../public/app/sections/home/home.tsx | 9 +- .../policy_details/policy_details.tsx | 194 +++++-- .../policy_details/tabs/tab_history.tsx | 22 +- .../policy_details/tabs/tab_summary.tsx | 124 +++-- .../sections/home/policy_list/policy_list.tsx | 112 +++- .../policy_list/policy_table/policy_table.tsx | 198 ++++--- .../home/repository_list/repository_list.tsx | 9 +- .../home/restore_list/restore_list.tsx | 3 +- .../snapshot_details/tabs/tab_summary.tsx | 9 +- .../home/snapshot_list/snapshot_list.tsx | 171 +++--- .../public/app/sections/index.ts | 2 + .../public/app/sections/policy_add/index.ts} | 2 +- .../app/sections/policy_add/policy_add.tsx | 132 +++++ .../public/app/sections/policy_edit/index.ts | 7 + .../app/sections/policy_edit/policy_edit.tsx | 210 ++++++++ .../repository_add/repository_add.tsx | 14 +- .../repository_edit/repository_edit.tsx | 5 +- .../restore_snapshot/restore_snapshot.tsx | 5 +- .../documentation/documentation_links.ts | 22 +- .../app/services/http/policy_requests.ts | 43 +- .../app/services/navigation/breadcrumb.ts | 144 +++-- .../app/services/navigation/doc_title.ts | 24 + .../public/app/services/navigation/index.ts | 1 + .../public/app/services/navigation/links.ts | 26 +- .../public/app/services/text/text.ts | 18 + .../public/app/services/validation/index.ts | 2 + .../services/validation/validate_policy.ts | 96 ++++ .../plugins/snapshot_restore/public/plugin.ts | 11 +- .../snapshot_restore/public/shared_imports.ts | 5 + .../plugins/snapshot_restore/public/shim.ts | 9 + .../server/client/elasticsearch_slm.ts | 14 + .../snapshot_restore/server/lib/index.ts | 4 +- .../snapshot_restore/server/routes/api/app.ts | 3 +- .../server/routes/api/policy.test.ts | 116 +++- .../server/routes/api/policy.ts | 69 ++- .../server/routes/api/snapshots.test.ts | 13 +- .../server/routes/api/snapshots.ts | 17 +- .../translations/translations/ja-JP.json | 39 -- .../translations/translations/zh-CN.json | 39 -- 84 files changed, 3695 insertions(+), 708 deletions(-) rename {x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps => src/plugins/es_ui_shared/public}/components/cron_editor/cron_daily.js (60%) rename {x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps => src/plugins/es_ui_shared/public}/components/cron_editor/cron_editor.js (88%) create mode 100644 src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js rename {x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps => src/plugins/es_ui_shared/public}/components/cron_editor/cron_monthly.js (63%) rename {x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps => src/plugins/es_ui_shared/public}/components/cron_editor/cron_weekly.js (63%) rename {x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps => src/plugins/es_ui_shared/public}/components/cron_editor/cron_yearly.js (65%) create mode 100644 src/plugins/es_ui_shared/public/components/cron_editor/index.d.ts create mode 100644 src/plugins/es_ui_shared/public/components/cron_editor/index.js rename {x-pack/legacy/plugins/rollup/public/crud_app => src/plugins/es_ui_shared/public/components/cron_editor}/services/cron.js (55%) create mode 100644 src/plugins/es_ui_shared/public/components/cron_editor/services/humanized_numbers.js create mode 100644 src/plugins/es_ui_shared/public/components/cron_editor/services/index.js delete mode 100644 x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_hourly.js delete mode 100644 x-pack/legacy/plugins/rollup/public/crud_app/services/humanized_numbers.js rename x-pack/legacy/plugins/snapshot_restore/{server => common}/lib/policy_serialization.test.ts (100%) rename x-pack/legacy/plugins/snapshot_restore/{server => common}/lib/policy_serialization.ts (70%) rename x-pack/legacy/plugins/snapshot_restore/{server => common}/lib/snapshot_serialization.test.ts (100%) rename x-pack/legacy/plugins/snapshot_restore/{server => common}/lib/snapshot_serialization.ts (80%) create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/_policy_form.scss create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/index.ts create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/navigation.tsx create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/policy_form.tsx create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/index.ts create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_settings.tsx rename x-pack/legacy/plugins/{rollup/public/crud_app/sections/job_create/steps/components/cron_editor/index.js => snapshot_restore/public/app/sections/policy_add/index.ts} (84%) create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/index.ts create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/doc_title.ts create mode 100644 x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts diff --git a/.i18nrc.json b/.i18nrc.json index 080fc84036ef9..6efa56fb771c2 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -26,7 +26,8 @@ "tsvb": "src/legacy/core_plugins/metrics", "kbnESQuery": "packages/kbn-es-query", "inspector": "src/plugins/inspector", - "kibana-react": "src/plugins/kibana_react" + "kibana-react": "src/plugins/kibana_react", + "esUi": "src/plugins/es_ui_shared" }, "exclude": ["src/legacy/ui/ui_render/ui_render_mixin.js"], "translations": [] diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_daily.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js similarity index 60% rename from x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_daily.js rename to src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js index 8199ea8bf0b21..de14cd43165c2 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_daily.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_daily.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 React, { Fragment } from 'react'; @@ -27,12 +40,12 @@ export const CronDaily = ({ )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > @@ -45,13 +58,13 @@ export const CronDaily = ({ )} - data-test-subj="rollupJobCreateFrequencyDailyHourSelect" + data-test-subj="cronFrequencyDailyHourSelect" /> @@ -68,7 +81,7 @@ export const CronDaily = ({ )} - data-test-subj="rollupJobCreateFrequencyDailyMinuteSelect" + data-test-subj="cronFrequencyDailyMinuteSelect" /> diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_editor.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js similarity index 88% rename from x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_editor.js rename to src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js index c0eb5bb624487..64d6405603dd7 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_editor.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 React, { Component, Fragment } from 'react'; @@ -27,7 +40,7 @@ import { WEEK, MONTH, YEAR, -} from '../../../../../services'; +} from './services'; import { CronHourly } from './cron_hourly'; import { CronDaily } from './cron_daily'; @@ -331,7 +344,7 @@ export class CronEditor extends Component { )} @@ -346,13 +359,13 @@ export class CronEditor extends Component { )} - data-test-subj="rollupJobCreateFrequencySelect" + data-test-subj="cronFrequencySelect" /> diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js new file mode 100644 index 0000000000000..a207998a7f73b --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_hourly.js @@ -0,0 +1,71 @@ +/* + * 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 React, { Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiFormRow, + EuiSelect, + EuiText, +} from '@elastic/eui'; + +export const CronHourly = ({ + minute, + minuteOptions, + onChange, +}) => ( + + + )} + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + onChange({ minute: e.target.value })} + fullWidth + prepend={( + + + + + + )} + data-test-subj="cronFrequencyHourlyMinuteSelect" + /> + + +); + +CronHourly.propTypes = { + minute: PropTypes.string.isRequired, + minuteOptions: PropTypes.array.isRequired, + onChange: PropTypes.func.isRequired, +}; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_monthly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js similarity index 63% rename from x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_monthly.js rename to src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js index 52a7701e4422e..e90a194d83d93 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_monthly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_monthly.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 React, { Fragment } from 'react'; @@ -29,12 +42,12 @@ export const CronMonthly = ({ )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > )} - data-test-subj="rollupJobCreateFrequencyMonthlyDateSelect" + data-test-subj="cronFrequencyMonthlyDateSelect" /> )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > @@ -76,13 +89,13 @@ export const CronMonthly = ({ )} - data-test-subj="rollupJobCreateFrequencyMonthlyHourSelect" + data-test-subj="cronFrequencyMonthlyHourSelect" /> @@ -99,7 +112,7 @@ export const CronMonthly = ({ )} - data-test-subj="rollupJobCreateFrequencyMonthlyMinuteSelect" + data-test-subj="cronFrequencyMonthlyMinuteSelect" /> diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_weekly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js similarity index 63% rename from x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_weekly.js rename to src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js index 8c41f366bb2be..fbf9e37e46b48 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_weekly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_weekly.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 React, { Fragment } from 'react'; @@ -29,12 +42,12 @@ export const CronWeekly = ({ )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > )} - data-test-subj="rollupJobCreateFrequencyWeeklyDaySelect" + data-test-subj="cronFrequencyWeeklyDaySelect" /> )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > @@ -76,13 +89,13 @@ export const CronWeekly = ({ )} - data-test-subj="rollupJobCreateFrequencyWeeklyHourSelect" + data-test-subj="cronFrequencyWeeklyHourSelect" /> @@ -99,7 +112,7 @@ export const CronWeekly = ({ )} - data-test-subj="rollupJobCreateFrequencyWeeklyMinuteSelect" + data-test-subj="cronFrequencyWeeklyMinuteSelect" /> diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_yearly.js b/src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js similarity index 65% rename from x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_yearly.js rename to src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js index 900e77f63accb..5e19ec7b35b0c 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_yearly.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_yearly.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 React, { Fragment } from 'react'; @@ -31,12 +44,12 @@ export const CronYearly = ({ )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > )} - data-test-subj="rollupJobCreateFrequencyYearlyMonthSelect" + data-test-subj="cronFrequencyYearlyMonthSelect" /> )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > )} - data-test-subj="rollupJobCreateFrequencyYearlyDateSelect" + data-test-subj="cronFrequencyYearlyDateSelect" /> )} fullWidth - data-test-subj="rollupCronFrequencyConfiguration" + data-test-subj="cronFrequencyConfiguration" > @@ -107,13 +120,13 @@ export const CronYearly = ({ )} - data-test-subj="rollupJobCreateFrequencyYearlyHourSelect" + data-test-subj="cronFrequencyYearlyHourSelect" /> @@ -130,7 +143,7 @@ export const CronYearly = ({ )} - data-test-subj="rollupJobCreateFrequencyYearlyMinuteSelect" + data-test-subj="cronFrequencyYearlyMinuteSelect" /> diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/index.d.ts b/src/plugins/es_ui_shared/public/components/cron_editor/index.d.ts new file mode 100644 index 0000000000000..b318587057c76 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/cron_editor/index.d.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +export declare const MINUTE: string; +export declare const HOUR: string; +export declare const DAY: string; +export declare const WEEK: string; +export declare const MONTH: string; +export declare const YEAR: string; +export declare const CronEditor: any; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/index.js b/src/plugins/es_ui_shared/public/components/cron_editor/index.js new file mode 100644 index 0000000000000..6c4539a6c3f75 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/cron_editor/index.js @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export { CronEditor } from './cron_editor'; +export { MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from './services'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/services/cron.js b/src/plugins/es_ui_shared/public/components/cron_editor/services/cron.js similarity index 55% rename from x-pack/legacy/plugins/rollup/public/crud_app/services/cron.js rename to src/plugins/es_ui_shared/public/components/cron_editor/services/cron.js index 97e474f8df27c..71f6253375ef1 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/services/cron.js +++ b/src/plugins/es_ui_shared/public/components/cron_editor/services/cron.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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. */ export const MINUTE = 'MINUTE'; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/services/humanized_numbers.js b/src/plugins/es_ui_shared/public/components/cron_editor/services/humanized_numbers.js new file mode 100644 index 0000000000000..b3cb58bea24e5 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/cron_editor/services/humanized_numbers.js @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +// The international ISO standard dictates Monday as the first day of the week, but cron patterns +// use Sunday as the first day, so we're going with the cron way. +const dayOrdinalToDayNameMap = { + 0: i18n.translate('esUi.cronEditor.day.sunday', { defaultMessage: 'Sunday' }), + 1: i18n.translate('esUi.cronEditor.day.monday', { defaultMessage: 'Monday' }), + 2: i18n.translate('esUi.cronEditor.day.tuesday', { defaultMessage: 'Tuesday' }), + 3: i18n.translate('esUi.cronEditor.day.wednesday', { defaultMessage: 'Wednesday' }), + 4: i18n.translate('esUi.cronEditor.day.thursday', { defaultMessage: 'Thursday' }), + 5: i18n.translate('esUi.cronEditor.day.friday', { defaultMessage: 'Friday' }), + 6: i18n.translate('esUi.cronEditor.day.saturday', { defaultMessage: 'Saturday' }), +}; + +const monthOrdinalToMonthNameMap = { + 0: i18n.translate('esUi.cronEditor.month.january', { defaultMessage: 'January' }), + 1: i18n.translate('esUi.cronEditor.month.february', { defaultMessage: 'February' }), + 2: i18n.translate('esUi.cronEditor.month.march', { defaultMessage: 'March' }), + 3: i18n.translate('esUi.cronEditor.month.april', { defaultMessage: 'April' }), + 4: i18n.translate('esUi.cronEditor.month.may', { defaultMessage: 'May' }), + 5: i18n.translate('esUi.cronEditor.month.june', { defaultMessage: 'June' }), + 6: i18n.translate('esUi.cronEditor.month.july', { defaultMessage: 'July' }), + 7: i18n.translate('esUi.cronEditor.month.august', { defaultMessage: 'August' }), + 8: i18n.translate('esUi.cronEditor.month.september', { defaultMessage: 'September' }), + 9: i18n.translate('esUi.cronEditor.month.october', { defaultMessage: 'October' }), + 10: i18n.translate('esUi.cronEditor.month.november', { defaultMessage: 'November' }), + 11: i18n.translate('esUi.cronEditor.month.december', { defaultMessage: 'December' }), +}; + +export function getOrdinalValue(number) { + // TODO: This is breaking reporting pdf generation. Possibly due to phantom not setting locale, + // which is needed by i18n (formatjs). Need to verify, fix, and restore i18n in place of static stings. + // return i18n.translate('esUi.cronEditor.number.ordinal', { + // defaultMessage: '{number, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', + // values: { number }, + // }); + // TODO: https://github.com/elastic/kibana/issues/27136 + + // Protects against falsey (including 0) values + const num = number && number.toString(); + let lastDigit = num && num.substr(-1); + let ordinal; + + if(!lastDigit) { + return number; + } + lastDigit = parseFloat(lastDigit); + + switch(lastDigit) { + case 1: + ordinal = 'st'; + break; + case 2: + ordinal = 'nd'; + break; + case 3: + ordinal = 'rd'; + break; + default: + ordinal = 'th'; + } + + return `${num}${ordinal}`; +} + +export function getDayName(dayOrdinal) { + return dayOrdinalToDayNameMap[dayOrdinal]; +} + +export function getMonthName(monthOrdinal) { + return monthOrdinalToMonthNameMap[monthOrdinal]; +} diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/services/index.js b/src/plugins/es_ui_shared/public/components/cron_editor/services/index.js new file mode 100644 index 0000000000000..cb4af15bf1945 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/cron_editor/services/index.js @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export * from './cron'; +export * from './humanized_numbers'; diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js index 62de4612d4c09..99a0aa0935152 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from '../../public/crud_app/services'; +import { MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from '../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/legacy/ui/public/index_patterns'; import { setupEnvironment, pageHelpers } from './helpers'; @@ -162,7 +162,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { describe('rollup cron', () => { const changeFrequency = (value) => { - find('rollupJobCreateFrequencySelect').simulate('change', { target: { value } }); + find('cronFrequencySelect').simulate('change', { target: { value } }); }; const generateStringSequenceOfNumbers = (total) => ( @@ -171,7 +171,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { describe('frequency', () => { it('should allow "minute", "hour", "day", "week", "month", "year"', () => { - const frequencySelect = find('rollupJobCreateFrequencySelect'); + const frequencySelect = find('cronFrequencySelect'); const options = frequencySelect.find('option').map(option => option.text()); expect(options).toEqual(['minute', 'hour', 'day', 'week', 'month', 'year']); }); @@ -179,7 +179,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { describe('every minute', () => { it('should not have any additional configuration', () => { changeFrequency(MINUTE); - expect(find('rollupCronFrequencyConfiguration').length).toBe(0); + expect(find('cronFrequencyConfiguration').length).toBe(0); }); }); @@ -189,12 +189,12 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should have 1 additional configuration', () => { - expect(find('rollupCronFrequencyConfiguration').length).toBe(1); - expect(exists('rollupJobCreateFrequencyHourlyMinuteSelect')).toBe(true); + expect(find('cronFrequencyConfiguration').length).toBe(1); + expect(exists('cronFrequencyHourlyMinuteSelect')).toBe(true); }); it('should allow to select any minute from 00 -> 59', () => { - const minutSelect = find('rollupJobCreateFrequencyHourlyMinuteSelect'); + const minutSelect = find('cronFrequencyHourlyMinuteSelect'); const options = minutSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); @@ -206,19 +206,19 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should have 1 additional configuration with hour and minute selects', () => { - expect(find('rollupCronFrequencyConfiguration').length).toBe(1); - expect(exists('rollupJobCreateFrequencyDailyHourSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyDailyMinuteSelect')).toBe(true); + expect(find('cronFrequencyConfiguration').length).toBe(1); + expect(exists('cronFrequencyDailyHourSelect')).toBe(true); + expect(exists('cronFrequencyDailyMinuteSelect')).toBe(true); }); it('should allow to select any hour from 00 -> 23', () => { - const hourSelect = find('rollupJobCreateFrequencyDailyHourSelect'); + const hourSelect = find('cronFrequencyDailyHourSelect'); const options = hourSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { - const minutSelect = find('rollupJobCreateFrequencyDailyMinuteSelect'); + const minutSelect = find('cronFrequencyDailyMinuteSelect'); const options = minutSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); @@ -230,14 +230,14 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should have 2 additional configurations with day, hour and minute selects', () => { - expect(find('rollupCronFrequencyConfiguration').length).toBe(2); - expect(exists('rollupJobCreateFrequencyWeeklyDaySelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyWeeklyHourSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyWeeklyMinuteSelect')).toBe(true); + expect(find('cronFrequencyConfiguration').length).toBe(2); + expect(exists('cronFrequencyWeeklyDaySelect')).toBe(true); + expect(exists('cronFrequencyWeeklyHourSelect')).toBe(true); + expect(exists('cronFrequencyWeeklyMinuteSelect')).toBe(true); }); it('should allow to select any day of the week', () => { - const hourSelect = find('rollupJobCreateFrequencyWeeklyDaySelect'); + const hourSelect = find('cronFrequencyWeeklyDaySelect'); const options = hourSelect.find('option').map(option => option.text()); expect(options).toEqual([ 'Sunday', @@ -251,13 +251,13 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should allow to select any hour from 00 -> 23', () => { - const hourSelect = find('rollupJobCreateFrequencyWeeklyHourSelect'); + const hourSelect = find('cronFrequencyWeeklyHourSelect'); const options = hourSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { - const minutSelect = find('rollupJobCreateFrequencyWeeklyMinuteSelect'); + const minutSelect = find('cronFrequencyWeeklyMinuteSelect'); const options = minutSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); @@ -269,26 +269,26 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should have 2 additional configurations with date, hour and minute selects', () => { - expect(find('rollupCronFrequencyConfiguration').length).toBe(2); - expect(exists('rollupJobCreateFrequencyMonthlyDateSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyMonthlyHourSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyMonthlyMinuteSelect')).toBe(true); + expect(find('cronFrequencyConfiguration').length).toBe(2); + expect(exists('cronFrequencyMonthlyDateSelect')).toBe(true); + expect(exists('cronFrequencyMonthlyHourSelect')).toBe(true); + expect(exists('cronFrequencyMonthlyMinuteSelect')).toBe(true); }); it('should allow to select any date of the month from 1st to 31st', () => { - const dateSelect = find('rollupJobCreateFrequencyMonthlyDateSelect'); + const dateSelect = find('cronFrequencyMonthlyDateSelect'); const options = dateSelect.find('option').map(option => option.text()); expect(options.length).toEqual(31); }); it('should allow to select any hour from 00 -> 23', () => { - const hourSelect = find('rollupJobCreateFrequencyMonthlyHourSelect'); + const hourSelect = find('cronFrequencyMonthlyHourSelect'); const options = hourSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { - const minutSelect = find('rollupJobCreateFrequencyMonthlyMinuteSelect'); + const minutSelect = find('cronFrequencyMonthlyMinuteSelect'); const options = minutSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); @@ -300,15 +300,15 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should have 3 additional configurations with month, date, hour and minute selects', () => { - expect(find('rollupCronFrequencyConfiguration').length).toBe(3); - expect(exists('rollupJobCreateFrequencyYearlyMonthSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyYearlyDateSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyYearlyHourSelect')).toBe(true); - expect(exists('rollupJobCreateFrequencyYearlyMinuteSelect')).toBe(true); + expect(find('cronFrequencyConfiguration').length).toBe(3); + expect(exists('cronFrequencyYearlyMonthSelect')).toBe(true); + expect(exists('cronFrequencyYearlyDateSelect')).toBe(true); + expect(exists('cronFrequencyYearlyHourSelect')).toBe(true); + expect(exists('cronFrequencyYearlyMinuteSelect')).toBe(true); }); it('should allow to select any month of the year', () => { - const monthSelect = find('rollupJobCreateFrequencyYearlyMonthSelect'); + const monthSelect = find('cronFrequencyYearlyMonthSelect'); const options = monthSelect.find('option').map(option => option.text()); expect(options).toEqual([ 'January', @@ -327,19 +327,19 @@ describe('Create Rollup Job, step 1: Logistics', () => { }); it('should allow to select any date of the month from 1st to 31st', () => { - const dateSelect = find('rollupJobCreateFrequencyYearlyDateSelect'); + const dateSelect = find('cronFrequencyYearlyDateSelect'); const options = dateSelect.find('option').map(option => option.text()); expect(options.length).toEqual(31); }); it('should allow to select any hour from 00 -> 23', () => { - const hourSelect = find('rollupJobCreateFrequencyYearlyHourSelect'); + const hourSelect = find('cronFrequencyYearlyHourSelect'); const options = hourSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(24)); }); it('should allow to select any miute from 00 -> 59', () => { - const minutSelect = find('rollupJobCreateFrequencyYearlyMinuteSelect'); + const minutSelect = find('cronFrequencyYearlyMinuteSelect'); const options = minutSelect.find('option').map(option => option.text()); expect(options).toEqual(generateStringSequenceOfNumbers(60)); }); diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_hourly.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_hourly.js deleted file mode 100644 index bab1704d4e721..0000000000000 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/cron_hourly.js +++ /dev/null @@ -1,58 +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, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { - EuiFormRow, - EuiSelect, - EuiText, -} from '@elastic/eui'; - -export const CronHourly = ({ - minute, - minuteOptions, - onChange, -}) => ( - - - )} - fullWidth - data-test-subj="rollupCronFrequencyConfiguration" - > - onChange({ minute: e.target.value })} - fullWidth - prepend={( - - - - - - )} - data-test-subj="rollupJobCreateFrequencyHourlyMinuteSelect" - /> - - -); - -CronHourly.propTypes = { - minute: PropTypes.string.isRequired, - minuteOptions: PropTypes.array.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js index e5c8eb2e2e17f..1efdcb7caec92 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/index.js @@ -5,5 +5,4 @@ */ export { FieldChooser } from './field_chooser'; -export { CronEditor } from './cron_editor'; export { StepError } from './step_error'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index 382d1b7ccca47..62b0045395099 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -24,10 +24,12 @@ import { EuiTitle, } from '@elastic/eui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CronEditor } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/index_patterns'; import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/indices'; import { logisticalDetailsUrl, cronUrl } from '../../../services'; -import { CronEditor, StepError } from './components'; +import { StepError } from './components'; const indexPatternIllegalCharacters = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.join(' '); const indexIllegalCharacters = INDEX_ILLEGAL_CHARACTERS_VISIBLE.join(' '); diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js index 09c427a49f028..db77844dcfe35 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js @@ -8,7 +8,8 @@ import cloneDeep from 'lodash/lang/cloneDeep'; import get from 'lodash/object/get'; import pick from 'lodash/object/pick'; -import { WEEK } from '../../../services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { WEEK } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; import { validateId } from './validate_id'; import { validateIndexPattern } from './validate_index_pattern'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/services/humanized_numbers.js b/x-pack/legacy/plugins/rollup/public/crud_app/services/humanized_numbers.js deleted file mode 100644 index ce779e62df926..0000000000000 --- a/x-pack/legacy/plugins/rollup/public/crud_app/services/humanized_numbers.js +++ /dev/null @@ -1,78 +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 { i18n } from '@kbn/i18n'; - -// The international ISO standard dictates Monday as the first day of the week, but cron patterns -// use Sunday as the first day, so we're going with the cron way. -const dayOrdinalToDayNameMap = { - 0: i18n.translate('xpack.rollupJobs.util.day.sunday', { defaultMessage: 'Sunday' }), - 1: i18n.translate('xpack.rollupJobs.util.day.monday', { defaultMessage: 'Monday' }), - 2: i18n.translate('xpack.rollupJobs.util.day.tuesday', { defaultMessage: 'Tuesday' }), - 3: i18n.translate('xpack.rollupJobs.util.day.wednesday', { defaultMessage: 'Wednesday' }), - 4: i18n.translate('xpack.rollupJobs.util.day.thursday', { defaultMessage: 'Thursday' }), - 5: i18n.translate('xpack.rollupJobs.util.day.friday', { defaultMessage: 'Friday' }), - 6: i18n.translate('xpack.rollupJobs.util.day.saturday', { defaultMessage: 'Saturday' }), -}; - -const monthOrdinalToMonthNameMap = { - 0: i18n.translate('xpack.rollupJobs.util.month.january', { defaultMessage: 'January' }), - 1: i18n.translate('xpack.rollupJobs.util.month.february', { defaultMessage: 'February' }), - 2: i18n.translate('xpack.rollupJobs.util.month.march', { defaultMessage: 'March' }), - 3: i18n.translate('xpack.rollupJobs.util.month.april', { defaultMessage: 'April' }), - 4: i18n.translate('xpack.rollupJobs.util.month.may', { defaultMessage: 'May' }), - 5: i18n.translate('xpack.rollupJobs.util.month.june', { defaultMessage: 'June' }), - 6: i18n.translate('xpack.rollupJobs.util.month.july', { defaultMessage: 'July' }), - 7: i18n.translate('xpack.rollupJobs.util.month.august', { defaultMessage: 'August' }), - 8: i18n.translate('xpack.rollupJobs.util.month.september', { defaultMessage: 'September' }), - 9: i18n.translate('xpack.rollupJobs.util.month.october', { defaultMessage: 'October' }), - 10: i18n.translate('xpack.rollupJobs.util.month.november', { defaultMessage: 'November' }), - 11: i18n.translate('xpack.rollupJobs.util.month.december', { defaultMessage: 'December' }), -}; - -export function getOrdinalValue(number) { - // TODO: This is breaking reporting pdf generation. Possibly due to phantom not setting locale, - // which is needed by i18n (formatjs). Need to verify, fix, and restore i18n in place of static stings. - // return i18n.translate('xpack.rollupJobs.util.number.ordinal', { - // defaultMessage: '{number, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', - // values: { number }, - // }); - // TODO: https://github.com/elastic/kibana/issues/27136 - - // Protects against falsey (including 0) values - const num = number && number.toString(); - let lastDigit = num && num.substr(-1); - let ordinal; - - if(!lastDigit) { - return number; - } - lastDigit = parseFloat(lastDigit); - - switch(lastDigit) { - case 1: - ordinal = 'st'; - break; - case 2: - ordinal = 'nd'; - break; - case 3: - ordinal = 'rd'; - break; - default: - ordinal = 'th'; - } - - return `${num}${ordinal}`; -} - -export function getDayName(dayOrdinal) { - return dayOrdinalToDayNameMap[dayOrdinal]; -} - -export function getMonthName(monthOrdinal) { - return monthOrdinalToMonthNameMap[monthOrdinal]; -} diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js b/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js index b3a7cdb9a286d..c52c9064b8d76 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/services/index.js @@ -23,17 +23,6 @@ export { createBreadcrumb, } from './breadcrumbs'; -export { - cronExpressionToParts, - cronPartsToExpression, - MINUTE, - HOUR, - DAY, - WEEK, - MONTH, - YEAR, -} from './cron'; - export { logisticalDetailsUrl, dateHistogramDetailsUrl, @@ -61,12 +50,6 @@ export { getHttp, } from './http_provider'; -export { - getOrdinalValue, - getDayName, - getMonthName, -} from './humanized_numbers'; - export { serializeJob, deserializeJob, diff --git a/x-pack/legacy/plugins/snapshot_restore/common/constants.ts b/x-pack/legacy/plugins/snapshot_restore/common/constants.ts index d876c6ffd581d..a881bf3081c5e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/constants.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/constants.ts @@ -53,3 +53,4 @@ export const APP_REQUIRED_CLUSTER_PRIVILEGES = [ 'cluster:admin/repository', ]; export const APP_RESTORE_INDEX_PRIVILEGES = ['monitor']; +export const APP_SLM_CLUSTER_PRIVILEGES = ['manage_slm']; diff --git a/x-pack/legacy/plugins/snapshot_restore/common/lib/index.ts b/x-pack/legacy/plugins/snapshot_restore/common/lib/index.ts index 0092d37b74a20..bede2689bb855 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/lib/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/lib/index.ts @@ -8,3 +8,9 @@ export { deserializeRestoreSettings, serializeRestoreSettings, } from './restore_settings_serialization'; +export { + deserializeSnapshotDetails, + deserializeSnapshotConfig, + serializeSnapshotConfig, +} from './snapshot_serialization'; +export { deserializePolicy, serializePolicy } from './policy_serialization'; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/policy_serialization.test.ts b/x-pack/legacy/plugins/snapshot_restore/common/lib/policy_serialization.test.ts similarity index 100% rename from x-pack/legacy/plugins/snapshot_restore/server/lib/policy_serialization.test.ts rename to x-pack/legacy/plugins/snapshot_restore/common/lib/policy_serialization.test.ts diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/policy_serialization.ts b/x-pack/legacy/plugins/snapshot_restore/common/lib/policy_serialization.ts similarity index 70% rename from x-pack/legacy/plugins/snapshot_restore/server/lib/policy_serialization.ts rename to x-pack/legacy/plugins/snapshot_restore/common/lib/policy_serialization.ts index 5abbc4270ec2f..dc52765670540 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/lib/policy_serialization.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/lib/policy_serialization.ts @@ -3,8 +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 { SlmPolicy, SlmPolicyEs } from '../../common/types'; -import { deserializeSnapshotConfig } from './'; +import { SlmPolicy, SlmPolicyEs, SlmPolicyPayload } from '../types'; +import { deserializeSnapshotConfig, serializeSnapshotConfig } from './'; export const deserializePolicy = (name: string, esPolicy: SlmPolicyEs): SlmPolicy => { const { @@ -16,6 +16,7 @@ export const deserializePolicy = (name: string, esPolicy: SlmPolicyEs): SlmPolic next_execution_millis: nextExecutionMillis, last_failure: lastFailure, last_success: lastSuccess, + in_progress: inProgress, } = esPolicy; const policy: SlmPolicy = { @@ -26,11 +27,14 @@ export const deserializePolicy = (name: string, esPolicy: SlmPolicyEs): SlmPolic snapshotName, schedule, repository, - config: deserializeSnapshotConfig(config), nextExecution, nextExecutionMillis, }; + if (config) { + policy.config = deserializeSnapshotConfig(config); + } + if (lastFailure) { const { snapshot_name: failureSnapshotName, @@ -70,5 +74,28 @@ export const deserializePolicy = (name: string, esPolicy: SlmPolicyEs): SlmPolic }; } + if (inProgress) { + const { name: inProgressSnapshotName } = inProgress; + + policy.inProgress = { + snapshotName: inProgressSnapshotName, + }; + } + return policy; }; + +export const serializePolicy = (policy: SlmPolicyPayload): SlmPolicyEs['policy'] => { + const { snapshotName: name, schedule, repository, config } = policy; + const policyEs: SlmPolicyEs['policy'] = { + name, + schedule, + repository, + }; + + if (config) { + policyEs.config = serializeSnapshotConfig(config); + } + + return policyEs; +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/snapshot_serialization.test.ts b/x-pack/legacy/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts similarity index 100% rename from x-pack/legacy/plugins/snapshot_restore/server/lib/snapshot_serialization.test.ts rename to x-pack/legacy/plugins/snapshot_restore/common/lib/snapshot_serialization.test.ts diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/snapshot_serialization.ts b/x-pack/legacy/plugins/snapshot_restore/common/lib/snapshot_serialization.ts similarity index 80% rename from x-pack/legacy/plugins/snapshot_restore/server/lib/snapshot_serialization.ts rename to x-pack/legacy/plugins/snapshot_restore/common/lib/snapshot_serialization.ts index 608d85cf8840b..b1f6d2005a2e3 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/lib/snapshot_serialization.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/lib/snapshot_serialization.ts @@ -6,12 +6,7 @@ import { sortBy } from 'lodash'; -import { - SnapshotDetails, - SnapshotDetailsEs, - SnapshotConfig, - SnapshotConfigEs, -} from '../../common/types'; +import { SnapshotDetails, SnapshotDetailsEs, SnapshotConfig, SnapshotConfigEs } from '../types'; export function deserializeSnapshotDetails( repository: string, @@ -114,3 +109,22 @@ export function deserializeSnapshotConfig(snapshotConfigEs: SnapshotConfigEs): S return config; }, {}); } + +export function serializeSnapshotConfig(snapshotConfig: SnapshotConfig): SnapshotConfigEs { + const { indices, ignoreUnavailable, includeGlobalState, partial, metadata } = snapshotConfig; + + const snapshotConfigEs: SnapshotConfigEs = { + indices, + ignore_unavailable: ignoreUnavailable, + include_global_state: includeGlobalState, + partial, + metadata, + }; + + return Object.entries(snapshotConfigEs).reduce((config: any, [key, value]) => { + if (value !== undefined) { + config[key] = value; + } + return config; + }, {}); +} diff --git a/x-pack/legacy/plugins/snapshot_restore/common/types/policy.ts b/x-pack/legacy/plugins/snapshot_restore/common/types/policy.ts index 54d17e853cc87..888cad13d213b 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/types/policy.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/types/policy.ts @@ -6,15 +6,18 @@ import { SnapshotConfig, SnapshotConfigEs } from './snapshot'; -export interface SlmPolicy { +export interface SlmPolicyPayload { name: string; - version: number; - modifiedDate: string; - modifiedDateMillis: number; snapshotName: string; schedule: string; repository: string; - config: SnapshotConfig; + config?: SnapshotConfig; +} + +export interface SlmPolicy extends SlmPolicyPayload { + version: number; + modifiedDate: string; + modifiedDateMillis: number; nextExecution: string; nextExecutionMillis: number; lastSuccess?: { @@ -28,6 +31,9 @@ export interface SlmPolicy { time: number; details: object | string; }; + inProgress?: { + snapshotName: string; + }; } export interface SlmPolicyEs { @@ -38,7 +44,7 @@ export interface SlmPolicyEs { name: string; schedule: string; repository: string; - config: SnapshotConfigEs; + config?: SnapshotConfigEs; }; next_execution: string; next_execution_millis: number; @@ -53,4 +59,11 @@ export interface SlmPolicyEs { time: number; details: string; }; + in_progress?: { + name: string; + uuid: string; + state: string; + start_time: string; + start_time_millis: number; + }; } diff --git a/x-pack/legacy/plugins/snapshot_restore/common/types/snapshot.ts b/x-pack/legacy/plugins/snapshot_restore/common/types/snapshot.ts index c896336cc943b..dd561bd50d352 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/types/snapshot.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/types/snapshot.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ export interface SnapshotConfig { - indices?: string[]; + indices?: string | string[]; ignoreUnavailable?: boolean; includeGlobalState?: boolean; partial?: boolean; @@ -14,7 +14,7 @@ export interface SnapshotConfig { } export interface SnapshotConfigEs { - indices?: string[]; + indices?: string | string[]; ignore_unavailable?: boolean; include_global_state?: boolean; partial?: boolean; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/app.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/app.tsx index 207044c4692fd..764c50bc47721 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/app.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/app.tsx @@ -8,9 +8,17 @@ import React, { useContext } from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import { EuiPageContent } from '@elastic/eui'; +import { APP_REQUIRED_CLUSTER_PRIVILEGES } from '../../common/constants'; import { SectionLoading, SectionError } from './components'; import { BASE_PATH, DEFAULT_SECTION, Section } from './constants'; -import { RepositoryAdd, RepositoryEdit, RestoreSnapshot, SnapshotRestoreHome } from './sections'; +import { + RepositoryAdd, + RepositoryEdit, + RestoreSnapshot, + SnapshotRestoreHome, + PolicyAdd, + PolicyEdit, +} from './sections'; import { useAppDependencies } from './index'; import { AuthorizationContext, WithPrivileges, NotAuthorizedSection } from './lib/authorization'; @@ -36,7 +44,7 @@ export const App: React.FunctionComponent = () => { error={apiError} /> ) : ( - + `cluster.${name}`)}> {({ isLoading, hasPrivileges, privilegesMissing }) => isLoading ? ( @@ -69,6 +77,8 @@ export const App: React.FunctionComponent = () => { path={`${BASE_PATH}/restore/:repositoryName/:snapshotId*`} component={RestoreSnapshot} /> + +
    diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/components/index.ts index a017299a78914..a367e529cf63b 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/index.ts @@ -16,3 +16,4 @@ export { SnapshotDeleteProvider } from './snapshot_delete_provider'; export { RestoreSnapshotForm } from './restore_snapshot_form'; export { PolicyExecuteProvider } from './policy_execute_provider'; export { PolicyDeleteProvider } from './policy_delete_provider'; +export { PolicyForm } from './policy_form'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_execute_provider.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_execute_provider.tsx index 3df081e9c9dba..c43ab02801e4e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_execute_provider.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_execute_provider.tsx @@ -87,7 +87,7 @@ export const PolicyExecuteProvider: React.FunctionComponent = ({ children title={ } @@ -102,18 +102,11 @@ export const PolicyExecuteProvider: React.FunctionComponent = ({ children confirmButtonText={ } data-test-subj="srExecutePolicyConfirmationModal" - > -

    - -

    - + /> ); }; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/_policy_form.scss b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/_policy_form.scss new file mode 100644 index 0000000000000..0a5187908f854 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/_policy_form.scss @@ -0,0 +1,16 @@ +/* + * Prevent switch controls from moving around when toggling content + */ +.snapshotRestore__policyForm__stepSettings { + .euiFormRow--hasEmptyLabelSpace { + min-height: auto; + margin-top: $euiFontSizeXS + $euiSizeS + ($euiSizeXXL / 4); + } +} + +/* + * Allow toggle mode link in indices field label to be flushed right + */ +.snapshotRestore__policyForm__stepSettings__indicesFieldWrapper .euiFormLabel { + width: 100%; +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/index.ts new file mode 100644 index 0000000000000..0da06da9e1f8e --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/index.ts @@ -0,0 +1,6 @@ +/* + * 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 { PolicyForm } from './policy_form'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/navigation.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/navigation.tsx new file mode 100644 index 0000000000000..ba9877a9e9f41 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/navigation.tsx @@ -0,0 +1,55 @@ +/* + * 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 { EuiStepsHorizontal } from '@elastic/eui'; +import { useAppDependencies } from '../../index'; + +interface Props { + currentStep: number; + maxCompletedStep: number; + updateCurrentStep: (step: number) => void; +} + +export const PolicyNavigation: React.FunctionComponent = ({ + currentStep, + maxCompletedStep, + updateCurrentStep, +}) => { + const { + core: { i18n }, + } = useAppDependencies(); + + const steps = [ + { + title: i18n.translate('xpack.snapshotRestore.policyForm.navigation.stepLogisticsName', { + defaultMessage: 'Logistics', + }), + isComplete: maxCompletedStep >= 1, + isSelected: currentStep === 1, + onClick: () => updateCurrentStep(1), + }, + { + title: i18n.translate('xpack.snapshotRestore.policyForm.navigation.stepSettingsName', { + defaultMessage: 'Snapshot settings', + }), + isComplete: maxCompletedStep >= 2, + isSelected: currentStep === 2, + disabled: maxCompletedStep < 1, + onClick: () => updateCurrentStep(2), + }, + { + title: i18n.translate('xpack.snapshotRestore.policyForm.navigation.stepReviewName', { + defaultMessage: 'Review', + }), + isComplete: maxCompletedStep >= 2, + isSelected: currentStep === 3, + disabled: maxCompletedStep < 2, + onClick: () => updateCurrentStep(3), + }, + ]; + + return ; +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/policy_form.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/policy_form.tsx new file mode 100644 index 0000000000000..6c631ab8e6c69 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/policy_form.tsx @@ -0,0 +1,217 @@ +/* + * 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, useState } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiSpacer, +} from '@elastic/eui'; +import { SlmPolicyPayload } from '../../../../common/types'; +import { PolicyValidation, validatePolicy } from '../../services/validation'; +import { useAppDependencies } from '../../index'; +import { PolicyStepLogistics, PolicyStepSettings, PolicyStepReview } from './steps'; +import { PolicyNavigation } from './navigation'; + +interface Props { + policy: SlmPolicyPayload; + indices: string[]; + currentUrl: string; + isEditing?: boolean; + isSaving: boolean; + saveError?: React.ReactNode; + clearSaveError: () => void; + onCancel: () => void; + onSave: (policy: SlmPolicyPayload) => void; +} + +export const PolicyForm: React.FunctionComponent = ({ + policy: originalPolicy, + indices, + currentUrl, + isEditing, + isSaving, + saveError, + clearSaveError, + onCancel, + onSave, +}) => { + const { + core: { + i18n: { FormattedMessage }, + }, + } = useAppDependencies(); + + // Step state + const [currentStep, setCurrentStep] = useState(1); + const [maxCompletedStep, setMaxCompletedStep] = useState(0); + const stepMap: { [key: number]: any } = { + 1: PolicyStepLogistics, + 2: PolicyStepSettings, + 3: PolicyStepReview, + }; + const CurrentStepForm = stepMap[currentStep]; + + // Policy state + const [policy, setPolicy] = useState({ + ...originalPolicy, + config: { + ...(originalPolicy.config || {}), + }, + }); + + // Policy validation state + const [validation, setValidation] = useState({ + isValid: true, + errors: {}, + }); + + const updatePolicy = (updatedFields: any): void => { + const newPolicy = { ...policy, ...updatedFields }; + const newValidation = validatePolicy(newPolicy); + setPolicy(newPolicy); + setValidation(newValidation); + }; + + const updateCurrentStep = (step: number) => { + if (maxCompletedStep < step - 1) { + return; + } + setCurrentStep(step); + setMaxCompletedStep(step - 1); + clearSaveError(); + }; + + const onBack = () => { + const previousStep = currentStep - 1; + setCurrentStep(previousStep); + setMaxCompletedStep(previousStep - 1); + clearSaveError(); + }; + + const onNext = () => { + if (!validation.isValid) { + return; + } + const nextStep = currentStep + 1; + setMaxCompletedStep(Math.max(currentStep, maxCompletedStep)); + setCurrentStep(nextStep); + }; + + const savePolicy = () => { + if (validation.isValid) { + onSave(policy); + } + }; + + const lastStep = Object.keys(stepMap).length; + + return ( + + + + + + + + {saveError ? ( + + {saveError} + + + ) : null} + + + + + {currentStep > 1 ? ( + + onBack()} + disabled={!validation.isValid} + > + + + + ) : null} + {currentStep < lastStep ? ( + + onNext()} + disabled={!validation.isValid} + > + + + + ) : null} + {currentStep === lastStep ? ( + + savePolicy()} + isLoading={isSaving} + > + {isSaving ? ( + + ) : isEditing ? ( + + ) : ( + + )} + + + ) : null} + + + + + onCancel()}> + + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/index.ts new file mode 100644 index 0000000000000..10dd696e3424f --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { SlmPolicyPayload } from '../../../../../common/types'; +import { PolicyValidation } from '../../../services/validation'; + +export interface StepProps { + policy: SlmPolicyPayload; + indices: string[]; + updatePolicy: (updatedSettings: Partial) => void; + isEditing: boolean; + currentUrl: string; + errors: PolicyValidation['errors']; + updateCurrentStep: (step: number) => void; +} + +export { PolicyStepLogistics } from './step_logistics'; +export { PolicyStepSettings } from './step_settings'; +export { PolicyStepReview } from './step_review'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx new file mode 100644 index 0000000000000..f96eb5347bc18 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx @@ -0,0 +1,507 @@ +/* + * 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, useState } from 'react'; + +import { + EuiDescribedFormGroup, + EuiTitle, + EuiFormRow, + EuiFieldText, + EuiSelect, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { Repository } from '../../../../../common/types'; +import { CronEditor } from '../../../../shared_imports'; +import { DEFAULT_POLICY_SCHEDULE, DEFAULT_POLICY_FREQUENCY } from '../../../constants'; +import { useLoadRepositories } from '../../../services/http'; +import { linkToAddRepository } from '../../../services/navigation'; +import { documentationLinksService } from '../../../services/documentation'; +import { useAppDependencies } from '../../../index'; +import { SectionLoading, SectionError } from '../../'; +import { StepProps } from './'; + +export const PolicyStepLogistics: React.FunctionComponent = ({ + policy, + updatePolicy, + isEditing, + currentUrl, + errors, +}) => { + const { + core: { i18n }, + } = useAppDependencies(); + const { FormattedMessage } = i18n; + + // Load repositories for repository dropdown field + const { + error: errorLoadingRepositories, + isLoading: isLoadingRepositories, + data: { repositories } = { + repositories: [], + }, + sendRequest: reloadRepositories, + } = useLoadRepositories(); + + // State for touched inputs + const [touched, setTouched] = useState({ + name: false, + snapshotName: false, + repository: false, + schedule: false, + }); + + // State for cron editor + const [simpleCron, setSimpleCron] = useState<{ + expression: string; + frequency: string; + }>({ + expression: DEFAULT_POLICY_SCHEDULE, + frequency: DEFAULT_POLICY_FREQUENCY, + }); + const [isAdvancedCronVisible, setIsAdvancedCronVisible] = useState( + Boolean(policy.schedule && policy.schedule !== DEFAULT_POLICY_SCHEDULE) + ); + const [fieldToPreferredValueMap, setFieldToPreferredValueMap] = useState({}); + + const renderNameField = () => ( + +

    + +

    + + } + description={ + + } + idAria="nameDescription" + fullWidth + > + + } + describedByIds={['nameDescription']} + isInvalid={touched.name && Boolean(errors.name)} + error={errors.name} + fullWidth + > + setTouched({ ...touched, name: true })} + onChange={e => { + updatePolicy({ + name: e.target.value, + }); + }} + placeholder={i18n.translate( + 'xpack.snapshotRestore.policyForm.stepLogistics.namePlaceholder', + { + defaultMessage: 'daily-snapshots', + description: + 'Example SLM policy name. Similar to index names, do not use spaces in translation.', + } + )} + data-test-subj="nameInput" + disabled={isEditing} + /> + +
    + ); + + const renderRepositoryField = () => ( + +

    + +

    + + } + description={ + + } + idAria="policyRepositoryDescription" + fullWidth + > + + } + describedByIds={['policyRepositoryDescription']} + isInvalid={touched.repository && Boolean(errors.repository)} + error={errors.repository} + fullWidth + > + {renderRepositorySelect()} + +
    + ); + + const renderRepositorySelect = () => { + if (isLoadingRepositories) { + return ( + + + + ); + } + + if (errorLoadingRepositories) { + return ( + + } + error={{ data: { error: 'test' } } || errorLoadingRepositories} + actions={ + reloadRepositories()} + color="danger" + iconType="refresh" + data-test-subj="reloadRepositoriesButton" + > + + + } + /> + ); + } + + if (repositories.length === 0) { + return ( + + } + error={{ + data: { + error: i18n.translate('xpack.snapshotRestore.policyForm.noRepositoriesErrorMessage', { + defaultMessage: 'You must register a repository to store your snapshots.', + }), + }, + }} + actions={ + + + + } + /> + ); + } else { + if (!policy.repository) { + updatePolicy({ + repository: repositories[0].name, + }); + } + } + + return ( + ({ + value: name, + text: name, + }))} + value={policy.repository || repositories[0].name} + onBlur={() => setTouched({ ...touched, repository: true })} + onChange={e => { + updatePolicy({ + repository: e.target.value, + }); + }} + fullWidth + data-test-subj="repositorySelect" + /> + ); + }; + + const renderSnapshotNameField = () => ( + +

    + +

    + + } + description={ + + } + idAria="policySnapshotNameDescription" + fullWidth + > + + } + describedByIds={['policySnapshotNameDescription']} + isInvalid={touched.snapshotName && Boolean(errors.snapshotName)} + error={errors.snapshotName} + helpText={ + + + + ), + }} + /> + } + fullWidth + > + { + updatePolicy({ + snapshotName: e.target.value.toLowerCase(), + }); + }} + onBlur={() => setTouched({ ...touched, snapshotName: true })} + placeholder={i18n.translate( + 'xpack.snapshotRestore.policyForm.stepLogistics.policySnapshotNamePlaceholder', + { + defaultMessage: '', + description: + 'Example date math snapshot name. Keeping the same syntax is important: ', + } + )} + data-test-subj="snapshotNameInput" + /> + +
    + ); + + const renderScheduleField = () => ( + +

    + +

    + + } + description={ + + } + idAria="policyScheduleDescription" + fullWidth + > + {isAdvancedCronVisible ? ( + + + } + describedByIds={['policyScheduleDescription']} + isInvalid={touched.schedule && Boolean(errors.schedule)} + error={errors.schedule} + helpText={ + + + + ), + }} + /> + } + fullWidth + > + { + updatePolicy({ + schedule: e.target.value, + }); + }} + onBlur={() => setTouched({ ...touched, schedule: true })} + placeholder={DEFAULT_POLICY_SCHEDULE} + data-test-subj="snapshotNameInput" + /> + + + + { + setIsAdvancedCronVisible(false); + updatePolicy({ + schedule: simpleCron.expression, + }); + }} + data-test-subj="showBasicCronLink" + > + + + + + ) : ( + + { + setSimpleCron({ + expression, + frequency, + }); + setFieldToPreferredValueMap(newFieldToPreferredValueMap); + updatePolicy({ + schedule: expression, + }); + }} + /> + + + { + setIsAdvancedCronVisible(true); + }} + data-test-subj="showAdvancedCronLink" + > + + + + + )} +
    + ); + + return ( + + {/* Step title and doc link */} + + + +

    + +

    +
    +
    + + + + + + +
    + + + {renderNameField()} + {renderSnapshotNameField()} + {renderRepositoryField()} + {renderScheduleField()} +
    + ); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx new file mode 100644 index 0000000000000..2599aa4b19bb1 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx @@ -0,0 +1,329 @@ +/* + * 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, useState } from 'react'; +import { + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiSpacer, + EuiTabbedContent, + EuiTitle, + EuiLink, + EuiIcon, + EuiToolTip, + EuiText, +} from '@elastic/eui'; +import { serializePolicy } from '../../../../../common/lib'; +import { useAppDependencies } from '../../../index'; +import { StepProps } from './'; + +export const PolicyStepReview: React.FunctionComponent = ({ + policy, + updateCurrentStep, +}) => { + const { + core: { i18n }, + } = useAppDependencies(); + const { FormattedMessage } = i18n; + const { name, snapshotName, schedule, repository, config } = policy; + const { indices, includeGlobalState, ignoreUnavailable, partial } = config || { + indices: undefined, + includeGlobalState: undefined, + ignoreUnavailable: undefined, + partial: undefined, + }; + + const [isShowingFullIndicesList, setIsShowingFullIndicesList] = useState(false); + const displayIndices = indices + ? typeof indices === 'string' + ? indices.split(',') + : indices + : undefined; + const hiddenIndicesCount = + displayIndices && displayIndices.length > 10 ? displayIndices.length - 10 : 0; + + const renderSummaryTab = () => ( + + + +

    + {' '} + + } + > + updateCurrentStep(1)}> + + + +

    +
    + + + + + + + + + {name} + + + + + + + + {snapshotName} + + + + + + + + + + + + {repository} + + + + + + + + {schedule} + + + + + + +

    + {' '} + + } + > + updateCurrentStep(2)}> + + + +

    +
    + + + + + + + + + + {displayIndices ? ( + +
      + {(isShowingFullIndicesList + ? displayIndices + : [...displayIndices].splice(0, 10) + ).map(index => ( +
    • + + {index} + +
    • + ))} + {hiddenIndicesCount ? ( +
    • + + {isShowingFullIndicesList ? ( + setIsShowingFullIndicesList(false)}> + {' '} + + + ) : ( + setIsShowingFullIndicesList(true)}> + {' '} + + + )} + +
    • + ) : null} +
    +
    + ) : ( + + )} +
    +
    +
    + + + + + + + {ignoreUnavailable ? ( + + ) : ( + + )} + + + +
    + + + + + + + + + {partial ? ( + + ) : ( + + )} + + + + + + + + + + {includeGlobalState === false ? ( + + ) : ( + + )} + + + + +
    + ); + + const renderRequestTab = () => { + const endpoint = `PUT _slm/policy/${name}`; + const json = JSON.stringify(serializePolicy(policy), null, 2); + return ( + + + + {`${endpoint}\n${json}`} + + + ); + }; + + return ( + + +

    + +

    +
    + + +
    + ); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_settings.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_settings.tsx new file mode 100644 index 0000000000000..642440a8c5e91 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_settings.tsx @@ -0,0 +1,454 @@ +/* + * 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, useState } from 'react'; + +import { + EuiDescribedFormGroup, + EuiTitle, + EuiFormRow, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiSpacer, + EuiSwitch, + EuiLink, + EuiSelectable, + EuiPanel, + EuiComboBox, +} from '@elastic/eui'; +import { Option } from '@elastic/eui/src/components/selectable/types'; +import { SlmPolicyPayload, SnapshotConfig } from '../../../../../common/types'; +import { documentationLinksService } from '../../../services/documentation'; +import { useAppDependencies } from '../../../index'; +import { StepProps } from './'; + +export const PolicyStepSettings: React.FunctionComponent = ({ + policy, + indices, + updatePolicy, + errors, +}) => { + const { + core: { i18n }, + } = useAppDependencies(); + const { FormattedMessage } = i18n; + const { config = {} } = policy; + + const updatePolicyConfig = (updatedFields: Partial): void => { + const newConfig = { ...config, ...updatedFields }; + updatePolicy({ + config: newConfig, + }); + }; + + // States for choosing all indices, or a subset, including caching previously chosen subset list + const [isAllIndices, setIsAllIndices] = useState(!Boolean(config.indices)); + const [indicesSelection, setIndicesSelection] = useState([...indices]); + const [indicesOptions, setIndicesOptions] = useState( + indices.map( + (index): Option => ({ + label: index, + checked: + isAllIndices || + // If indices is a string, we default to custom input mode, so we mark individual indices + // as selected if user goes back to list mode + typeof config.indices === 'string' || + (Array.isArray(config.indices) && config.indices.includes(index)) + ? 'on' + : undefined, + }) + ) + ); + + // State for using selectable indices list or custom patterns + // Users with more than 100 indices will probably want to use an index pattern to select + // them instead, so we'll default to showing them the index pattern input. + const [selectIndicesMode, setSelectIndicesMode] = useState<'list' | 'custom'>( + typeof config.indices === 'string' || + (Array.isArray(config.indices) && config.indices.length > 100) + ? 'custom' + : 'list' + ); + + // State for custom patterns + const [indexPatterns, setIndexPatterns] = useState( + typeof config.indices === 'string' ? config.indices.split(',') : [] + ); + + const renderIndicesField = () => ( + +

    + +

    + + } + description={ + + } + idAria="indicesDescription" + fullWidth + > + + + + } + checked={isAllIndices} + onChange={e => { + const isChecked = e.target.checked; + setIsAllIndices(isChecked); + if (isChecked) { + updatePolicyConfig({ indices: undefined }); + } else { + updatePolicyConfig({ + indices: + selectIndicesMode === 'custom' + ? indexPatterns.join(',') + : [...(indicesSelection || [])], + }); + } + }} + /> + {isAllIndices ? null : ( + + + + + + + + { + setSelectIndicesMode('custom'); + updatePolicyConfig({ indices: indexPatterns.join(',') }); + }} + > + + + + + ) : ( + + + + + + { + setSelectIndicesMode('list'); + updatePolicyConfig({ indices: indicesSelection }); + }} + > + + + + + ) + } + helpText={ + selectIndicesMode === 'list' ? ( + 0 ? ( + { + // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed + indicesOptions.forEach((option: Option) => { + option.checked = undefined; + }); + updatePolicyConfig({ indices: [] }); + setIndicesSelection([]); + }} + > + + + ) : ( + { + // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed + indicesOptions.forEach((option: Option) => { + option.checked = 'on'; + }); + updatePolicyConfig({ indices: [...indices] }); + setIndicesSelection([...indices]); + }} + > + + + ), + }} + /> + ) : null + } + isInvalid={Boolean(errors.indices)} + error={errors.indices} + > + {selectIndicesMode === 'list' ? ( + { + const newSelectedIndices: string[] = []; + options.forEach(({ label, checked }) => { + if (checked === 'on') { + newSelectedIndices.push(label); + } + }); + setIndicesOptions(options); + updatePolicyConfig({ indices: newSelectedIndices }); + setIndicesSelection(newSelectedIndices); + }} + searchable + height={300} + > + {(list, search) => ( + + {search} + {list} + + )} + + ) : ( + ({ label: index }))} + placeholder={i18n.translate( + 'xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder', + { + defaultMessage: 'Enter index patterns, i.e. logstash-*', + } + )} + selectedOptions={indexPatterns.map(pattern => ({ label: pattern }))} + onCreateOption={(pattern: string) => { + if (!pattern.trim().length) { + return; + } + const newPatterns = [...indexPatterns, pattern]; + setIndexPatterns(newPatterns); + updatePolicyConfig({ + indices: newPatterns.join(','), + }); + }} + onChange={(patterns: Array<{ label: string }>) => { + const newPatterns = patterns.map(({ label }) => label); + setIndexPatterns(newPatterns); + updatePolicyConfig({ + indices: newPatterns.join(','), + }); + }} + /> + )} + + + )} + + +
    + ); + + const renderIgnoreUnavailableField = () => ( + +

    + +

    + + } + description={ + + } + idAria="policyIgnoreUnavailableDescription" + fullWidth + > + + + } + checked={Boolean(config.ignoreUnavailable)} + onChange={e => { + updatePolicyConfig({ + ignoreUnavailable: e.target.checked, + }); + }} + /> + +
    + ); + + const renderPartialField = () => ( + +

    + +

    + + } + description={ + + } + idAria="policyPartialDescription" + fullWidth + > + + + } + checked={Boolean(config.partial)} + onChange={e => { + updatePolicyConfig({ + partial: e.target.checked, + }); + }} + /> + +
    + ); + + const renderIncludeGlobalStateField = () => ( + +

    + +

    + + } + description={ + + } + idAria="policyIncludeGlobalStateDescription" + fullWidth + > + + + } + checked={config.includeGlobalState === undefined || config.includeGlobalState} + onChange={e => { + updatePolicyConfig({ + includeGlobalState: e.target.checked, + }); + }} + /> + +
    + ); + return ( +
    + {/* Step title and doc link */} + + + +

    + +

    +
    +
    + + + + + + +
    + + + {renderIndicesField()} + {renderIgnoreUnavailableField()} + {renderPartialField()} + {renderIncludeGlobalStateField()} +
    + ); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_delete_provider.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_delete_provider.tsx index 509eeb0201825..f0991819f957f 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_delete_provider.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_delete_provider.tsx @@ -155,7 +155,8 @@ export const RepositoryDeleteProvider: React.FunctionComponent = ({ child

    ) : ( @@ -174,7 +175,8 @@ export const RepositoryDeleteProvider: React.FunctionComponent = ({ child

    diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx index 2ea5d54b7de3e..8a0d8039bb7cd 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx @@ -56,6 +56,8 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = label: index, checked: isAllIndices || + // If indices is a string, we default to custom input mode, so we mark individual indices + // as selected if user goes back to list mode typeof restoreIndices === 'string' || (Array.isArray(restoreIndices) && restoreIndices.includes(index)) ? 'on' @@ -97,7 +99,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent =

    @@ -113,7 +115,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = > @@ -234,6 +236,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = restoreIndices && restoreIndices.length > 0 ? ( { + // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed indicesOptions.forEach((option: Option) => { option.checked = undefined; }); @@ -252,6 +255,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = ) : ( { + // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed indicesOptions.forEach((option: Option) => { option.checked = 'on'; }); diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_error.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_error.tsx index 6d65addeb4cb9..2ad6f0870c140 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_error.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_error.tsx @@ -16,9 +16,15 @@ interface Props { message?: string; }; }; + actions?: JSX.Element; } -export const SectionError: React.FunctionComponent = ({ title, error, ...rest }) => { +export const SectionError: React.FunctionComponent = ({ + title, + error, + actions, + ...rest +}) => { const { error: errorString, cause, // wrapEsError() on the server adds a "cause" array @@ -27,10 +33,10 @@ export const SectionError: React.FunctionComponent = ({ title, error, ... return ( -
    {message || errorString}
    + {cause ? message || errorString :

    {message || errorString}

    } {cause && ( - +
      {cause.map((causeMsg, i) => (
    • {causeMsg}
    • @@ -38,6 +44,7 @@ export const SectionError: React.FunctionComponent = ({ title, error, ...
    )} + {actions ? actions : null}
    ); }; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_loading.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_loading.tsx index 4c6273682a0e4..aff3363c0aa60 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_loading.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/section_loading.tsx @@ -6,13 +6,37 @@ import React from 'react'; -import { EuiEmptyPrompt, EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { + EuiEmptyPrompt, + EuiLoadingSpinner, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiTextColor, +} from '@elastic/eui'; interface Props { + inline?: boolean; children: React.ReactNode; + [key: string]: any; } -export const SectionLoading: React.FunctionComponent = ({ children }) => { +export const SectionLoading: React.FunctionComponent = ({ inline, children, ...rest }) => { + if (inline) { + return ( + + + + + + + {children} + + + + ); + } + return ( } diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts index 61722bada4d13..d95c243aeed62 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { DAY } from '../../shared_imports'; + export const BASE_PATH = '/management/elasticsearch/snapshot_restore'; export const DEFAULT_SECTION: Section = 'snapshots'; export type Section = 'repositories' | 'snapshots' | 'restore_status' | 'policies'; @@ -86,6 +88,9 @@ export const REMOVE_INDEX_SETTINGS_SUGGESTIONS: string[] = INDEX_SETTING_SUGGEST setting => !UNREMOVABLE_INDEX_SETTINGS.includes(setting) ); +export const DEFAULT_POLICY_SCHEDULE = '0 30 1 * * ?'; +export const DEFAULT_POLICY_FREQUENCY = DAY; + // UI Metric constants export const UIM_APP_NAME = 'snapshot_restore'; export const UIM_REPOSITORY_LIST_LOAD = 'repository_list_load'; @@ -112,3 +117,5 @@ export const UIM_POLICY_DETAIL_PANEL_HISTORY_TAB = 'policy_detail_panel_last_suc export const UIM_POLICY_EXECUTE = 'policy_execute'; export const UIM_POLICY_DELETE = 'policy_delete'; export const UIM_POLICY_DELETE_MANY = 'policy_delete_many'; +export const UIM_POLICY_CREATE = 'policy_create'; +export const UIM_POLICY_UPDATE = 'policy_update'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/index.scss b/x-pack/legacy/plugins/snapshot_restore/public/app/index.scss index 8e42fb4598799..b680f4d3ebf90 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/index.scss +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/index.scss @@ -11,4 +11,5 @@ // snapshotRestore__legend-isLoading @import 'components/restore_snapshot_form/restore_snapshot_form'; +@import 'components/policy_form/policy_form'; @import 'sections/home/home'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/_home.scss b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/_home.scss index 96a08e40a3411..c714222daa98b 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/_home.scss +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/_home.scss @@ -18,4 +18,14 @@ background: $euiColorLightestShade; } } +} + +/* + * 1. Make in progress snapshot loading indicator be centered vertically + * when it is inside tooltip wrapper + */ +.snapshotRestore__policyTable { + .euiToolTipAnchor { + display: flex; + } } \ No newline at end of file diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx index 2e1029204dbac..e3ec7675068ed 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx @@ -22,7 +22,7 @@ import { import { BASE_PATH, Section } from '../../constants'; import { useAppDependencies } from '../../index'; -import { breadcrumbService } from '../../services/navigation'; +import { breadcrumbService, docTitleService } from '../../services/navigation'; import { RepositoryList } from './repository_list'; import { SnapshotList } from './snapshot_list'; @@ -92,10 +92,11 @@ export const SnapshotRestoreHome: React.FunctionComponent { - breadcrumbService.setBreadcrumbs('home'); - }, []); + breadcrumbService.setBreadcrumbs(section || 'home'); + docTitleService.setTitle(section || 'home'); + }, [section]); return ( diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx index ab658373283c8..e4b6ad28e324d 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/policy_details.tsx @@ -16,6 +16,10 @@ import { EuiTabs, EuiTab, EuiButton, + EuiPopover, + EuiContextMenu, + EuiButtonIcon, + EuiLink, } from '@elastic/eui'; import { SlmPolicy } from '../../../../../../common/types'; @@ -26,6 +30,7 @@ import { } from '../../../../constants'; import { useLoadPolicy } from '../../../../services/http'; import { uiMetricService } from '../../../../services/ui_metric'; +import { linkToEditPolicy, linkToSnapshot } from '../../../../services/navigation'; import { SectionError, @@ -64,6 +69,7 @@ export const PolicyDetails: React.FunctionComponent = ({ const { trackUiMetric } = uiMetricService; const { error, data: policyDetails, sendRequest: reload } = useLoadPolicy(policyName); const [activeTab, setActiveTab] = useState(TAB_SUMMARY); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); // Reset tab when we look at a different policy useEffect(() => { @@ -183,53 +189,103 @@ export const PolicyDetails: React.FunctionComponent = ({ /> - {policyDetails ? ( - - - - {deletePolicyPrompt => { - return ( - deletePolicyPrompt([policyName], onPolicyDeleted)} - > - - - ); - }} - - - - - {executePolicyPrompt => { - return ( - - executePolicyPrompt(policyName, () => { - onPolicyExecuted(); - reload(); - }) - } - fill - color="primary" - data-test-subj="srPolicyDetailsExecuteActionButton" - > - - - ); - }} - - - + + {executePolicyPrompt => { + return ( + + {deletePolicyPrompt => { + return ( + setIsPopoverOpen(!isPopoverOpen)} + iconType="arrowDown" + fill + > + + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + panelPaddingSize="none" + withTitle + anchorPosition="rightUp" + repositionOnScroll + > + { + executePolicyPrompt(policyName, () => + // Wait a little bit for policy to execute before reloading policy table + // and policy details so that History tab information is updated with + // results of the execution + setTimeout(() => { + onPolicyExecuted(); + reload(); + }, 2000) + ); + }, + disabled: Boolean(policyDetails.policy.inProgress), + }, + { + name: i18n.translate( + 'xpack.snapshotRestore.policyDetails.editButtonLabel', + { + defaultMessage: 'Edit', + } + ), + icon: 'pencil', + href: linkToEditPolicy(policyName), + }, + { + name: i18n.translate( + 'xpack.snapshotRestore.policyDetails.deleteButtonLabel', + { + defaultMessage: 'Delete', + } + ), + icon: 'trash', + onClick: () => + deletePolicyPrompt([policyName], onPolicyDeleted), + }, + ], + }, + ]} + /> + + ); + }} + + ); + }} + ) : null} @@ -245,11 +301,49 @@ export const PolicyDetails: React.FunctionComponent = ({ maxWidth={550} > - -

    - {policyName} -

    -
    + + + + + +

    + {policyName} +

    +
    + + reload()} + /> + +
    +
    +
    + {policyDetails && policyDetails.policy && policyDetails.policy.inProgress ? ( + + + + + + + + ) : null} +
    {renderTabs()}
    diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_history.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_history.tsx index 481a24b50b15d..0a8774c0c85a6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_history.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_history.tsx @@ -55,12 +55,11 @@ export const TabHistory: React.FunctionComponent = ({ policy }) => { - + @@ -107,12 +106,11 @@ export const TabHistory: React.FunctionComponent = ({ policy }) => { - + @@ -140,7 +138,7 @@ export const TabHistory: React.FunctionComponent = ({ policy }) => { @@ -154,13 +152,13 @@ export const TabHistory: React.FunctionComponent = ({ policy }) => { setOptions={{ showLineNumbers: false, tabSize: 2, - maxLines: Infinity, }} editorProps={{ $blockScrolling: Infinity, }} minLines={6} - maxLines={6} + maxLines={12} + wrapEnabled={true} showGutter={false} aria-label={ = ({ policy }) => {

    , time: , diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx index eadd9a9867b0b..ea29d6492cb4b 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.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, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -41,36 +41,45 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { nextExecutionMillis, config, } = policy; - const { includeGlobalState, ignoreUnavailable, indices, partial } = config; + const { includeGlobalState, ignoreUnavailable, indices, partial } = config || { + includeGlobalState: undefined, + ignoreUnavailable: undefined, + indices: undefined, + partial: undefined, + }; // Only show 10 indices initially const [isShowingFullIndicesList, setIsShowingFullIndicesList] = useState(false); - const hiddenIndicesCount = indices && indices.length > 10 ? indices.length - 10 : 0; + const displayIndices = typeof indices === 'string' ? indices.split(',') : indices; + const hiddenIndicesCount = + displayIndices && displayIndices.length > 10 ? displayIndices.length - 10 : 0; const shortIndicesList = - indices && indices.length ? ( -

      - {[...indices].splice(0, 10).map((index: string) => ( -
    • - - {index} - -
    • - ))} - {hiddenIndicesCount ? ( -
    • - - setIsShowingFullIndicesList(true)}> - {' '} - - - -
    • - ) : null} -
    + displayIndices && displayIndices.length ? ( + +
      + {[...displayIndices].splice(0, 10).map((index: string) => ( +
    • + + {index} + +
    • + ))} + {hiddenIndicesCount ? ( +
    • + + setIsShowingFullIndicesList(true)}> + {' '} + + + +
    • + ) : null} +
    +
    ) : ( = ({ policy }) => { /> ); const fullIndicesList = - indices && indices.length && indices.length > 10 ? ( -
      - {indices.map((index: string) => ( -
    • - - {index} - -
    • - ))} - {hiddenIndicesCount ? ( -
    • - - setIsShowingFullIndicesList(false)}> - {' '} - - - -
    • - ) : null} -
    + displayIndices && displayIndices.length && displayIndices.length > 10 ? ( + +
      + {displayIndices.map((index: string) => ( +
    • + + {index} + +
    • + ))} + {hiddenIndicesCount ? ( +
    • + + setIsShowingFullIndicesList(false)}> + {' '} + + + +
    • + ) : null} +
    +
    ) : null; + // Reset indices list state when clicking through different policies + useEffect(() => { + return () => { + setIsShowingFullIndicesList(false); + }; + }, []); + return ( @@ -180,7 +198,7 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { @@ -200,7 +218,7 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { - {isShowingFullIndicesList ? fullIndicesList : shortIndicesList} + {isShowingFullIndicesList ? fullIndicesList : shortIndicesList}
    diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_list.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_list.tsx index d94f3b0310387..a4664ea414526 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_list.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_list.tsx @@ -7,13 +7,16 @@ import React, { Fragment, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { SlmPolicy } from '../../../../../common/types'; +import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common/constants'; import { SectionError, SectionLoading } from '../../../components'; import { BASE_PATH, UIM_POLICY_LIST_LOAD } from '../../../constants'; import { useAppDependencies } from '../../../index'; import { useLoadPolicies } from '../../../services/http'; import { uiMetricService } from '../../../services/ui_metric'; +import { linkToAddPolicy, linkToPolicy } from '../../../services/navigation'; +import { WithPrivileges, NotAuthorizedSection } from '../../../lib/authorization'; import { PolicyDetails } from './policy_details'; import { PolicyTable } from './policy_table'; @@ -44,9 +47,7 @@ export const PolicyList: React.FunctionComponent { - return history.createHref({ - pathname: `${BASE_PATH}/policies/${newPolicyName}`, - }); + return linkToPolicy(newPolicyName); }; const closePolicyDetails = () => { @@ -72,7 +73,7 @@ export const PolicyList: React.FunctionComponent

    } + actions={ + + + + } data-test-subj="emptyPrompt" /> ); } else { + const policySchedules = policies.map((policy: SlmPolicy) => policy.schedule); + const hasDuplicateSchedules = policySchedules.length > new Set(policySchedules).size; content = ( - + + {hasDuplicateSchedules ? ( + + + } + color="warning" + iconType="alert" + > + + + + + ) : null} + + ); } return ( -
    - {policyName ? ( - - ) : null} - {content} -
    + `cluster.${name}`)}> + {({ hasPrivileges, privilegesMissing }) => + hasPrivileges ? ( +
    + {policyName ? ( + + ) : null} + {content} +
    + ) : ( + + } + message={ + + } + /> + ) + } +
    ); }; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx index 7db94b47c3ab6..2382f16e1f894 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx @@ -13,6 +13,7 @@ import { EuiLink, EuiToolTip, EuiButtonIcon, + EuiLoadingSpinner, } from '@elastic/eui'; import { SlmPolicy } from '../../../../../../common/types'; @@ -24,6 +25,7 @@ import { PolicyDeleteProvider, } from '../../../../components'; import { uiMetricService } from '../../../../services/ui_metric'; +import { linkToAddPolicy, linkToEditPolicy } from '../../../../services/navigation'; interface Props { policies: SlmPolicy[]; @@ -55,16 +57,34 @@ export const PolicyTable: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - render: (name: SlmPolicy['name']) => { + render: (name: SlmPolicy['name'], { inProgress }: SlmPolicy) => { return ( - /* eslint-disable-next-line @elastic/eui/href-or-on-click */ - trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK)} - href={openPolicyDetailsUrl(name)} - data-test-subj="policyLink" - > - {name} - + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK)} + href={openPolicyDetailsUrl(name)} + data-test-subj="policyLink" + > + {name} + + + {inProgress ? ( + + + + + + ) : null} + ); }, }, @@ -95,7 +115,7 @@ export const PolicyTable: React.FunctionComponent = ({ { field: 'nextExecutionMillis', name: i18n.translate('xpack.snapshotRestore.policyList.table.nextExecutionColumnTitle', { - defaultMessage: 'Next execution', + defaultMessage: 'Next snapshot', }), truncateText: true, sortable: true, @@ -109,64 +129,96 @@ export const PolicyTable: React.FunctionComponent = ({ }), actions: [ { - render: ({ name }: SlmPolicy) => { - return ( - - {executePolicyPrompt => { - const label = i18n.translate( - 'xpack.snapshotRestore.policyList.table.actionExecuteTooltip', - { defaultMessage: 'Run policy' } - ); - return ( - - executePolicyPrompt(name, onPolicyExecuted)} - /> - - ); - }} - - ); - }, - }, - { - render: ({ name }: SlmPolicy) => { + render: ({ name, inProgress }: SlmPolicy) => { return ( - - {deletePolicyPrompt => { - const label = i18n.translate( - 'xpack.snapshotRestore.policyList.table.actionDeleteTooltip', - { defaultMessage: 'Delete' } - ); - return ( - - + + + {executePolicyPrompt => { + return ( + deletePolicyPrompt([name], onPolicyDeleted)} - /> - - ); - }} - + > + executePolicyPrompt(name, onPolicyExecuted)} + disabled={Boolean(inProgress)} + /> + + ); + }} + +
    + + + + + + + + {deletePolicyPrompt => { + return ( + + deletePolicyPrompt([name], onPolicyDeleted)} + /> + + ); + }} + + +
    ); }, }, @@ -237,6 +289,19 @@ export const PolicyTable: React.FunctionComponent = ({ />
    + + + + +
    ), box: { @@ -268,6 +333,7 @@ export const PolicyTable: React.FunctionComponent = ({ return ( { - return history.createHref({ - pathname: `${BASE_PATH}/repositories/${newRepositoryName}`, - }); + return linkToRepository(newRepositoryName); }; const closeRepositoryDetails = () => { @@ -116,9 +115,7 @@ export const RepositoryList: React.FunctionComponent { } return ( - + `index.${name}`)}> {({ hasPrivileges, privilegesMissing }) => hasPrivileges ? (
    {content}
    diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx index a5e886d0af077..bbec23d30622d 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { EuiDescriptionList, @@ -112,6 +112,13 @@ export const TabSummary: React.SFC = ({ snapshotDetails }) => { ) : null; + // Reset indices list state when clicking through different snapshots + useEffect(() => { + return () => { + setIsShowingFullIndicesList(false); + }; + }, []); + return ( diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx index f6b716bcc18b6..7946d77ce8fab 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx @@ -7,15 +7,22 @@ import React, { Fragment, useState, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { parse } from 'querystring'; +import { EuiButton, EuiCallOut, EuiLink, EuiEmptyPrompt, EuiSpacer, EuiIcon } from '@elastic/eui'; -import { EuiButton, EuiCallOut, EuiIcon, EuiLink, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; - +import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common/constants'; import { SectionError, SectionLoading } from '../../../components'; import { BASE_PATH, UIM_SNAPSHOT_LIST_LOAD } from '../../../constants'; +import { WithPrivileges } from '../../../lib/authorization'; import { useAppDependencies } from '../../../index'; import { documentationLinksService } from '../../../services/documentation'; import { useLoadSnapshots } from '../../../services/http'; -import { linkToRepositories } from '../../../services/navigation'; +import { + linkToRepositories, + linkToAddRepository, + linkToPolicies, + linkToAddPolicy, + linkToSnapshot, +} from '../../../services/navigation'; import { uiMetricService } from '../../../services/ui_metric'; import { SnapshotDetails } from './snapshot_details'; @@ -42,7 +49,7 @@ export const SnapshotList: React.FunctionComponent { - return history.createHref({ - pathname: `${BASE_PATH}/snapshots/${encodeURIComponent( - repositoryNameToOpen - )}/${encodeURIComponent(snapshotIdToOpen)}`, - }); + return linkToSnapshot(repositoryNameToOpen, snapshotIdToOpen); }; const closeSnapshotDetails = () => { @@ -138,37 +141,22 @@ export const SnapshotList: React.FunctionComponent } body={ - -

    - - - - ), - }} - /> -

    -

    - - {' '} - - -

    -
    +

    + + + + ), + }} + /> +

    } /> ); @@ -194,9 +182,7 @@ export const SnapshotList: React.FunctionComponent

    } body={ - -

    - -

    -

    - - {' '} - - -

    - + `cluster.${name}`)}> + {({ hasPrivileges }) => + hasPrivileges ? ( + +

    + + + + ), + }} + /> +

    +

    + {policies.length === 0 ? ( + + + + ) : ( + + + + )} +

    +
    + ) : ( + +

    + +

    +

    + + {' '} + + +

    +
    + ) + } +
    } data-test-subj="emptyPrompt" /> diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/index.ts index 1e89132252bec..ddd579a1a292f 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/index.ts @@ -8,3 +8,5 @@ export { SnapshotRestoreHome } from './home'; export { RepositoryAdd } from './repository_add'; export { RepositoryEdit } from './repository_edit'; export { RestoreSnapshot } from './restore_snapshot'; +export { PolicyAdd } from './policy_add'; +export { PolicyEdit } from './policy_edit'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/index.js b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/index.ts similarity index 84% rename from x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/index.js rename to x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/index.ts index 764ff52dc73b9..45fa1353210cf 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/components/cron_editor/index.js +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { CronEditor } from './cron_editor'; +export { PolicyAdd } from './policy_add'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx new file mode 100644 index 0000000000000..3f186dad142bb --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx @@ -0,0 +1,132 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { SlmPolicyPayload } from '../../../../common/types'; + +import { PolicyForm, SectionError, SectionLoading } from '../../components'; +import { useAppDependencies } from '../../index'; +import { BASE_PATH, DEFAULT_POLICY_SCHEDULE } from '../../constants'; +import { breadcrumbService, docTitleService } from '../../services/navigation'; +import { addPolicy, useLoadIndicies } from '../../services/http'; + +export const PolicyAdd: React.FunctionComponent = ({ + history, + location: { pathname }, +}) => { + const { + core: { + i18n: { FormattedMessage }, + }, + } = useAppDependencies(); + const [isSaving, setIsSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + const { + error: errorLoadingIndices, + isLoading: isLoadingIndices, + data: { indices } = { + indices: [], + }, + } = useLoadIndicies(); + + // Set breadcrumb and page title + useEffect(() => { + breadcrumbService.setBreadcrumbs('policyAdd'); + docTitleService.setTitle('policyAdd'); + }, []); + + const onSave = async (newPolicy: SlmPolicyPayload) => { + setIsSaving(true); + setSaveError(null); + const { name } = newPolicy; + const { error } = await addPolicy(newPolicy); + setIsSaving(false); + if (error) { + setSaveError(error); + } else { + history.push(`${BASE_PATH}/policies/${name}`); + } + }; + + const onCancel = () => { + history.push(`${BASE_PATH}/policies`); + }; + + const emptyPolicy: SlmPolicyPayload = { + name: '', + snapshotName: '', + schedule: DEFAULT_POLICY_SCHEDULE, + repository: '', + config: {}, + }; + + const renderSaveError = () => { + return saveError ? ( + + } + error={saveError} + data-test-subj="savePolicyApiError" + /> + ) : null; + }; + + const clearSaveError = () => { + setSaveError(null); + }; + + return ( + + + +

    + +

    +
    + + {isLoadingIndices ? ( + + + + ) : errorLoadingIndices ? ( + + } + error={errorLoadingIndices} + /> + ) : ( + + )} +
    +
    + ); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/index.ts new file mode 100644 index 0000000000000..68414d0ccf506 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { PolicyEdit } from './policy_edit'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx new file mode 100644 index 0000000000000..4ada745062c6f --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx @@ -0,0 +1,210 @@ +/* + * 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, { useEffect, useState, Fragment } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { SlmPolicyPayload } from '../../../../common/types'; + +import { SectionError, SectionLoading, PolicyForm } from '../../components'; +import { BASE_PATH } from '../../constants'; +import { useAppDependencies } from '../../index'; +import { breadcrumbService, docTitleService } from '../../services/navigation'; +import { editPolicy, useLoadPolicy, useLoadIndicies } from '../../services/http'; + +interface MatchParams { + name: string; +} + +export const PolicyEdit: React.FunctionComponent> = ({ + match: { + params: { name }, + }, + history, + location: { pathname }, +}) => { + const { + core: { i18n }, + } = useAppDependencies(); + const { FormattedMessage } = i18n; + + // Set breadcrumb and page title + useEffect(() => { + breadcrumbService.setBreadcrumbs('policyEdit'); + docTitleService.setTitle('policyEdit'); + }, []); + + // Policy state with default empty policy + const [policy, setPolicy] = useState({ + name: '', + snapshotName: '', + schedule: '', + repository: '', + config: {}, + }); + + const { + error: errorLoadingIndices, + isLoading: isLoadingIndices, + data: { indices } = { + indices: [], + }, + } = useLoadIndicies(); + + // Load policy + const { error: errorLoadingPolicy, isLoading: isLoadingPolicy, data: policyData } = useLoadPolicy( + name + ); + + // Update policy state when data is loaded + useEffect(() => { + if (policyData && policyData.policy) { + setPolicy(policyData.policy); + } + }, [policyData]); + + // Saving policy states + const [isSaving, setIsSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + // Save policy + const onSave = async (editedPolicy: SlmPolicyPayload) => { + setIsSaving(true); + setSaveError(null); + const { error } = await editPolicy(editedPolicy); + setIsSaving(false); + if (error) { + setSaveError(error); + } else { + history.push(`${BASE_PATH}/policies/${name}`); + } + }; + + const onCancel = () => { + history.push(`${BASE_PATH}/policies/${name}`); + }; + + const renderLoading = () => { + return errorLoadingPolicy ? ( + + + + ) : ( + + + + ); + }; + + const renderError = () => { + if (errorLoadingPolicy) { + const notFound = errorLoadingPolicy.status === 404; + const errorObject = notFound + ? { + data: { + error: i18n.translate('xpack.snapshotRestore.editPolicy.policyNotFoundErrorMessage', { + defaultMessage: `The policy '{name}' does not exist.`, + values: { + name, + }, + }), + }, + } + : errorLoadingPolicy; + return ( + + } + error={errorObject} + /> + ); + } + + if (errorLoadingIndices) { + return ( + + } + error={errorLoadingIndices} + /> + ); + } + }; + + const renderSaveError = () => { + return saveError ? ( + + } + error={saveError} + /> + ) : null; + }; + + const clearSaveError = () => { + setSaveError(null); + }; + + const renderContent = () => { + if (isLoadingPolicy || isLoadingIndices) { + return renderLoading(); + } + if (errorLoadingPolicy || errorLoadingIndices) { + return renderError(); + } + + return ( + + + + ); + }; + + return ( + + + +

    + +

    +
    + + {renderContent()} +
    +
    + ); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx index 28de033bd2d00..b4a76ff4329cf 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx @@ -5,6 +5,7 @@ */ import React, { useEffect, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; +import { parse } from 'querystring'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { Repository, EmptyRepository } from '../../../../common/types'; @@ -12,10 +13,13 @@ import { Repository, EmptyRepository } from '../../../../common/types'; import { RepositoryForm, SectionError } from '../../components'; import { BASE_PATH, Section } from '../../constants'; import { useAppDependencies } from '../../index'; -import { breadcrumbService } from '../../services/navigation'; +import { breadcrumbService, docTitleService } from '../../services/navigation'; import { addRepository } from '../../services/http'; -export const RepositoryAdd: React.FunctionComponent = ({ history }) => { +export const RepositoryAdd: React.FunctionComponent = ({ + history, + location: { search }, +}) => { const { core: { i18n: { FormattedMessage }, @@ -25,9 +29,10 @@ export const RepositoryAdd: React.FunctionComponent = ({ hi const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(null); - // Set breadcrumb + // Set breadcrumb and page title useEffect(() => { breadcrumbService.setBreadcrumbs('repositoryAdd'); + docTitleService.setTitle('repositoryAdd'); }, []); const onSave = async (newRepository: Repository | EmptyRepository) => { @@ -39,7 +44,8 @@ export const RepositoryAdd: React.FunctionComponent = ({ hi if (error) { setSaveError(error); } else { - history.push(`${BASE_PATH}/${section}/${name}`); + const { redirect } = parse(search.replace(/^\?/, '')); + history.push(redirect ? (redirect as string) : `${BASE_PATH}/${section}/${name}`); } }; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_edit/repository_edit.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_edit/repository_edit.tsx index 1c6b4eaba9d77..8544ea8f5ef1a 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_edit/repository_edit.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_edit/repository_edit.tsx @@ -12,7 +12,7 @@ import { Repository, EmptyRepository } from '../../../../common/types'; import { RepositoryForm, SectionError, SectionLoading } from '../../components'; import { BASE_PATH, Section } from '../../constants'; import { useAppDependencies } from '../../index'; -import { breadcrumbService } from '../../services/navigation'; +import { breadcrumbService, docTitleService } from '../../services/navigation'; import { editRepository, useLoadRepository } from '../../services/http'; interface MatchParams { @@ -31,9 +31,10 @@ export const RepositoryEdit: React.FunctionComponent { breadcrumbService.setBreadcrumbs('repositoryEdit'); + docTitleService.setTitle('repositoryEdit'); }, []); // Repository state with default empty repository diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx index baa46855184c3..53956cd007633 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/restore_snapshot/restore_snapshot.tsx @@ -11,7 +11,7 @@ import { SnapshotDetails, RestoreSettings } from '../../../../common/types'; import { BASE_PATH } from '../../constants'; import { SectionError, SectionLoading, RestoreSnapshotForm } from '../../components'; import { useAppDependencies } from '../../index'; -import { breadcrumbService } from '../../services/navigation'; +import { breadcrumbService, docTitleService } from '../../services/navigation'; import { useLoadSnapshot, executeRestore } from '../../services/http'; interface MatchParams { @@ -30,9 +30,10 @@ export const RestoreSnapshot: React.FunctionComponent { breadcrumbService.setBreadcrumbs('restoreSnapshot'); + docTitleService.setTitle('restoreSnapshot'); }, []); // Snapshot details state with default empty snapshot diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts index 324f4d026c0b9..219292e7b0813 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts @@ -10,10 +10,16 @@ import { REPOSITORY_DOC_PATHS } from '../../constants'; class DocumentationLinksService { private esDocBasePath: string = ''; private esPluginDocBasePath: string = ''; + private esStackOverviewDocBasePath: string = ''; - public init(esDocBasePath: string, esPluginDocBasePath: string): void { + public init( + esDocBasePath: string, + esPluginDocBasePath: string, + esStackOverviewDocBasePath: string + ): void { this.esDocBasePath = esDocBasePath; this.esPluginDocBasePath = esPluginDocBasePath; + this.esStackOverviewDocBasePath = esStackOverviewDocBasePath; } public getRepositoryPluginDocUrl() { @@ -42,7 +48,7 @@ class DocumentationLinksService { } public getSnapshotDocUrl() { - return `${this.esDocBasePath}/modules-snapshots.html#_snapshot`; + return `${this.esDocBasePath}/modules-snapshots.html#snapshots-take-snapshot`; } public getRestoreDocUrl() { @@ -56,6 +62,18 @@ class DocumentationLinksService { public getIndexSettingsUrl() { return `${this.esDocBasePath}/index-modules.html`; } + + public getDateMathIndexNamesUrl() { + return `${this.esDocBasePath}/date-math-index-names.html`; + } + + public getSlmUrl() { + return `${this.esDocBasePath}/slm-api-put.html`; + } + + public getCronUrl() { + return `${this.esStackOverviewDocBasePath}/trigger-schedule.html#schedule-cron`; + } } export const documentationLinksService = new DocumentationLinksService(); diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/policy_requests.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/policy_requests.ts index 6a2c9c685a01f..f8266833ec3e6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/policy_requests.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/policy_requests.ts @@ -4,8 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import { API_BASE_PATH } from '../../../../common/constants'; -import { SlmPolicy } from '../../../../common/types'; -import { UIM_POLICY_EXECUTE, UIM_POLICY_DELETE, UIM_POLICY_DELETE_MANY } from '../../constants'; +import { SlmPolicy, SlmPolicyPayload } from '../../../../common/types'; +import { + UIM_POLICY_EXECUTE, + UIM_POLICY_DELETE, + UIM_POLICY_DELETE_MANY, + UIM_POLICY_CREATE, + UIM_POLICY_UPDATE, +} from '../../constants'; import { uiMetricService } from '../ui_metric'; import { httpService } from './http'; import { useRequest, sendRequest } from './use_request'; @@ -24,6 +30,13 @@ export const useLoadPolicy = (name: SlmPolicy['name']) => { }); }; +export const useLoadIndicies = () => { + return useRequest({ + path: httpService.addBasePath(`${API_BASE_PATH}policies/indices`), + method: 'get', + }); +}; + export const executePolicy = async (name: SlmPolicy['name']) => { const result = sendRequest({ path: httpService.addBasePath(`${API_BASE_PATH}policy/${encodeURIComponent(name)}/run`), @@ -47,3 +60,29 @@ export const deletePolicies = async (names: Array) => { trackUiMetric(names.length > 1 ? UIM_POLICY_DELETE_MANY : UIM_POLICY_DELETE); return result; }; + +export const addPolicy = async (newPolicy: SlmPolicyPayload) => { + const result = sendRequest({ + path: httpService.addBasePath(`${API_BASE_PATH}policies`), + method: 'put', + body: newPolicy, + }); + + const { trackUiMetric } = uiMetricService; + trackUiMetric(UIM_POLICY_CREATE); + return result; +}; + +export const editPolicy = async (editedPolicy: SlmPolicyPayload) => { + const result = await sendRequest({ + path: httpService.addBasePath( + `${API_BASE_PATH}policies/${encodeURIComponent(editedPolicy.name)}` + ), + method: 'put', + body: editedPolicy, + }); + + const { trackUiMetric } = uiMetricService; + trackUiMetric(UIM_POLICY_UPDATE); + return result; +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/breadcrumb.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/breadcrumb.ts index fa9b886fa55b8..23d3f215d058c 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/breadcrumb.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/breadcrumb.ts @@ -4,50 +4,128 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../constants'; import { textService } from '../text'; +import { + linkToHome, + linkToSnapshots, + linkToRepositories, + linkToPolicies, + linkToRestoreStatus, +} from './'; class BreadcrumbService { private chrome: any; - private breadcrumbs: any = { - management: {}, - home: {}, - repositoryAdd: {}, - repositoryEdit: {}, - restoreSnapshot: {}, + private breadcrumbs: { + [key: string]: Array<{ + text: string; + href?: string; + }>; + } = { + management: [], + home: [], + snapshots: [], + repositories: [], + policies: [], + restore_status: [], + repositoryAdd: [], + repositoryEdit: [], + restoreSnapshot: [], + policyAdd: [], + policyEdit: [], }; public init(chrome: any, managementBreadcrumb: any): void { this.chrome = chrome; - this.breadcrumbs.management = managementBreadcrumb; - this.breadcrumbs.home = { - text: textService.breadcrumbs.home, - href: `#${BASE_PATH}`, - }; - this.breadcrumbs.repositoryAdd = { - text: textService.breadcrumbs.repositoryAdd, - }; - this.breadcrumbs.repositoryEdit = { - text: textService.breadcrumbs.repositoryEdit, - }; - this.breadcrumbs.restoreSnapshot = { - text: textService.breadcrumbs.restoreSnapshot, - }; + this.breadcrumbs.management = [managementBreadcrumb]; + + // Home and sections + this.breadcrumbs.home = [ + ...this.breadcrumbs.management, + { + text: textService.breadcrumbs.home, + href: linkToHome(), + }, + ]; + this.breadcrumbs.snapshots = [ + ...this.breadcrumbs.home, + { + text: textService.breadcrumbs.snapshots, + href: linkToSnapshots(), + }, + ]; + this.breadcrumbs.repositories = [ + ...this.breadcrumbs.home, + { + text: textService.breadcrumbs.repositories, + href: linkToRepositories(), + }, + ]; + this.breadcrumbs.policies = [ + ...this.breadcrumbs.home, + { + text: textService.breadcrumbs.policies, + href: linkToPolicies(), + }, + ]; + this.breadcrumbs.restore_status = [ + ...this.breadcrumbs.home, + { + text: textService.breadcrumbs.restore_status, + href: linkToRestoreStatus(), + }, + ]; + + // Inner pages + this.breadcrumbs.repositoryAdd = [ + ...this.breadcrumbs.repositories, + { + text: textService.breadcrumbs.repositoryAdd, + }, + ]; + this.breadcrumbs.repositoryEdit = [ + ...this.breadcrumbs.repositories, + { + text: textService.breadcrumbs.repositoryEdit, + }, + ]; + this.breadcrumbs.restoreSnapshot = [ + ...this.breadcrumbs.snapshots, + { + text: textService.breadcrumbs.restoreSnapshot, + }, + ]; + this.breadcrumbs.policyAdd = [ + ...this.breadcrumbs.policies, + { + text: textService.breadcrumbs.policyAdd, + }, + ]; + this.breadcrumbs.policyEdit = [ + ...this.breadcrumbs.policies, + { + text: textService.breadcrumbs.policyEdit, + }, + ]; } public setBreadcrumbs(type: string): void { - if (!this.breadcrumbs[type]) { - return; - } - if (type === 'home') { - this.chrome.breadcrumbs.set([this.breadcrumbs.management, this.breadcrumbs.home]); - } else { - this.chrome.breadcrumbs.set([ - this.breadcrumbs.management, - this.breadcrumbs.home, - this.breadcrumbs[type], - ]); - } + const newBreadcrumbs = this.breadcrumbs[type] + ? [...this.breadcrumbs[type]] + : [...this.breadcrumbs.home]; + + // Pop off last breadcrumb + const lastBreadcrumb = newBreadcrumbs.pop() as { + text: string; + href?: string; + }; + + // Put last breadcrumb back without href + newBreadcrumbs.push({ + ...lastBreadcrumb, + href: undefined, + }); + + this.chrome.breadcrumbs.set(newBreadcrumbs); } } diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/doc_title.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/doc_title.ts new file mode 100644 index 0000000000000..a42d09f2a2f45 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/doc_title.ts @@ -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 { textService } from '../text'; + +class DocTitleService { + private changeDocTitle: any = () => {}; + + public init(changeDocTitle: any): void { + this.changeDocTitle = changeDocTitle; + } + + public setTitle(page?: string): void { + if (!page || page === 'home') { + this.changeDocTitle(`${textService.breadcrumbs.home}`); + } else if (textService.breadcrumbs[page]) { + this.changeDocTitle(`${textService.breadcrumbs[page]} - ${textService.breadcrumbs.home}`); + } + } +} + +export const docTitleService = new DocTitleService(); diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/index.ts index f1e3c537c5d70..badb47600329d 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/index.ts @@ -5,4 +5,5 @@ */ export { breadcrumbService } from './breadcrumb'; +export { docTitleService } from './doc_title'; export * from './links'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/links.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/links.ts index 9f8426e84e214..6f95000726106 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/links.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/navigation/links.ts @@ -6,6 +6,10 @@ import { BASE_PATH } from '../../constants'; +export function linkToHome() { + return `#${BASE_PATH}`; +} + export function linkToRepositories() { return `#${BASE_PATH}/repositories`; } @@ -18,8 +22,10 @@ export function linkToEditRepository(repositoryName: string) { return `#${BASE_PATH}/edit_repository/${encodeURIComponent(repositoryName)}`; } -export function linkToAddRepository() { - return `#${BASE_PATH}/add_repository`; +export function linkToAddRepository(redirect?: string) { + return `#${BASE_PATH}/add_repository${ + redirect ? `?redirect=${encodeURIComponent(redirect)}` : '' + }`; } export function linkToSnapshots(repositoryName?: string, policyName?: string) { @@ -44,6 +50,22 @@ export function linkToRestoreSnapshot(repositoryName: string, snapshotName: stri )}`; } +export function linkToPolicies() { + return `#${BASE_PATH}/policies`; +} + export function linkToPolicy(policyName: string) { return `#${BASE_PATH}/policies/${encodeURIComponent(policyName)}`; } + +export function linkToEditPolicy(policyName: string) { + return `#${BASE_PATH}/edit_policy/${encodeURIComponent(policyName)}`; +} + +export function linkToAddPolicy() { + return `#${BASE_PATH}/add_policy`; +} + +export function linkToRestoreStatus() { + return `#${BASE_PATH}/restore_status`; +} diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/text/text.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/text/text.ts index 50e6555e9bce4..ec92250373a05 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/text/text.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/text/text.ts @@ -51,6 +51,18 @@ class TextService { home: i18n.translate('xpack.snapshotRestore.home.breadcrumbTitle', { defaultMessage: 'Snapshot and Restore', }), + snapshots: i18n.translate('xpack.snapshotRestore.snapshots.breadcrumbTitle', { + defaultMessage: 'Snapshots', + }), + repositories: i18n.translate('xpack.snapshotRestore.repositories.breadcrumbTitle', { + defaultMessage: 'Repositories', + }), + policies: i18n.translate('xpack.snapshotRestore.policies.breadcrumbTitle', { + defaultMessage: 'Policies', + }), + restore_status: i18n.translate('xpack.snapshotRestore.restoreStatus.breadcrumbTitle', { + defaultMessage: 'Restore Status', + }), repositoryAdd: i18n.translate('xpack.snapshotRestore.addRepository.breadcrumbTitle', { defaultMessage: 'Add repository', }), @@ -60,6 +72,12 @@ class TextService { restoreSnapshot: i18n.translate('xpack.snapshotRestore.restoreSnapshot.breadcrumbTitle', { defaultMessage: 'Restore snapshot', }), + policyAdd: i18n.translate('xpack.snapshotRestore.addPolicy.breadcrumbTitle', { + defaultMessage: 'Add policy', + }), + policyEdit: i18n.translate('xpack.snapshotRestore.editPolicy.breadcrumbTitle', { + defaultMessage: 'Edit policy', + }), }; } diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/index.ts index f987d432f02f6..7fd755497eec6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/index.ts @@ -11,3 +11,5 @@ export { } from './validate_repository'; export { RestoreValidation, validateRestore } from './validate_restore'; + +export { PolicyValidation, validatePolicy } from './validate_policy'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts new file mode 100644 index 0000000000000..53c62da97bdac --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SlmPolicyPayload } from '../../../../common/types'; +import { textService } from '../text'; + +export interface PolicyValidation { + isValid: boolean; + errors: { [key: string]: React.ReactNode[] }; +} + +const isStringEmpty = (str: string | null): boolean => { + return str ? !Boolean(str.trim()) : true; +}; + +export const validatePolicy = (policy: SlmPolicyPayload): PolicyValidation => { + const i18n = textService.i18n; + + const { name, snapshotName, schedule, repository, config } = policy; + + const validation: PolicyValidation = { + isValid: true, + errors: { + name: [], + snapshotName: [], + schedule: [], + repository: [], + indices: [], + }, + }; + + if (isStringEmpty(name)) { + validation.errors.name.push( + i18n.translate('xpack.snapshotRestore.policyValidation.nameRequiredError', { + defaultMessage: 'Policy name is required.', + }) + ); + } + + if (isStringEmpty(snapshotName)) { + validation.errors.snapshotName.push( + i18n.translate('xpack.snapshotRestore.policyValidation.snapshotNameRequiredError', { + defaultMessage: 'Snapshot name is required.', + }) + ); + } + + if (isStringEmpty(schedule)) { + validation.errors.schedule.push( + i18n.translate('xpack.snapshotRestore.policyValidation.scheduleRequiredError', { + defaultMessage: 'Schedule is required.', + }) + ); + } + + if (isStringEmpty(repository)) { + validation.errors.repository.push( + i18n.translate('xpack.snapshotRestore.policyValidation.repositoryRequiredError', { + defaultMessage: 'Repository is required.', + }) + ); + } + + if (config && typeof config.indices === 'string' && config.indices.trim().length === 0) { + validation.errors.indices.push( + i18n.translate('xpack.snapshotRestore.policyValidation.indexPatternRequiredError', { + defaultMessage: 'At least one index pattern is required.', + }) + ); + } + + if (config && Array.isArray(config.indices) && config.indices.length === 0) { + validation.errors.indices.push( + i18n.translate('xpack.snapshotRestore.policyValidation.indicesRequiredError', { + defaultMessage: 'You must select at least one index.', + }) + ); + } + + // Remove fields with no errors + validation.errors = Object.entries(validation.errors) + .filter(([key, value]) => value.length > 0) + .reduce((errs: PolicyValidation['errors'], [key, value]) => { + errs[key] = value; + return errs; + }, {}); + + // Set overall validations status + if (Object.keys(validation.errors).length > 0) { + validation.isValid = false; + } + + return validation; +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts b/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts index f590237bec737..cd6d7233722bd 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/plugin.ts @@ -11,7 +11,7 @@ import { AppCore, AppPlugins } from './app/types'; import template from './index.html'; import { Core, Plugins } from './shim'; -import { breadcrumbService } from './app/services/navigation'; +import { breadcrumbService, docTitleService } from './app/services/navigation'; import { documentationLinksService } from './app/services/documentation'; import { httpService } from './app/services/http'; import { textService } from './app/services/text'; @@ -21,7 +21,7 @@ const REACT_ROOT_ID = 'snapshotRestoreReactRoot'; export class Plugin { public start(core: Core, plugins: Plugins): void { - const { i18n, routing, http, chrome, notification, documentation } = core; + const { i18n, routing, http, chrome, notification, documentation, docTitle } = core; const { management, uiMetric } = plugins; // Register management section @@ -38,8 +38,13 @@ export class Plugin { // Initialize services textService.init(i18n); breadcrumbService.init(chrome, management.constants.BREADCRUMB); - documentationLinksService.init(documentation.esDocBasePath, documentation.esPluginDocBasePath); uiMetricService.init(uiMetric.createUiStatsReporter); + documentationLinksService.init( + documentation.esDocBasePath, + documentation.esPluginDocBasePath, + documentation.esStackOverviewDocBasePath + ); + docTitleService.init(docTitle.change); const unmountReactApp = (): void => { const elem = document.getElementById(REACT_ROOT_ID); diff --git a/x-pack/legacy/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/legacy/plugins/snapshot_restore/public/shared_imports.ts index 3d93b882733ab..c79eaa08de95f 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/shared_imports.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/shared_imports.ts @@ -11,3 +11,8 @@ export { sendRequest, useRequest, } from '../../../../../src/plugins/es_ui_shared/public/request'; + +export { + CronEditor, + DAY, +} from '../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/shim.ts b/x-pack/legacy/plugins/snapshot_restore/public/shim.ts index 77604f90fd570..9c9d2d7d3ea86 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/shim.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/shim.ts @@ -12,6 +12,7 @@ import { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } from 'ui/documentation_links'; import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; import { fatalError, toastNotifications } from 'ui/notify'; import routes from 'ui/routes'; +import { docTitle } from 'ui/doc_title/doc_title'; import { HashRouter } from 'react-router-dom'; @@ -52,6 +53,10 @@ export interface Core extends AppCore { documentation: { esDocBasePath: string; esPluginDocBasePath: string; + esStackOverviewDocBasePath: string; + }; + docTitle: { + change: typeof docTitle.change; }; } @@ -108,6 +113,10 @@ export function createShim(): { core: Core; plugins: Plugins } { documentation: { esDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`, esPluginDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`, + esStackOverviewDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack-overview/${DOC_LINK_VERSION}/`, + }, + docTitle: { + change: docTitle.change, }, }, plugins: { diff --git a/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts b/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts index c37cc51f67eb0..79196f9bbe385 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts @@ -60,4 +60,18 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) ], method: 'PUT', }); + + slm.updatePolicy = ca({ + urls: [ + { + fmt: '/_slm/policy/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'PUT', + }); }; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts b/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts index b0d65ff06d80e..6e54f997209ab 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts @@ -9,7 +9,5 @@ export { serializeRepositorySettings, } from './repository_serialization'; export { cleanSettings } from './clean_settings'; -export { deserializeSnapshotDetails, deserializeSnapshotConfig } from './snapshot_serialization'; -export { deserializeRestoreShard } from './restore_serialization'; export { getManagedRepositoryName } from './get_managed_repository_name'; -export { deserializePolicy } from './policy_serialization'; +export { deserializeRestoreShard } from './restore_serialization'; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts index c97317858f98a..6c7ad0ae30387 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/app.ts @@ -8,6 +8,7 @@ import { wrapCustomError } from '../../../../../server/lib/create_router/error_w import { APP_REQUIRED_CLUSTER_PRIVILEGES, APP_RESTORE_INDEX_PRIVILEGES, + APP_SLM_CLUSTER_PRIVILEGES, } from '../../../common/constants'; // NOTE: now we import it from our "public" folder, but when the Authorisation lib // will move to the "es_ui_shared" plugin, it will be imported from its "static" folder @@ -65,7 +66,7 @@ export const getPrivilegesHandler: RouterRouteHandler = async ( path: '/_security/user/_has_privileges', method: 'POST', body: { - cluster: APP_REQUIRED_CLUSTER_PRIVILEGES, + cluster: [...APP_REQUIRED_CLUSTER_PRIVILEGES, ...APP_SLM_CLUSTER_PRIVILEGES], }, } ); diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts index f2335d4f78dd9..52e6449559bcc 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts @@ -4,7 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import { Request, ResponseToolkit } from 'hapi'; -import { getAllHandler, getOneHandler, executeHandler, deleteHandler } from './policy'; +import { + getAllHandler, + getOneHandler, + executeHandler, + deleteHandler, + createHandler, + updateHandler, + getIndicesHandler, +} from './policy'; describe('[Snapshot and Restore API Routes] Restore', () => { const mockRequest = {} as Request; @@ -209,4 +217,110 @@ describe('[Snapshot and Restore API Routes] Restore', () => { ).resolves.toEqual(expectedResponse); }); }); + + describe('createHandler()', () => { + const name = 'fooPolicy'; + const mockCreateRequest = ({ + payload: { + name, + }, + } as unknown) as Request; + + it('should return successful ES response', async () => { + const mockEsResponse = { acknowledged: true }; + const callWithRequest = jest + .fn() + .mockReturnValueOnce({}) + .mockReturnValueOnce(mockEsResponse); + const expectedResponse = { ...mockEsResponse }; + await expect( + createHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) + ).resolves.toEqual(expectedResponse); + }); + + it('should return error if policy with the same name already exists', async () => { + const mockEsResponse = { [name]: {} }; + const callWithRequest = jest.fn().mockReturnValue(mockEsResponse); + await expect( + createHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) + ).rejects.toThrow(); + }); + + it('should throw if ES error', async () => { + const callWithRequest = jest + .fn() + .mockReturnValueOnce({}) + .mockRejectedValueOnce(new Error()); + await expect( + createHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) + ).rejects.toThrow(); + }); + }); + + describe('updateHandler()', () => { + const name = 'fooPolicy'; + const mockCreateRequest = ({ + params: { + name, + }, + payload: { + name, + }, + } as unknown) as Request; + + it('should return successful ES response', async () => { + const mockEsResponse = { acknowledged: true }; + const callWithRequest = jest + .fn() + .mockReturnValueOnce({ [name]: {} }) + .mockReturnValueOnce(mockEsResponse); + const expectedResponse = { ...mockEsResponse }; + await expect( + updateHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) + ).resolves.toEqual(expectedResponse); + }); + + it('should throw if ES error', async () => { + const callWithRequest = jest.fn().mockRejectedValueOnce(new Error()); + await expect( + updateHandler(mockCreateRequest, callWithRequest, mockResponseToolkit) + ).rejects.toThrow(); + }); + }); + + describe('getIndicesHandler()', () => { + it('should arrify and sort index names returned from ES', async () => { + const mockEsResponse = [ + { + index: 'fooIndex', + }, + { + index: 'barIndex', + }, + ]; + const callWithRequest = jest.fn().mockReturnValueOnce(mockEsResponse); + const expectedResponse = { + indices: ['barIndex', 'fooIndex'], + }; + await expect( + getIndicesHandler(mockRequest, callWithRequest, mockResponseToolkit) + ).resolves.toEqual(expectedResponse); + }); + + it('should return empty array if no indices returned from ES', async () => { + const mockEsResponse: any[] = []; + const callWithRequest = jest.fn().mockReturnValueOnce(mockEsResponse); + const expectedResponse = { indices: [] }; + await expect( + getIndicesHandler(mockRequest, callWithRequest, mockResponseToolkit) + ).resolves.toEqual(expectedResponse); + }); + + it('should throw if ES error', async () => { + const callWithRequest = jest.fn().mockRejectedValueOnce(new Error()); + await expect( + getIndicesHandler(mockRequest, callWithRequest, mockResponseToolkit) + ).rejects.toThrow(); + }); + }); }); diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts index 28b75b706bcad..ed16a44bccdc6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts @@ -8,14 +8,17 @@ import { wrapCustomError, wrapEsError, } from '../../../../../server/lib/create_router/error_wrappers'; -import { SlmPolicyEs, SlmPolicy } from '../../../common/types'; -import { deserializePolicy } from '../../lib'; +import { SlmPolicyEs, SlmPolicy, SlmPolicyPayload } from '../../../common/types'; +import { deserializePolicy, serializePolicy } from '../../../common/lib'; export function registerPolicyRoutes(router: Router) { router.get('policies', getAllHandler); router.get('policy/{name}', getOneHandler); router.post('policy/{name}/run', executeHandler); router.delete('policies/{names}', deleteHandler); + router.put('policies', createHandler); + router.put('policies/{name}', updateHandler); + router.get('policies/indices', getIndicesHandler); } export const getAllHandler: RouterRouteHandler = async ( @@ -96,3 +99,65 @@ export const deleteHandler: RouterRouteHandler = async (req, callWithRequest) => return response; }; + +export const createHandler: RouterRouteHandler = async (req, callWithRequest) => { + const policy = req.payload as SlmPolicyPayload; + const { name } = policy; + const conflictError = wrapCustomError( + new Error('There is already a policy with that name.'), + 409 + ); + + // Check that policy with the same name doesn't already exist + try { + const policyByName = await callWithRequest('slm.policy', { name }); + if (policyByName[name]) { + throw conflictError; + } + } catch (e) { + // Rethrow conflict error but silently swallow all others + if (e === conflictError) { + throw e; + } + } + + // Otherwise create new policy + return await callWithRequest('slm.updatePolicy', { + name, + body: serializePolicy(policy), + }); +}; + +export const updateHandler: RouterRouteHandler = async (req, callWithRequest) => { + const { name } = req.params; + const policy = req.payload as SlmPolicyPayload; + + // Check that policy with the given name exists + // If it doesn't exist, 404 will be thrown by ES and will be returned + await callWithRequest('slm.policy', { name }); + + // Otherwise update policy + return await callWithRequest('slm.updatePolicy', { + name, + body: serializePolicy(policy), + }); +}; + +export const getIndicesHandler: RouterRouteHandler = async ( + req, + callWithRequest +): Promise<{ + indices: string[]; +}> => { + // Get indices + const indices: Array<{ + index: string; + }> = await callWithRequest('cat.indices', { + format: 'json', + h: 'index', + }); + + return { + indices: indices.map(({ index }) => index).sort(), + }; +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index 7b55b4a34d071..70c66071c1bf6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -55,6 +55,10 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { const mockRequest = {} as Request; test('combines snapshots and their repositories returned from ES', async () => { + const mockSnapshotGetPolicyEsResponse = { + fooPolicy: {}, + }; + const mockSnapshotGetRepositoryEsResponse = { fooRepository: {}, barRepository: {}, @@ -80,6 +84,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { const callWithRequest = jest .fn() + .mockReturnValueOnce(mockSnapshotGetPolicyEsResponse) .mockReturnValueOnce(mockSnapshotGetRepositoryEsResponse) .mockReturnValueOnce(mockGetSnapshotsFooResponse) .mockReturnValueOnce(mockGetSnapshotsBarResponse); @@ -87,6 +92,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { const expectedResponse = { errors: {}, repositories: ['fooRepository', 'barRepository'], + policies: ['fooPolicy'], snapshots: [ { ...defaultSnapshot, @@ -108,12 +114,17 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { }); test('returns empty arrays if no snapshots returned from ES', async () => { + const mockSnapshotGetPolicyEsResponse = {}; const mockSnapshotGetRepositoryEsResponse = {}; - const callWithRequest = jest.fn().mockReturnValue(mockSnapshotGetRepositoryEsResponse); + const callWithRequest = jest + .fn() + .mockReturnValue(mockSnapshotGetPolicyEsResponse) + .mockReturnValue(mockSnapshotGetRepositoryEsResponse); const expectedResponse = { errors: [], snapshots: [], repositories: [], + policies: [], }; const response = await getAllHandler(mockRequest, callWithRequest, mockResponseToolkit); diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts index c18947a371222..1067f3e207b82 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -9,8 +9,9 @@ import { wrapCustomError, } from '../../../../../server/lib/create_router/error_wrappers'; import { SnapshotDetails, SnapshotDetailsEs } from '../../../common/types'; +import { deserializeSnapshotDetails } from '../../../common/lib'; import { Plugins } from '../../../shim'; -import { deserializeSnapshotDetails, getManagedRepositoryName } from '../../lib'; +import { getManagedRepositoryName } from '../../lib'; let callWithInternalUser: any; @@ -27,10 +28,21 @@ export const getAllHandler: RouterRouteHandler = async ( ): Promise<{ snapshots: SnapshotDetails[]; errors: any[]; + policies: string[]; repositories: string[]; managedRepository?: string; }> => { const managedRepository = await getManagedRepositoryName(callWithInternalUser); + let policies: string[] = []; + + // Attempt to retrieve policies + // This could fail if user doesn't have access to read SLM policies + try { + const policiesByName = await callWithRequest('slm.policies'); + policies = Object.keys(policiesByName); + } catch (e) { + // Silently swallow error as policy names aren't required in UI + } /* * TODO: For 8.0, replace the logic in this handler with one call to `GET /_snapshot/_all/_all` @@ -44,7 +56,7 @@ export const getAllHandler: RouterRouteHandler = async ( const repositoryNames = Object.keys(repositoriesByName); if (repositoryNames.length === 0) { - return { snapshots: [], errors: [], repositories: [] }; + return { snapshots: [], errors: [], repositories: [], policies }; } const snapshots: SnapshotDetails[] = []; @@ -86,6 +98,7 @@ export const getAllHandler: RouterRouteHandler = async ( return { snapshots, + policies, repositories, errors, }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0ad30e7701d11..080b1b09182aa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8682,26 +8682,6 @@ "xpack.rollupJobs.createAction.jobIdAlreadyExistsErrorMessage": "ID「{jobConfigId}」のジョブが既に存在します。", "xpack.rollupJobs.createBreadcrumbTitle": "作成", "xpack.rollupJobs.createTitle": "ロールアップジョブを作成", - "xpack.rollupJobs.cronEditor.cronDaily.fieldHour.textAtLabel": "時点で", - "xpack.rollupJobs.cronEditor.cronDaily.fieldTimeLabel": "時間", - "xpack.rollupJobs.cronEditor.cronHourly.fieldMinute.textAtLabel": "時点で", - "xpack.rollupJobs.cronEditor.cronHourly.fieldTimeLabel": "分", - "xpack.rollupJobs.cronEditor.cronMonthly.fieldDateLabel": "日付", - "xpack.rollupJobs.cronEditor.cronMonthly.fieldHour.textAtLabel": "時点で", - "xpack.rollupJobs.cronEditor.cronMonthly.fieldTimeLabel": "時間", - "xpack.rollupJobs.cronEditor.cronMonthly.textOnTheLabel": "On the", - "xpack.rollupJobs.cronEditor.cronWeekly.fieldDateLabel": "日", - "xpack.rollupJobs.cronEditor.cronWeekly.fieldHour.textAtLabel": "時点で", - "xpack.rollupJobs.cronEditor.cronWeekly.fieldTimeLabel": "時間", - "xpack.rollupJobs.cronEditor.cronWeekly.textOnLabel": "オン", - "xpack.rollupJobs.cronEditor.cronYearly.fieldDate.textOnTheLabel": "On the", - "xpack.rollupJobs.cronEditor.cronYearly.fieldDateLabel": "日付", - "xpack.rollupJobs.cronEditor.cronYearly.fieldHour.textAtLabel": "時点で", - "xpack.rollupJobs.cronEditor.cronYearly.fieldMonth.textInLabel": "In", - "xpack.rollupJobs.cronEditor.cronYearly.fieldMonthLabel": "月", - "xpack.rollupJobs.cronEditor.cronYearly.fieldTimeLabel": "時間", - "xpack.rollupJobs.cronEditor.fieldFrequencyLabel": "頻度", - "xpack.rollupJobs.cronEditor.textEveryLabel": "毎", "xpack.rollupJobs.deleteAction.errorTitle": "ロールアップジョブの削除中にエラーが発生", "xpack.rollupJobs.deleteAction.successMultipleNotificationTitle": "{count} 件のロールアップジョブが削除されました", "xpack.rollupJobs.deleteAction.successSingleNotificationTitle": "ロールアップジョブ「{jobId}」が削除されました", @@ -8785,25 +8765,6 @@ "xpack.rollupJobs.rollupIndexPatternsTitle": "ロールアップインデックスパターンを有効にする", "xpack.rollupJobs.startJobsAction.errorTitle": "ロールアップジョブの開始中にエラーが発生", "xpack.rollupJobs.stopJobsAction.errorTitle": "ロールアップジョブの停止中にエラーが発生", - "xpack.rollupJobs.util.day.friday": "金曜日", - "xpack.rollupJobs.util.day.monday": "月曜日", - "xpack.rollupJobs.util.day.saturday": "土曜日", - "xpack.rollupJobs.util.day.sunday": "日曜日", - "xpack.rollupJobs.util.day.thursday": "木曜日", - "xpack.rollupJobs.util.day.tuesday": "火曜日", - "xpack.rollupJobs.util.day.wednesday": "水曜日", - "xpack.rollupJobs.util.month.april": "4 月", - "xpack.rollupJobs.util.month.august": "8 月", - "xpack.rollupJobs.util.month.december": "12 月", - "xpack.rollupJobs.util.month.february": "2 月", - "xpack.rollupJobs.util.month.january": "1 月", - "xpack.rollupJobs.util.month.july": "7 月", - "xpack.rollupJobs.util.month.june": "6 月", - "xpack.rollupJobs.util.month.march": "3 月", - "xpack.rollupJobs.util.month.may": "5 月", - "xpack.rollupJobs.util.month.november": "11 月", - "xpack.rollupJobs.util.month.october": "10 月", - "xpack.rollupJobs.util.month.september": "9 月", "xpack.searchProfiler.aggregationProfileTabTitle": "集約プロフィール", "xpack.searchProfiler.basicLicenseTitle": "ベーシック", "xpack.searchProfiler.formIndexLabel": "インデックス", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4508f98b00b1c..f52883a033122 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8824,26 +8824,6 @@ "xpack.rollupJobs.createAction.jobIdAlreadyExistsErrorMessage": "ID 为 “{jobConfigId}” 的作业已存在。", "xpack.rollupJobs.createBreadcrumbTitle": "创建", "xpack.rollupJobs.createTitle": "创建汇总/打包作业", - "xpack.rollupJobs.cronEditor.cronDaily.fieldHour.textAtLabel": "在", - "xpack.rollupJobs.cronEditor.cronDaily.fieldTimeLabel": "时间", - "xpack.rollupJobs.cronEditor.cronHourly.fieldMinute.textAtLabel": "在", - "xpack.rollupJobs.cronEditor.cronHourly.fieldTimeLabel": "分钟", - "xpack.rollupJobs.cronEditor.cronMonthly.fieldDateLabel": "日期", - "xpack.rollupJobs.cronEditor.cronMonthly.fieldHour.textAtLabel": "在", - "xpack.rollupJobs.cronEditor.cronMonthly.fieldTimeLabel": "时间", - "xpack.rollupJobs.cronEditor.cronMonthly.textOnTheLabel": "处于", - "xpack.rollupJobs.cronEditor.cronWeekly.fieldDateLabel": "天", - "xpack.rollupJobs.cronEditor.cronWeekly.fieldHour.textAtLabel": "在", - "xpack.rollupJobs.cronEditor.cronWeekly.fieldTimeLabel": "时间", - "xpack.rollupJobs.cronEditor.cronWeekly.textOnLabel": "开启", - "xpack.rollupJobs.cronEditor.cronYearly.fieldDate.textOnTheLabel": "处于", - "xpack.rollupJobs.cronEditor.cronYearly.fieldDateLabel": "日期", - "xpack.rollupJobs.cronEditor.cronYearly.fieldHour.textAtLabel": "在", - "xpack.rollupJobs.cronEditor.cronYearly.fieldMonth.textInLabel": "于", - "xpack.rollupJobs.cronEditor.cronYearly.fieldMonthLabel": "月", - "xpack.rollupJobs.cronEditor.cronYearly.fieldTimeLabel": "时间", - "xpack.rollupJobs.cronEditor.fieldFrequencyLabel": "频率", - "xpack.rollupJobs.cronEditor.textEveryLabel": "所有", "xpack.rollupJobs.deleteAction.errorTitle": "删除汇总/打包作业时出错", "xpack.rollupJobs.deleteAction.successMultipleNotificationTitle": "已删除 {count} 个汇总/打包作业", "xpack.rollupJobs.deleteAction.successSingleNotificationTitle": "已删除汇总/打包作业“{jobId}”", @@ -8927,25 +8907,6 @@ "xpack.rollupJobs.rollupIndexPatternsTitle": "启用汇总索引模式", "xpack.rollupJobs.startJobsAction.errorTitle": "启动汇总/打包作业时出错", "xpack.rollupJobs.stopJobsAction.errorTitle": "停止汇总/打包作业时出错", - "xpack.rollupJobs.util.day.friday": "星期五", - "xpack.rollupJobs.util.day.monday": "星期一", - "xpack.rollupJobs.util.day.saturday": "星期六", - "xpack.rollupJobs.util.day.sunday": "星期日", - "xpack.rollupJobs.util.day.thursday": "星期四", - "xpack.rollupJobs.util.day.tuesday": "星期二", - "xpack.rollupJobs.util.day.wednesday": "星期三", - "xpack.rollupJobs.util.month.april": "四月", - "xpack.rollupJobs.util.month.august": "八月", - "xpack.rollupJobs.util.month.december": "十二月", - "xpack.rollupJobs.util.month.february": "二月", - "xpack.rollupJobs.util.month.january": "一月", - "xpack.rollupJobs.util.month.july": "七月", - "xpack.rollupJobs.util.month.june": "六月", - "xpack.rollupJobs.util.month.march": "三月", - "xpack.rollupJobs.util.month.may": "五月", - "xpack.rollupJobs.util.month.november": "十一月", - "xpack.rollupJobs.util.month.october": "十月", - "xpack.rollupJobs.util.month.september": "九月", "xpack.searchProfiler.aggregationProfileTabTitle": "聚合配置文件", "xpack.searchProfiler.basicLicenseTitle": "基础级", "xpack.searchProfiler.formIndexLabel": "索引", From 52011362a2d76c60b0fa3da9c8f3543c6c2bb652 Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 27 Aug 2019 14:38:10 -0700 Subject: [PATCH 39/66] skip flaky suite (#43960) --- x-pack/legacy/plugins/code/server/__tests__/multi_node.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts b/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts index 9c97a268962e3..0b4a9c576eb4f 100644 --- a/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts +++ b/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts @@ -63,7 +63,8 @@ const xpackOption = { }, }; -describe('code in multiple nodes', () => { +// FLAKY: https://github.com/elastic/kibana/issues/43960 +describe.skip('code in multiple nodes', () => { const codeNodeUuid = 'c4add484-0cba-4e05-86fe-4baa112d9e53'; const nonodeNodeUuid = '22b75e04-0e50-4647-9643-6b1b1d88beaf'; let codePort: number; From cb17672a953673dbe7670edf1fb98e1df945d9bb Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Wed, 28 Aug 2019 00:46:43 +0300 Subject: [PATCH 40/66] Update run_i18n_extract.ts (#44092) call `process.exit()` on `i18n_extact` complete. this ensures the process closes when spawned from a child process. --- src/dev/run_i18n_extract.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/run_i18n_extract.ts b/src/dev/run_i18n_extract.ts index cbc795a738ac8..5492b5cd6795c 100644 --- a/src/dev/run_i18n_extract.ts +++ b/src/dev/run_i18n_extract.ts @@ -88,6 +88,7 @@ run( log.error(error); } } + process.exit(); }, { flags: { From 670f1ae814d03a78824dbd86636b9458511a39de Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Tue, 27 Aug 2019 18:00:51 -0400 Subject: [PATCH 41/66] Fix truncation of long filter bar items (#43874) --- .../public/filter/filter_bar/_global_filter_group.scss | 9 +++++++++ .../public/filter/filter_bar/_global_filter_item.scss | 7 +++++++ .../data/public/filter/filter_bar/filter_bar.tsx | 4 ++-- .../data/public/filter/filter_bar/filter_item.tsx | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss index 7fbe295b95571..f4586c7c0fbc3 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_group.scss @@ -25,6 +25,15 @@ transition: height $euiAnimSpeedNormal $euiAnimSlightResistance; } +.globalFilterGroup__filterFlexItem { + overflow: hidden; + padding-bottom: 2px; // Allow the shadows of the pills to show +} + +.globalFilterBar__flexItem { + max-width: calc(100% - #{$euiSizeXS}); // Width minus margin around each flex itm +} + @include euiBreakpoint('xs', 's') { .globalFilterGroup__wrapper-isVisible { // EUI Flexbox adds too much margin between responded items, this just moves it up diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss index f128d6db189e6..caf3b0b796b9e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/_global_filter_item.scss @@ -40,9 +40,16 @@ left: 0; width: $euiSizeXS; background-color: $euiColorVis0; + border-top-left-radius: $euiBorderRadius / 2; + border-bottom-left-radius: $euiBorderRadius / 2; } } .globalFilterItem__editorForm { padding: $euiSizeM; } + +.globalFilterItem__popover, +.globalFilterItem__popoverAnchor { + display: block; +} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index ca2b26cc3202d..d235cddccf165 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -77,7 +77,7 @@ class FilterBarUI extends Component { /> - + { private renderItems() { return this.props.filters.map((filter, i) => ( - + { return ( Date: Tue, 27 Aug 2019 17:25:24 -0500 Subject: [PATCH 42/66] eui to 13.6.1 (#44149) --- package.json | 2 +- .../plugins/kbn_tp_run_pipeline/package.json | 2 +- .../plugins/kbn_tp_custom_visualizations/package.json | 2 +- .../plugins/kbn_tp_embeddable_explorer/package.json | 2 +- .../plugins/kbn_tp_sample_panel_action/package.json | 2 +- .../plugins/kbn_tp_visualize_embedding/package.json | 2 +- x-pack/package.json | 2 +- yarn.lock | 8 ++++---- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index a9be8d5f36b6c..bec1b5e148d61 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@babel/register": "^7.5.5", "@elastic/charts": "^10.2.0", "@elastic/datemath": "5.0.2", - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index f870e9518dcfd..4ff17d490b0ad 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index f970f0d219b62..0a2a4d0151f88 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index cd1ca420f41d4..30454c901cfea 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index ae9ff2956c4bb..cfa46eb2dceef 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index 47c8ac5a9df89..be080683bfc1d 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/x-pack/package.json b/x-pack/package.json index d02c62c8c27a8..2d062ec4730b3 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -183,7 +183,7 @@ "@babel/runtime": "^7.5.5", "@elastic/ctags-langserver": "^0.1.8", "@elastic/datemath": "5.0.2", - "@elastic/eui": "13.6.0", + "@elastic/eui": "13.6.1", "@elastic/javascript-typescript-langserver": "^0.2.2", "@elastic/lsp-extension": "^0.1.2", "@elastic/maki": "6.1.0", diff --git a/yarn.lock b/yarn.lock index 7c3c91023c576..73352c090cce3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,10 +1129,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@13.6.0": - version "13.6.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-13.6.0.tgz#a01b66f84bfc7eec2303df1c8d9a7d7443b59049" - integrity sha512-a7hD9jLIvA2yJZi+btDIRGRHRSjCwb/0/en3U29MtNB2cOEnNYQ1MGcI5/eIz6eKi93eb0ASG9qq6xVqVaAezQ== +"@elastic/eui@13.6.1": + version "13.6.1" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-13.6.1.tgz#4bcef855c9e5bb368436e18d79443b2f1fbeffef" + integrity sha512-i2f9J+Cf8/2fydIAT8MapvAtP//XZxybH0cU3UYASvc5P8DBt6rU/jdWLcHJhTHYZFDP+PjMMGzxWQNUS40IGg== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" From 871abf5dea2f6859f0059cd5ebfc597bb5e2fb2f Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 27 Aug 2019 16:57:17 -0700 Subject: [PATCH 43/66] Refactor doc table to only accept hits (#43634) * Refactor doc table and remove search source from it * Fix auto refresh * Remove reference to courier * Update doc table tests * Fix isFetchRequired logic * Remove references to courier * Fix import from ui/courier * Add courier back in for refresh interval --- .../dashboard/dashboard_app_controller.tsx | 13 -- .../discover/doc_table/__tests__/doc_table.js | 47 +++---- .../public/discover/doc_table/doc_table.html | 2 +- .../public/discover/doc_table/doc_table.js | 95 ++----------- .../discover/doc_table/lib/get_sort.d.ts | 27 ++++ .../discover/embeddable/search_embeddable.ts | 127 +++++++++++++----- .../embeddable/search_embeddable_factory.ts | 2 - .../discover/embeddable/search_template.html | 6 +- .../public/discover/embeddable/types.ts | 3 +- .../kibana/public/discover/types.d.ts | 3 +- src/legacy/ui/public/courier/index.d.ts | 1 + .../utils/courier_inspector_utils.d.ts | 52 +++++++ .../translations/translations/ja-JP.json | 6 +- .../translations/translations/zh-CN.json | 6 +- 14 files changed, 217 insertions(+), 173 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/discover/doc_table/lib/get_sort.d.ts create mode 100644 src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index cbb8a66301e35..081ad3fa3a95d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -447,7 +447,6 @@ export class DashboardAppController { }, [] ); - courier.fetch(); }; const updateStateFromSavedQuery = (savedQuery: SavedQuery) => { @@ -465,7 +464,6 @@ export class DashboardAppController { timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); } } - courier.fetch(); }; $scope.$watch('savedQuery', (newSavedQuery: SavedQuery, oldSavedQuery: SavedQuery) => { @@ -517,17 +515,6 @@ export class DashboardAppController { ); $scope.timefilterSubscriptions$ = new Subscription(); - // The only reason this is here is so that search embeddables work on a dashboard with - // a refresh interval turned on. This kicks off the search poller. It should be - // refactored so no embeddables need to listen to the timefilter directly but instead - // the container tells it when to reload. - $scope.timefilterSubscriptions$.add( - subscribeWithScope($scope, timefilter.getFetch$(), { - next: () => { - courier.fetch(); - }, - }) - ); $scope.timefilterSubscriptions$.add( subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js index 11462d76c816f..417d521dd44ed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js @@ -23,7 +23,8 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import 'ui/private'; import '..'; -import FixturesStubbedSearchSourceProvider from 'fixtures/stubbed_search_source'; +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import hits from 'fixtures/real_hits'; // Load the kibana app dependencies. @@ -37,7 +38,7 @@ let $scope; let $timeout; -let searchSource; +let indexPattern; const init = function ($elem, props) { ngMock.inject(function ($rootScope, $compile, _$timeout_) { @@ -67,12 +68,22 @@ describe('docTable', function () { beforeEach(ngMock.module('kibana')); beforeEach(function () { - $elem = angular.element(''); + $elem = angular.element(` + + `); ngMock.inject(function (Private) { - searchSource = Private(FixturesStubbedSearchSourceProvider); + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); }); init($elem, { - searchSource, + indexPattern, + hits: [...hits], + totalHitCount: hits.length, columns: [], sorting: ['@timestamp', 'desc'] }); @@ -88,18 +99,8 @@ describe('docTable', function () { expect($elem.text()).to.not.be.empty(); }); - it('should set the indexPattern to that of the searchSource', function () { - expect($scope.indexPattern).to.be(searchSource.getField('index')); - }); - - it('should set size and sort on the searchSource', function () { - expect($scope.searchSource.setField.getCall(0).args[0]).to.be('size'); - expect($scope.searchSource.setField.getCall(1).args[0]).to.be('sort'); - }); - it('should have an addRows function that increases the row count', function () { expect($scope.addRows).to.be.a(Function); - searchSource.crankResults(); $scope.$digest(); expect($scope.limit).to.be(50); $scope.addRows(); @@ -109,26 +110,12 @@ describe('docTable', function () { it('should reset the row limit when results are received', function () { $scope.limit = 100; expect($scope.limit).to.be(100); - searchSource.crankResults(); + $scope.hits = [...hits]; $scope.$digest(); expect($scope.limit).to.be(50); }); - it('should put the hits array on scope', function () { - expect($scope.hits).to.be(undefined); - searchSource.crankResults(); - $scope.$digest(); - expect($scope.hits).to.be.an(Array); - }); - - it('should destroy the searchSource when the scope is destroyed', function () { - expect(searchSource.destroy.called).to.be(false); - $scope.$destroy(); - expect(searchSource.destroy.called).to.be(true); - }); - it('should have a header and a table element', function () { - searchSource.crankResults(); $scope.$digest(); expect($elem.find('thead').length).to.be(1); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.html b/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.html index b73f204626b9c..b6ac1d3fd8b4a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.html +++ b/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.html @@ -1,7 +1,7 @@
    diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.js index 51d774fd2c150..c639ee8d80938 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/discover/doc_table/doc_table.js @@ -18,9 +18,7 @@ */ import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import html from './doc_table.html'; -import { getSort } from './lib/get_sort'; import './infinite_scroll'; import './components/table_header'; import './components/table_row'; @@ -28,22 +26,21 @@ import { dispatchRenderComplete } from 'ui/render_complete'; import { uiModules } from 'ui/modules'; import './components/pager'; import './lib/pager'; -import { getRequestInspectorStats, getResponseInspectorStats } from 'ui/courier/utils/courier_inspector_utils'; -import { toastNotifications } from 'ui/notify'; import { getLimitedSearchResultsMessage } from './doc_table_strings'; uiModules.get('app/discover') - .directive('docTable', function (config, getAppState, pagerFactory, $filter, courier) { + .directive('docTable', function (config, getAppState, pagerFactory, $filter) { return { restrict: 'E', template: html, scope: { sorting: '=', columns: '=', - hits: '=?', // You really want either hits & indexPattern, OR searchSource - indexPattern: '=?', - searchSource: '=?', + hits: '=', + totalHitCount: '=', + indexPattern: '=', + isLoading: '=?', infiniteScroll: '=?', filter: '=?', filters: '=?', @@ -93,83 +90,19 @@ uiModules.get('app/discover') if ($scope.columns.length === 0) $scope.columns.push('_source'); }); - $scope.$watch('searchSource', function () { - if (!$scope.searchSource) return; + $scope.$watch('hits', hits => { + if (!hits) return; - $scope.indexPattern = $scope.searchSource.getField('index'); - - $scope.searchSource.setField('size', config.get('discover:sampleSize')); - $scope.searchSource.setField('sort', getSort($scope.sorting, $scope.indexPattern)); - - // Set the watcher after initialization - $scope.$watchCollection('sorting', function (newSort, oldSort) { - // Don't react if sort values didn't really change - if (newSort === oldSort) return; - $scope.searchSource.setField('sort', getSort(newSort, $scope.indexPattern)); - $scope.searchSource.fetchQueued(); - }); - - $scope.$on('$destroy', function () { - if ($scope.searchSource) $scope.searchSource.destroy(); - }); - - function onResults(resp) { // Reset infinite scroll limit - $scope.limit = 50; - - // Abort if something changed - if ($scope.searchSource !== $scope.searchSource) return; - - $scope.hits = resp.hits.hits; - if ($scope.hits.length === 0) { - dispatchRenderComplete($el[0]); - } - // We limit the number of returned results, but we want to show the actual number of hits, not - // just how many we retrieved. - $scope.totalHitCount = resp.hits.total; - $scope.pager = pagerFactory.create($scope.hits.length, 50, 1); - calculateItemsOnPage(); - - return $scope.searchSource.onResults().then(onResults); - } + $scope.limit = 50; - function startSearching() { - let inspectorRequest = undefined; - if (_.has($scope, 'inspectorAdapters.requests')) { - $scope.inspectorAdapters.requests.reset(); - const title = i18n.translate('kbn.docTable.inspectorRequestDataTitle', { - defaultMessage: 'Data', - }); - const description = i18n.translate('kbn.docTable.inspectorRequestDescription', { - defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', - }); - inspectorRequest = $scope.inspectorAdapters.requests.start(title, { description }); - inspectorRequest.stats(getRequestInspectorStats($scope.searchSource)); - $scope.searchSource.getSearchRequestBody().then(body => { - inspectorRequest.json(body); - }); - } - $scope.searchSource.onResults() - .then(resp => { - if (inspectorRequest) { - inspectorRequest - .stats(getResponseInspectorStats($scope.searchSource, resp)) - .ok({ json: resp }); - } - return resp; - }) - .then(onResults) - .catch(error => { - toastNotifications.addError(error, { - title: i18n.translate('kbn.docTable.errorTitle', { - defaultMessage: 'Error fetching data' - }), - }); - startSearching(); - }); + if (hits.length === 0) { + dispatchRenderComplete($el[0]); } - startSearching(); - courier.fetch(); + + if ($scope.infiniteScroll) return; + $scope.pager = pagerFactory.create(hits.length, 50, 1); + calculateItemsOnPage(); }); $scope.pageOfItems = []; diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/lib/get_sort.d.ts b/src/legacy/core_plugins/kibana/public/discover/doc_table/lib/get_sort.d.ts new file mode 100644 index 0000000000000..50c63a514da04 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/doc_table/lib/get_sort.d.ts @@ -0,0 +1,27 @@ +/* + * 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 { StaticIndexPattern } from 'ui/index_patterns'; +import { SortOrder } from '../components/table_header/helpers'; + +export function getSort( + sort?: SortOrder[], + indexPattern?: StaticIndexPattern, + defaultSortOrder?: SortOrder +): any; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index 614bbd8b12a64..afbbcf79173db 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -22,13 +22,20 @@ import { getFilterGenerator } from 'ui/filter_manager'; import angular from 'angular'; import _ from 'lodash'; import { SearchSource } from 'ui/courier'; +import { + getRequestInspectorStats, + getResponseInspectorStats, +} from 'ui/courier/utils/courier_inspector_utils'; import { StaticIndexPattern } from 'ui/index_patterns'; import { RequestAdapter } from 'ui/inspector/adapters'; import { Adapters } from 'ui/inspector/types'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter, FilterStateStore } from '@kbn/es-query'; -import { getTime, TimeRange } from 'ui/timefilter'; +import chrome from 'ui/chrome'; +import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; +import { timefilter, getTime, TimeRange } from 'ui/timefilter'; import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; import { APPLY_FILTER_TRIGGER, @@ -40,19 +47,26 @@ import * as columnActions from '../doc_table/actions/columns'; import { SavedSearch } from '../types'; import searchTemplate from './search_template.html'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; +import { getSort } from '../doc_table/lib/get_sort'; +import { SortOrder } from '../doc_table/components/table_header/helpers'; + +const config = chrome.getUiSettingsClient(); interface SearchScope extends ng.IScope { columns?: string[]; description?: string; - sort?: string[][]; - searchSource?: SearchSource; + sort?: SortOrder[]; sharedItemTitle?: string; inspectorAdapters?: Adapters; - setSortOrder?: (sortPair: [[string, string]]) => void; + setSortOrder?: (sortPair: SortOrder[]) => void; removeColumn?: (column: string) => void; addColumn?: (column: string) => void; moveColumn?: (column: string, index: number) => void; filter?: (field: { name: string; scripted: boolean }, value: string[], operator: string) => void; + hits?: any[]; + indexPattern?: StaticIndexPattern; + totalHitCount?: number; + isLoading?: boolean; } export interface FilterManager { @@ -70,7 +84,6 @@ export interface FilterManager { interface SearchEmbeddableConfig { $rootScope: ng.IRootScopeService; $compile: ng.ICompileService; - courier: any; savedSearch: SavedSearch; editUrl: string; indexPatterns?: StaticIndexPattern[]; @@ -90,7 +103,7 @@ export class SearchEmbeddable extends Embeddable private panelTitle: string = ''; private filtersSearchSource: SearchSource; private searchInstance?: JQLite; - private courier: any; + private autoRefreshFetchSubscription?: Subscription; private subscription?: Subscription; public readonly type = SEARCH_EMBEDDABLE_TYPE; private filterGen: FilterManager; @@ -103,7 +116,6 @@ export class SearchEmbeddable extends Embeddable { $rootScope, $compile, - courier, savedSearch, editUrl, indexPatterns, @@ -121,13 +133,14 @@ export class SearchEmbeddable extends Embeddable ); this.filterGen = getFilterGenerator(queryFilter); - this.courier = courier; this.savedSearch = savedSearch; this.$rootScope = $rootScope; this.$compile = $compile; this.inspectorAdaptors = { requests: new RequestAdapter(), }; + this.initializeSearchScope(); + this.autoRefreshFetchSubscription = timefilter.getAutoRefreshFetch$().subscribe(this.fetch); this.subscription = Rx.merge(this.getOutput$(), this.getInput$()).subscribe(() => { this.panelTitle = this.output.title || ''; @@ -152,10 +165,8 @@ export class SearchEmbeddable extends Embeddable * @param {ContainerState} containerState */ public render(domNode: HTMLElement) { - this.initializeSearchScope(); if (!this.searchScope) { throw new Error('Search scope not defined'); - return; } this.searchInstance = this.$compile(searchTemplate)(this.searchScope); const rootNode = angular.element(domNode); @@ -177,42 +188,45 @@ export class SearchEmbeddable extends Embeddable if (this.subscription) { this.subscription.unsubscribe(); } + if (this.autoRefreshFetchSubscription) { + this.autoRefreshFetchSubscription.unsubscribe(); + } } private initializeSearchScope() { - const searchScope: SearchScope = this.$rootScope.$new(); + const searchScope: SearchScope = (this.searchScope = this.$rootScope.$new()); searchScope.description = this.savedSearch.description; - searchScope.searchSource = this.savedSearch.searchSource; searchScope.inspectorAdapters = this.inspectorAdaptors; - const timeRangeSearchSource = searchScope.searchSource.create(); + const { searchSource } = this.savedSearch; + const indexPattern = (searchScope.indexPattern = searchSource.getField('index')); + + const timeRangeSearchSource = searchSource.create(); timeRangeSearchSource.setField('filter', () => { if (!this.searchScope || !this.input.timeRange) { return; } - return getTime(this.searchScope.searchSource.getField('index'), this.input.timeRange); + return getTime(indexPattern, this.input.timeRange); }); - this.filtersSearchSource = searchScope.searchSource.create(); + this.filtersSearchSource = searchSource.create(); this.filtersSearchSource.setParent(timeRangeSearchSource); - searchScope.searchSource.setParent(this.filtersSearchSource); + searchSource.setParent(this.filtersSearchSource); this.pushContainerStateParamsToScope(searchScope); - searchScope.setSortOrder = sortPair => { - searchScope.sort = sortPair; - this.updateInput({ sort: searchScope.sort }); + searchScope.setSortOrder = sort => { + this.updateInput({ sort }); }; searchScope.addColumn = (columnName: string) => { if (!searchScope.columns) { return; } - this.savedSearch.searchSource.getField('index').popularizeField(columnName, 1); + indexPattern.popularizeField(columnName, 1); columnActions.addColumn(searchScope.columns, columnName); - searchScope.columns = searchScope.columns; this.updateInput({ columns: searchScope.columns }); }; @@ -220,9 +234,7 @@ export class SearchEmbeddable extends Embeddable if (!searchScope.columns) { return; } - this.savedSearch.searchSource.getField('index').popularizeField(columnName, 1); columnActions.removeColumn(searchScope.columns, columnName); - this.updateInput({ columns: searchScope.columns }); }; @@ -235,9 +247,7 @@ export class SearchEmbeddable extends Embeddable }; searchScope.filter = async (field, value, operator) => { - const index = this.savedSearch.searchSource.getField('index').id; - - let filters = this.filterGen.generate(field, value, operator, index); + let filters = this.filterGen.generate(field, value, operator, indexPattern.id); filters = filters.map(filter => ({ ...filter, $state: { store: FilterStateStore.APP_STATE }, @@ -250,31 +260,76 @@ export class SearchEmbeddable extends Embeddable }, }); }; - - this.searchScope = searchScope; } public reload() { - this.courier.fetch(); + this.fetch(); } + private fetch = async () => { + if (!this.searchScope) return; + + const { searchSource } = this.savedSearch; + searchSource.setField('size', config.get('discover:sampleSize')); + searchSource.setField('sort', getSort(this.searchScope.sort, this.searchScope.indexPattern)); + + // Log request to inspector + this.inspectorAdaptors.requests.reset(); + const title = i18n.translate('kbn.embeddable.inspectorRequestDataTitle', { + defaultMessage: 'Data', + }); + const description = i18n.translate('kbn.embeddable.inspectorRequestDescription', { + defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', + }); + const inspectorRequest = this.inspectorAdaptors.requests.start(title, { description }); + inspectorRequest.stats(getRequestInspectorStats(searchSource)); + searchSource.getSearchRequestBody().then((body: any) => { + inspectorRequest.json(body); + }); + + this.searchScope.isLoading = true; + + try { + // Make the request + const resp = await searchSource.fetch(); + + this.searchScope.isLoading = false; + + // Log response to inspector + inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); + + // Apply the changes to the angular scope + this.searchScope.$apply(() => { + this.searchScope!.hits = resp.hits.hits; + this.searchScope!.totalHitCount = resp.hits.total; + }); + } catch (error) { + toastNotifications.addError(error, { + title: i18n.translate('kbn.embeddable.errorTitle', { + defaultMessage: 'Error fetching data', + }), + }); + } + }; + private pushContainerStateParamsToScope(searchScope: SearchScope) { + const isFetchRequired = + !onlyDisabledFiltersChanged(this.input.filters, this.prevFilters) || + !_.isEqual(this.prevQuery, this.input.query) || + !_.isEqual(this.prevTimeRange, this.input.timeRange) || + !_.isEqual(searchScope.sort, this.input.sort || this.savedSearch.sort); + // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. searchScope.columns = this.input.columns || this.savedSearch.columns; searchScope.sort = this.input.sort || this.savedSearch.sort; searchScope.sharedItemTitle = this.panelTitle; - if ( - !onlyDisabledFiltersChanged(this.input.filters, this.prevFilters) || - !_.isEqual(this.prevQuery, this.input.query) || - !_.isEqual(this.prevTimeRange, this.input.timeRange) - ) { + if (isFetchRequired) { this.filtersSearchSource.setField('filter', this.input.filters); this.filtersSearchSource.setField('query', this.input.query); - // Sadly this is neccessary to tell the angular component to refetch the data. - this.courier.fetch(); + this.fetch(); this.prevFilters = this.input.filters; this.prevQuery = this.input.query; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index 9cbf1f2dace29..88dafaacf7357 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -77,7 +77,6 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< const $compile = $injector.get('$compile'); const $rootScope = $injector.get('$rootScope'); - const courier = $injector.get('courier'); const searchLoader = $injector.get('savedSearches'); const editUrl = chrome.addBasePath(`/app/kibana${searchLoader.urlFor(savedObjectId)}`); @@ -88,7 +87,6 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< const savedObject = await searchLoader.get(savedObjectId); return new SearchEmbeddable( { - courier, savedSearch: savedObject, $rootScope, $compile, diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_template.html b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_template.html index 3fbcb16ea17da..e188d230ea307 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_template.html +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_template.html @@ -1,5 +1,4 @@ diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts index 104d298f50c33..981580079ae18 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -27,6 +27,7 @@ import { EmbeddableOutput, IEmbeddable, } from '../../../../embeddable_api/public/np_ready/public'; +import { SortOrder } from '../doc_table/components/table_header/helpers'; export interface SearchInput extends EmbeddableInput { timeRange: TimeRange; @@ -34,7 +35,7 @@ export interface SearchInput extends EmbeddableInput { filters?: Filter[]; hidePanelTitles?: boolean; columns?: string[]; - sort?: string[][]; + sort?: SortOrder[]; } export interface SearchOutput extends EmbeddableOutput { diff --git a/src/legacy/core_plugins/kibana/public/discover/types.d.ts b/src/legacy/core_plugins/kibana/public/discover/types.d.ts index 6de969888f166..f285e94369893 100644 --- a/src/legacy/core_plugins/kibana/public/discover/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/discover/types.d.ts @@ -18,6 +18,7 @@ */ import { SearchSource } from 'ui/courier'; +import { SortOrder } from './doc_table/components/table_header/helpers'; export interface SavedSearch { readonly id: string; @@ -25,7 +26,7 @@ export interface SavedSearch { searchSource: SearchSource; description?: string; columns: string[]; - sort: string[][]; + sort: SortOrder[]; destroy: () => void; } export interface SavedSearchLoader { diff --git a/src/legacy/ui/public/courier/index.d.ts b/src/legacy/ui/public/courier/index.d.ts index c06518697422b..93556c2666c9a 100644 --- a/src/legacy/ui/public/courier/index.d.ts +++ b/src/legacy/ui/public/courier/index.d.ts @@ -19,3 +19,4 @@ export * from './search_source'; export * from './search_strategy'; +export * from './utils/courier_inspector_utils'; diff --git a/src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts b/src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts new file mode 100644 index 0000000000000..7f638d357a9e1 --- /dev/null +++ b/src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts @@ -0,0 +1,52 @@ +/* + * 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 { SearchSource } from 'ui/courier'; + +interface InspectorStat { + label: string; + value: string; + description: string; +} + +interface RequestInspectorStats { + indexPattern: InspectorStat; + indexPatternId: InspectorStat; +} + +interface ResponseInspectorStats { + queryTime: InspectorStat; + hitsTotal: InspectorStat; + hits: InspectorStat; + requestTime: InspectorStat; +} + +interface Response { + took: number; + hits: { + total: number; + hits: any[]; + }; +} + +export function getRequestInspectorStats(searchSource: SearchSource): RequestInspectorStats; +export function getResponseInspectorStats( + searchSource: SearchSource, + resp: Response +): ResponseInspectorStats; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 080b1b09182aa..15584c3f1fd06 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1550,9 +1550,6 @@ "kbn.doc.failedToLocateDocumentDescription": "ドキュメントが見つかりませんでした", "kbn.doc.loadingDescription": "読み込み中…", "kbn.doc.somethingWentWrongDescription": "おっと。何か問題が起こりました。ドキュメントが見つからなかっただけではなくて、探すことすらできませんでした。インボックスかタイプが存在しないようです。Elasticsearch をチェックしてみてください。何かおかしなことが起きています。", - "kbn.docTable.errorTitle": "データの取得中にエラーが発生", - "kbn.docTable.inspectorRequestDataTitle": "データ", - "kbn.docTable.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", "kbn.docTable.limitedSearchResultLabel": "{resultCount} 件の結果に制限。検索結果の絞り込み。", "kbn.docTable.noResultsTitle": "結果が見つかりませんでした", "kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "{columnName} 列を左に移動", @@ -1571,6 +1568,9 @@ "kbn.docTable.tableRow.toggleRowDetailsButtonAriaLabel": "行の詳細を切り替える", "kbn.docTable.tableRow.viewSingleDocumentLinkText": "単一のドキュメントを表示", "kbn.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", + "kbn.embeddable.errorTitle": "データの取得中にエラーが発生", + "kbn.embeddable.inspectorRequestDataTitle": "データ", + "kbn.embeddable.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", "kbn.home.addData.addDataToKibanaDescription": "これらのソリューションで、データを作成済みのダッシュボードと監視システムへとすぐに変えることができます。", "kbn.home.addData.addDataToKibanaTitle": "Kibana にデータを追加", "kbn.home.addData.apm.addApmButtonLabel": "APM を追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f52883a033122..621702307388d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1550,9 +1550,6 @@ "kbn.doc.failedToLocateDocumentDescription": "无法找到文档", "kbn.doc.loadingDescription": "正在加载……", "kbn.doc.somethingWentWrongDescription": "哎呦。出了问题。不是我找不到您的文档,而是无法尝试。索引缺失或类型缺失。去问问 Elasticsearch,似乎哪里不对劲。", - "kbn.docTable.errorTitle": "提取数据时出错", - "kbn.docTable.inspectorRequestDataTitle": "数据", - "kbn.docTable.inspectorRequestDescription": "此请求将查询 Elasticsearch 以获取搜索的数据。", "kbn.docTable.limitedSearchResultLabel": "仅限 {resultCount} 个结果。优化您的搜索。", "kbn.docTable.noResultsTitle": "找不到结果", "kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "向左移动“{columnName}”列", @@ -1571,6 +1568,9 @@ "kbn.docTable.tableRow.toggleRowDetailsButtonAriaLabel": "切换行详细信息", "kbn.docTable.tableRow.viewSingleDocumentLinkText": "查看单个文档", "kbn.docTable.tableRow.viewSurroundingDocumentsLinkText": "查看周围文档", + "kbn.embeddable.errorTitle": "提取数据时出错", + "kbn.embeddable.inspectorRequestDataTitle": "数据", + "kbn.embeddable.inspectorRequestDescription": "此请求将查询 Elasticsearch 以获取搜索的数据。", "kbn.home.addData.addDataToKibanaDescription": "使用这些解决方案可快速将您的数据转换成预建仪表板和监测系统。", "kbn.home.addData.addDataToKibanaTitle": "将数据添加到 Kibana", "kbn.home.addData.apm.addApmButtonLabel": "添加 APM", From e492cce634f4f5d68b8855ddd17076d0762376ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Tue, 27 Aug 2019 20:03:42 -0400 Subject: [PATCH 44/66] Task manager fix flaky assertValidInterval test (#44163) * Fix flaky test * Re-enable test --- x-pack/legacy/plugins/task_manager/lib/intervals.test.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts b/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts index 4ae98f39cdbd8..ac28b81eaf490 100644 --- a/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts +++ b/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts @@ -24,15 +24,14 @@ beforeAll(() => { afterAll(() => fakeTimer.restore()); -// FLAKY: https://github.com/elastic/kibana/issues/44086 -describe.skip('taskIntervals', () => { +describe('taskIntervals', () => { describe('assertValidInterval', () => { test('it accepts intervals in the form `Nm`', () => { - expect(() => assertValidInterval(`${_.random(1000)}m`)).not.toThrow(); + expect(() => assertValidInterval(`${_.random(1, 1000)}m`)).not.toThrow(); }); test('it accepts intervals in the form `Ns`', () => { - expect(() => assertValidInterval(`${_.random(1000)}s`)).not.toThrow(); + expect(() => assertValidInterval(`${_.random(1, 1000)}s`)).not.toThrow(); }); test('it rejects 0 based intervals', () => { From df4260cb25dc652d2c8ac0a15bf75574c572f267 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 27 Aug 2019 18:39:24 -0600 Subject: [PATCH 45/66] [Maps] Refactor draw filter UI to be similar to create filter from feature geometry UI (#44014) * refactor draw UI to use same UI as create geometry filter from features * update FeatureGeometryFilterForm to use GeometryFilterForm * add jest test for GeomemtryFilterForm component * fix i18n translations * update ToolsControl jest snapshots * use __ in css class names --- .../geometry_filter_form.test.js.snap | 282 ++++++++++++++++++ .../maps/public/components/_index.scss | 12 + .../public/components/geometry_filter_form.js | 210 +++++++++++++ .../components/geometry_filter_form.test.js | 71 +++++ .../map/_feature_tooltip.scss | 12 - .../map/feature_geometry_filter_form.js | 205 ++----------- .../connected_components/map/mb/view.js | 9 +- .../toolbar_overlay/_index.scss | 2 + .../__snapshots__/tools_control.test.js.snap | 96 +++++- .../toolbar_overlay/tools_control/_index.scss | 3 + .../tools_control/tools_control.js | 215 +++++-------- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 13 files changed, 763 insertions(+), 358 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap create mode 100644 x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js create mode 100644 x-pack/legacy/plugins/maps/public/components/geometry_filter_form.test.js create mode 100644 x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/_index.scss diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap b/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap new file mode 100644 index 0000000000000..92ddcabaf07a3 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap @@ -0,0 +1,282 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not render relation select when geo field is geo_point 1`] = ` + + + + + + + + + + My index + + +
    + my geo field + , + "value": "My index/my geo field", + }, + ] + } + valueOfSelected="My index/my geo field" + /> +
    + + Create filter + +
    +`; + +exports[`should not show "within" relation when filter geometry is not closed 1`] = ` + + + + + + + + + + My index + + +
    + my geo field + , + "value": "My index/my geo field", + }, + ] + } + valueOfSelected="My index/my geo field" + /> +
    + + + + + Create filter + +
    +`; + +exports[`should render relation select when geo field is geo_shape 1`] = ` + + + + + + + + + + My index + + +
    + my geo field + , + "value": "My index/my geo field", + }, + ] + } + valueOfSelected="My index/my geo field" + /> +
    + + + + + Create filter + +
    +`; diff --git a/x-pack/legacy/plugins/maps/public/components/_index.scss b/x-pack/legacy/plugins/maps/public/components/_index.scss index 34d65963bc923..9611bd4cd539a 100644 --- a/x-pack/legacy/plugins/maps/public/components/_index.scss +++ b/x-pack/legacy/plugins/maps/public/components/_index.scss @@ -1,3 +1,15 @@ .mapMetricEditorPanel { margin-bottom: $euiSizeS; } + +.mapGeometryFilter__geoFieldSuperSelect { + height: $euiSizeXL * 2; +} + +.mapGeometryFilter__geoFieldSuperSelectWrapper { + height: $euiSizeXL * 3; +} + +.mapGeometryFilter__geoFieldItem { + padding: $euiSizeXS; +} diff --git a/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js new file mode 100644 index 0000000000000..f0ac8d2748d07 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.js @@ -0,0 +1,210 @@ +/* + * 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + EuiForm, + EuiFormRow, + EuiSuperSelect, + EuiTextColor, + EuiText, + EuiFieldText, + EuiButton, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + ES_GEO_FIELD_TYPE, + ES_SPATIAL_RELATIONS, +} from '../../common/constants'; +import { getEsSpatialRelationLabel } from '../../common/i18n_getters'; + +const GEO_FIELD_VALUE_DELIMITER = '/'; // `/` is not allowed in index pattern name so should not have collisions + +function createIndexGeoFieldName({ indexPatternTitle, geoFieldName }) { + return `${indexPatternTitle}${GEO_FIELD_VALUE_DELIMITER}${geoFieldName}`; +} + +function splitIndexGeoFieldName(value) { + const split = value.split(GEO_FIELD_VALUE_DELIMITER); + return { + indexPatternTitle: split[0], + geoFieldName: split[1] + }; +} + +export class GeometryFilterForm extends Component { + + static propTypes = { + buttonLabel: PropTypes.string.isRequired, + geoFields: PropTypes.array.isRequired, + intitialGeometryLabel: PropTypes.string.isRequired, + onSubmit: PropTypes.func.isRequired, + isFilterGeometryClosed: PropTypes.bool, + }; + + static defaultProps = { + isFilterGeometryClosed: true, + }; + + state = { + geoFieldTag: this.props.geoFields.length ? createIndexGeoFieldName(this.props.geoFields[0]) : '', + geometryLabel: this.props.intitialGeometryLabel, + relation: ES_SPATIAL_RELATIONS.INTERSECTS, + }; + + _getSelectedGeoField = () => { + if (!this.state.geoFieldTag) { + return null; + } + + const { + indexPatternTitle, + geoFieldName + } = splitIndexGeoFieldName(this.state.geoFieldTag); + + return this.props.geoFields.find(option => { + return option.indexPatternTitle === indexPatternTitle + && option.geoFieldName === geoFieldName; + }); + } + + _onGeoFieldChange = selectedValue => { + this.setState({ geoFieldTag: selectedValue }); + } + + _onGeometryLabelChange = e => { + this.setState({ + geometryLabel: e.target.value, + }); + } + + _onRelationChange = e => { + this.setState({ + relation: e.target.value, + }); + } + + _onSubmit = () => { + const geoField = this._getSelectedGeoField(); + this.props.onSubmit({ + geometryLabel: this.state.geometryLabel, + indexPatternId: geoField.indexPatternId, + geoFieldName: geoField.geoFieldName, + geoFieldType: geoField.geoFieldType, + relation: this.state.relation, + }); + } + + _renderRelationInput() { + if (!this.state.geoFieldTag) { + return null; + } + + const { geoFieldType } = this._getSelectedGeoField(); + + // relationship only used when filtering geo_shape fields + if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) { + return null; + } + + const spatialRelations = this.props.isFilterGeometryClosed + ? Object.values(ES_SPATIAL_RELATIONS) + : Object.values(ES_SPATIAL_RELATIONS).filter(relation => { + // can not filter by within relation when filtering geometry is not closed + return relation !== ES_SPATIAL_RELATIONS.WITHIN; + }); + const options = spatialRelations + .map(relation => { + return { + value: relation, + text: getEsSpatialRelationLabel(relation) + }; + }); + + return ( + + + + + ); + } + + render() { + const options = this.props.geoFields.map(({ indexPatternTitle, geoFieldName }) => { + return { + inputDisplay: ( + + + {indexPatternTitle} + +
    + {geoFieldName} +
    + ), + value: createIndexGeoFieldName({ indexPatternTitle, geoFieldName }) + }; + }); + return ( + + + + + + + + + + + + {this._renderRelationInput()} + + + {this.props.buttonLabel} + + + ); + } +} + diff --git a/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.test.js b/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.test.js new file mode 100644 index 0000000000000..b1e679591aa6a --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/components/geometry_filter_form.test.js @@ -0,0 +1,71 @@ +/* + * 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 { GeometryFilterForm } from './geometry_filter_form'; + +const defaultProps = { + buttonLabel: 'Create filter', + intitialGeometryLabel: 'My shape', + onSubmit: () => {}, +}; + +test('should not render relation select when geo field is geo_point', async () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render relation select when geo field is geo_shape', async () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); + +test('should not show "within" relation when filter geometry is not closed', async () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/_feature_tooltip.scss b/x-pack/legacy/plugins/maps/public/connected_components/map/_feature_tooltip.scss index d603b1c64c9fa..e0425284a204e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/_feature_tooltip.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/_feature_tooltip.scss @@ -21,15 +21,3 @@ .mapFeatureTooltip__propertyValue { max-width: $euiSizeXL * 5; } - -.mapFeatureTooltip__geoFieldItem { - padding: $euiSizeXS; -} - -.mapFeatureTooltip_geoFieldSuperSelect { - height: $euiSizeXL * 2; -} - -.mapFeatureTooltip_geoFieldSuperSelectWrapper { - height: $euiSizeXL * 3; -} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js index 700a0b35b6271..4b26db917b39e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/feature_geometry_filter_form.js @@ -5,50 +5,17 @@ */ import React, { Component, Fragment } from 'react'; -import { - EuiIcon, - EuiForm, - EuiFormRow, - EuiSuperSelect, - EuiTextColor, - EuiText, - EuiFieldText, - EuiButton, - EuiSelect, - EuiSpacer, -} from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { createSpatialFilterWithGeometry } from '../../elasticsearch_geo_utils'; import { - ES_GEO_FIELD_TYPE, - ES_SPATIAL_RELATIONS, GEO_JSON_TYPE, } from '../../../common/constants'; -import { getEsSpatialRelationLabel } from '../../../common/i18n_getters'; - -const GEO_FIELD_VALUE_DELIMITER = '/'; // `/` is not allowed in index pattern name so should not have collisions - -function createIndexGeoFieldName({ indexPatternTitle, geoFieldName }) { - return `${indexPatternTitle}${GEO_FIELD_VALUE_DELIMITER}${geoFieldName}`; -} - -function splitIndexGeoFieldName(value) { - const split = value.split(GEO_FIELD_VALUE_DELIMITER); - return { - indexPatternTitle: split[0], - geoFieldName: split[1] - }; -} +import { GeometryFilterForm } from '../../components/geometry_filter_form'; export class FeatureGeometryFilterForm extends Component { - state = { - geoFieldTag: createIndexGeoFieldName(this.props.geoFields[0]), - geometryLabel: this.props.feature.geometry.type.toLowerCase(), - relation: ES_SPATIAL_RELATIONS.INTERSECTS, - }; - componentDidMount() { this.props.reevaluateTooltipPosition(); } @@ -57,47 +24,14 @@ export class FeatureGeometryFilterForm extends Component { this.props.reevaluateTooltipPosition(); } - _getSelectedGeoField = () => { - if (!this.state.geoFieldTag) { - return null; - } - - const { - indexPatternTitle, - geoFieldName - } = splitIndexGeoFieldName(this.state.geoFieldTag); - - return this.props.geoFields.find(option => { - return option.indexPatternTitle === indexPatternTitle - && option.geoFieldName === geoFieldName; - }); - } - - _onGeoFieldChange = selectedValue => { - this.setState({ geoFieldTag: selectedValue }); - } - - _onGeometryLabelChange = e => { - this.setState({ - geometryLabel: e.target.value, - }); - } - - _onRelationChange = e => { - this.setState({ - relation: e.target.value, - }); - } - - _createFilter = () => { - const geoField = this._getSelectedGeoField(); + _createFilter = ({ geometryLabel, indexPatternId, geoFieldName, geoFieldType, relation }) => { const filter = createSpatialFilterWithGeometry({ geometry: this.props.feature.geometry, - geometryLabel: this.state.geometryLabel, - indexPatternId: geoField.indexPatternId, - geoFieldName: geoField.geoFieldName, - geoFieldType: geoField.geoFieldType, - relation: this.state.relation, + geometryLabel, + indexPatternId, + geoFieldName, + geoFieldType, + relation, }); this.props.addFilters([filter]); this.props.onClose(); @@ -119,8 +53,8 @@ export class FeatureGeometryFilterForm extends Component { @@ -128,115 +62,18 @@ export class FeatureGeometryFilterForm extends Component { ); } - _renderRelationInput() { - if (!this.state.geoFieldTag) { - return null; - } - - const { geoFieldType } = this._getSelectedGeoField(); - - // relationship only used when filtering geo_shape fields - if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) { - return null; - } - - const options = Object.values(ES_SPATIAL_RELATIONS) - .filter(relation => { - // line geometries can not filter by within relation since there is no closed shape - if (this.props.feature.geometry.type === GEO_JSON_TYPE.LINE_STRING - || this.props.feature.geometry.type === GEO_JSON_TYPE.MULTI_LINE_STRING) { - return relation !== ES_SPATIAL_RELATIONS.WITHIN; - } - - return true; - }) - .map(relation => { - return { - value: relation, - text: getEsSpatialRelationLabel(relation) - }; - }); - - return ( - - - - - ); - } - _renderForm() { - const options = this.props.geoFields.map(({ indexPatternTitle, geoFieldName }) => { - return { - inputDisplay: ( - - - {indexPatternTitle} - -
    - {geoFieldName} -
    - ), - value: createIndexGeoFieldName({ indexPatternTitle, geoFieldName }) - }; - }); return ( - - - - - - - - - - - - {this._renderRelationInput()} - - - - - + ); } @@ -248,6 +85,4 @@ export class FeatureGeometryFilterForm extends Component { ); } - } - diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 1d1ea0df93943..28c32f59912c7 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -36,7 +36,6 @@ import chrome from 'ui/chrome'; import { spritesheet } from '@elastic/maki'; import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; -import { i18n } from '@kbn/i18n'; import { DrawTooltip } from './draw_tooltip'; const mbDrawModes = MapboxDraw.modes; @@ -104,20 +103,16 @@ export class MBMapContainer extends React.Component { indexPatternId: this.props.drawState.indexPatternId, geoFieldName: this.props.drawState.geoFieldName, geoFieldType: this.props.drawState.geoFieldType, + geometryLabel: this.props.drawState.geometryLabel, + relation: this.props.drawState.relation, }; const filter = isBoundingBox ? createSpatialFilterWithBoundingBox({ ...options, - geometryLabel: i18n.translate('xpack.maps.drawControl.defaultEnvelopeLabel', { - defaultMessage: 'extent' - }), geometry: getBoundingBoxGeometry(geometry) }) : createSpatialFilterWithGeometry({ ...options, - geometryLabel: i18n.translate('xpack.maps.drawControl.defaultShapeLabel', { - defaultMessage: 'shape' - }), geometry }); this.props.addFilters([filter]); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/_index.scss b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/_index.scss index d969e27504c8d..94dadb4346f85 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/_index.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/_index.scss @@ -1,3 +1,5 @@ +@import './tools_control/index'; + .mapToolbarOverlay { position: absolute; top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin diff --git a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap index 73fe6617583cd..203405dce7f57 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.js.snap @@ -37,17 +37,57 @@ exports[`Should render cancel button when drawing 1`] = ` "items": Array [ Object { "name": "Draw shape to filter data", - "onClick": [Function], - "panel": undefined, + "panel": 1, }, Object { "name": "Draw bounds to filter data", - "onClick": [Function], - "panel": undefined, + "panel": 2, }, ], "title": "Tools", }, + Object { + "content": , + "id": 1, + "title": "Draw shape to filter data", + }, + Object { + "content": , + "id": 2, + "title": "Draw bounds to filter data", + }, ] } /> @@ -105,17 +145,57 @@ exports[`renders 1`] = ` "items": Array [ Object { "name": "Draw shape to filter data", - "onClick": [Function], - "panel": undefined, + "panel": 1, }, Object { "name": "Draw bounds to filter data", - "onClick": [Function], - "panel": undefined, + "panel": 2, }, ], "title": "Tools", }, + Object { + "content": , + "id": 1, + "title": "Draw shape to filter data", + }, + Object { + "content": , + "id": 2, + "title": "Draw bounds to filter data", + }, ] } /> diff --git a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/_index.scss b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/_index.scss new file mode 100644 index 0000000000000..de86299d559e8 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/_index.scss @@ -0,0 +1,3 @@ +.mapDrawControl__geometryFilterForm { + padding: $euiSizeS; +} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js index 56cfbcc764196..722c615ea4324 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.js @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import { EuiButtonIcon, EuiPopover, EuiContextMenu, - EuiSelectable, - EuiHighlight, - EuiTextColor, EuiFlexGroup, EuiFlexItem, EuiButton, @@ -19,152 +16,101 @@ import { import { i18n } from '@kbn/i18n'; import { DRAW_TYPE } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; +import { GeometryFilterForm } from '../../../components/geometry_filter_form'; -const RESET_STATE = { - isPopoverOpen: false, - drawType: null -}; +const DRAW_SHAPE_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', { + defaultMessage: 'Draw shape to filter data', +}); + +const DRAW_BOUNDS_LABEL = i18n.translate('xpack.maps.toolbarOverlay.drawBoundsLabel', { + defaultMessage: 'Draw bounds to filter data', +}); export class ToolsControl extends Component { state = { - ...RESET_STATE + isPopoverOpen: false }; _togglePopover = () => { this.setState(prevState => ({ - ...RESET_STATE, isPopoverOpen: !prevState.isPopoverOpen, })); }; _closePopover = () => { - this.setState(RESET_STATE); + this.setState({ isPopoverOpen: false }); }; - _onIndexPatternSelection = (options) => { - const selection = options.find((option) => option.checked); - this._initiateDraw( - this.state.drawType, - selection.value - ); - }; - - _initiateDraw = (drawType, indexContext) => { + _initiateShapeDraw = (options) => { this.props.initiateDraw({ - drawType, - ...indexContext + drawType: DRAW_TYPE.POLYGON, + ...options }); this._closePopover(); - }; - - _selectPolygonDrawType = () => { - this.setState({ drawType: DRAW_TYPE.POLYGON }); - } - - _selectBoundsDrawType = () => { - this.setState({ drawType: DRAW_TYPE.BOUNDS }); } - _getDrawPanels() { - - const needsIndexPatternSelectionPanel = this.props.geoFields.length > 1; - - const drawPolygonAction = { - name: i18n.translate('xpack.maps.toolbarOverlay.drawShapeLabel', { - defaultMessage: 'Draw shape to filter data', - }), - onClick: needsIndexPatternSelectionPanel - ? this._selectPolygonDrawType - : () => { - this._initiateDraw(DRAW_TYPE.POLYGON, this.props.geoFields[0]); - }, - panel: needsIndexPatternSelectionPanel - ? this._getIndexPatternSelectionPanel(1) - : undefined - }; - - const drawBoundsAction = { - name: i18n.translate('xpack.maps.toolbarOverlay.drawBoundsLabel', { - defaultMessage: 'Draw bounds to filter data', - }), - onClick: needsIndexPatternSelectionPanel - ? this._selectBoundsDrawType - : () => { - this._initiateDraw(DRAW_TYPE.BOUNDS, this.props.geoFields[0]); - }, - panel: needsIndexPatternSelectionPanel - ? this._getIndexPatternSelectionPanel(2) - : undefined - }; - - return flattenPanelTree({ - id: 0, - title: i18n.translate('xpack.maps.toolbarOverlay.tools.toolbarTitle', { - defaultMessage: 'Tools', - }), - items: [drawPolygonAction, drawBoundsAction] - }); - } - - _getIndexPatternSelectionPanel(id) { - const options = this.props.geoFields.map((geoField) => { - return { - label: `${geoField.indexPatternTitle} : ${geoField.geoFieldName}`, - value: geoField - }; + _initiateBoundsDraw = (options) => { + this.props.initiateDraw({ + drawType: DRAW_TYPE.BOUNDS, + ...options }); + this._closePopover(); + }; - const renderGeoField = (option, searchValue) => { - return ( - - - - {option.value.indexPatternTitle} - - -
    - - {option.value.geoFieldName} - -
    - ); - }; - - const indexPatternSelection = ( - - {(list, search) => ( -
    - {search} - {list} -
    - )} -
    - ); - - return { - id: id, - title: i18n.translate('xpack.maps.toolbarOverlay.geofield.toolbarTitle', { - defaultMessage: 'Select geo field', - }), - content: indexPatternSelection - }; + _getDrawPanels() { + return [ + { + id: 0, + title: i18n.translate('xpack.maps.toolbarOverlay.tools.toolbarTitle', { + defaultMessage: 'Tools', + }), + items: [ + { + name: DRAW_SHAPE_LABEL, + panel: 1 + }, + { + name: DRAW_BOUNDS_LABEL, + panel: 2 + } + ] + }, + { + id: 1, + title: DRAW_SHAPE_LABEL, + content: ( + + ) + }, + { + id: 2, + title: DRAW_BOUNDS_LABEL, + content: ( + + ) + } + ]; } _renderToolsButton() { @@ -227,18 +173,3 @@ export class ToolsControl extends Component { ); } } - -function flattenPanelTree(tree, array = []) { - array.push(tree); - - if (tree.items) { - tree.items.forEach(item => { - if (item.panel) { - flattenPanelTree(item.panel, array); - item.panel = item.panel.id; - } - }); - } - - return array; -} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 15584c3f1fd06..e49f77381f36e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5638,8 +5638,6 @@ "xpack.maps.styles.vector.symbolSizeLabel": "シンボルのサイズ", "xpack.maps.toolbarOverlay.drawBoundsLabel": "境界を描いてデータをフィルタリング", "xpack.maps.toolbarOverlay.drawShapeLabel": "シェイプを描いてデータをフィルタリング", - "xpack.maps.toolbarOverlay.geofield.toolbarTitle": "ジオフィールドを選択", - "xpack.maps.toolbarOverlay.indexPattern.filterListTitle": "フィルターリスト", "xpack.maps.toolbarOverlay.tools.toolbarTitle": "ツール", "xpack.maps.tooltip.closeAriaLabel": "ツールヒントを閉じる", "xpack.maps.tooltip.filterOnPropertyAriaLabel": "プロパティのフィルター", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 621702307388d..222e7e30967eb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5781,8 +5781,6 @@ "xpack.maps.styles.vector.symbolSizeLabel": "符号大小", "xpack.maps.toolbarOverlay.drawBoundsLabel": "绘制边界以筛选数据", "xpack.maps.toolbarOverlay.drawShapeLabel": "绘制形状以筛选数据", - "xpack.maps.toolbarOverlay.geofield.toolbarTitle": "选择地理字段", - "xpack.maps.toolbarOverlay.indexPattern.filterListTitle": "筛选列表", "xpack.maps.toolbarOverlay.tools.toolbarTitle": "工具", "xpack.maps.tooltip.closeAriaLabel": "关闭工具提示", "xpack.maps.tooltip.filterOnPropertyAriaLabel": "基于属性筛选", From 7a27c7a474ade11fd8961e73de4635ec317ff5e5 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 27 Aug 2019 19:50:27 -0500 Subject: [PATCH 46/66] [Logs UI] Fix redirect to logs stream (#43806) * [Logs UI] Fix redirect to logs stream * Add functional test for link-to logs * Revert i18n wrapper * Update snapshots * Hard-code test result instead of using faulty import * Fix typo in test --- .../pages/link_to/redirect_to_logs.test.tsx | 30 +++++++------- .../public/pages/link_to/redirect_to_logs.tsx | 7 ++-- x-pack/test/functional/apps/infra/index.ts | 1 + x-pack/test/functional/apps/infra/link_to.ts | 39 +++++++++++++++++++ 4 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 x-pack/test/functional/apps/infra/link_to.ts diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx index a3f7f38b480b1..82dad661a6f53 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx @@ -18,11 +18,11 @@ describe('RedirectToLogs component', () => { ).dive(); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct user-defined filter', () => { @@ -33,11 +33,11 @@ describe('RedirectToLogs component', () => { ).dive(); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); it('renders a redirect with the correct custom source id', () => { @@ -46,11 +46,11 @@ describe('RedirectToLogs component', () => { ).dive(); expect(component).toMatchInlineSnapshot(` - -`); + + `); }); }); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index e7f7db5267b82..065544ca417b6 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { injectI18n } from '@kbn/i18n/react'; import compose from 'lodash/fp/compose'; import React from 'react'; import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom'; @@ -20,17 +20,16 @@ interface RedirectToLogsProps extends RedirectToLogsType { match: RouteMatch<{ sourceId?: string; }>; - intl: InjectedIntl; } export const RedirectToLogs = injectI18n(({ location, match }: RedirectToLogsProps) => { const sourceId = match.params.sourceId || 'default'; - const filter = getFilterFromLocation(location); const searchString = compose( replaceLogFilterInQueryString(filter), replaceLogPositionInQueryString(getTimeFromLocation(location)), replaceSourceIdInQueryString(sourceId) )(''); - return ; + + return ; }); diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index ac747dca23b4a..b706dc8cce546 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -14,5 +14,6 @@ export default ({ loadTestFile }: FtrProviderContext) => { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./logs_source_configuration')); loadTestFile(require.resolve('./metrics_source_configuration')); + loadTestFile(require.resolve('./link_to')); }); }; diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts new file mode 100644 index 0000000000000..efe60b41badcc --- /dev/null +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common']); + const retry = getService('retry'); + const browser = getService('browser'); + + describe('Infra link-to', function() { + this.tags('smoke'); + it('redirects to the logs app and parses URL search params correctly', async () => { + const location = { + hash: '', + pathname: '/link-to/logs', + search: '?time=1565707203194&filter=trace.id:433b4651687e18be2c6c8e3b11f53d09', + state: undefined, + }; + const expectedSearchString = + "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default&_g=()"; + const expectedRedirect = `/logs/stream?${expectedSearchString}`; + + await pageObjects.common.navigateToActualUrl( + 'infraOps', + `${location.pathname}${location.search}` + ); + await retry.tryForTime(5000, async () => { + const currentUrl = await browser.getCurrentUrl(); + const [, currentHash] = decodeURIComponent(currentUrl).split('#'); + expect(currentHash).to.contain(expectedRedirect); + }); + }); + }); +}; From cd418a1dd08d82346f2e1cf3bf4b502bc485395f Mon Sep 17 00:00:00 2001 From: spalger Date: Tue, 27 Aug 2019 18:02:09 -0700 Subject: [PATCH 47/66] Revert "disable flaky tests (#43017)" This reverts commit c65b9752cbfde1c0c40013036374b656693fb0e8. --- x-pack/test/functional/apps/machine_learning/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/index.ts b/x-pack/test/functional/apps/machine_learning/index.ts index d3d96d63b55d9..31f7d67717ae3 100644 --- a/x-pack/test/functional/apps/machine_learning/index.ts +++ b/x-pack/test/functional/apps/machine_learning/index.ts @@ -10,9 +10,7 @@ export default function({ loadTestFile }: FtrProviderContext) { this.tags('ciGroup3'); loadTestFile(require.resolve('./feature_controls')); - - // FLAKY: https://github.com/elastic/kibana/issues/43017 - // loadTestFile(require.resolve('./pages')); - // loadTestFile(require.resolve('./create_single_metric_job')); + loadTestFile(require.resolve('./pages')); + loadTestFile(require.resolve('./create_single_metric_job')); }); } From 1595abebe6bd9fc27bc3c3c911a81b7fc7816551 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 27 Aug 2019 18:12:49 -0700 Subject: [PATCH 48/66] [babel-preset/webpack] use corejs 3 (#44154) --- packages/kbn-babel-preset/webpack_preset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-babel-preset/webpack_preset.js b/packages/kbn-babel-preset/webpack_preset.js index fcd7e76cfde98..09510ac3eca47 100644 --- a/packages/kbn-babel-preset/webpack_preset.js +++ b/packages/kbn-babel-preset/webpack_preset.js @@ -25,7 +25,7 @@ module.exports = () => { { useBuiltIns: 'entry', modules: 'cjs', - corejs: 2, + corejs: 3, }, ], require('./common_preset'), From eee003236ff312b59cc5f6d64ab3243b956a7e75 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Tue, 27 Aug 2019 21:45:37 -0400 Subject: [PATCH 49/66] [Canvas] Embedding Workpads in other Websites (#42545) * [1] Create JSON export endpoint * [2] Runtime Created + Test Environment * [3] Reorganizing + Improving UI + CSS Modules * [4] Make External Embed asset downloadable * [5] More UI work * [5] Fixing UI Bugs * A few tweaks * Add README * Addressing feedback * Fix yarn.lock * Addressing changes to types * Updating renovate config * Add docs; address feedback * Missed adding a file * Revising runtime build script + tree shaking * Addressing feedback * Add TS + Examples to Workpad Export; create Snapshot Service * migrate external_runtime script to dev/run * Convert Workpad Export to TS; Snapshot * Scope EUI and other CSS; remove snapshot service (for now) * Update snapshot testing; provide better script * Update runtime API for effortless embeds * Major fixes and changes; switched to flyout for embedding * Switch to HAPI static file service; correct Canvas Workpad type * Add ts-ignore to inert handler * Initial ZIP File impl * Finishing feedback from peers and meetings * Clean-up * Use EUI SASS vars, copy changes * Updating build scripts * Fix Renovate --- .sass-lint.yml | 29 +- package.json | 1 + renovate.json5 | 8 + .../canvas/.storybook/storyshots.test.js | 8 + .../canvas/__tests__/fixtures/workpads.ts | 10 + .../plugins/canvas/common/lib/constants.ts | 6 + .../canvas/common/lib/{fetch.js => fetch.ts} | 10 + .../plugins/canvas/external_runtime/README.md | 54 + .../canvas/external_runtime/api/embed.tsx | 100 + .../canvas/external_runtime/api/index.ts | 10 + .../external_runtime/components/app.tsx | 56 + .../components/canvas.module.scss | 26 + .../external_runtime/components/canvas.tsx | 86 + .../components/footer/footer.module.scss | 27 + .../components/footer/footer.tsx | 61 + .../components/footer/index.ts | 7 + .../components/footer/page_controls.tsx | 62 + .../footer/page_preview.module.scss | 15 + .../components/footer/page_preview.tsx | 55 + .../components/footer/scrubber.module.scss | 26 + .../components/footer/scrubber.tsx | 41 + .../footer/settings/autoplay_settings.tsx | 38 + .../components/footer/settings/index.ts | 7 + .../components/footer/settings/settings.tsx | 98 + .../footer/settings/toolbar_settings.tsx | 42 + .../components/footer/title.tsx | 33 + .../components/page.module.scss | 6 + .../external_runtime/components/page.tsx | 37 + .../components/rendered_element.module.scss | 19 + .../components/rendered_element.tsx | 88 + .../canvas/external_runtime/constants.d.ts | 13 + .../canvas/external_runtime/constants.js | 25 + .../external_runtime/context/actions.ts | 100 + .../canvas/external_runtime/context/index.ts | 8 + .../external_runtime/context/reducer.ts | 105 + .../canvas/external_runtime/context/state.tsx | 86 + .../plugins/canvas/external_runtime/demo.gif | Bin 0 -> 6625183 bytes .../canvas/external_runtime/index.html | 14 + .../plugins/canvas/external_runtime/index.ts | 27 + .../canvas/external_runtime/postcss.config.js | 25 + .../canvas/external_runtime/template.html | 14 + .../canvas/external_runtime/test/austin.json | 29645 ++++++++++++++++ .../canvas/external_runtime/test/test.json | 2944 ++ .../plugins/canvas/external_runtime/types.ts | 57 + .../canvas/external_runtime/webpack.config.js | 182 + .../workpad_export.examples.storyshot | 136 +- .../external_embed_flyout.examples.tsx | 32 + .../flyout/external_embed_flyout.tsx | 191 + .../workpad_header/workpad_export/index.ts | 38 +- .../workpad_export/workpad_export.tsx | 54 +- .../workpad_page/integration_utils.js | 56 +- .../public/components/workpad_page/utils.js | 58 + .../canvas/public/lib/download_workpad.js | 34 + .../canvas/public/state/selectors/workpad.js | 31 + .../canvas/scripts/external_runtime.js | 114 + .../plugins/canvas/server/routes/index.js | 2 + .../canvas/server/routes/workpad_snapshots.ts | 64 + x-pack/legacy/plugins/canvas/types/canvas.ts | 22 +- x-pack/package.json | 6 + x-pack/tasks/build.js | 11 +- yarn.lock | 176 +- 61 files changed, 35139 insertions(+), 197 deletions(-) rename x-pack/legacy/plugins/canvas/common/lib/{fetch.js => fetch.ts} (67%) create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/README.md create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/api/index.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/canvas.module.scss create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.module.scss create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/index.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_controls.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.module.scss create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/page_preview.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.module.scss create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/scrubber.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/autoplay_settings.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/index.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/settings.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/settings/toolbar_settings.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/footer/title.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/page.module.scss create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/page.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.module.scss create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/components/rendered_element.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/constants.d.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/constants.js create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/context/actions.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/context/index.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/context/reducer.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/context/state.tsx create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/demo.gif create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/index.html create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/index.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/postcss.config.js create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/template.html create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/test/austin.json create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/test/test.json create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/types.ts create mode 100644 x-pack/legacy/plugins/canvas/external_runtime/webpack.config.js create mode 100644 x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/external_embed_flyout.examples.tsx create mode 100644 x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/external_embed_flyout.tsx create mode 100644 x-pack/legacy/plugins/canvas/public/components/workpad_page/utils.js create mode 100644 x-pack/legacy/plugins/canvas/scripts/external_runtime.js create mode 100644 x-pack/legacy/plugins/canvas/server/routes/workpad_snapshots.ts diff --git a/.sass-lint.yml b/.sass-lint.yml index 16bb58c33d23f..5e4e6bc333509 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -7,32 +7,29 @@ files: - 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' + ignore: + - 'x-pack/legacy/plugins/canvas/external_runtime/**/*.s+(a|c)ss' rules: quotes: - 2 - - - style: 'single' + - style: 'single' # } else { style on one line, like our JS brace-style: - 2 - - - style: '1tbs' + - style: '1tbs' variable-name-format: - 2 - - - convention: 'camelcase' + - convention: 'camelcase' # Needs regex, right now we ignore class-name-format: 0 # Order how you please property-sort-order: 0 hex-notation: - 2 - - - style: 'uppercase' + - style: 'uppercase' mixin-name-format: - 2 - - - allow-leading-underscore: false + - allow-leading-underscore: false convention: 'camelcase' # Use none instead of 0 for no border border-zero: @@ -47,8 +44,7 @@ rules: indentation: 2 function-name-format: - 2 - - - allow-leading-underscore: false + - allow-leading-underscore: false convention: 'camelcase' # This removes the need for ::hover pseudo-element: 0 @@ -62,23 +58,20 @@ rules: force-attribute-nesting: 0 no-qualifying-elements: - 2 - - - # Allows input[type=search] + - # Allows input[type=search] allow-element-with-attribute: 1 # Files can end without a newline final-newline: 0 # We use some rare duplicate property values for browser variance no-duplicate-properties: - 2 - - - exclude: + - exclude: - 'font-size' - 'word-break' # Put a line-break between sections of CSS, but allow quicky one-liners for legibility empty-line-between-blocks: - 2 - - - allow-single-line-rulesets: 1 + - allow-single-line-rulesets: 1 # Warns are nice for deprecations and development no-warn: 0 # Transition all is useful in certain situations and there's no recent info to suggest slowdown diff --git a/package.json b/package.json index bec1b5e148d61..f62e1d63c0ad4 100644 --- a/package.json +++ b/package.json @@ -418,6 +418,7 @@ "nock": "10.0.6", "node-sass": "^4.9.4", "normalize-path": "^3.0.0", + "null-loader": "^3.0.0", "nyc": "^14.1.1", "pixelmatch": "4.0.2", "pkg-up": "^2.0.0", diff --git a/renovate.json5 b/renovate.json5 index 97707013cffad..0607fd039412b 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -577,6 +577,14 @@ '@types/zen-observable', ], }, + { + groupSlug: 'archiver', + groupName: 'archiver related packages', + packageNames: [ + 'archiver', + '@types/archiver', + ], + }, { groupSlug: 'base64-js', groupName: 'base64-js related packages', diff --git a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js index 333d8e3c05beb..c02309f3ae757 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js @@ -53,6 +53,14 @@ jest.mock( } ); +// Disabling this test due to https://github.com/elastic/eui/issues/2242 +jest.mock( + '../public/components/workpad_header/workpad_export/flyout/__examples__/external_embed_flyout.examples', + () => { + return 'Disabled Panel'; + } +); + addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots diff --git a/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts b/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts index 0251095c9e75e..d7ebbd87c97e6 100644 --- a/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts +++ b/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts @@ -6,6 +6,16 @@ import { CanvasWorkpad, CanvasElement, CanvasPage } from '../../types'; const BaseWorkpad: CanvasWorkpad = { + '@created': '2019-02-08T18:35:23.029Z', + '@timestamp': '2019-02-08T18:35:23.029Z', + assets: { + 'asset-ada763f1-295e-4188-8e08-b5bed9e006a1': { + id: 'asset-ada763f1-295e-4188-8e08-b5bed9e006a1', + '@created': '2018-01-17T19:13:09.185Z', + type: 'dataurl', + value: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=', + }, + }, name: 'base workpad', id: 'base-workpad', width: 0, diff --git a/x-pack/legacy/plugins/canvas/common/lib/constants.ts b/x-pack/legacy/plugins/canvas/common/lib/constants.ts index 381d5c2900a0c..45ee8b4bd9704 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/constants.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/constants.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RUNTIME_NAME } from '../../external_runtime/constants'; + export const CANVAS_TYPE = 'canvas-workpad'; export const CUSTOM_ELEMENT_TYPE = 'canvas-element'; export const CANVAS_APP = 'canvas'; @@ -33,3 +35,7 @@ export const CANVAS_LAYOUT_STAGE_CONTENT_SELECTOR = `canvasLayout__stageContent` export const DATATABLE_COLUMN_TYPES = ['string', 'number', 'null', 'boolean', 'date']; export const LAUNCHED_FULLSCREEN = 'workpad-full-screen-launch'; export const LAUNCHED_FULLSCREEN_AUTOPLAY = 'workpad-full-screen-launch-with-autoplay'; +export const API_ROUTE_SNAPSHOT_BASE = '/public/canvas'; +export const API_ROUTE_SNAPSHOT_ZIP = `${API_ROUTE_SNAPSHOT_BASE}/zip`; +export const API_ROUTE_SNAPSHOT_RUNTIME = `${API_ROUTE_SNAPSHOT_BASE}/runtime`; +export const API_ROUTE_SNAPSHOT_RUNTIME_DOWNLOAD = `${API_ROUTE_SNAPSHOT_BASE}/${RUNTIME_NAME}.js`; diff --git a/x-pack/legacy/plugins/canvas/common/lib/fetch.js b/x-pack/legacy/plugins/canvas/common/lib/fetch.ts similarity index 67% rename from x-pack/legacy/plugins/canvas/common/lib/fetch.js rename to x-pack/legacy/plugins/canvas/common/lib/fetch.ts index 8af72b9aef25b..dd975eb4c2023 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/fetch.js +++ b/x-pack/legacy/plugins/canvas/common/lib/fetch.ts @@ -15,3 +15,13 @@ export const fetch = axios.create({ }, timeout: FETCH_TIMEOUT, }); + +export const arrayBufferFetch = axios.create({ + responseType: 'arraybuffer', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'kbn-xsrf': 'professionally-crafted-string-of-text', + }, + timeout: FETCH_TIMEOUT, +}); diff --git a/x-pack/legacy/plugins/canvas/external_runtime/README.md b/x-pack/legacy/plugins/canvas/external_runtime/README.md new file mode 100644 index 0000000000000..222cc16f5420b --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/README.md @@ -0,0 +1,54 @@ +# Canvas External Embeds + +![Canvas External Runtime](demo.gif) + +## Introduction + +The external runtime is designed to render Canvas Workpads outside of Kibana in a different website or application. It uses the intermediate, "transient" state of a workpad, which is a JSON-blob state after element expressions are evaluated, but before the elements are rendered to the screen. This "transient" state, therefore, has no dependency or access to ES/Kibana data, making it lightweight and portable. + +This directory contains the code necessary to build and test this external runtime. + +## Building + +Run `node scripts/external_runtime`. The runtime will be built and stored `external_runtime/build`. + +## Development + +To start the `webpack-dev-server` and test a workpad, simply run: + +`/canvas: node scripts/external_runtime --dev --run` + +A browser window should automatically open. If not, navigate to [`http://localhost:9001/`](http://localhost:9001). + +### Customizing + +The `index.html` file contains a call to the `CanvasEmbed` runtime. Currently, you can embed by object or by url: + +```html + +... +
    + +``` + +There are two test workpads available: `/test/test.json` and `/test/austin.json`. + +### Options + +The [`api/embed.tsx`]('./api/embed') file contains the base class with available options to configure the embed: + +```typescript +height?: number; +width?: number; +page?: number; +``` + +More options are available, but have not yet been exposed, (e.g. toolbar hide, etc) + +## Testing + +You can load a Workpad in Canvas, click "Export" and then "Embed on a website". You can then download a ZIP file with the runtime, the workpad and a sample HTML file. + +After extracting to a directory, you can then start a small web server to load the HTML file. The easiest way, if you have `python` installed, is to run `python -m SimpleHTTPServer 8000` from the extracted directory. diff --git a/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx b/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx new file mode 100644 index 0000000000000..23fc23195a474 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/api/embed.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render } from 'react-dom'; +import { App } from '../components/app'; +import { CanvasRenderedWorkpad } from '../types'; + +export interface Options { + /** The preferred height to scale the embedded workpad. If only `height` is + * specified, `width` will be calculated by the workpad ratio. If both are + * specified, the ratio will be overriden by an absolute size. */ + height?: number; + /** The preferred width to scale the embedded workpad. If only `width` is + * specified, `height` will be calculated by the workpad ratio. If both are + * specified, the ratio will be overriden by an absolute size. */ + width?: number; + /** The initial page to display. */ + page?: number; +} + +const PREFIX = 'kbn-canvas'; +const EMBED = `${PREFIX}-embed`; + +const getAttributes = (element: Element, attributes: string[]) => { + const result: { [key: string]: string } = {}; + attributes.forEach(attribute => { + const key = `${PREFIX}-${attribute}`; + const value = element.getAttribute(key); + + if (value) { + result[attribute] = value; + element.removeAttribute(key); + } + }); + + return result; +}; + +const getWorkpad = async (url: string): Promise => { + const workpadResponse = await fetch(url); + + if (workpadResponse.ok) { + return await workpadResponse.json(); + } + + return null; +}; + +const updateArea = async (area: Element) => { + const { url, page: pageAttr, height: heightAttr, weight: widthAttr } = getAttributes(area, [ + 'url', + 'page', + 'height', + 'width', + ]); + + if (url) { + const workpad = await getWorkpad(url); + + if (workpad) { + const page = pageAttr ? parseInt(pageAttr, 10) : null; + let height = heightAttr ? parseInt(heightAttr, 10) : null; + let width = widthAttr ? parseInt(widthAttr, 10) : null; + + if (height && !width) { + // If we have a height but no width, the width should honor the workpad ratio. + width = workpad.width * (height / workpad.height); + } else if (width && !height) { + // If we have a width but no height, the height should honor the workpad ratio. + height = workpad.height * (width / workpad.width); + } + + const options = { + height: height || workpad.height, + width: width || workpad.width, + page: page ? page : workpad.page, + }; + + area.classList.add('kbnCanvas'); + area.removeAttribute(EMBED); + + render(, area); + } + } +}; + +/** + * This is an abstract embedding component. It provides all of the scaling and + * other option handling for embedding strategies. + */ +export const embed = () => { + const embedAreas = document.querySelectorAll(`[${EMBED}]`); + const validAreas = Array.from(embedAreas).filter(area => area.getAttribute(EMBED) === 'canvas'); + + validAreas.forEach(updateArea); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/api/index.ts b/x-pack/legacy/plugins/canvas/external_runtime/api/index.ts new file mode 100644 index 0000000000000..3a5b6af05f64c --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/api/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'whatwg-fetch'; +import 'babel-polyfill'; + +export * from './embed'; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx new file mode 100644 index 0000000000000..936349155ff90 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/app.tsx @@ -0,0 +1,56 @@ +/* + * 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'; +// @ts-ignore Untyped package +import { RenderFunctionsRegistry } from 'data/interpreter'; +import { Canvas } from './canvas'; +import { + initialExternalEmbedState, + ExternalEmbedStateProvider, + ExternalEmbedState, +} from '../context'; +// @ts-ignore Untyped local +import { renderFunctions } from '../../canvas_plugin_src/renderers'; +import { CanvasRenderedWorkpad } from '../types'; + +interface Props { + height: number; + width: number; + page: number; + workpad: CanvasRenderedWorkpad; +} + +/** + * The overall Embedded Workpad app; the highest-layer component. + */ +export const App = (props: Props) => { + const { workpad, page, height, width } = props; + + // Register all of the rendering experessions with a bespoke registry. + const renderersRegistry = new RenderFunctionsRegistry(); + + renderFunctions.forEach((fn: Function | undefined) => { + if (fn) { + renderersRegistry.register(fn); + } + }); + + const initialState: ExternalEmbedState = { + ...initialExternalEmbedState, + height, + page, + renderersRegistry, + width, + workpad, + }; + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.module.scss b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.module.scss new file mode 100644 index 0000000000000..0e1d24886804d --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.module.scss @@ -0,0 +1,26 @@ +:global html body .kbnCanvas { + height: auto; +} + +:global .kbnCanvas :local .root { + position: relative; + overflow: hidden; + transition: height 1s; +} + +.container { + composes: canvas from global; + composes: canvasContainer from global; +} + +:global .kbnCanvas :local .container { + align-items: center; + display: flex; + justify-content: center; + pointer-events: none; +} + +:global .kbnCanvas :local .page { + position: absolute; + transform-origin: center center; +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx new file mode 100644 index 0000000000000..7a999157f2fd9 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/canvas.tsx @@ -0,0 +1,86 @@ +/* + * 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 { useExternalEmbedState, setPage, setScrubberVisible } from '../context'; +import { Page } from './page'; +import { Footer, FOOTER_HEIGHT } from './footer'; +import { getTimeInterval } from '../../public/lib/time_interval'; + +// @ts-ignore CSS Module +import css from './canvas.module'; + +let timeout: number = 0; + +/** + * The "stage" for a workpad, which composes the toolbar and other components. + */ +export const Canvas = () => { + const [ + { workpad, height: containerHeight, width: containerWidth, page, settings, refs }, + dispatch, + ] = useExternalEmbedState(); + + if (!workpad) { + return null; + } + + const { toolbar, autoplay } = settings; + const { height, width, pages } = workpad; + const ratio = Math.max(width / containerWidth, height / containerHeight); + const transform = `scale3d(${containerHeight / (containerHeight * ratio)}, ${containerWidth / + (containerWidth * ratio)}, 1)`; + + const pageStyle = { + height, + transform, + width, + }; + + if (autoplay.enabled && autoplay.interval) { + // We need to clear the timeout every time, even if it doesn't need to be or + // it's null. Since one could select a different page from the scrubber at + // any point, or change the interval, we need to make sure the interval is + // killed on React re-render-- otherwise the pages will start bouncing around + // as timeouts are accumulated. + clearTimeout(timeout); + + timeout = setTimeout( + () => dispatch(setPage(page >= workpad.pages.length - 1 ? 0 : page + 1)), + getTimeInterval(autoplay.interval) + ); + } + + const [toolbarHidden, setToolbarHidden] = useState(toolbar.autohide); + const rootHeight = containerHeight + (toolbar.autohide ? 0 : FOOTER_HEIGHT); + + const hideToolbar = (hidden: boolean) => { + if (settings.toolbar.autohide) { + if (hidden) { + // Hide the scrubber if we hide the toolbar. + dispatch(setScrubberVisible(false)); + } + setToolbarHidden(hidden); + } + }; + + return ( +
    hideToolbar(false)} + onMouseLeave={() => hideToolbar(true)} + ref={refs.stage} + > +
    +
    + +
    +
    +
    +
    + ); +}; diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.module.scss b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.module.scss new file mode 100644 index 0000000000000..d7a38d0fc5916 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.module.scss @@ -0,0 +1,27 @@ +@import '@elastic/eui/src/global_styling/variables/_size.scss'; +@import '@elastic/eui/src/global_styling/variables/_colors.scss'; + +:global .kbnCanvas :local .root .bar { + position: absolute; +} + +.bar { + composes: euiBottomBar from global; +} + +:global .kbnCanvas :local .bar { + transition: bottom 0.25s; + padding: $euiSizeM; +} + +:global .kbnCanvas :local .title { + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; + white-space: nowrap; + flex-grow: 1; +} + +:global .kbnCanvas .euiIcon__fillNegative { + fill: $euiColorGhost !important; +} diff --git a/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx new file mode 100644 index 0000000000000..6716d46fba52a --- /dev/null +++ b/x-pack/legacy/plugins/canvas/external_runtime/components/footer/footer.tsx @@ -0,0 +1,61 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { useExternalEmbedState, setScrubberVisible } from '../../context'; +import { Scrubber } from './scrubber'; +import { Title } from './title'; +import { PageControls } from './page_controls'; +import { Settings } from './settings'; + +// @ts-ignore CSS Module +import css from './footer.module'; + +export const FOOTER_HEIGHT = 48; + +interface Props { + hidden?: boolean; +} + +/** + * The footer of the Embedded Workpad. + */ +export const Footer = ({ hidden = false }: Props) => { + const [{ workpad, settings }] = useExternalEmbedState(); + if (!workpad) { + return null; + } + + const { autohide } = settings.toolbar; + + // If autohide is enabled, and the toolbar is hidden, set the scrubber + // visibility to hidden. This is useful for state changes where one + // sets the footer to hidden, and the scrubber would be left open with + // no toolbar. + if (autohide && hidden) { + setScrubberVisible(false); + } + + return ( +
    + +