diff --git a/.i18nrc.json b/.i18nrc.json index be37f3b77a378..1bffb14c377b0 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -30,6 +30,7 @@ "xpack.searchProfiler": "x-pack/plugins/searchprofiler", "xpack.security": "x-pack/plugins/security", "xpack.spaces": "x-pack/plugins/spaces", + "xpack.upgradeAssistant": "x-pack/plugins/upgrade_assistant", "xpack.watcher": "x-pack/plugins/watcher" }, "exclude": [ diff --git a/src/legacy/core_plugins/elasticsearch/index.d.ts b/src/legacy/core_plugins/elasticsearch/index.d.ts index f4463b0b8aede..a744a4cc22e53 100644 --- a/src/legacy/core_plugins/elasticsearch/index.d.ts +++ b/src/legacy/core_plugins/elasticsearch/index.d.ts @@ -371,7 +371,7 @@ export interface CallClusterWithRequest { ( request: Request, endpoint: string, - clientParams: GenericParams, + clientParams: any, options?: CallClusterOptions ): Promise; } diff --git a/x-pack/index.js b/x-pack/index.js index 94bfca7424b5a..c7480f966c815 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -29,6 +29,7 @@ import { kueryAutocomplete } from './plugins/kuery_autocomplete'; import { canvas } from './plugins/canvas'; import { infra } from './plugins/infra'; import { rollup } from './plugins/rollup'; +import { upgradeAssistant } from './plugins/upgrade_assistant'; module.exports = function (kibana) { return [ @@ -57,5 +58,6 @@ module.exports = function (kibana) { kueryAutocomplete(kibana), infra(kibana), rollup(kibana), + upgradeAssistant(kibana), ]; }; diff --git a/x-pack/plugins/__mocks__/ui/chrome.js b/x-pack/plugins/__mocks__/ui/chrome.js index 73f48348ae326..38f31a42a51d0 100644 --- a/x-pack/plugins/__mocks__/ui/chrome.js +++ b/x-pack/plugins/__mocks__/ui/chrome.js @@ -29,6 +29,8 @@ function getInjected(key) { return 'apm*'; case 'mlEnabled': return true; + case 'isCloudEnabled': + return false; default: throw new Error(`Unexpected config key: ${key}`); } diff --git a/x-pack/plugins/infra/types/eui.d.ts b/x-pack/plugins/infra/types/eui.d.ts index a05e6e19671a1..6629a798895f6 100644 --- a/x-pack/plugins/infra/types/eui.d.ts +++ b/x-pack/plugins/infra/types/eui.d.ts @@ -95,6 +95,8 @@ declare module '@elastic/eui' { rel?: string; target?: string; type?: string; + hasActiveFilters?: boolean; + numFilters?: number; }; export const EuiFilterButton: React.SFC; diff --git a/x-pack/plugins/upgrade_assistant/common/version.ts b/x-pack/plugins/upgrade_assistant/common/version.ts new file mode 100644 index 0000000000000..1e3746257fcd5 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/common/version.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 pkg from '../../../package.json'; + +// Extract version information +const currentVersionNum = pkg.version as string; +const matches = currentVersionNum.match(/^([1-9]+)\.([0-9]+)\.([0-9]+)$/)!; + +export const CURRENT_MAJOR_VERSION = matches[1]; +export const NEXT_MAJOR_VERSION = (parseInt(CURRENT_MAJOR_VERSION, 10) + 1).toString(); diff --git a/x-pack/plugins/upgrade_assistant/index.ts b/x-pack/plugins/upgrade_assistant/index.ts new file mode 100644 index 0000000000000..f0926f026bf01 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { Server } from 'hapi'; +import Joi from 'joi'; +import { resolve } from 'path'; +import { initServer } from './server'; + +export function upgradeAssistant(kibana: any) { + return new kibana.Plugin({ + id: 'upgrade_assistant', + require: ['elasticsearch'], + uiExports: { + managementSections: ['plugins/upgrade_assistant'], + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + }, + publicDir: resolve(__dirname, 'public'), + + config() { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + + init(server: Server) { + // Add server routes and initialize the plugin here + initServer(server); + }, + }); +} diff --git a/x-pack/plugins/upgrade_assistant/public/_app.scss b/x-pack/plugins/upgrade_assistant/public/_app.scss new file mode 100644 index 0000000000000..533bdfbea8c59 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/_app.scss @@ -0,0 +1,4 @@ +upgrade-assistant { + flex-grow: 1; + background-color: $euiColorLightestShade; +} diff --git a/x-pack/plugins/upgrade_assistant/public/app.tsx b/x-pack/plugins/upgrade_assistant/public/app.tsx new file mode 100644 index 0000000000000..3abb3baad5117 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/app.tsx @@ -0,0 +1,36 @@ +/* + * 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 { EuiPage, EuiPageBody, EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui'; +import { FormattedMessage, injectI18nProvider } from '@kbn/i18n/react'; + +import { NEXT_MAJOR_VERSION } from '../common/version'; +import { UpgradeAssistantTabs } from './components/tabs'; + +export const RootComponentUI: React.StatelessComponent = () => ( + + + + + +

+ +

+
+
+
+ +
+
+); + +export const RootComponent = injectI18nProvider(RootComponentUI); diff --git a/x-pack/plugins/upgrade_assistant/public/components/_index.scss b/x-pack/plugins/upgrade_assistant/public/components/_index.scss new file mode 100644 index 0000000000000..8026031922301 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/_index.scss @@ -0,0 +1,2 @@ +@import './tabs/checkup/index'; +@import './tabs/overview/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/components/latest_minor_banner.tsx b/x-pack/plugins/upgrade_assistant/public/components/latest_minor_banner.tsx new file mode 100644 index 0000000000000..1f24377eba6cd --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/latest_minor_banner.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../common/version'; + +export const LatestMinorBanner: React.StatelessComponent = () => ( + + } + color="warning" + iconType="help" + > +

+ + + + ), + nextEsVersion: `${NEXT_MAJOR_VERSION}.x`, + currentEsVersion: `${CURRENT_MAJOR_VERSION}.x`, + }} + /> +

+
+); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs.test.tsx new file mode 100644 index 0000000000000..d059778908313 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs.test.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 { mountWithIntl } from 'test_utils/enzyme_helpers'; + +jest.mock('axios', () => ({ + get: jest.fn(), +})); + +import { UpgradeAssistantTabs } from './tabs'; +import { LoadingState } from './types'; + +import axios from 'axios'; +import { OverviewTab } from './tabs/overview'; + +// Used to wait for promises to resolve and renders to finish before reading updates +const promisesToResolve = () => new Promise(resolve => setTimeout(resolve, 0)); + +describe('UpgradeAssistantTabs', () => { + test('renders loading state', async () => { + // @ts-ignore + axios.get.mockReturnValue( + new Promise(resolve => { + /* never resolve */ + }) + ); + const wrapper = mountWithIntl(); + // Should pass down loading status to child component + expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Loading); + }); + + test('successful data fetch', async () => { + // @ts-ignore + axios.get.mockResolvedValue({ + data: { + cluster: [], + indices: [], + }, + }); + const wrapper = mountWithIntl(); + expect(axios.get).toHaveBeenCalled(); + await promisesToResolve(); + wrapper.update(); + // Should pass down success status to child component + expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Success); + }); + + test('network failure', async () => { + // @ts-ignore + axios.get.mockRejectedValue(new Error(`oh no!`)); + const wrapper = mountWithIntl(); + await promisesToResolve(); + wrapper.update(); + // Should pass down error status to child component + expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Error); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs.tsx new file mode 100644 index 0000000000000..87bf036361591 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs.tsx @@ -0,0 +1,145 @@ +/* + * 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 axios from 'axios'; +import { findIndex } from 'lodash'; +import React from 'react'; + +import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; + +import chrome from 'ui/chrome'; + +import { UpgradeAssistantStatus } from '../../server/lib/es_migration_apis'; +import { LatestMinorBanner } from './latest_minor_banner'; +import { CheckupTab } from './tabs/checkup'; +import { OverviewTab } from './tabs/overview'; +import { LoadingState, UpgradeAssistantTabProps } from './types'; + +interface TabsState { + loadingState: LoadingState; + checkupData?: UpgradeAssistantStatus; + selectedTabIndex: number; +} + +export class UpgradeAssistantTabsUI extends React.Component< + ReactIntl.InjectedIntlProps, + TabsState +> { + constructor(props: ReactIntl.InjectedIntlProps) { + super(props); + + this.state = { + loadingState: LoadingState.Loading, + selectedTabIndex: 0, + }; + } + + public componentDidMount() { + this.loadData(); + } + + public render() { + const { selectedTabIndex } = this.state; + const tabs = this.tabs; + + return ( + + ); + } + + private onTabClick = (selectedTab: EuiTabbedContentTab) => { + const selectedTabIndex = findIndex(this.tabs, { id: selectedTab.id }); + if (selectedTabIndex === -1) { + throw new Error(`Clicked tab did not exist in tabs array`); + } + + this.setSelectedTabIndex(selectedTabIndex); + }; + + private setSelectedTabIndex = (selectedTabIndex: number) => { + this.setState({ selectedTabIndex }); + }; + + private loadData = async () => { + try { + this.setState({ loadingState: LoadingState.Loading }); + const resp = await axios.get(chrome.addBasePath('/api/upgrade_assistant/status')); + this.setState({ + loadingState: LoadingState.Success, + checkupData: resp.data, + }); + } catch (e) { + this.setState({ loadingState: LoadingState.Error }); + } + }; + + private get tabs() { + const { intl } = this.props; + const { loadingState, checkupData } = this.state; + const commonProps: UpgradeAssistantTabProps = { + loadingState, + refreshCheckupData: this.loadData, + setSelectedTabIndex: this.setSelectedTabIndex, + // Remove this in last minor of the current major (eg. 6.7) + alertBanner: , + }; + + return [ + { + id: 'overview', + name: intl.formatMessage({ + id: 'xpack.upgradeAssistant.overviewTab.overviewTabTitle', + defaultMessage: 'Overview', + }), + content: , + }, + { + id: 'cluster', + name: intl.formatMessage({ + id: 'xpack.upgradeAssistant.checkupTab.clusterTabLabel', + defaultMessage: 'Cluster', + }), + content: ( + + ), + }, + { + id: 'indices', + name: intl.formatMessage({ + id: 'xpack.upgradeAssistant.checkupTab.indicesTabLabel', + defaultMessage: 'Indices', + }), + content: ( + + ), + }, + ]; + } +} + +export const UpgradeAssistantTabs = injectI18n(UpgradeAssistantTabsUI); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/__fixtures__/checkup_api_response.json b/x-pack/plugins/upgrade_assistant/public/components/tabs/__fixtures__/checkup_api_response.json new file mode 100644 index 0000000000000..167da6b32f20a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/__fixtures__/checkup_api_response.json @@ -0,0 +1,870 @@ +{ + "cluster": [ + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 3", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 3 4", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 3 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 1 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + }, + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings 0 1 2 3 4 5", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + } + ], + "nodes": [], + "indices": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]", + "index": ".monitoring-es-6-2018.11.07" + }, + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: tweet, field: liked]]", + "index": "twitter" + }, + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]", + "index": ".kibana" + }, + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]", + "index": ".watcher-history-6-2018.11.07" + }, + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: doc, field: snapshot]]", + "index": ".monitoring-kibana-6-2018.11.07" + }, + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: tweet, field: liked]]", + "index": "twitter2" + }, + { + "index": "twitter", + "level": "critical", + "message": "This index must be reindexed in order to upgrade the Elastic Stack.", + "details": "Reindexing is irreversible, so always back up your index before proceeding.", + "actions": [ + { + "label": "Reindex in Console", + "url": "/app/kibana#/dev_tools/console?load_from=%2Fapi%2Fupgrade_assistant%2Freindex%2Fconsole_template%2Ftwitter.json" + } + ], + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html" + }, + { + "index": ".triggered_watches", + "level": "critical", + "message": "This index must be upgraded in order to upgrade the Elastic Stack.", + "details": "Upgrading is irreversible, so always back up your index before proceeding.", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html" + }, + { + "index": ".reindex-status", + "level": "critical", + "message": "This index must be reindexed in order to upgrade the Elastic Stack.", + "details": "Reindexing is irreversible, so always back up your index before proceeding.", + "actions": [ + { + "label": "Reindex in Console", + "url": "/app/kibana#/dev_tools/console?load_from=%2Fapi%2Fupgrade_assistant%2Freindex%2Fconsole_template%2F.reindex-status.json" + } + ], + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html" + }, + { + "index": "twitter2", + "level": "critical", + "message": "This index must be reindexed in order to upgrade the Elastic Stack.", + "details": "Reindexing is irreversible, so always back up your index before proceeding.", + "actions": [ + { + "label": "Reindex in Console", + "url": "/app/kibana#/dev_tools/console?load_from=%2Fapi%2Fupgrade_assistant%2Freindex%2Fconsole_template%2Ftwitter2.json" + } + ], + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html" + }, + { + "index": ".watches", + "level": "critical", + "message": "This index must be upgraded in order to upgrade the Elastic Stack.", + "details": "Upgrading is irreversible, so always back up your index before proceeding.", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html" + } + ] +} \ No newline at end of file diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap new file mode 100644 index 0000000000000..9bf3c3836b5ad --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap @@ -0,0 +1,518 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CheckupTab render with deprecations 1`] = ` + + + +

+ + index + , + } + } + /> +

+
+ + + + } + > +

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

+
+ +
+ + + + + + + + + +
+`; + +exports[`CheckupTab render with error 1`] = ` + + + +

+ + index + , + } + } + /> +

+
+ + + + } + > +

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

+
+ +
+ + + + } + /> + + +
+`; + +exports[`CheckupTab render without deprecations 1`] = ` + + + +

+ + index + , + } + } + /> +

+
+ + + + } + > +

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

+
+ +
+ + + +

+ + index + , + } + } + /> +

+

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

+
+ } + iconColor="subdued" + iconType="faceHappy" + title={ +

+ +

+ } + /> + + + +`; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap new file mode 100644 index 0000000000000..b2868cbb92644 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FilterBar renders 1`] = ` + + + + all + + + critical + + + +`; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap new file mode 100644 index 0000000000000..a70b850ca4976 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GroupByBar renders 1`] = ` + + + + by issue + + + by index + + + +`; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/_index.scss b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/_index.scss new file mode 100644 index 0000000000000..430d1e0aedf7b --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/_index.scss @@ -0,0 +1 @@ +@import './deprecations/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.test.tsx new file mode 100644 index 0000000000000..b910803c12726 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.test.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { shallow } from 'enzyme'; +import React from 'react'; + +import { LoadingState } from '../../types'; +import AssistanceData from '../__fixtures__/checkup_api_response.json'; +import { CheckupTab } from './checkup_tab'; + +const defaultProps = { + checkupLabel: 'index', + deprecations: AssistanceData.indices, + showBackupWarning: true, + refreshCheckupData: jest.fn(), + loadingState: LoadingState.Success, + setSelectedTabIndex: jest.fn(), +}; + +/** + * Mostly a dumb container with copy, test the three main states. + */ +describe('CheckupTab', () => { + test('render with deprecations', () => { + expect(shallow()).toMatchSnapshot(); + }); + + test('render without deprecations', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); + }); + + test('render with error', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.tsx new file mode 100644 index 0000000000000..af3270769ff21 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/checkup_tab.tsx @@ -0,0 +1,247 @@ +/* + * 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 { find } from 'lodash'; +import React, { Fragment } from 'react'; + +import { + // @ts-ignore + EuiAccordion, + EuiCallOut, + EuiEmptyPrompt, + EuiLink, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { + GroupByOption, + LevelFilterOption, + LoadingState, + UpgradeAssistantTabComponent, + UpgradeAssistantTabProps, +} from '../../types'; +import { CheckupControls } from './controls'; +import { GroupedDeprecations } from './deprecations/grouped'; + +interface CheckupTabProps extends UpgradeAssistantTabProps { + checkupLabel: string; + showBackupWarning?: boolean; +} + +interface CheckupTabState { + currentFilter: LevelFilterOption; + search: string; + currentGroupBy: GroupByOption; +} + +/** + * Displays a list of deprecations that filterable and groupable. Can be used for cluster, + * nodes, or indices checkups. + */ +export class CheckupTab extends UpgradeAssistantTabComponent { + constructor(props: CheckupTabProps) { + super(props); + + this.state = { + // initialize to all filters + currentFilter: LevelFilterOption.all, + search: '', + currentGroupBy: GroupByOption.message, + }; + } + + public render() { + const { + alertBanner, + checkupLabel, + deprecations, + loadingState, + refreshCheckupData, + setSelectedTabIndex, + showBackupWarning = false, + } = this.props; + const { currentFilter, search, currentGroupBy } = this.state; + + return ( + + + +

+ {checkupLabel}, + nextEsVersion: `${NEXT_MAJOR_VERSION}.x`, + }} + /> +

+
+ + + + {alertBanner && ( + + {alertBanner} + + + )} + + {showBackupWarning && ( + + + } + color="warning" + iconType="help" + > +

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

+
+ +
+ )} + + + + {loadingState === LoadingState.Error ? ( + + } + color="danger" + iconType="cross" + /> + ) : deprecations && deprecations.length > 0 ? ( + + + + {this.renderCheckupData()} + + ) : ( + + + + } + body={ + +

+ {checkupLabel}, + }} + /> +

+

+ setSelectedTabIndex(0)}> + + + ), + }} + /> +

+
+ } + /> + )} +
+
+
+ ); + } + + private changeFilter = (filter: LevelFilterOption) => { + this.setState({ currentFilter: filter }); + }; + + private changeSearch = (search: string) => { + this.setState({ search }); + }; + + private changeGroupBy = (groupBy: GroupByOption) => { + this.setState({ currentGroupBy: groupBy }); + }; + + private availableGroupByOptions() { + const { deprecations } = this.props; + + if (!deprecations) { + return []; + } + + return Object.keys(GroupByOption).filter(opt => find(deprecations, opt)) as GroupByOption[]; + } + + private renderCheckupData() { + const { deprecations } = this.props; + const { currentFilter, currentGroupBy, search } = this.state; + + return ( + + ); + } +} diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/constants.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/constants.tsx new file mode 100644 index 0000000000000..aeb5801c9f6b5 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/constants.tsx @@ -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 { IconColor } from '@elastic/eui'; +import { invert } from 'lodash'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; + +export const LEVEL_MAP: { [level: string]: number } = { + warning: 0, + critical: 1, +}; + +export const REVERSE_LEVEL_MAP: { [idx: number]: DeprecationInfo['level'] } = invert(LEVEL_MAP); + +export const COLOR_MAP: { [level: string]: IconColor } = { + warning: 'default', + critical: 'danger', +}; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/controls.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/controls.tsx new file mode 100644 index 0000000000000..7c7fcd2789f21 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/controls.tsx @@ -0,0 +1,74 @@ +/* + * 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, { StatelessComponent } from 'react'; + +import { EuiButton, EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { GroupByOption, LevelFilterOption, LoadingState } from '../../types'; +import { FilterBar } from './filter_bar'; +import { GroupByBar } from './group_by_bar'; + +interface CheckupControlsProps extends ReactIntl.InjectedIntlProps { + allDeprecations?: DeprecationInfo[]; + loadingState: LoadingState; + loadData: () => void; + currentFilter: LevelFilterOption; + onFilterChange: (filter: LevelFilterOption) => void; + search: string; + onSearchChange: (filter: string) => void; + availableGroupByOptions: GroupByOption[]; + currentGroupBy: GroupByOption; + onGroupByChange: (groupBy: GroupByOption) => void; +} + +export const CheckupControlsUI: StatelessComponent = ({ + allDeprecations, + loadingState, + loadData, + currentFilter, + onFilterChange, + search, + onSearchChange, + availableGroupByOptions, + currentGroupBy, + onGroupByChange, + intl, +}) => ( + + + onSearchChange(e.target.value)} + /> + + + {/* These two components provide their own EuiFlexItem wrappers */} + + + + + + + + + +); + +export const CheckupControls = injectI18n(CheckupControlsUI); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_cell.scss b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_cell.scss new file mode 100644 index 0000000000000..e53fd9b254cf0 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_cell.scss @@ -0,0 +1,4 @@ +.upgDeprecationCell { + overflow: hidden; + padding: $euiSize 0 0 $euiSizeL; +} diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_index.scss b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_index.scss new file mode 100644 index 0000000000000..4ebfb7dae4291 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/_index.scss @@ -0,0 +1,20 @@ +@import './cell'; + +.upgDeprecations { + // Pull the container through the padding of EuiPageContent + margin-left: -$euiSizeL; + margin-right: -$euiSizeL; +} + +.upgDeprecations__item { + padding: $euiSize $euiSizeL; + border-top: $euiBorderThin; + + &:last-of-type { + margin-bottom: -$euiSizeL; + } +} + +.upgDeprecations__itemName { + font-weight: $euiFontWeightMedium; +} diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/cell.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/cell.tsx new file mode 100644 index 0000000000000..700e74fa822a3 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/cell.tsx @@ -0,0 +1,95 @@ +/* + * 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, { ReactNode, StatelessComponent } from 'react'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface DeprecationCellProps { + items?: Array<{ title?: string; body: string }>; + docUrl?: string; + headline?: string; + healthColor?: string; + actions?: Array<{ + label: string; + url: string; + }>; + children?: ReactNode; +} + +/** + * Used to display a deprecation with links to docs, a health indicator, and other descriptive information. + */ +export const DeprecationCell: StatelessComponent = ({ + headline, + healthColor, + actions, + docUrl, + items = [], + children, +}) => ( +
+ + {healthColor && ( + + + + )} + + + {headline && ( + +

{headline}

+
+ )} + + {docUrl && ( +
+ + + + +
+ )} + + {items.map(item => ( +
+ + {item.title &&
{item.title}
} +

{item.body}

+
+
+ ))} +
+ + {actions && + actions.map(button => ( + + + {button.label} + + + ))} +
+ + + + {children} +
+); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx new file mode 100644 index 0000000000000..a66244b0e7886 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/count_summary.tsx @@ -0,0 +1,41 @@ +/* + * 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, StatelessComponent } from 'react'; + +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; + +export const DeprecationCountSummary: StatelessComponent<{ + deprecations: EnrichedDeprecationInfo[]; + allDeprecations: EnrichedDeprecationInfo[]; +}> = ({ deprecations, allDeprecations }) => ( + + {allDeprecations.length ? ( + + ) : ( + + )} + {deprecations.length !== allDeprecations.length && ( + + {'. '} + + + )} + +); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.test.tsx new file mode 100644 index 0000000000000..42b3df6ac45bb --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.test.tsx @@ -0,0 +1,190 @@ +/* + * 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 { range } from 'lodash'; +import React from 'react'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; + +import { EuiBadge, EuiPagination } from '@elastic/eui'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { GroupByOption, LevelFilterOption } from '../../../types'; +import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped'; + +describe('filterDeps', () => { + test('filters on levels', () => { + const fd = filterDeps(LevelFilterOption.critical); + expect(fd({ level: 'critical' } as DeprecationInfo)).toBe(true); + expect(fd({ level: 'warning' } as DeprecationInfo)).toBe(false); + }); + + test('filters on search', () => { + const fd = filterDeps(LevelFilterOption.critical, 'wow'); + expect(fd({ level: 'critical', message: 'the wow error' } as DeprecationInfo)).toBe(true); + expect(fd({ level: 'critical', message: 'other error' } as DeprecationInfo)).toBe(false); + }); +}); + +describe('GroupedDeprecations', () => { + const defaultProps = { + currentFilter: LevelFilterOption.all, + search: '', + currentGroupBy: GroupByOption.message, + allDeprecations: [ + { message: 'Cluster error 1', url: '', level: 'warning' }, + { message: 'Cluster error 2', url: '', level: 'critical' }, + ] as EnrichedDeprecationInfo[], + }; + + describe('expand + collapse all', () => { + const expectNumOpen = (wrapper: any, numExpected: number) => + expect(wrapper.find('div.euiAccordion-isOpen')).toHaveLength(numExpected); + + test('clicking opens and closes panels', () => { + const wrapper = mountWithIntl(); + expectNumOpen(wrapper, 0); + + // Test expand all + wrapper.find('button[data-test-subj="expandAll"]').simulate('click'); + expectNumOpen(wrapper, 2); + + // Test collapse all + wrapper.find('button[data-test-subj="collapseAll"]').simulate('click'); + expectNumOpen(wrapper, 0); + }); + + test('clicking overrides current state when some are open', () => { + const wrapper = mountWithIntl(); + + // Open a single deprecation + wrapper + .find('button.euiAccordion__button') + .first() + .simulate('click'); + expectNumOpen(wrapper, 1); + + // Test expand all + wrapper.find('button[data-test-subj="expandAll"]').simulate('click'); + expectNumOpen(wrapper, 2); + + // Close a single deprecation + wrapper + .find('button.euiAccordion__button') + .first() + .simulate('click'); + expectNumOpen(wrapper, 1); + + // Test collapse all + wrapper.find('button[data-test-subj="collapseAll"]').simulate('click'); + expectNumOpen(wrapper, 0); + }); + }); + + describe('pagination', () => { + const paginationProps = { + ...defaultProps, + allDeprecations: range(0, 40).map(i => ({ + message: `Message ${i}`, + level: 'warning', + })) as DeprecationInfo[], + }; + + test('it only displays 25 items', () => { + const wrapper = shallowWithIntl(); + expect(wrapper.find(DeprecationAccordion)).toHaveLength(25); + }); + + test('it displays pagination', () => { + const wrapper = shallowWithIntl(); + expect(wrapper.find(EuiPagination).exists()).toBe(true); + }); + + test('shows next page on click', () => { + const wrapper = mountWithIntl(); + wrapper.find('button[data-test-subj="pagination-button-next"]').simulate('click'); + expect(wrapper.find(DeprecationAccordion)).toHaveLength(15); // 40 total - 25 first page = 15 second page + }); + }); + + describe('grouping', () => { + test('group by message', () => { + const wrapper = shallowWithIntl( + + ); + + // Only 2 groups should exist b/c there are only 2 unique messages + expect(wrapper.find(DeprecationAccordion)).toHaveLength(2); + }); + + test('group by index', () => { + const wrapper = shallowWithIntl( + + ); + + // Only 3 groups should exist b/c there are only 3 unique indexes + expect(wrapper.find(DeprecationAccordion)).toHaveLength(3); + }); + }); +}); + +describe('DeprecationAccordion', () => { + const defaultProps = { + id: 'x', + title: 'Issue 1', + currentGroupBy: GroupByOption.message, + forceExpand: false, + deprecations: [{ index: 'index1' }, { index: 'index2' }] as EnrichedDeprecationInfo[], + }; + + test('shows indices count badge', () => { + const wrapper = mountWithIntl(); + expect( + wrapper + .find(EuiBadge) + .find('[data-test-subj="indexCount"]') + .text() + ).toEqual('2'); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.tsx new file mode 100644 index 0000000000000..bdf7296e9ff0f --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/grouped.tsx @@ -0,0 +1,256 @@ +/* + * 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 { groupBy } from 'lodash'; +import React, { Fragment, StatelessComponent } from 'react'; + +import { + EuiAccordion, + EuiBadge, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiPagination, + EuiSpacer, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { GroupByOption, LevelFilterOption } from '../../../types'; + +import { DeprecationCountSummary } from './count_summary'; +import { DeprecationHealth } from './health'; +import { DeprecationList } from './list'; + +// exported only for testing +export const filterDeps = (level: LevelFilterOption, search: string = '') => { + const conditions: Array<(dep: DeprecationInfo) => boolean> = []; + + if (level !== LevelFilterOption.all) { + conditions.push((dep: DeprecationInfo) => dep.level === level); + } + + if (search.length > 0) { + // Change everything to lower case for a case-insensitive comparison + conditions.push(dep => { + try { + const searchReg = new RegExp(search.toLowerCase()); + return Boolean( + dep.message.toLowerCase().match(searchReg) || + (dep.details && dep.details.match(searchReg)) + ); + } catch (e) { + // ignore any regexp errors. + return true; + } + }); + } + + // Return true if every condition function returns true (boolean AND) + return (dep: DeprecationInfo) => conditions.map(c => c(dep)).every(t => t); +}; + +/** + * A single accordion item for a grouped deprecation item. + */ +export const DeprecationAccordion: StatelessComponent<{ + id: string; + deprecations: EnrichedDeprecationInfo[]; + title: string; + currentGroupBy: GroupByOption; + forceExpand: boolean; +}> = ({ id, deprecations, title, currentGroupBy, forceExpand }) => { + const hasIndices = Boolean( + currentGroupBy === GroupByOption.message && deprecations.filter(d => d.index).length + ); + const numIndices = hasIndices ? deprecations.length : null; + + return ( + {title}} + extraAction={ +
+ {hasIndices && ( + + + {numIndices}{' '} + + +   + + )} + +
+ } + > + +
+ ); +}; + +interface GroupedDeprecationsProps { + currentFilter: LevelFilterOption; + search: string; + currentGroupBy: GroupByOption; + allDeprecations?: EnrichedDeprecationInfo[]; +} + +interface GroupedDeprecationsState { + forceExpand: true | false | null; + expandNumber: number; + currentPage: number; +} + +const PER_PAGE = 25; + +/** + * Collection of calculated fields based on props, extracted for reuse in + * `render` and `getDerivedStateFromProps`. + */ +const CalcFields = { + filteredDeprecations(props: GroupedDeprecationsProps) { + const { allDeprecations = [], currentFilter, search } = props; + return allDeprecations.filter(filterDeps(currentFilter, search)); + }, + + groups(props: GroupedDeprecationsProps) { + const { currentGroupBy } = props; + return groupBy(CalcFields.filteredDeprecations(props), currentGroupBy); + }, + + numPages(props: GroupedDeprecationsProps) { + return Math.ceil(Object.keys(CalcFields.groups(props)).length / PER_PAGE); + }, +}; + +/** + * Displays groups of deprecation messages in an accordion. + */ +export class GroupedDeprecations extends React.Component< + GroupedDeprecationsProps, + GroupedDeprecationsState +> { + public static getDerivedStateFromProps( + nextProps: GroupedDeprecationsProps, + { currentPage }: GroupedDeprecationsState + ) { + // If filters change and the currentPage is now bigger than the num of pages we're going to show, + // reset the current page to 0. + if (currentPage >= CalcFields.numPages(nextProps)) { + return { currentPage: 0 }; + } else { + return null; + } + } + + public state = { + forceExpand: false, + // `expandNumber` is used as workaround to force EuiAccordion to re-render by + // incrementing this number (used as a key) when expand all or collapse all is clicked. + expandNumber: 0, + currentPage: 0, + }; + + public render() { + const { currentGroupBy, allDeprecations = [] } = this.props; + const { forceExpand, expandNumber, currentPage } = this.state; + + const filteredDeprecations = CalcFields.filteredDeprecations(this.props); + const groups = CalcFields.groups(this.props); + + return ( + + + + this.setExpand(true)} + data-test-subj="expandAll" + > + + + + + this.setExpand(false)} + data-test-subj="collapseAll" + > + + + + + + + + + + + +
+ {Object.keys(groups) + .sort() + // Apply pagination + .slice(currentPage * PER_PAGE, (currentPage + 1) * PER_PAGE) + .map(groupName => [ + , + ])} + + {/* Only show pagination if we have more than PER_PAGE. */} + {Object.keys(groups).length > PER_PAGE && ( + + + + + + + + + + )} +
+
+ ); + } + + private setExpand = (forceExpand: boolean) => { + this.setState({ forceExpand, expandNumber: this.state.expandNumber + 1 }); + }; + + private setPage = (currentPage: number) => this.setState({ currentPage }); +} diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/health.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/health.tsx new file mode 100644 index 0000000000000..ab4daece767a0 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/health.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { countBy } from 'lodash'; +import React, { StatelessComponent } from 'react'; + +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { COLOR_MAP, LEVEL_MAP, REVERSE_LEVEL_MAP } from '../constants'; + +const LocalizedLevels: { [level: string]: string } = { + warning: i18n.translate('xpack.upgradeAssistant.checkupTab.deprecations.warningLabel', { + defaultMessage: 'warning', + }), + critical: i18n.translate('xpack.upgradeAssistant.checkupTab.deprecations.criticalLabel', { + defaultMessage: 'critical', + }), +}; + +export const LocalizedActions: { [level: string]: string } = { + warning: i18n.translate('xpack.upgradeAssistant.checkupTab.deprecations.warningActionTooltip', { + defaultMessage: 'Resolving this issue before upgrading is advised, but not required.', + }), + critical: i18n.translate('xpack.upgradeAssistant.checkupTab.deprecations.criticalActionTooltip', { + defaultMessage: 'Resolve this issue before upgrading.', + }), +}; + +interface DeprecationHealthProps { + deprecations: DeprecationInfo[]; + single?: boolean; +} + +const SingleHealth: StatelessComponent<{ level: DeprecationInfo['level']; label: string }> = ({ + level, + label, +}) => ( + + + {label} + +   + +); + +/** + * Displays a summary health for a list of deprecations that shows the number and level of severity + * deprecations in the list. + */ +export const DeprecationHealth: StatelessComponent = ({ + deprecations, + single = false, +}) => { + if (deprecations.length === 0) { + return ; + } + + const levels = deprecations.map(d => LEVEL_MAP[d.level]); + + if (single) { + const highest = Math.max(...levels); + const highestLevel = REVERSE_LEVEL_MAP[highest]; + + return ; + } + + const countByLevel = countBy(levels); + + return ( + + {Object.keys(countByLevel) + .map(k => parseInt(k, 10)) + .sort() + .map(level => [level, REVERSE_LEVEL_MAP[level]]) + .map(([numLevel, stringLevel]) => ( + + ))} + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index.tsx new file mode 100644 index 0000000000000..edd472a2116db --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index.tsx @@ -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 { GroupedDeprecations } from './grouped'; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.test.tsx new file mode 100644 index 0000000000000..7c862856af927 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.test.tsx @@ -0,0 +1,106 @@ +/* + * 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 { shallowWithIntl } from 'test_utils/enzyme_helpers'; + +import { IndexDeprecationTableProps, IndexDeprecationTableUI } from './index_table'; + +describe('IndexDeprecationTable', () => { + const actions = [{ label: 'Do it', url: 'http://justdoit.com' }]; + const defaultProps = { + indices: [ + { index: 'index1', details: 'Index 1 deets', actions }, + { index: 'index2', details: 'Index 2 deets', actions }, + { index: 'index3', details: 'Index 3 deets', actions }, + ], + } as IndexDeprecationTableProps; + + // Relying pretty heavily on EUI to implement the table functionality correctly. + // This test simply verifies that the props passed to EuiBaseTable are the ones + // expected. + test('render', () => { + expect(shallowWithIntl()).toMatchInlineSnapshot(` + +`); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.tsx new file mode 100644 index 0000000000000..b94354efac4a7 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/index_table.tsx @@ -0,0 +1,161 @@ +/* + * 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 { sortBy } from 'lodash'; +import React from 'react'; + +import { EuiBasicTable, EuiButton } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; + +const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000]; + +export interface IndexDeprecationDetails { + index: string; + details?: string; + actions?: Array<{ + label: string; + url: string; + }>; +} + +export interface IndexDeprecationTableProps extends ReactIntl.InjectedIntlProps { + indices: IndexDeprecationDetails[]; +} + +interface IndexDeprecationTableState { + sortField: string; + sortDirection: 'asc' | 'desc'; + pageIndex: number; + pageSize: number; +} + +export class IndexDeprecationTableUI extends React.Component< + IndexDeprecationTableProps, + IndexDeprecationTableState +> { + constructor(props: IndexDeprecationTableProps) { + super(props); + + this.state = { + sortField: 'index', + sortDirection: 'asc', + pageIndex: 0, + pageSize: 10, + }; + } + + public render() { + const { intl } = this.props; + const { pageIndex, pageSize, sortField, sortDirection } = this.state; + + const columns = [ + { + field: 'index', + name: intl.formatMessage({ + id: 'xpack.upgradeAssistant.checkupTab.deprecations.indexTable.indexColumnLabel', + defaultMessage: 'Index', + }), + sortable: true, + }, + { + field: 'details', + name: intl.formatMessage({ + id: 'xpack.upgradeAssistant.checkupTab.deprecations.indexTable.detailsColumnLabel', + defaultMessage: 'Details', + }), + }, + ]; + + if (this.actionsColumn) { + // @ts-ignore + columns.push(this.actionsColumn); + } + + const sorting = { sort: { field: sortField, direction: sortDirection } }; + const pagination = { + pageIndex, + pageSize, + ...this.pageSizeOptions(), + }; + + return ( + + ); + } + + private getRows() { + const { sortField, sortDirection, pageIndex, pageSize } = this.state; + const { indices } = this.props; + + let sorted = sortBy(indices, sortField); + if (sortDirection === 'desc') { + sorted = sorted.reverse(); + } + + const start = pageIndex * pageSize; + return sorted.slice(start, start + pageSize); + } + + private onTableChange = (tableProps: any) => { + this.setState({ + sortField: tableProps.sort.field, + sortDirection: tableProps.sort.direction, + pageIndex: tableProps.page.index, + pageSize: tableProps.page.size, + }); + }; + + private pageSizeOptions() { + const { indices } = this.props; + const totalItemCount = indices.length; + + // If we only have that smallest page size, don't show any page size options. + if (totalItemCount <= PAGE_SIZES[0]) { + return { totalItemCount, pageSizeOptions: [], hidePerPageOptions: true }; + } + + // Keep a size option if the # of items is larger than the previous option. + // This avoids having a long list of useless page sizes. + const pageSizeOptions = PAGE_SIZES.filter((perPage, idx) => { + return idx === 0 || totalItemCount > PAGE_SIZES[idx - 1]; + }); + + return { totalItemCount, pageSizeOptions, hidePerPageOptions: false }; + } + + private get actionsColumn() { + // NOTE: this naive implementation assumes all indices in the table have + // the same actions (can still have different URLs). This should work for known usecases. + const { indices } = this.props; + if (!indices.find(i => i.actions !== undefined)) { + return null; + } + + const actions = indices[0].actions!; + + return { + actions: actions.map((action, idx) => ({ + render(index: IndexDeprecationDetails) { + const { url, label } = index.actions![idx]; + return ( + + {label} + + ); + }, + })), + }; + } +} + +export const IndexDeprecationTable = injectI18n(IndexDeprecationTableUI); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.test.tsx new file mode 100644 index 0000000000000..01eea28dd4048 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.test.tsx @@ -0,0 +1,128 @@ +/* + * 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 { shallow } from 'enzyme'; +import React from 'react'; + +import { EnrichedDeprecationInfo } from 'x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis'; +import { GroupByOption } from '../../../types'; +import { DeprecationList } from './list'; + +describe('DeprecationList', () => { + describe('group by message', () => { + const defaultProps = { + deprecations: [ + { message: 'Issue 1', url: '', level: 'warning' }, + { message: 'Issue 1', url: '', level: 'warning' }, + ] as EnrichedDeprecationInfo[], + currentGroupBy: GroupByOption.message, + }; + + test('shows simple messages when index field is not present', () => { + expect(shallow()).toMatchInlineSnapshot(` +
+ + +
+`); + }); + + test('shows index deprecation when index field is present', () => { + // Add index fields to deprecation items + const props = { + ...defaultProps, + deprecations: defaultProps.deprecations.map((d, index) => ({ + ...d, + index: index.toString(), + })), + }; + const wrapper = shallow(); + expect(wrapper).toMatchInlineSnapshot(` + +`); + }); + }); + + describe('group by index', () => { + const defaultProps = { + deprecations: [ + { message: 'Issue 1', index: 'index1', url: '', level: 'warning' }, + { message: 'Issue 2', index: 'index1', url: '', level: 'warning' }, + ] as EnrichedDeprecationInfo[], + currentGroupBy: GroupByOption.index, + }; + + test('shows detailed messages', () => { + expect(shallow()).toMatchInlineSnapshot(` +
+ + +
+`); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.tsx new file mode 100644 index 0000000000000..a4071a5649d81 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/deprecations/list.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { StatelessComponent } from 'react'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { EnrichedDeprecationInfo } from '../../../../../server/lib/es_migration_apis'; +import { GroupByOption } from '../../../types'; + +import { COLOR_MAP, LEVEL_MAP } from '../constants'; +import { DeprecationCell } from './cell'; +import { IndexDeprecationDetails, IndexDeprecationTable } from './index_table'; + +const sortByLevelDesc = (a: DeprecationInfo, b: DeprecationInfo) => { + return -1 * (LEVEL_MAP[a.level] - LEVEL_MAP[b.level]); +}; + +/** + * Used to show a single deprecation message with any detailed information. + */ +const MessageDeprecation: StatelessComponent<{ deprecation: EnrichedDeprecationInfo }> = ({ + deprecation, +}) => { + const items = []; + + if (deprecation.details) { + items.push({ body: deprecation.details }); + } + + return ( + + ); +}; + +/** + * Used to show a single (simple) deprecation message with any detailed information. + */ +const SimpleMessageDeprecation: StatelessComponent<{ deprecation: EnrichedDeprecationInfo }> = ({ + deprecation, +}) => { + const items = []; + + if (deprecation.details) { + items.push({ body: deprecation.details }); + } + + return ; +}; + +interface IndexDeprecationProps { + deprecation: DeprecationInfo; + indices: IndexDeprecationDetails[]; +} + +/** + * Shows a single deprecation and table of affected indices with details for each index. + */ +const IndexDeprecation: StatelessComponent = ({ deprecation, indices }) => { + return ( + + + + ); +}; + +/** + * A list of deprecations that is either shown as individual deprecation cells or as a + * deprecation summary for a list of indices. + */ +export const DeprecationList: StatelessComponent<{ + deprecations: EnrichedDeprecationInfo[]; + currentGroupBy: GroupByOption; +}> = ({ deprecations, currentGroupBy }) => { + // If we're grouping by message and the first deprecation has an index field, show an index + // group deprecation. Otherwise, show each message. + if (currentGroupBy === GroupByOption.message && deprecations[0].index !== undefined) { + // If we're grouping by index we assume that every deprecation message is the same + // issue and that each deprecation will have an index associated with it. + const indices = deprecations.map(dep => ({ + index: dep.index!, + details: dep.details, + actions: dep.actions, + })); + + return ; + } else if (currentGroupBy === GroupByOption.index) { + // If we're grouping by index show all info for each message + return ( +
+ {deprecations.sort(sortByLevelDesc).map(dep => ( + + ))} +
+ ); + } else { + return ( +
+ {deprecations.sort(sortByLevelDesc).map(dep => ( + + ))} +
+ ); + } +}; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.test.tsx new file mode 100644 index 0000000000000..9d0c9ea9ddb56 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { LevelFilterOption } from '../../types'; +import { FilterBar } from './filter_bar'; + +const defaultProps = { + allDeprecations: [ + { level: LevelFilterOption.critical }, + { level: LevelFilterOption.critical }, + ] as DeprecationInfo[], + currentFilter: LevelFilterOption.critical, + onFilterChange: jest.fn(), +}; + +describe('FilterBar', () => { + test('renders', () => { + expect(shallow()).toMatchSnapshot(); + }); + + test('clicking button calls onFilterChange', () => { + const wrapper = mount(); + wrapper.find('button.euiFilterButton-hasActiveFilters').simulate('click'); + expect(defaultProps.onFilterChange).toHaveBeenCalledTimes(1); + expect(defaultProps.onFilterChange.mock.calls[0][0]).toEqual(LevelFilterOption.critical); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.tsx new file mode 100644 index 0000000000000..e04acfd5bf51e --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/filter_bar.tsx @@ -0,0 +1,68 @@ +/* + * 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 { groupBy } from 'lodash'; +import React from 'react'; + +import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; +import { LevelFilterOption } from '../../types'; + +const LocalizedOptions: { [option: string]: string } = { + all: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.filterBar.allButtonLabel', { + defaultMessage: 'all', + }), + critical: i18n.translate( + 'xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel', + { defaultMessage: 'critical' } + ), +}; + +const allFilterOptions = Object.keys(LevelFilterOption) as LevelFilterOption[]; + +interface FilterBarProps { + allDeprecations?: DeprecationInfo[]; + currentFilter: LevelFilterOption; + onFilterChange(level: LevelFilterOption): void; +} + +export const FilterBar: React.StatelessComponent = ({ + allDeprecations = [], + currentFilter, + onFilterChange, +}) => { + const levelGroups = groupBy(allDeprecations, 'level'); + const levelCounts = Object.keys(levelGroups).reduce( + (counts, level) => { + counts[level] = levelGroups[level].length; + return counts; + }, + {} as { [level: string]: number } + ); + + const allCount = allDeprecations.length; + + return ( + + + {allFilterOptions.map(option => ( + + {LocalizedOptions[option]} + + ))} + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.test.tsx new file mode 100644 index 0000000000000..0b31cc8adfc90 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.test.tsx @@ -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 { mount, shallow } from 'enzyme'; +import React from 'react'; + +import { GroupByOption } from '../../types'; +import { GroupByBar } from './group_by_bar'; + +const defaultProps = { + availableGroupByOptions: [GroupByOption.message, GroupByOption.index], + currentGroupBy: GroupByOption.message, + onGroupByChange: jest.fn(), +}; + +describe('GroupByBar', () => { + test('renders', () => { + expect(shallow()).toMatchSnapshot(); + }); + + test('clicking button calls onGroupByChange', () => { + const wrapper = mount(); + wrapper.find('button.euiFilterButton-hasActiveFilters').simulate('click'); + expect(defaultProps.onGroupByChange).toHaveBeenCalledTimes(1); + expect(defaultProps.onGroupByChange.mock.calls[0][0]).toEqual(GroupByOption.message); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.tsx new file mode 100644 index 0000000000000..6b1053faf6b6d --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/group_by_bar.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 React from 'react'; + +import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { GroupByOption } from '../../types'; + +const LocalizedOptions: { [option: string]: string } = { + message: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel', { + defaultMessage: 'by issue', + }), + index: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel', { + defaultMessage: 'by index', + }), +}; + +interface GroupByBarProps { + availableGroupByOptions: GroupByOption[]; + currentGroupBy: GroupByOption; + onGroupByChange: (groupBy: GroupByOption) => void; +} + +export const GroupByBar: React.StatelessComponent = ({ + availableGroupByOptions, + currentGroupBy, + onGroupByChange, +}) => { + if (availableGroupByOptions.length <= 1) { + return null; + } + + return ( + + + {availableGroupByOptions.map(option => ( + + {LocalizedOptions[option]} + + ))} + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/index.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/index.tsx new file mode 100644 index 0000000000000..bb217de1df5ff --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/checkup/index.tsx @@ -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 { CheckupTab } from './checkup_tab'; diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/_index.scss b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/_index.scss new file mode 100644 index 0000000000000..a6bbc6ba13298 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/_index.scss @@ -0,0 +1 @@ +@import './steps'; \ No newline at end of file diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/_steps.scss b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/_steps.scss new file mode 100644 index 0000000000000..789c22fe1c28d --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/_steps.scss @@ -0,0 +1,6 @@ +.upgSteps { + .euiStep:last-child .euiStep__content { + padding-bottom: 0; + margin-bottom: 0; + } +} diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/deprecation_logging_toggle.tsx new file mode 100644 index 0000000000000..74c4a9a5025fc --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/deprecation_logging_toggle.tsx @@ -0,0 +1,124 @@ +/* + * 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 axios from 'axios'; +import React from 'react'; + +import { EuiLoadingSpinner, EuiSwitch } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; + +import chrome from 'ui/chrome'; +import { LoadingState } from '../../types'; + +interface DeprecationLoggingTabState { + loadingState: LoadingState; + loggingEnabled?: boolean; +} + +export class DeprecationLoggingToggleUI extends React.Component< + ReactIntl.InjectedIntlProps, + DeprecationLoggingTabState +> { + constructor(props: ReactIntl.InjectedIntlProps) { + super(props); + + this.state = { + loadingState: LoadingState.Loading, + }; + } + + public componentWillMount() { + this.loadData(); + } + + public render() { + const { loggingEnabled, loadingState } = this.state; + + // Show a spinner until we've done the initial load. + if (loadingState === LoadingState.Loading && loggingEnabled === undefined) { + return ; + } + + return ( + + ); + } + + private renderLoggingState() { + const { intl } = this.props; + const { loggingEnabled, loadingState } = this.state; + + if (loadingState === LoadingState.Error) { + return intl.formatMessage({ + id: + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel', + defaultMessage: 'Could not load logging state', + }); + } else if (loggingEnabled) { + return intl.formatMessage({ + id: + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel', + defaultMessage: 'On', + }); + } else { + return intl.formatMessage({ + id: + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.disabledLabel', + defaultMessage: 'Off', + }); + } + } + + private loadData = async () => { + try { + this.setState({ loadingState: LoadingState.Loading }); + const resp = await axios.get( + chrome.addBasePath('/api/upgrade_assistant/deprecation_logging') + ); + this.setState({ + loadingState: LoadingState.Success, + loggingEnabled: resp.data.isEnabled, + }); + } catch (e) { + this.setState({ loadingState: LoadingState.Error }); + } + }; + + private toggleLogging = async () => { + try { + // Optimistically toggle the UI + const newEnabled = !this.state.loggingEnabled; + this.setState({ loadingState: LoadingState.Loading, loggingEnabled: newEnabled }); + + const resp = await axios.put( + chrome.addBasePath('/api/upgrade_assistant/deprecation_logging'), + { + isEnabled: newEnabled, + }, + { + headers: { + 'kbn-xsrf': chrome.getXsrfToken(), + }, + } + ); + + this.setState({ + loadingState: LoadingState.Success, + loggingEnabled: resp.data.isEnabled, + }); + } catch (e) { + this.setState({ loadingState: LoadingState.Error }); + } + }; +} + +export const DeprecationLoggingToggle = injectI18n(DeprecationLoggingToggleUI); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/index.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/index.tsx new file mode 100644 index 0000000000000..4ffd2cfa05f28 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/index.tsx @@ -0,0 +1,67 @@ +/* + * 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, StatelessComponent } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, + // @ts-ignore + EuiStat, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { LoadingState, UpgradeAssistantTabProps } from '../../types'; +import { Steps } from './steps'; + +export const OverviewTab: StatelessComponent = props => ( + + + + +

+ +

+
+ + + + {props.alertBanner && ( + + {props.alertBanner} + + + + )} + + + + {props.loadingState === LoadingState.Success ? ( + + ) : ( + + + + + + )} + + +
+); diff --git a/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/steps.tsx b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/steps.tsx new file mode 100644 index 0000000000000..861db5a74c7a6 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/tabs/overview/steps.tsx @@ -0,0 +1,275 @@ +/* + * 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, StatelessComponent } from 'react'; + +import { + EuiFormRow, + EuiLink, + EuiNotificationBadge, + EuiSpacer, + // @ts-ignore + EuiStat, + EuiSteps, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import chrome from 'ui/chrome'; + +import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../../common/version'; +import { UpgradeAssistantTabProps } from '../../types'; +import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; + +// Leaving these here even if unused so they are picked up for i18n static analysis +// Keep this until last minor release (when next major is also released). +const WAIT_FOR_RELEASE_STEP = { + title: i18n.translate('xpack.upgradeAssistant.overviewTab.steps.waitForReleaseStep.stepTitle', { + defaultMessage: 'Wait for the Elasticsearch {nextEsVersion} release', + values: { + nextEsVersion: `${NEXT_MAJOR_VERSION}.0`, + }, + }), + children: ( + + +

+ +

+
+
+ ), +}; + +// Swap in this step for the one above it on the last minor release. +// @ts-ignore +const START_UPGRADE_STEP = { + title: i18n.translate('xpack.upgradeAssistant.overviewTab.steps.startUpgradeStep.stepTitle', { + defaultMessage: 'Start your upgrade', + }), + children: ( + + +

+ {chrome.getInjected('isCloudEnabled', false) ? ( + + ) : ( + + + + ), + }} + /> + )} +

+
+
+ ), +}; + +export const StepsUI: StatelessComponent< + UpgradeAssistantTabProps & ReactIntl.InjectedIntlProps +> = ({ checkupData, setSelectedTabIndex, intl }) => { + const countByType = Object.keys(checkupData!).reduce( + (counts, checkupType) => { + counts[checkupType] = checkupData![checkupType].length; + return counts; + }, + {} as { [checkupType: string]: number } + ); + + return ( + + {countByType.cluster ? ( + +

+ setSelectedTabIndex(1)}> + + + ), + }} + /> +

+

+ {countByType.cluster} + ), + }} + /> +

+
+ ) : ( +

+ +

+ )} + + ), + }, + { + title: countByType.indices + ? intl.formatMessage({ + id: 'xpack.upgradeAssistant.overviewTab.steps.indicesStep.issuesRemainingStepTitle', + defaultMessage: 'Check for issues with your indices', + }) + : intl.formatMessage({ + id: + 'xpack.upgradeAssistant.overviewTab.steps.indicesStep.noIssuesRemainingStepTitle', + defaultMessage: 'Your index settings are ready', + }), + status: countByType.indices ? 'warning' : 'complete', + children: ( + + {countByType.indices ? ( + +

+ setSelectedTabIndex(2)}> + + + ), + }} + /> +

+

+ {countByType.indices} + ), + }} + /> +

+
+ ) : ( +

+ +

+ )} +
+ ), + }, + { + title: intl.formatMessage({ + id: 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.stepTitle', + defaultMessage: 'Review the Elasticsearch deprecation logs', + }), + children: ( + + +

+ + + + ), + nextEsVersion: `${NEXT_MAJOR_VERSION}.0`, + }} + /> +

+
+ + + + + + +
+ ), + }, + + // Swap in START_UPGRADE_STEP on the last minor release. + WAIT_FOR_RELEASE_STEP, + ]} + /> + ); +}; + +export const Steps = injectI18n(StepsUI); diff --git a/x-pack/plugins/upgrade_assistant/public/components/types.ts b/x-pack/plugins/upgrade_assistant/public/components/types.ts new file mode 100644 index 0000000000000..351d10027e858 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/components/types.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { + EnrichedDeprecationInfo, + UpgradeAssistantStatus, +} from '../../server/lib/es_migration_apis'; + +export interface UpgradeAssistantTabProps { + alertBanner?: React.ReactNode; + checkupData?: UpgradeAssistantStatus; + deprecations?: EnrichedDeprecationInfo[]; + refreshCheckupData: () => Promise; + loadingState: LoadingState; + setSelectedTabIndex: (tabIndex: number) => void; +} + +export class UpgradeAssistantTabComponent< + T extends UpgradeAssistantTabProps = UpgradeAssistantTabProps, + S = {} +> extends React.Component {} + +export enum LoadingState { + Loading, + Success, + Error, +} + +export enum LevelFilterOption { + all = 'all', + critical = 'critical', +} + +export enum GroupByOption { + message = 'message', + index = 'index', + node = 'node', +} diff --git a/x-pack/plugins/upgrade_assistant/public/index.scss b/x-pack/plugins/upgrade_assistant/public/index.scss new file mode 100644 index 0000000000000..d9ba9a436792a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/index.scss @@ -0,0 +1,4 @@ +@import 'ui/public/styles/_styling_constants'; + +@import './app'; +@import './components/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/index.tsx b/x-pack/plugins/upgrade_assistant/public/index.tsx new file mode 100644 index 0000000000000..31cc4b1dbf6bd --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/index.tsx @@ -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 { i18n } from '@kbn/i18n'; +// @ts-ignore +import { management } from 'ui/management'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +// @ts-ignore +import routes from 'ui/routes'; +import { NEXT_MAJOR_VERSION } from '../common/version'; +import { RootComponent } from './app'; + +const BASE_PATH = `/management/elasticsearch/upgrade_assistant`; + +function startApp() { + management.getSection('elasticsearch').register('upgrade_assistant', { + visible: true, + display: i18n.translate('xpack.upgradeAssistant.appTitle', { + defaultMessage: '{version} Upgrade Assistant', + values: { version: `${NEXT_MAJOR_VERSION}.0` }, + }), + order: 100, + url: `#${BASE_PATH}`, + }); + + uiModules.get('kibana').directive('upgradeAssistant', (reactDirective: any) => { + return reactDirective(RootComponent); + }); + + routes.when(`${BASE_PATH}/:view?`, { + template: '', + }); +} + +startApp(); diff --git a/x-pack/plugins/upgrade_assistant/server/index.ts b/x-pack/plugins/upgrade_assistant/server/index.ts new file mode 100644 index 0000000000000..759fdd288d615 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/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 { Legacy } from 'kibana'; +import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; +import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; + +export function initServer(server: Legacy.Server) { + registerClusterCheckupRoutes(server); + registerDeprecationLoggingRoutes(server); +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json new file mode 100644 index 0000000000000..438c5c3246ddf --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json @@ -0,0 +1,68 @@ +{ + "cluster_settings": [ + { + "level": "warning", + "message": "Template patterns are no longer using `template` field, but `index_patterns` instead", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + "details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template" + }, + { + "level": "warning", + "message": "one or more templates use deprecated mapping settings", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}" + } + ], + "node_settings": [], + "index_settings": { + ".monitoring-es-6-2018.11.07": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]" + } + ], + "twitter": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: tweet, field: liked]]" + } + ], + ".kibana": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]" + } + ], + ".watcher-history-6-2018.11.07": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]" + } + ], + ".monitoring-kibana-6-2018.11.07": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: doc, field: snapshot]]" + } + ], + "twitter2": [ + { + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + "details": "[[type: tweet, field: liked]]" + } + ] + }, + "ml_settings": [] +} \ No newline at end of file diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap new file mode 100644 index 0000000000000..cb0e134a95c96 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getUpgradeAssistantStatus returns the correct shape of data 1`] = ` +Object { + "cluster": Array [ + Object { + "details": "templates using \`template\` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template", + "level": "warning", + "message": "Template patterns are no longer using \`template\` field, but \`index_patterns\` instead", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal", + }, + Object { + "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}", + "level": "warning", + "message": "one or more templates use deprecated mapping settings", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html", + }, + ], + "indices": Array [ + Object { + "details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]", + "index": ".monitoring-es-6-2018.11.07", + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + }, + Object { + "details": "[[type: tweet, field: liked]]", + "index": "twitter", + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + }, + Object { + "details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]", + "index": ".kibana", + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + }, + Object { + "details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]", + "index": ".watcher-history-6-2018.11.07", + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + }, + Object { + "details": "[[type: doc, field: snapshot]]", + "index": ".monitoring-kibana-6-2018.11.07", + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + }, + Object { + "details": "[[type: tweet, field: liked]]", + "index": "twitter2", + "level": "warning", + "message": "Coercion of boolean fields", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", + }, + ], + "nodes": Array [], +} +`; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts new file mode 100644 index 0000000000000..cb93bc4b33d6d --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + getDeprecationLoggingStatus, + isDeprecationLoggingEnabled, + setDeprecationLogging, +} from './es_deprecation_logging_apis'; + +describe('getDeprecationLoggingStatus', () => { + it('calls cluster.getSettings', async () => { + const callWithRequest = jest.fn(); + await getDeprecationLoggingStatus(callWithRequest, {} as any); + expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.getSettings', { + includeDefaults: true, + }); + }); +}); + +describe('setDeprecationLogging', () => { + describe('isEnabled = true', async () => { + it('calls cluster.putSettings with logger.deprecation = WARN', async () => { + const callWithRequest = jest.fn(); + await setDeprecationLogging(callWithRequest, {} as any, true); + expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.putSettings', { + body: { transient: { 'logger.deprecation': 'WARN' } }, + }); + }); + }); + + describe('isEnabled = false', async () => { + it('calls cluster.putSettings with logger.deprecation = ERROR', async () => { + const callWithRequest = jest.fn(); + await setDeprecationLogging(callWithRequest, {} as any, false); + expect(callWithRequest).toHaveBeenCalledWith({}, 'cluster.putSettings', { + body: { transient: { 'logger.deprecation': 'ERROR' } }, + }); + }); + }); +}); + +describe('isDeprecationLoggingEnabled', () => { + ['default', 'persistent', 'transient'].forEach(tier => { + ['ALL', 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ALL'].forEach(level => { + it(`returns true when ${tier} is set to ${level}`, () => { + expect(isDeprecationLoggingEnabled({ [tier]: { logger: { deprecation: level } } })).toBe( + true + ); + }); + }); + }); + + ['default', 'persistent', 'transient'].forEach(tier => { + ['ERROR', 'FATAL'].forEach(level => { + it(`returns false when ${tier} is set to ${level}`, () => { + expect(isDeprecationLoggingEnabled({ [tier]: { logger: { deprecation: level } } })).toBe( + false + ); + }); + }); + }); + + it('allows transient to override persistent and default', () => { + expect( + isDeprecationLoggingEnabled({ + default: { logger: { deprecation: 'FATAL' } }, + persistent: { logger: { deprecation: 'FATAL' } }, + transient: { logger: { deprecation: 'WARN' } }, + }) + ).toBe(true); + }); + + it('allows persistent to override default', () => { + expect( + isDeprecationLoggingEnabled({ + default: { logger: { deprecation: 'FATAL' } }, + persistent: { logger: { deprecation: 'WARN' } }, + }) + ).toBe(true); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts new file mode 100644 index 0000000000000..2cf0a8f5020fd --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { get } from 'lodash'; + +import { Legacy } from 'kibana'; +import { CallClusterWithRequest } from 'src/legacy/core_plugins/elasticsearch'; + +interface DeprecationLoggingStatus { + isEnabled: boolean; +} + +export async function getDeprecationLoggingStatus( + callWithRequest: CallClusterWithRequest, + req: Legacy.Request +): Promise { + const response = await callWithRequest(req, 'cluster.getSettings', { + includeDefaults: true, + }); + + return { + isEnabled: isDeprecationLoggingEnabled(response), + }; +} + +export async function setDeprecationLogging( + callWithRequest: CallClusterWithRequest, + req: Legacy.Request, + isEnabled: boolean +): Promise { + const response = await callWithRequest(req, 'cluster.putSettings', { + body: { + transient: { + 'logger.deprecation': isEnabled ? 'WARN' : 'ERROR', + }, + }, + }); + + return { + isEnabled: isDeprecationLoggingEnabled(response), + }; +} + +export function isDeprecationLoggingEnabled(settings: any) { + const deprecationLogLevel = ['default', 'persistent', 'transient'].reduce( + (currentLogLevel, settingsTier) => + get(settings, [settingsTier, 'logger', 'deprecation'], currentLogLevel), + 'WARN' + ); + + return ['ALL', 'TRACE', 'DEBUG', 'INFO', 'WARN'].includes(deprecationLogLevel); +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts new file mode 100644 index 0000000000000..c97f85b3c8a61 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { getUpgradeAssistantStatus } from './es_migration_apis'; + +import { DeprecationAPIResponse } from 'src/legacy/core_plugins/elasticsearch'; +import fakeDeprecations from './__fixtures__/fake_deprecations.json'; + +describe('getUpgradeAssistantStatus', () => { + let deprecationsResponse: DeprecationAPIResponse; + + const callWithRequest = jest.fn().mockImplementation(async (req, api, { path }) => { + if (path === '/_xpack/migration/deprecations') { + return deprecationsResponse; + } else { + throw new Error(`Unexpected API call: ${path}`); + } + }); + + beforeEach(() => { + deprecationsResponse = _.cloneDeep(fakeDeprecations); + }); + + it('calls /_xpack/migration/deprecations', async () => { + await getUpgradeAssistantStatus(callWithRequest, {} as any, '/'); + expect(callWithRequest).toHaveBeenCalledWith({}, 'transport.request', { + path: '/_xpack/migration/deprecations', + method: 'GET', + }); + }); + + it('returns the correct shape of data', async () => { + const resp = await getUpgradeAssistantStatus(callWithRequest, {} as any, '/'); + expect(resp).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts new file mode 100644 index 0000000000000..0178eebb16cc0 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; + +import { Request } from 'hapi'; +import { DeprecationAPIResponse, DeprecationInfo } from 'src/legacy/core_plugins/elasticsearch'; + +export interface EnrichedDeprecationInfo extends DeprecationInfo { + index?: string; + node?: string; + actions?: Array<{ + label: string; + url: string; + }>; +} + +export interface UpgradeAssistantStatus { + cluster: EnrichedDeprecationInfo[]; + nodes: EnrichedDeprecationInfo[]; + indices: EnrichedDeprecationInfo[]; + + [checkupType: string]: EnrichedDeprecationInfo[]; +} + +export async function getUpgradeAssistantStatus( + callWithRequest: any, + req: Request, + basePath: string +): Promise { + const deprecations = (await callWithRequest(req, 'transport.request', { + path: '/_xpack/migration/deprecations', + method: 'GET', + })) as DeprecationAPIResponse; + + return { + cluster: deprecations.cluster_settings, + nodes: deprecations.node_settings, + indices: getCombinedIndexInfos(deprecations, basePath), + }; +} + +// Combines the information from the migration assistance api and the deprecation api into a single array. +// Enhances with information about which index the deprecation applies to and adds buttons for accessing the +// reindex UI. +const getCombinedIndexInfos = (deprecations: DeprecationAPIResponse, basePath: string) => + Object.keys(deprecations.index_settings).reduce( + (indexDeprecations, indexName) => { + return indexDeprecations.concat( + deprecations.index_settings[indexName].map( + d => ({ ...d, index: indexName } as EnrichedDeprecationInfo) + ) + ); + }, + [] as EnrichedDeprecationInfo[] + ); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts new file mode 100644 index 0000000000000..2f177d6f54ccc --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { Server } from 'hapi'; +import { registerClusterCheckupRoutes } from './cluster_checkup'; + +// Need to require to get mock on named export to work. +// tslint:disable:no-var-requires +const MigrationApis = require('../lib/es_migration_apis'); +MigrationApis.getUpgradeAssistantStatus = jest.fn(); + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the es_migration_apis test. + */ +describe('reindex template API', () => { + const server = new Server(); + server.plugins = { + elasticsearch: { + getCluster: () => ({ callWithRequest: jest.fn() } as any), + } as any, + } as any; + server.config = () => ({ get: () => '' } as any); + + registerClusterCheckupRoutes(server); + + describe('GET /api/upgrade_assistant/reindex/{indexName}.json', () => { + it('returns a template', async () => { + MigrationApis.getUpgradeAssistantStatus.mockResolvedValue({ + cluster: [], + indices: [], + nodes: [], + }); + const resp = await server.inject({ + method: 'GET', + url: '/api/upgrade_assistant/status', + }); + + expect(resp.statusCode).toEqual(200); + expect(resp.payload).toMatchInlineSnapshot( + `"{\\"cluster\\":[],\\"indices\\":[],\\"nodes\\":[]}"` + ); + }); + + it('returns an 403 error if it throws forbidden', async () => { + const e: any = new Error(`you can't go here!`); + e.status = 403; + + MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(e); + const resp = await server.inject({ + method: 'GET', + url: '/api/upgrade_assistant/status', + }); + + expect(resp.statusCode).toEqual(403); + }); + + it('returns an 500 error if it throws', async () => { + MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(new Error(`scary error!`)); + const resp = await server.inject({ + method: 'GET', + url: '/api/upgrade_assistant/status', + }); + + expect(resp.statusCode).toEqual(500); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts new file mode 100644 index 0000000000000..cb776793b288a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { Legacy } from 'kibana'; + +import { getUpgradeAssistantStatus } from '../lib/es_migration_apis'; + +export function registerClusterCheckupRoutes(server: Legacy.Server) { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); + const basePath = server.config().get('server.basePath'); + + server.route({ + path: '/api/upgrade_assistant/status', + method: 'GET', + async handler(request) { + try { + return await getUpgradeAssistantStatus(callWithRequest, request, basePath); + } catch (e) { + if (e.status === 403) { + return Boom.forbidden(e.message); + } + + return Boom.boomify(e, { + statusCode: 500, + }); + } + }, + }); +} diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts new file mode 100644 index 0000000000000..dc687eccc4563 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -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 { Server } from 'hapi'; +import { registerDeprecationLoggingRoutes } from './deprecation_logging'; + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the es_deprecation_logging_apis test. + */ +describe('deprecation logging API', () => { + const callWithRequest = jest.fn(); + const server = new Server(); + server.plugins = { + elasticsearch: { + getCluster: () => ({ callWithRequest } as any), + } as any, + } as any; + + registerDeprecationLoggingRoutes(server); + + describe('GET /api/upgrade_assistant/deprecation_logging', () => { + it('returns isEnabled', async () => { + callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'WARN' } } }); + const resp = await server.inject({ + method: 'GET', + url: '/api/upgrade_assistant/deprecation_logging', + }); + + expect(resp.statusCode).toEqual(200); + expect(JSON.parse(resp.payload)).toEqual({ isEnabled: true }); + }); + + it('returns an error if it throws', async () => { + callWithRequest.mockRejectedValue(new Error(`scary error!`)); + const resp = await server.inject({ + method: 'GET', + url: '/api/upgrade_assistant/deprecation_logging', + }); + + expect(resp.statusCode).toEqual(500); + }); + }); + + describe('PUT /api/upgrade_assistant/deprecation_logging', () => { + it('returns isEnabled', async () => { + callWithRequest.mockResolvedValue({ default: { logger: { deprecation: 'ERROR' } } }); + const resp = await server.inject({ + method: 'GET', + url: '/api/upgrade_assistant/deprecation_logging', + payload: { + isEnabled: false, + }, + }); + + expect(JSON.parse(resp.payload)).toEqual({ isEnabled: false }); + }); + + it('returns an error if it throws', async () => { + callWithRequest.mockRejectedValue(new Error(`scary error!`)); + const resp = await server.inject({ + method: 'PUT', + url: '/api/upgrade_assistant/deprecation_logging', + payload: { + isEnabled: false, + }, + }); + + expect(resp.statusCode).toEqual(500); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts new file mode 100644 index 0000000000000..3f67dafbf2fa6 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import Joi from 'joi'; +import { Legacy } from 'kibana'; + +import { + getDeprecationLoggingStatus, + setDeprecationLogging, +} from '../lib/es_deprecation_logging_apis'; + +export function registerDeprecationLoggingRoutes(server: Legacy.Server) { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); + + server.route({ + path: '/api/upgrade_assistant/deprecation_logging', + method: 'GET', + async handler(request) { + try { + return await getDeprecationLoggingStatus(callWithRequest, request); + } catch (e) { + return Boom.boomify(e, { statusCode: 500 }); + } + }, + }); + + server.route({ + path: '/api/upgrade_assistant/deprecation_logging', + method: 'PUT', + options: { + validate: { + payload: Joi.object({ + isEnabled: Joi.boolean(), + }), + }, + }, + async handler(request) { + try { + const { isEnabled } = request.payload as { isEnabled: boolean }; + return await setDeprecationLogging(callWithRequest, request, isEnabled); + } catch (e) { + return Boom.boomify(e, { statusCode: 500 }); + } + }, + }); +} diff --git a/x-pack/test/functional/apps/status_page/index.ts b/x-pack/test/functional/apps/status_page/index.ts index 86a9f0ad17dc3..77439f79c0050 100644 --- a/x-pack/test/functional/apps/status_page/index.ts +++ b/x-pack/test/functional/apps/status_page/index.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { TestInvoker } from './lib/types'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; // tslint:disable:no-default-export -export default function statusPage({ loadTestFile }: TestInvoker) { +export default function statusPage({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { describe('Status page', function statusPageTestSuite() { this.tags('ciGroup4'); diff --git a/x-pack/test/functional/apps/upgrade_assistant/index.ts b/x-pack/test/functional/apps/upgrade_assistant/index.ts new file mode 100644 index 0000000000000..1d099175782de --- /dev/null +++ b/x-pack/test/functional/apps/upgrade_assistant/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; + +// tslint:disable:no-default-export +export default function upgradeCheckup({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('Upgrade checkup ', function upgradeAssistantTestSuite() { + this.tags('ciGroup4'); + + loadTestFile(require.resolve('./upgrade_assistant')); + }); +} diff --git a/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts new file mode 100644 index 0000000000000..9b12b20fe6023 --- /dev/null +++ b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; + +// tslint:disable:no-default-export +export default function upgradeAssistantFunctionalTests({ + getService, + getPageObjects, +}: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['upgradeAssistant']); + + describe('Upgrade Checkup', () => { + before(async () => await esArchiver.load('empty_kibana')); + after(async () => await esArchiver.unload('empty_kibana')); + + it('allows user to navigate to upgrade checkup', async () => { + await PageObjects.upgradeAssistant.navigateToPage(); + await PageObjects.upgradeAssistant.expectUpgradeAssistant(); + }); + + it('allows user to toggle deprecation logging', async () => { + await PageObjects.upgradeAssistant.navigateToPage(); + await PageObjects.upgradeAssistant.expectDeprecationLoggingLabel('On'); + await PageObjects.upgradeAssistant.toggleDeprecationLogging(); + await PageObjects.upgradeAssistant.expectDeprecationLoggingLabel('Off'); + }); + + it('allows user to open cluster tab', async () => { + await PageObjects.upgradeAssistant.navigateToPage(); + await PageObjects.upgradeAssistant.clickTab('cluster'); + await PageObjects.upgradeAssistant.expectIssueSummary('You have no cluster issues.'); + }); + + it('allows user to open indices tab', async () => { + await PageObjects.upgradeAssistant.navigateToPage(); + await PageObjects.upgradeAssistant.clickTab('indices'); + await PageObjects.upgradeAssistant.expectIssueSummary('You have no index issues.'); + }); + }); +} diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index a41ae41298e49..7d5a599d8e828 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -19,6 +19,7 @@ import { SpaceSelectorPageProvider, InfraHomePageProvider, StatusPagePageProvider, + UpgradeAssistantProvider, } from './page_objects'; import { @@ -77,6 +78,7 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/grok_debugger'), resolve(__dirname, './apps/infra'), resolve(__dirname, './apps/status_page'), + resolve(__dirname, './apps/upgrade_assistant'), ], // define the name and providers for services that should be @@ -127,6 +129,7 @@ export default async function ({ readConfigFile }) { spaceSelector: SpaceSelectorPageProvider, infraHome: InfraHomePageProvider, statusPage: StatusPagePageProvider, + upgradeAssistant: UpgradeAssistantProvider, }, servers: kibanaFunctionalConfig.get('servers'), diff --git a/x-pack/test/functional/page_objects/index.js b/x-pack/test/functional/page_objects/index.js index aed7b75a01968..bffe8583e3dda 100644 --- a/x-pack/test/functional/page_objects/index.js +++ b/x-pack/test/functional/page_objects/index.js @@ -14,3 +14,4 @@ export { ReportingPageProvider } from './reporting_page'; export { SpaceSelectorPageProvider } from './space_selector_page'; export { InfraHomePageProvider } from './infra_home_page'; export { StatusPagePageProvider } from './status_page'; +export { UpgradeAssistantProvider } from './upgrade_assistant'; diff --git a/x-pack/test/functional/page_objects/upgrade_assistant.js b/x-pack/test/functional/page_objects/upgrade_assistant.js new file mode 100644 index 0000000000000..d21b1c944c9c6 --- /dev/null +++ b/x-pack/test/functional/page_objects/upgrade_assistant.js @@ -0,0 +1,74 @@ +/* + * 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 'expect.js'; + +export function UpgradeAssistantProvider({ getService, getPageObjects }) { + const retry = getService('retry'); + const log = getService('log'); + const browser = getService('browser'); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'settings', 'security']); + + class UpgradeAssistant { + async initTests() { + log.debug('UpgradeAssistant:initTests'); + } + + async navigateToPage() { + return await retry.try(async () => { + await PageObjects.common.navigateToApp('settings'); + await testSubjects.click('upgrade_assistant'); + }); + } + + async expectUpgradeAssistant() { + return await retry.try(async () => { + log.debug(`expectUpgradeAssistant()`); + expect(testSubjects.exists('upgradeAssistantRoot')).to.be.true; + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/upgrade_assistant`); + }); + } + + async toggleDeprecationLogging() { + return await retry.try(async () => { + log.debug('toggleDeprecationLogging()'); + await testSubjects.click('upgradeAssistantDeprecationToggle'); + }); + } + + async expectDeprecationLoggingLabel(labelText) { + return await retry.try(async () => { + log.debug('expectDeprecationLoggingLabel()'); + const toggle = await testSubjects.find('upgradeAssistantDeprecationToggle'); + const div = await toggle.getProperty('parentElement'); + const label = await div.findByCssSelector('label'); + expect(await label.getVisibleText()).to.eql(labelText); + }); + } + + async clickTab(tabId) { + return await retry.try(async () => { + log.debug('clickTab()'); + const tab = await find.byCssSelector(`.euiTabs .euiTab#${tabId}`); + await tab.click(); + }); + } + + async expectIssueSummary(summary) { + return await retry.try(async () => { + log.debug('expectIssueSummary()'); + const summaryEl = await testSubjects.find('upgradeAssistantIssueSummary'); + const summaryElText = await summaryEl.getVisibleText(); + expect(summaryElText).to.eql(summary); + }); + } + } + + return new UpgradeAssistant(); +}