From b09e3a622fe5f3f6a1a6d431e81afffac7119d27 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Fri, 14 Jun 2019 14:21:53 -0400 Subject: [PATCH] [Monitoring] Metricbeat migration flyout and instructions (#35228) * Initial attempt at a reactor of how this works * Enter and exiting setup mode with migration buttons working * Adding monitoring url step back in and some small cleanup * Elasticsearch steps * Add missing file * Better organization here * Remove this debug logic * Clean up * PR feedback * Add in monospacing * Persist monitoring url in local storage * Rework the steps * Change node to server, and add missing files * Fix linting issues * Fix api integration tests * PR feedback * Pass down if the product is the "primary" or not, then use that to show certain warnings in the UI (just supported for Kibana right now) * Elasticsearch migration will work slightly differently in that all nodes must be partially migrated before we can disable internal collection * More PR feedback * PR feedback * Better links * Fix tests * This should open in a new tab * PR feedback * Design and PR feedback * Fix these tests * PR feedback * Remove debug * PR feedback * Update the import path * Update this import path too * PR feedback * Fix i18n --- x-pack/plugins/monitoring/common/constants.js | 2 + .../nodes/__tests__/nodes.test.js | 1 + .../components/elasticsearch/nodes/nodes.js | 40 ++- .../components/kibana/instances/index.js | 7 + .../components/kibana/instances/instances.js | 171 +++++++++ .../metricbeat_migration/constants.js | 9 + .../metricbeat_migration/flyout/flyout.js | 328 ++++++++++++++++++ .../metricbeat_migration/flyout/index.js | 7 + .../components/monospace/index.js | 8 + .../components/monospace/monospace.js | 12 + .../common_elasticsearch_instructions.js | 10 + ...isable_internal_collection_instructions.js | 195 +++++++++++ .../enable_metricbeat_instructions.js | 271 +++++++++++++++ .../instruction_steps/elasticsearch/index.js | 8 + .../get_instruction_steps.js | 38 ++ .../instruction_steps/index.js | 7 + .../kibana/common_kibana_instructions.js | 10 + ...isable_internal_collection_instructions.js | 235 +++++++++++++ .../kibana/enable_metricbeat_instructions.js | 267 ++++++++++++++ .../instruction_steps/kibana/index.js | 8 + .../public/components/renderers/index.js | 7 + .../public/components/renderers/setup_mode.js | 68 ++++ .../public/components/table/eui_table.js | 94 ++++- .../public/directives/main/index.html | 10 +- .../public/lib/ensure_minimum_time.js | 45 +++ .../public/lib/ensure_minimum_time.test.js | 34 ++ .../monitoring/public/lib/setup_mode.js | 126 +++++++ .../public/views/elasticsearch/nodes/index.js | 28 +- .../public/views/kibana/instances/index.js | 176 ++-------- .../setup/collection/get_collection_status.js | 45 ++- .../setup/collection/fixtures/detect_apm.json | 18 +- .../collection/fixtures/detect_beats.json | 15 +- .../fixtures/detect_beats_management.json | 18 +- .../collection/fixtures/detect_logstash.json | 18 +- .../fixtures/detect_logstash_management.json | 18 +- .../fixtures/es_and_kibana_exclusive_mb.json | 9 + .../collection/fixtures/es_and_kibana_mb.json | 9 + .../fixtures/kibana_exclusive_mb.json | 9 + .../setup/collection/fixtures/kibana_mb.json | 9 + 39 files changed, 2200 insertions(+), 190 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/components/kibana/instances/index.js create mode 100644 x-pack/plugins/monitoring/public/components/kibana/instances/instances.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/constants.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/index.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/index.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/monospace.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/common_elasticsearch_instructions.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/disable_internal_collection_instructions.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/enable_metricbeat_instructions.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/index.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/index.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/common_kibana_instructions.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/disable_internal_collection_instructions.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/enable_metricbeat_instructions.js create mode 100644 x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/index.js create mode 100644 x-pack/plugins/monitoring/public/components/renderers/index.js create mode 100644 x-pack/plugins/monitoring/public/components/renderers/setup_mode.js create mode 100644 x-pack/plugins/monitoring/public/lib/ensure_minimum_time.js create mode 100644 x-pack/plugins/monitoring/public/lib/ensure_minimum_time.test.js create mode 100644 x-pack/plugins/monitoring/public/lib/setup_mode.js diff --git a/x-pack/plugins/monitoring/common/constants.js b/x-pack/plugins/monitoring/common/constants.js index bd6dd89ee395..56450cf614db 100644 --- a/x-pack/plugins/monitoring/common/constants.js +++ b/x-pack/plugins/monitoring/common/constants.js @@ -165,6 +165,8 @@ export const INDEX_PATTERN_FILEBEAT = 'filebeat-*'; // This is the unique token that exists in monitoring indices collected by metricbeat export const METRICBEAT_INDEX_NAME_UNIQUE_TOKEN = '-mb-'; +// We use this for metricbeat migration to identify specific products that we do not have constants for +export const ELASTICSEARCH_CUSTOM_ID = 'elasticsearch'; /** * The id of the infra source owned by the monitoring plugin. */ diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/nodes.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/nodes.test.js index 8ab69e13bdc0..54aa88026453 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/nodes.test.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/nodes.test.js @@ -75,6 +75,7 @@ const getProps = (field) => { upTime: 28056934, version: ['8.0.0'] }, + setupMode: {}, nodes, sorting: { sort: { field: 'name', direction: 'asc' } diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 3e6411b31e9a..a5db041d9be3 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { NodeStatusIcon } from '../node'; import { extractIp } from '../../../lib/extract_ip'; // TODO this is only used for elasticsearch nodes summary / node detail, so it should be moved to components/elasticsearch/nodes/lib import { ClusterStatus } from '../cluster_status'; @@ -18,6 +18,8 @@ import { EuiPageContent, EuiPageBody, EuiPanel, + EuiCallOut, + EuiButton, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -207,7 +209,37 @@ const getColumns = showCgroupMetricsElasticsearch => { export function ElasticsearchNodes({ clusterStatus, nodes, showCgroupMetricsElasticsearch, ...props }) { const columns = getColumns(showCgroupMetricsElasticsearch); - const { sorting, pagination, onTableChange } = props; + const { sorting, pagination, onTableChange, setupMode } = props; + + let disableInternalCollectionForMigrationMessage = null; + if (setupMode.data) { + if (setupMode.data.totalUniquePartiallyMigratedCount === setupMode.data.totalUniqueInstanceCount) { + disableInternalCollectionForMigrationMessage = ( + + +

+ {i18n.translate('xpack.monitoring.elasticsearch.nodes.metribeatMigration.disableInternalCollectionDescription', { + defaultMessage: `All of your Elasticsearch servers are monitored using Metricbeat, + but you need to disable internal collection to finish the migration.` + })} +

+ setupMode.openFlyout()} size="s" color="warning" fill> + {i18n.translate('xpack.monitoring.elasticsearch.nodes.metribeatMigration.disableInternalCollectionMigrationButtonLabel', { + defaultMessage: 'Disable and finish migration' + })} + +
+ +
+ ); + } + } return ( @@ -216,6 +248,7 @@ export function ElasticsearchNodes({ clusterStatus, nodes, showCgroupMetricsElas + {disableInternalCollectionForMigrationMessage} { + const columns = [ + { + name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', { + defaultMessage: 'Name' + }), + field: 'name', + render: (name, kibana) => ( + { + scope.$evalAsync(() => { + kbnUrl.changePath(`/kibana/instances/${kibana.kibana.uuid}`); + }); + }} + data-test-subj={`kibanaLink-${name}`} + > + { name } + + ) + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', { + defaultMessage: 'Status' + }), + field: 'status', + render: (status, kibana) => ( +
+   + { !kibana.availability ? ( + + ) : capitalize(status) } +
+ ) + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.loadAverageColumnTitle', { + defaultMessage: 'Load Average' + }), + field: 'os.load.1m', + render: value => ( + + {formatMetric(value, '0.00')} + + ) + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.memorySizeColumnTitle', { + defaultMessage: 'Memory Size' + }), + field: 'process.memory.resident_set_size_in_bytes', + render: value => ( + + {formatNumber(value, 'byte')} + + ) + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.requestsColumnTitle', { + defaultMessage: 'Requests' + }), + field: 'requests.total', + render: value => ( + + {formatNumber(value, 'int_commas')} + + ) + }, + { + name: i18n.translate('xpack.monitoring.kibana.listing.responseTimeColumnTitle', { + defaultMessage: 'Response Times' + }), + // It is possible this does not exist through MB collection + field: 'response_times.average', + render: (value, kibana) => { + if (!value) { + return null; + } + + return ( +
+
+ { (formatNumber(value, 'int_commas') + ' ms avg') } +
+
+ { formatNumber(kibana.response_times.max, 'int_commas') } ms max +
+
+ ); + } + } + ]; + + return columns; +}; + +export class KibanaInstances extends PureComponent { + render() { + const { + instances, + clusterStatus, + angular, + setupMode, + sorting, + pagination, + onTableChange + } = this.props; + + const dataFlattened = instances.map(item => ({ + ...item, + name: item.kibana.name, + status: item.kibana.status, + })); + + return ( + + + + + + + + + + + + ); + } +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/constants.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/constants.js new file mode 100644 index 000000000000..3c01b1c5395f --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/constants.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const INSTRUCTION_STEP_SET_MONITORING_URL = 'setMonitoringUrl'; +export const INSTRUCTION_STEP_ENABLE_METRICBEAT = 'enableMetricbeat'; +export const INSTRUCTION_STEP_DISABLE_INTERNAL = 'disableInternal'; diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js new file mode 100644 index 000000000000..f15d6c363ab5 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/flyout.js @@ -0,0 +1,328 @@ +/* + * 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, Component } from 'react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiForm, + EuiFormRow, + EuiFieldText, + EuiButton, + EuiSteps, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiLink, + EuiText, +} from '@elastic/eui'; +import { getInstructionSteps } from '../instruction_steps'; +import { Storage } from '../../../../../../../src/legacy/ui/public/storage/storage'; +import { STORAGE_KEY, ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants'; +import { ensureMinimumTime } from '../../../lib/ensure_minimum_time'; +import { i18n } from '@kbn/i18n'; +import { + INSTRUCTION_STEP_SET_MONITORING_URL, + INSTRUCTION_STEP_ENABLE_METRICBEAT, + INSTRUCTION_STEP_DISABLE_INTERNAL +} from '../constants'; +import { KIBANA_SYSTEM_ID } from '../../../../../telemetry/common/constants'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +const storage = new Storage(window.localStorage); +const ES_MONITORING_URL_KEY = `${STORAGE_KEY}.mb_migration.esMonitoringUrl`; +const AUTO_CHECK_INTERVAL_IN_MS = 5000; +const DEFAULT_ES_MONITORING_URL = 'http://localhost:9200'; + +export class Flyout extends Component { + constructor(props) { + super(props); + + let esMonitoringUrl = storage.get(ES_MONITORING_URL_KEY); + if (!esMonitoringUrl) { + esMonitoringUrl = props.monitoringHosts ? props.monitoringHosts[0] : DEFAULT_ES_MONITORING_URL; + } + + this.checkInterval = null; + + let activeStep = INSTRUCTION_STEP_SET_MONITORING_URL; + if (props.product && props.product.isPartiallyMigrated) { + activeStep = INSTRUCTION_STEP_DISABLE_INTERNAL; + } + + this.state = { + activeStep, + esMonitoringUrl, + checkedStatusByStep: { + [INSTRUCTION_STEP_ENABLE_METRICBEAT]: false, + [INSTRUCTION_STEP_DISABLE_INTERNAL]: false, + }, + checkingMigrationStatus: false, + }; + } + + componentWillUpdate(_nextProps, nextState) { + // We attempt to provide a better UX for the user by automatically rechecking + // the status of their current step, once they have initiated a check manually. + // The logic here aims to remove the recheck one they have moved on from the + // step + + const thisActiveStep = this.state.activeStep; + const nextActiveStep = nextState.activeStep; + const nextEnableMbStatus = nextState.checkedStatusByStep[INSTRUCTION_STEP_ENABLE_METRICBEAT]; + const nowEnableMbStatus = this.state.checkedStatusByStep[INSTRUCTION_STEP_ENABLE_METRICBEAT]; + const nextDisableInternalStatus = nextState.checkedStatusByStep[INSTRUCTION_STEP_DISABLE_INTERNAL]; + const nowDisableInternalStatus = this.state.checkedStatusByStep[INSTRUCTION_STEP_DISABLE_INTERNAL]; + + const setupInterval = (nextEnableMbStatus && !nowEnableMbStatus) || (nextDisableInternalStatus && !nowDisableInternalStatus); + const removeInterval = thisActiveStep !== nextActiveStep; + if (removeInterval) { + clearInterval(this.checkInterval); + this.clearInterval = null; + } + + if (setupInterval) { + this.checkInterval = setInterval(async () => { + await this.checkForMigrationStatus(); + }, AUTO_CHECK_INTERVAL_IN_MS); + } + } + + componentWillUnmount() { + clearInterval(this.checkInterval); + } + + checkForMigrationStatus = async () => { + this.setState({ checkingMigrationStatus: true }); + await ensureMinimumTime(this.props.updateProduct(), 1000); + this.setState(state => ({ + ...state, + checkingMigrationStatus: false, + checkedStatusByStep: { + ...state.checkedStatusByStep, + [this.state.activeStep]: true, + } + })); + } + + setEsMonitoringUrl = esMonitoringUrl => { + storage.set(ES_MONITORING_URL_KEY, esMonitoringUrl); + this.setState({ esMonitoringUrl }); + } + + renderActiveStep() { + const { product, productName, onClose, meta } = this.props; + const { + activeStep, + esMonitoringUrl, + checkedStatusByStep, + checkingMigrationStatus, + } = this.state; + + switch (activeStep) { + case INSTRUCTION_STEP_SET_MONITORING_URL: + return ( + + + this.setEsMonitoringUrl(e.target.value)} + /> + + + ); + case INSTRUCTION_STEP_ENABLE_METRICBEAT: + case INSTRUCTION_STEP_DISABLE_INTERNAL: + const instructionSteps = getInstructionSteps(productName, product, activeStep, meta, { + doneWithMigration: onClose, + esMonitoringUrl, + checkForMigrationStatus: this.checkForMigrationStatus, + checkingMigrationStatus, + hasCheckedStatus: checkedStatusByStep[activeStep], + autoCheckIntervalInMs: AUTO_CHECK_INTERVAL_IN_MS, + }); + + return ( + + + + ); + } + + return null; + } + + renderActiveStepNextButton() { + const { product, productName } = this.props; + const { activeStep, esMonitoringUrl } = this.state; + + // It is possible that, during the migration steps, products are not reporting + // monitoring data for a period of time outside the window of our server-side check + // and this is most likely temporary so we want to be defensive and not error out + // and hopefully wait for the next check and this state will be self-corrected. + if (!product) { + return null; + } + + let willDisableDoneButton = !product.isFullyMigrated; + let willShowNextButton = activeStep !== INSTRUCTION_STEP_DISABLE_INTERNAL; + + if (activeStep === INSTRUCTION_STEP_ENABLE_METRICBEAT && productName === ELASTICSEARCH_CUSTOM_ID) { + willShowNextButton = false; + willDisableDoneButton = !product.isPartiallyMigrated; + } + + if (willShowNextButton) { + let isDisabled = false; + let nextStep = null; + if (activeStep === INSTRUCTION_STEP_SET_MONITORING_URL) { + isDisabled = !esMonitoringUrl || esMonitoringUrl.length === 0; + if (product.isPartiallyMigrated || product.isFullyMigrated) { + nextStep = INSTRUCTION_STEP_DISABLE_INTERNAL; + } + else { + nextStep = INSTRUCTION_STEP_ENABLE_METRICBEAT; + } + } + else if (activeStep === INSTRUCTION_STEP_ENABLE_METRICBEAT) { + isDisabled = !product.isPartiallyMigrated && !product.isFullyMigrated; + nextStep = INSTRUCTION_STEP_DISABLE_INTERNAL; + } + + return ( + this.setState({ activeStep: nextStep })} + > + {i18n.translate('xpack.monitoring.metricbeatMigration.flyout.nextButtonLabel', { + defaultMessage: 'Next' + })} + + ); + } + + return ( + + {i18n.translate('xpack.monitoring.metricbeatMigration.flyout.doneButtonLabel', { + defaultMessage: 'Done' + })} + + ); + } + + getDocumentationTitle() { + const { productName } = this.props; + + let documentationUrl = null; + if (productName === KIBANA_SYSTEM_ID) { + documentationUrl = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/monitoring-metricbeat.html`; + } + else if (productName === ELASTICSEARCH_CUSTOM_ID) { + documentationUrl = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/configuring-metricbeat.html`; + } + + if (!documentationUrl) { + return null; + } + + return ( + + + Read more about this migration. + + + ); + } + + render() { + const { onClose, instance, productName } = this.props; + + let instanceType = null; + let instanceName = instance ? instance.name : null; + + if (productName === KIBANA_SYSTEM_ID) { + instanceType = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.kibanaInstance', { + defaultMessage: 'instance', + }); + } + else if (productName === ELASTICSEARCH_CUSTOM_ID) { + if (instance) { + instanceType = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.elasticsearchNode', { + defaultMessage: 'node', + }); + } + else { + instanceName = i18n.translate('xpack.monitoring.metricbeatMigration.flyout.elasticsearchNodesTitle', { + defaultMessage: 'Elasticsearch nodes', + }); + } + } + + return ( + + + +

+ {i18n.translate('xpack.monitoring.metricbeatMigration.flyout.flyoutTitle', { + defaultMessage: 'Migrate {instanceName} {instanceType} to Metricbeat', + values: { + instanceName, + instanceType + } + })} +

+
+ {this.getDocumentationTitle()} +
+ + {this.renderActiveStep()} + + + + + + {i18n.translate('xpack.monitoring.metricbeatMigration.flyout.closeButtonLabel', { + defaultMessage: 'Close' + })} + + + + {this.renderActiveStepNextButton()} + + + +
+ ); + } +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/index.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/index.js new file mode 100644 index 000000000000..ac90a57df6c5 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/flyout/index.js @@ -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 { Flyout } from './flyout'; diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/index.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/index.js new file mode 100644 index 000000000000..b6c9bc24f756 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/index.js @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + +export { Monospace } from './monospace'; diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/monospace.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/monospace.js new file mode 100644 index 000000000000..5f576148244f --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/components/monospace/monospace.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + + +import React from 'react'; + +export const Monospace = ({ children }) => ( + {children} +); diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/common_elasticsearch_instructions.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/common_elasticsearch_instructions.js new file mode 100644 index 000000000000..215de10c68b8 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/common_elasticsearch_instructions.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +export const statusTitle = i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.statusTitle', { + defaultMessage: `Migration status` +}); diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/disable_internal_collection_instructions.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/disable_internal_collection_instructions.js new file mode 100644 index 000000000000..91aaca02de05 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/disable_internal_collection_instructions.js @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { formatTimestampToDuration } from '../../../../../common'; +import { CALCULATE_DURATION_SINCE } from '../../../../../common/constants'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_elasticsearch_instructions'; + +export function getElasticsearchInstructionsForDisablingInternalCollection(product, meta, { + checkForMigrationStatus, + checkingMigrationStatus, + hasCheckedStatus, + autoCheckIntervalInMs, +}) { + const disableInternalCollectionStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollectionTitle', { + defaultMessage: 'Disable internal collection of Elasticsearch monitoring metrics' + }), + children: ( + + +

+ xpack.monitoring.elasticsearch.collection.enabled + ) + }} + /> +

+
+ + + {`PUT _cluster/settings +{ + "persistent": { + "xpack.monitoring.elasticsearch.collection.enabled": false + } +} + `} + +
+ ) + }; + + let migrationStatusStep = null; + if (!product || !product.isFullyMigrated) { + let status = null; + if (hasCheckedStatus) { + let lastInternallyCollectedMessage = ''; + // It is possible that, during the migration steps, products are not reporting + // monitoring data for a period of time outside the window of our server-side check + // and this is most likely temporary so we want to be defensive and not error out + // and hopefully wait for the next check and this state will be self-corrected. + if (product) { + const lastInternallyCollectedTimestamp = product.lastInternallyCollectedTimestamp || product.lastTimestamp; + const secondsSinceLastInternalCollectionLabel = + formatTimestampToDuration(lastInternallyCollectedTimestamp, CALCULATE_DURATION_SINCE); + lastInternallyCollectedMessage = (); + } + + status = ( + + + +

+ +

+

+ {lastInternallyCollectedMessage} +

+
+
+ ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollection.checkingStatusButtonLabel', + { + defaultMessage: 'Checking...' + } + ); + } else { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollection.checkStatusButtonLabel', + { + defaultMessage: 'Check' + } + ); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate( + 'xpack.monitoring.metricbeatMigration.elasticsearchInstructions.disableInternalCollection.statusDescription', + { + defaultMessage: 'Check that no documents are coming from internal collection.' + } + )} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + disableInternalCollectionStep, + migrationStatusStep + ]; +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/enable_metricbeat_instructions.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/enable_metricbeat_instructions.js new file mode 100644 index 000000000000..a8e2b81ec724 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/enable_metricbeat_instructions.js @@ -0,0 +1,271 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_elasticsearch_instructions'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +export function getElasticsearchInstructionsForEnablingMetricbeat(product, _meta, { + esMonitoringUrl, + hasCheckedStatus, + checkingMigrationStatus, + checkForMigrationStatus, + autoCheckIntervalInMs +}) { + const securitySetup = ( + + + + + {` `} + + + + + ) + }} + /> + + )} + /> + + ); + + const installMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.installMetricbeatTitle', { + defaultMessage: 'Install Metricbeat on the same server as Elasticsearch' + }), + children: ( + +

+ + + +

+
+ ) + }; + + + const enableMetricbeatModuleStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.enableMetricbeatModuleTitle', { + defaultMessage: 'Enable and configure the Elasticsearch x-pack module in Metricbeat' + }), + children: ( + + + metricbeat modules enable elasticsearch-xpack + + + +

+ modules.d/elasticsearch-xpack.yml + ), + url: ( + http://localhost:9200 + ) + }} + /> +

+
+ {securitySetup} +
+ ) + }; + + const configureMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.configureMetricbeatTitle', { + defaultMessage: 'Configure Metricbeat to send to the monitoring cluster' + }), + children: ( + + + metricbeat.yml + ) + }} + /> + + + + {`output.elasticsearch: + hosts: ["${esMonitoringUrl}"] ## Monitoring cluster + + # Optional protocol and basic auth credentials. + #protocol: "https" + #username: "elastic" + #password: "changeme" +`} + + {securitySetup} + + + ) + }; + + const startMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.startMetricbeatTitle', { + defaultMessage: 'Start Metricbeat' + }), + children: ( + +

+ + + +

+
+ ) + }; + + let migrationStatusStep = null; + if (product.isInternalCollector) { + let status = null; + if (hasCheckedStatus) { + status = ( + + + + + ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.checkingStatusButtonLabel', { + defaultMessage: 'Checking for data...' + }); + } else { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.checkStatusButtonLabel', { + defaultMessage: 'Check for data' + }); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate('xpack.monitoring.metricbeatMigration.elasticsearchInstructions.statusDescription', { + defaultMessage: 'Check that data is received from the Metricbeat' + })} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else if (product.isPartiallyMigrated || product.isFullyMigrated) { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + installMetricbeatStep, + enableMetricbeatModuleStep, + configureMetricbeatStep, + startMetricbeatStep, + migrationStatusStep + ]; +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/index.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/index.js new file mode 100644 index 000000000000..a619cb420f8f --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/elasticsearch/index.js @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { getElasticsearchInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions'; +export { getElasticsearchInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions'; diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js new file mode 100644 index 000000000000..c12df364092a --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/get_instruction_steps.js @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + getKibanaInstructionsForEnablingMetricbeat, + getKibanaInstructionsForDisablingInternalCollection, +} from './kibana'; +import { + getElasticsearchInstructionsForEnablingMetricbeat, + getElasticsearchInstructionsForDisablingInternalCollection +} from './elasticsearch'; +import { + INSTRUCTION_STEP_ENABLE_METRICBEAT, + INSTRUCTION_STEP_DISABLE_INTERNAL +} from '../constants'; + +export function getInstructionSteps(productName, product, step, meta, opts) { + switch (productName) { + case 'kibana': + if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { + return getKibanaInstructionsForEnablingMetricbeat(product, meta, opts); + } + if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { + return getKibanaInstructionsForDisablingInternalCollection(product, meta, opts); + } + case 'elasticsearch': + if (step === INSTRUCTION_STEP_ENABLE_METRICBEAT) { + return getElasticsearchInstructionsForEnablingMetricbeat(product, meta, opts); + } + if (step === INSTRUCTION_STEP_DISABLE_INTERNAL) { + return getElasticsearchInstructionsForDisablingInternalCollection(product, meta, opts); + } + } + return []; +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/index.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/index.js new file mode 100644 index 000000000000..9ab40eacc2e5 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './get_instruction_steps'; diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/common_kibana_instructions.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/common_kibana_instructions.js new file mode 100644 index 000000000000..c4cf1c0e9102 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/common_kibana_instructions.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +export const statusTitle = i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.statusTitle', { + defaultMessage: `Migration status` +}); diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/disable_internal_collection_instructions.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/disable_internal_collection_instructions.js new file mode 100644 index 000000000000..e4219fe47c3c --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/disable_internal_collection_instructions.js @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { formatTimestampToDuration } from '../../../../../common'; +import { CALCULATE_DURATION_SINCE } from '../../../../../common/constants'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_kibana_instructions'; + +export function getKibanaInstructionsForDisablingInternalCollection(product, meta, { + checkForMigrationStatus, + checkingMigrationStatus, + hasCheckedStatus, + autoCheckIntervalInMs, +}) { + let restartWarning = null; + if (product.isPrimary) { + restartWarning = ( + + + + +

+ +

+
+
+
+ ); + } + + const disableInternalCollectionStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.title', { + defaultMessage: 'Disable internal collection of Kibana monitoring metrics' + }), + children: ( + + +

+ kibana.yml + ) + }} + /> +

+
+ + + xpack.monitoring.kibana.collection.enabled: false + + + +

+ xpack.monitoring.enabled + ), + defaultValue: ( + true + ) + }} + /> +

+
+ {restartWarning} +
+ ) + }; + + let migrationStatusStep = null; + if (!product || !product.isFullyMigrated) { + let status = null; + if (hasCheckedStatus) { + let lastInternallyCollectedMessage = ''; + // It is possible that, during the migration steps, products are not reporting + // monitoring data for a period of time outside the window of our server-side check + // and this is most likely temporary so we want to be defensive and not error out + // and hopefully wait for the next check and this state will be self-corrected. + if (product) { + const lastInternallyCollectedTimestamp = product.lastInternallyCollectedTimestamp || product.lastTimestamp; + const secondsSinceLastInternalCollectionLabel = + formatTimestampToDuration(lastInternallyCollectedTimestamp, CALCULATE_DURATION_SINCE); + lastInternallyCollectedMessage = (); + } + + status = ( + + + +

+ +

+

+ {lastInternallyCollectedMessage} +

+
+
+ ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.checkingStatusButtonLabel', + { + defaultMessage: 'Checking...' + } + ); + } else { + buttonLabel = i18n.translate( + 'xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.checkStatusButtonLabel', + { + defaultMessage: 'Check' + } + ); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate( + 'xpack.monitoring.metricbeatMigration.kibanaInstructions.disableInternalCollection.statusDescription', + { + defaultMessage: 'Check that no documents are coming from internal collection.' + } + )} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + disableInternalCollectionStep, + migrationStatusStep + ]; +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/enable_metricbeat_instructions.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/enable_metricbeat_instructions.js new file mode 100644 index 000000000000..f8fa01087d12 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/enable_metricbeat_instructions.js @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import React, { Fragment } from 'react'; +import { + EuiSpacer, + EuiCodeBlock, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiCallOut, + EuiText +} from '@elastic/eui'; +import { Monospace } from '../components/monospace'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { statusTitle } from './common_kibana_instructions'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +export function getKibanaInstructionsForEnablingMetricbeat(product, _meta, { + esMonitoringUrl, + hasCheckedStatus, + checkingMigrationStatus, + checkForMigrationStatus, + autoCheckIntervalInMs +}) { + const securitySetup = ( + + + + + {` `} + + + + + ) + }} + /> + + )} + /> + + ); + const installMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.installMetricbeatTitle', { + defaultMessage: 'Install Metricbeat on the same server as Kibana' + }), + children: ( + +

+ + + +

+
+ ) + }; + + const enableMetricbeatModuleStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.enableMetricbeatModuleTitle', { + defaultMessage: 'Enable and configure the Kibana x-pack module in Metricbeat' + }), + children: ( + + + metricbeat modules enable kibana-xpack + + + +

+ hosts + ), + file: ( + modules.d/kibana-xpack.yml + ) + }} + /> +

+
+ {securitySetup} +
+ ) + }; + + const configureMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.configureMetricbeatTitle', { + defaultMessage: 'Configure Metricbeat to send to the monitoring cluster' + }), + children: ( + + + metricbeat.yml + ) + }} + /> + + + + {`output.elasticsearch: + hosts: ["${esMonitoringUrl}"] ## Monitoring cluster + + # Optional protocol and basic auth credentials. + #protocol: "https" + #username: "elastic" + #password: "changeme" +`} + + {securitySetup} + + + ) + }; + + const startMetricbeatStep = { + title: i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.startMetricbeatTitle', { + defaultMessage: 'Start Metricbeat' + }), + children: ( + +

+ + + +

+
+ ) + }; + + let migrationStatusStep = null; + if (product.isInternalCollector) { + let status = null; + if (hasCheckedStatus) { + status = ( + + + + + ); + } + + let buttonLabel; + if (checkingMigrationStatus) { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.checkingStatusButtonLabel', { + defaultMessage: 'Checking for data...' + }); + } else { + buttonLabel = i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.checkStatusButtonLabel', { + defaultMessage: 'Check for data' + }); + } + + migrationStatusStep = { + title: statusTitle, + status: 'incomplete', + children: ( + + + + +

+ {i18n.translate('xpack.monitoring.metricbeatMigration.kibanaInstructions.statusDescription', { + defaultMessage: 'Check that data is received from the Metricbeat' + })} +

+
+
+ + + {buttonLabel} + + +
+ {status} +
+ ) + }; + } + else if (product.isPartiallyMigrated || product.isFullyMigrated) { + migrationStatusStep = { + title: statusTitle, + status: 'complete', + children: ( + +

+ +

+
+ ) + }; + } + + return [ + installMetricbeatStep, + enableMetricbeatModuleStep, + configureMetricbeatStep, + startMetricbeatStep, + migrationStatusStep + ]; +} diff --git a/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/index.js b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/index.js new file mode 100644 index 000000000000..674459250243 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/metricbeat_migration/instruction_steps/kibana/index.js @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { getKibanaInstructionsForDisablingInternalCollection } from './disable_internal_collection_instructions'; +export { getKibanaInstructionsForEnablingMetricbeat } from './enable_metricbeat_instructions'; diff --git a/x-pack/plugins/monitoring/public/components/renderers/index.js b/x-pack/plugins/monitoring/public/components/renderers/index.js new file mode 100644 index 000000000000..52feadf490dc --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/renderers/index.js @@ -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 { SetupModeRenderer } from './setup_mode'; diff --git a/x-pack/plugins/monitoring/public/components/renderers/setup_mode.js b/x-pack/plugins/monitoring/public/components/renderers/setup_mode.js new file mode 100644 index 000000000000..17f877267549 --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/renderers/setup_mode.js @@ -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 React from 'react'; +import { setAngularState, getSetupModeState, initSetupModeState, updateSetupModeData } from '../../lib/setup_mode'; +import { Flyout } from '../metricbeat_migration/flyout'; +import { ELASTICSEARCH_CUSTOM_ID } from '../../../common/constants'; + +export class SetupModeRenderer extends React.Component { + state = { + renderState: false, + isFlyoutOpen: false, + instance: null, + } + + componentWillMount() { + const { scope, injector } = this.props; + setAngularState(scope, injector); + initSetupModeState(() => this.setState({ renderState: true })); + } + + getFlyout(data, meta) { + const { productName } = this.props; + const { isFlyoutOpen, instance } = this.state; + if (!data || !isFlyoutOpen) { + return null; + } + + let product = instance ? data.byUuid[instance.uuid] : null; + const isFullyOrPartiallyMigrated = data.totalUniquePartiallyMigratedCount === data.totalUniqueInstanceCount + || data.totalUniqueFullyMigratedCount === data.totalUniqueInstanceCount; + if (!product && productName === ELASTICSEARCH_CUSTOM_ID && isFullyOrPartiallyMigrated) { + product = Object.values(data.byUuid)[0]; + } + + return ( + this.setState({ isFlyoutOpen: false })} + productName={productName} + product={product} + meta={meta} + instance={instance} + updateProduct={updateSetupModeData} + /> + ); + } + + render() { + const { render, productName } = this.props; + const setupModeState = getSetupModeState(); + const data = setupModeState.data ? setupModeState.data[productName] : null; + const meta = setupModeState.data ? setupModeState.data._meta : null; + + return render({ + setupMode: { + data, + enabled: setupModeState.enabled, + productName, + updateSetupModeData, + openFlyout: (instance) => this.setState({ isFlyoutOpen: true, instance }), + closeFlyout: () => this.setState({ isFlyoutOpen: false }), + }, + flyoutComponent: this.getFlyout(data, meta), + }); + } +} diff --git a/x-pack/plugins/monitoring/public/components/table/eui_table.js b/x-pack/plugins/monitoring/public/components/table/eui_table.js index cfe952e249d8..89fa530547ad 100644 --- a/x-pack/plugins/monitoring/public/components/table/eui_table.js +++ b/x-pack/plugins/monitoring/public/components/table/eui_table.js @@ -5,9 +5,15 @@ */ import React from 'react'; +import { get } from 'lodash'; import { - EuiInMemoryTable + EuiInMemoryTable, + EuiBadge, + EuiButtonEmpty, + EuiHealth } from '@elastic/eui'; +import { ELASTICSEARCH_CUSTOM_ID } from '../../../common/constants'; +import { i18n } from '@kbn/i18n'; export class EuiMonitoringTable extends React.PureComponent { render() { @@ -15,6 +21,9 @@ export class EuiMonitoringTable extends React.PureComponent { rows: items, search = {}, columns: _columns, + setupMode, + uuidField, + nameField, ...props } = this.props; @@ -37,6 +46,89 @@ export class EuiMonitoringTable extends React.PureComponent { return column; }); + if (setupMode && setupMode.enabled) { + columns.push({ + name: i18n.translate('xpack.monitoring.euiTable.setupStatusTitle', { + defaultMessage: 'Setup Status' + }), + field: uuidField, + render: (uuid) => { + const list = get(setupMode, 'data.byUuid', {}); + const status = list[uuid] || {}; + + let statusBadge = null; + if (status.isInternalCollector) { + statusBadge = ( + + {i18n.translate('xpack.monitoring.euiTable.isInternalCollectorLabel', { + defaultMessage: 'Internal collection' + })} + + ); + } + else if (status.isPartiallyMigrated) { + statusBadge = ( + + {i18n.translate('xpack.monitoring.euiTable.isPartiallyMigratedLabel', { + defaultMessage: 'Internal collection and Metricbeat collection' + })} + + ); + } + else if (status.isFullyMigrated) { + statusBadge = ( + + {i18n.translate('xpack.monitoring.euiTable.isFullyMigratedLabel', { + defaultMessage: 'Metricbeat collection' + })} + + ); + } + else { + statusBadge = i18n.translate('xpack.monitoring.euiTable.migrationStatusUnknown', { + defaultMessage: 'N/A' + }); + } + + return statusBadge; + } + }); + + columns.push({ + name: i18n.translate('xpack.monitoring.euiTable.setupActionTitle', { + defaultMessage: 'Setup Action' + }), + field: uuidField, + render: (uuid, product) => { + const list = get(setupMode, 'data.byUuid', {}); + const status = list[uuid] || {}; + const instance = { + uuid: get(product, uuidField), + name: get(product, nameField), + }; + + // Migrating from partially to fully for Elasticsearch involves changing a cluster + // setting which impacts all nodes in the cluster, which we have a separate callout + // for. Since it does not make sense to do this on a per node basis, show nothing here + if (status.isPartiallyMigrated && setupMode.productName === ELASTICSEARCH_CUSTOM_ID) { + return null; + } + + if (status.isInternalCollector || status.isPartiallyMigrated) { + return ( + setupMode.openFlyout(instance)}> + {i18n.translate('xpack.monitoring.euiTable.migrateButtonLabel', { + defaultMessage: 'Migrate' + })} + + ); + } + + return null; + } + }); + } + return (
- +
@@ -204,10 +204,10 @@ i18n-default-message="Advanced" > -
diff --git a/x-pack/plugins/monitoring/public/lib/ensure_minimum_time.js b/x-pack/plugins/monitoring/public/lib/ensure_minimum_time.js new file mode 100644 index 000000000000..28275622c4ff --- /dev/null +++ b/x-pack/plugins/monitoring/public/lib/ensure_minimum_time.js @@ -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. + */ + + +/** + * When you make an async request, typically you want to show the user a spinner while they wait. + * However, if the request takes less than 300 ms, the spinner will flicker in the UI and the user + * won't have time to register it as a spinner. This function ensures the spinner (or whatever + * you're showing the user) displays for at least 300 ms, even if the request completes before then. + */ + +export const DEFAULT_MINIMUM_TIME_MS = 300; + +export async function ensureMinimumTime(promiseOrPromises, minimumTimeMs = DEFAULT_MINIMUM_TIME_MS) { + let returnValue; + + // https://kibana-ci.elastic.co/job/elastic+kibana+6.x+multijob-intake/128/console + // We're having periodic failures around the timing here. I'm not exactly sure + // why it's not consistent but I'm going to add some buffer space here to + // prevent these random failures + const bufferedMinimumTimeMs = minimumTimeMs + 5; + + // Block on the async action and start the clock. + const asyncActionStartTime = new Date().getTime(); + if (Array.isArray(promiseOrPromises)) { + returnValue = await Promise.all(promiseOrPromises); + } else { + returnValue = await promiseOrPromises; + } + + // Measure how long the async action took to complete. + const asyncActionCompletionTime = new Date().getTime(); + const asyncActionDuration = asyncActionCompletionTime - asyncActionStartTime; + + // Wait longer if the async action completed too quickly. + if (asyncActionDuration < bufferedMinimumTimeMs) { + const additionalWaitingTime = bufferedMinimumTimeMs - (asyncActionCompletionTime - asyncActionStartTime); + await new Promise(resolve => setTimeout(resolve, additionalWaitingTime)); + } + + return returnValue; +} diff --git a/x-pack/plugins/monitoring/public/lib/ensure_minimum_time.test.js b/x-pack/plugins/monitoring/public/lib/ensure_minimum_time.test.js new file mode 100644 index 000000000000..3c899cb576bb --- /dev/null +++ b/x-pack/plugins/monitoring/public/lib/ensure_minimum_time.test.js @@ -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 { ensureMinimumTime } from './ensure_minimum_time'; + +describe('ensureMinimumTime', () => { + it('resolves single promise', async (done) => { + const promiseA = new Promise(resolve => resolve('a')); + const a = await ensureMinimumTime(promiseA, 0); + expect(a).toBe('a'); + done(); + }); + + it('resolves multiple promises', async (done) => { + const promiseA = new Promise(resolve => resolve('a')); + const promiseB = new Promise(resolve => resolve('b')); + const [ a, b ] = await ensureMinimumTime([promiseA, promiseB], 0); + expect(a).toBe('a'); + expect(b).toBe('b'); + done(); + }); + + it('resolves in the amount of time provided, at minimum', async (done) => { + const startTime = new Date().getTime(); + const promise = new Promise(resolve => resolve()); + await ensureMinimumTime(promise, 100); + const endTime = new Date().getTime(); + expect(endTime - startTime).toBeGreaterThanOrEqual(100); + done(); + }); +}); diff --git a/x-pack/plugins/monitoring/public/lib/setup_mode.js b/x-pack/plugins/monitoring/public/lib/setup_mode.js new file mode 100644 index 000000000000..e45801286054 --- /dev/null +++ b/x-pack/plugins/monitoring/public/lib/setup_mode.js @@ -0,0 +1,126 @@ +/* + * 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 { ajaxErrorHandlersProvider } from './ajax_error_handler'; + +const angularState = { + injector: null, + scope: null, +}; + +export const setAngularState = ($scope, $injector) => { + angularState.scope = $scope; + angularState.injector = $injector; +}; +const checkAngularState = () => { + if (!angularState.injector || !angularState.scope) { + throw 'Unable to interact with setup mode because the angular injector was not previously set.' + + ' This needs to be set by calling `setAngularState`.'; + } +}; + +const setupModeState = { + enabled: false, + data: null, + callbacks: [] +}; + +export const getSetupModeState = () => setupModeState; + +export const fetchCollectionData = async () => { + checkAngularState(); + + const http = angularState.injector.get('$http'); + const globalState = angularState.injector.get('globalState'); + const clusterUuid = globalState.cluster_uuid; + const ccs = globalState.ccs; + + let url = '../api/monitoring/v1/setup/collection'; + if (clusterUuid) { + url += `/${clusterUuid}`; + } + + try { + const response = await http.post(url, { ccs }); + return response.data; + } + catch (err) { + const Private = angularState.injector.get('Private'); + const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); + return ajaxErrorHandlers(err); + } +}; + +const notifySetupModeDataChange = () => { + setupModeState.callbacks.forEach(cb => cb()); +}; + +export const updateSetupModeData = async () => { + setupModeState.data = await fetchCollectionData(); + notifySetupModeDataChange(); +}; + +export const toggleSetupMode = inSetupMode => { + checkAngularState(); + + const globalState = angularState.injector.get('globalState'); + angularState.scope.$evalAsync(async () => { + setupModeState.enabled = inSetupMode; + globalState.inSetupMode = inSetupMode; + globalState.save(); + setSetupModeMenuItem(); // eslint-disable-line no-use-before-define + notifySetupModeDataChange(); + + if (inSetupMode) { + await updateSetupModeData(); + } + }); +}; + +const setSetupModeMenuItem = () => { + // Disabling this for this initial release. This will be added back in + // in a subsequent PR + // checkAngularState(); + + // const globalState = angularState.injector.get('globalState'); + // const navItems = globalState.inSetupMode + // ? [ + // { + // key: 'exit', + // label: 'Exit Setup Mode', + // description: 'Exit setup mode', + // run: () => toggleSetupMode(false), + // testId: 'exitSetupMode' + // }, + // { + // key: 'refresh', + // label: 'Refresh Setup Data', + // description: 'Refresh data used for setup mode', + // run: () => updateSetupModeData(), + // testId: 'refreshSetupModeData' + // } + // ] + // : [{ + // key: 'enter', + // label: 'Enter Setup Mode', + // description: 'Enter setup mode', + // run: () => toggleSetupMode(true), + // testId: 'enterSetupMode' + // }]; + + // angularState.scope.topNavMenu = [...navItems]; +}; + +export const initSetupModeState = (callback) => { + checkAngularState(); + setSetupModeMenuItem(); + callback && setupModeState.callbacks.push(callback); + + const globalState = angularState.injector.get('globalState'); + if (globalState.inSetupMode) { + toggleSetupMode(true); + } +}; diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js index 2eb76fc74b9f..cda04d24b820 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { find } from 'lodash'; import uiRoutes from 'ui/routes'; @@ -13,6 +13,7 @@ import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { ElasticsearchNodes } from '../../../components'; import { I18nContext } from 'ui/i18n'; +import { SetupModeRenderer } from '../../../components/renderers'; uiRoutes.when('/elasticsearch/nodes', { template, @@ -54,13 +55,24 @@ uiRoutes.when('/elasticsearch/nodes', { this.renderReact = ({ clusterStatus, nodes }) => { super.renderReact( - ( + + {flyoutComponent} + + + )} /> ); diff --git a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js index bcdd5059ca77..f669d8c95397 100644 --- a/x-pack/plugins/monitoring/public/views/kibana/instances/index.js +++ b/x-pack/plugins/monitoring/public/views/kibana/instances/index.js @@ -4,111 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { capitalize } from 'lodash'; -import { I18nContext } from 'ui/i18n'; +import React, { Fragment } from 'react'; import uiRoutes from'ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { getPageData } from './get_page_data'; import template from './index.html'; -import { EuiPage, EuiPageBody, EuiPageContent, EuiPanel, EuiSpacer, EuiLink } from '@elastic/eui'; -import { ClusterStatus } from '../../../components/kibana/cluster_status'; -import { EuiMonitoringTable } from '../../../components/table'; -import { KibanaStatusIcon } from '../../../components/kibana/status_icon'; -import { formatMetric, formatNumber } from '../../../lib/format_number'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -const getColumns = (kbnUrl, scope) => ([ - { - name: i18n.translate('xpack.monitoring.kibana.listing.nameColumnTitle', { - defaultMessage: 'Name' - }), - field: 'name', - render: (name, kibana) => ( - { - scope.$evalAsync(() => { - kbnUrl.changePath(`/kibana/instances/${kibana.kibana.uuid}`); - }); - }} - data-test-subj={`kibanaLink-${name}`} - > - { name } - - ) - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.statusColumnTitle', { - defaultMessage: 'Status' - }), - field: 'status', - render: (status, kibana) => ( -
-   - { !kibana.availability ? ( - - ) : capitalize(status) } -
- ) - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.loadAverageColumnTitle', { - defaultMessage: 'Load Average' - }), - field: 'os.load.1m', - render: value => ( - - {formatMetric(value, '0.00')} - - ) - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.memorySizeColumnTitle', { - defaultMessage: 'Memory Size' - }), - field: 'process.memory.resident_set_size_in_bytes', - render: value => ( - - {formatNumber(value, 'byte')} - - ) - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.requestsColumnTitle', { - defaultMessage: 'Requests' - }), - field: 'requests.total', - render: value => ( - - {formatNumber(value, 'int_commas')} - - ) - }, - { - name: i18n.translate('xpack.monitoring.kibana.listing.responseTimeColumnTitle', { - defaultMessage: 'Response Times' - }), - field: 'response_times.average', - render: (value, kibana) => ( -
-
- { value && (formatNumber(value, 'int_commas') + ' ms avg') } -
-
- { formatNumber(kibana.response_times.max, 'int_commas') } ms max -
-
- ) - } -]); +import { KibanaInstances } from 'plugins/monitoring/components/kibana/instances'; +import { SetupModeRenderer } from '../../../components/renderers'; +import { I18nContext } from 'ui/i18n'; uiRoutes.when('/kibana/instances', { template, @@ -134,51 +38,41 @@ uiRoutes.when('/kibana/instances', { const kbnUrl = $injector.get('kbnUrl'); + const renderReact = () => { + this.renderReact( + + ( + + {flyoutComponent} + + + )} + /> + + ); + }; + $scope.$watch(() => this.data, data => { - if (!data || !data.kibanas) { + if (!data) { return; } - const dataFlattened = data.kibanas.map(item => ({ - ...item, - name: item.kibana.name, - status: item.kibana.status, - })); - - this.renderReact( - - - - - - - - - - - - - - - ); + renderReact(); }); } } diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index a5c5ea15659d..4f6352074333 100644 --- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -5,14 +5,14 @@ */ import { get, uniq } from 'lodash'; -import { METRICBEAT_INDEX_NAME_UNIQUE_TOKEN } from '../../../../common/constants'; +import { METRICBEAT_INDEX_NAME_UNIQUE_TOKEN, ELASTICSEARCH_CUSTOM_ID } from '../../../../common/constants'; import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID, LOGSTASH_SYSTEM_ID } from '../../../../../telemetry/common/constants'; +const NUMBER_OF_SECONDS_AGO_TO_LOOK = 30; const APM_CUSTOM_ID = 'apm'; -const ELASTICSEARCH_CUSTOM_ID = 'elasticsearch'; const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid) => { - const start = get(req.payload, 'timeRange.min', 'now-30s'); + const start = get(req.payload, 'timeRange.min', `now-${NUMBER_OF_SECONDS_AGO_TO_LOOK}s`); const end = get(req.payload, 'timeRange.max', 'now'); const filters = [ @@ -247,6 +247,9 @@ function shouldSkipBucket(product, bucket) { * @param {*} clusterUuid Optional and will be used to filter down the query if used */ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { + const config = req.server.config(); + const kibanaUuid = config.get('server.uuid'); + const PRODUCTS = [ { name: KIBANA_SYSTEM_ID }, { name: BEATS_SYSTEM_ID }, @@ -273,8 +276,9 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { const productStatus = { totalUniqueInstanceCount: 0, totalUniqueFullyMigratedCount: 0, + totalUniquePartiallyMigratedCount: 0, detected: null, - byUuid: null, + byUuid: {}, }; const fullyMigratedUuidsMap = {}; @@ -291,7 +295,6 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { const singleIndexBucket = indexBuckets[0]; const isFullyMigrated = singleIndexBucket.key.includes(METRICBEAT_INDEX_NAME_UNIQUE_TOKEN); - const map = isFullyMigrated ? fullyMigratedUuidsMap : internalCollectorsUuidsMap; const uuidBuckets = get(singleIndexBucket, `${uuidBucketName}.buckets`, []); for (const bucket of uuidBuckets) { @@ -301,9 +304,13 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { const { key, by_timestamp: byTimestamp } = bucket; if (!map[key]) { map[key] = { lastTimestamp: get(byTimestamp, 'value') }; + if (product.name === KIBANA_SYSTEM_ID && key === kibanaUuid) { + map[key].isPrimary = true; + } } } productStatus.totalUniqueInstanceCount = Object.keys(map).length; + productStatus.totalUniquePartiallyMigratedCount = Object.keys(partiallyMigratedUuidsMap).length; productStatus.totalUniqueFullyMigratedCount = Object.keys(fullyMigratedUuidsMap).length; productStatus.byUuid = { ...Object.keys(internalCollectorsUuidsMap).reduce((accum, uuid) => ({ @@ -337,11 +344,14 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { const { key, by_timestamp: byTimestamp } = bucket; if (!map[key]) { if (otherMap[key]) { - partiallyMigratedUuidsMap[key] = true; + partiallyMigratedUuidsMap[key] = otherMap[key] || {}; delete otherMap[key]; } else { - map[key] = true; + map[key] = {}; + if (product.name === KIBANA_SYSTEM_ID && key === kibanaUuid) { + map[key].isPrimary = true; + } } } if (!isFullyMigrated) { @@ -355,19 +365,30 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { ...Object.keys(fullyMigratedUuidsMap), ...Object.keys(partiallyMigratedUuidsMap) ]).length; + productStatus.totalUniquePartiallyMigratedCount = Object.keys(partiallyMigratedUuidsMap).length; productStatus.totalUniqueFullyMigratedCount = Object.keys(fullyMigratedUuidsMap).length; productStatus.byUuid = { ...Object.keys(internalCollectorsUuidsMap).reduce((accum, uuid) => ({ ...accum, - [uuid]: { isInternalCollector: true } + [uuid]: { + isInternalCollector: true, + ...internalCollectorsUuidsMap[uuid] + } }), {}), ...Object.keys(partiallyMigratedUuidsMap).reduce((accum, uuid) => ({ ...accum, - [uuid]: { isPartiallyMigrated: true, lastInternallyCollectedTimestamp: internalTimestamps[0] } + [uuid]: { + isPartiallyMigrated: true, + lastInternallyCollectedTimestamp: internalTimestamps[0], + ...partiallyMigratedUuidsMap[uuid] + } }), {}), ...Object.keys(fullyMigratedUuidsMap).reduce((accum, uuid) => ({ ...accum, - [uuid]: { isFullyMigrated: true } + [uuid]: { + isFullyMigrated: true, + ...fullyMigratedUuidsMap[uuid] + } }), {}), }; } @@ -378,5 +399,9 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid) => { }; }, {}); + status._meta = { + secondsAgo: NUMBER_OF_SECONDS_AGO_TO_LOOK, + }; + return status; }; diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_apm.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_apm.json index 6bd7d34d420b..67c36c322c93 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_apm.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_apm.json @@ -1,42 +1,50 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} }, "beats": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": true }, - "byUuid": null + "byUuid": {} }, "elasticsearch": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} } } diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats.json index e0f25d710242..2d6526289cd5 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats.json @@ -1,11 +1,16 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 1, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "5b2de169-2785-441b-ae8c-186a1936b17d": { "isFullyMigrated": true, + "isPrimary": true, "lastTimestamp": 1554841069921 } } @@ -13,30 +18,34 @@ "beats": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": true }, - "byUuid": null + "byUuid": {} }, "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "elasticsearch": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 1, "detected": null, "byUuid": { "agI8JhXhShasvuDgq0VxRg": { diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats_management.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats_management.json index ca526eaa8012..6f5e55e8c244 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats_management.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats_management.json @@ -1,42 +1,50 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} }, "beats": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": true }, - "byUuid": null + "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "elasticsearch": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} } } diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash.json index 7c15ba7ca595..e84bb8765e32 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash.json @@ -1,42 +1,50 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} }, "beats": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": true }, - "byUuid": null + "byUuid": {} }, "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "elasticsearch": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} } } diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash_management.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash_management.json index 7c15ba7ca595..e84bb8765e32 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash_management.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash_management.json @@ -1,42 +1,50 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} }, "beats": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": true }, - "byUuid": null + "byUuid": {} }, "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "mightExist": false }, - "byUuid": null + "byUuid": {} }, "elasticsearch": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": { "doesExist": true }, - "byUuid": null + "byUuid": {} } } diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_exclusive_mb.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_exclusive_mb.json index 48ce571b382d..00cd1c225d0a 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_exclusive_mb.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_exclusive_mb.json @@ -1,11 +1,16 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 1, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "5b2de169-2785-441b-ae8c-186a1936b17d": { "isFullyMigrated": true, + "isPrimary": true, "lastTimestamp": 1554821587077 } } @@ -13,6 +18,7 @@ "beats": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "8eba4902-df80-43b0-b6c2-ed8ca290984e": { @@ -24,12 +30,14 @@ "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "4134a00e-89e4-4896-a3d4-c3a9aa03a594": { @@ -41,6 +49,7 @@ "elasticsearch": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 1, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "agI8JhXhShasvuDgq0VxRg": { diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_mb.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_mb.json index 2daa3c6c49e5..0133ae505bdd 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_mb.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_mb.json @@ -1,11 +1,16 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 1, "detected": null, "byUuid": { "5b2de169-2785-441b-ae8c-186a1936b17d": { "isPartiallyMigrated": true, + "isPrimary": true, "lastInternallyCollectedTimestamp": 1554821412725 } } @@ -13,6 +18,7 @@ "beats": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "8eba4902-df80-43b0-b6c2-ed8ca290984e": { @@ -24,12 +30,14 @@ "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "4134a00e-89e4-4896-a3d4-c3a9aa03a594": { @@ -41,6 +49,7 @@ "elasticsearch": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 1, "detected": null, "byUuid": { "agI8JhXhShasvuDgq0VxRg": { diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_exclusive_mb.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_exclusive_mb.json index 287f99db70ea..be239b509824 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_exclusive_mb.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_exclusive_mb.json @@ -1,11 +1,16 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 1, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "5b2de169-2785-441b-ae8c-186a1936b17d": { "isFullyMigrated": true, + "isPrimary": true, "lastTimestamp": 1554821537079 } } @@ -13,6 +18,7 @@ "beats": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "8eba4902-df80-43b0-b6c2-ed8ca290984e": { @@ -24,12 +30,14 @@ "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "4134a00e-89e4-4896-a3d4-c3a9aa03a594": { @@ -41,6 +49,7 @@ "elasticsearch": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 1, "detected": null, "byUuid": { "agI8JhXhShasvuDgq0VxRg": { diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_mb.json b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_mb.json index 971d224c759a..3da9fe55e8a2 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_mb.json +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/kibana_mb.json @@ -1,11 +1,16 @@ { + "_meta": { + "secondsAgo": 30 + }, "kibana": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 1, "detected": null, "byUuid": { "5b2de169-2785-441b-ae8c-186a1936b17d": { "isPartiallyMigrated": true, + "isPrimary": true, "lastInternallyCollectedTimestamp": 1554821352739 } } @@ -13,6 +18,7 @@ "beats": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "8eba4902-df80-43b0-b6c2-ed8ca290984e": { @@ -24,12 +30,14 @@ "apm": { "totalUniqueInstanceCount": 0, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": {} }, "logstash": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "4134a00e-89e4-4896-a3d4-c3a9aa03a594": { @@ -41,6 +49,7 @@ "elasticsearch": { "totalUniqueInstanceCount": 1, "totalUniqueFullyMigratedCount": 0, + "totalUniquePartiallyMigratedCount": 0, "detected": null, "byUuid": { "agI8JhXhShasvuDgq0VxRg": {