From 4941feb4a87f7a6a6debea172f3749bdf76c0cd7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 15 May 2018 16:12:08 -0600 Subject: [PATCH] Add status check to "add data" tutorials (#17732) * add statusCheck configuration to instructionSetSchema * add status check step to instruction set * track status check state * query elasticsearch for hits * display message when status check completes * clean up * use callout to display status check results * Updated status check * update tutorial snapshot * add jest tests for InstructionSet component * pass function as prop instead of wrapping in new function * refactor checkInstructionSetStatus * update snapshots that broke after rebase * Suggested changes (#24) * update jest test for statusCheckState prop enum * update tutorial snapshots --- .../common/tutorials/tutorial_schema.js | 15 +- .../instruction_set.test.js.snap | 731 ++++++++++++++++++ .../__snapshots__/tutorial.test.js.snap | 6 + .../components/tutorial/instruction_set.js | 99 ++- .../tutorial/instruction_set.test.js | 125 +++ .../tutorial/status_check_states.js | 5 + .../home/components/tutorial/tutorial.js | 97 ++- .../kibana/server/tutorials/apm/index.js | 4 +- .../kibana/server/tutorials/apm/on_prem.js | 184 +++-- 9 files changed, 1179 insertions(+), 87 deletions(-) create mode 100644 src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap create mode 100644 src/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js create mode 100644 src/core_plugins/kibana/public/home/components/tutorial/status_check_states.js diff --git a/src/core_plugins/kibana/common/tutorials/tutorial_schema.js b/src/core_plugins/kibana/common/tutorials/tutorial_schema.js index 9baebd8c85678..58fb0ba9e14a1 100644 --- a/src/core_plugins/kibana/common/tutorials/tutorial_schema.js +++ b/src/core_plugins/kibana/common/tutorials/tutorial_schema.js @@ -25,6 +25,18 @@ const artifactsSchema = Joi.object({ }), }); +const statusCheckSchema = Joi.object({ + title: Joi.string(), + text: Joi.string(), + btnLabel: Joi.string(), + success: Joi.string(), + error: Joi.string(), + esHitsCheck: Joi.object({ + index: Joi.string().required(), + query: Joi.object().required(), + }).required(), +}); + const instructionSchema = Joi.object({ title: Joi.string(), textPre: Joi.string(), @@ -40,7 +52,8 @@ const instructionVariantSchema = Joi.object({ const instructionSetSchema = Joi.object({ title: Joi.string(), // Variants (OSes, languages, etc.) for which tutorial instructions are specified. - instructionVariants: Joi.array().items(instructionVariantSchema).required() + instructionVariants: Joi.array().items(instructionVariantSchema).required(), + statusCheck: statusCheckSchema, }); const paramSchema = Joi.object({ diff --git a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap new file mode 100644 index 0000000000000..e574f575a5517 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/instruction_set.test.js.snap @@ -0,0 +1,731 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render 1`] = ` +
+ + +
+ title1 +
+
+ +
+ + + OSX + + + Windows + + + + , + "key": 0, + "title": "step 1", + }, + Object { + "children": , + "key": 1, + "title": "step 2", + }, + ] + } + /> +
+`; + +exports[`statusCheckState checking status 1`] = ` +
+ + +
+ title1 +
+
+ +
+ + + OSX + + + Windows + + + + , + "key": 0, + "title": "step 1", + }, + Object { + "children": , + "key": 1, + "title": "step 2", + }, + Object { + "children": + + + +

+ custom status check description +

+
+
+ + + custom btn label + + +
+ +
, + "key": "checkStatusStep", + "status": "incomplete", + "title": "custom title", + }, + ] + } + /> +
+`; + +exports[`statusCheckState failed status check - error 1`] = ` +
+ + +
+ title1 +
+
+ +
+ + + OSX + + + Windows + + + + , + "key": 0, + "title": "step 1", + }, + Object { + "children": , + "key": 1, + "title": "step 2", + }, + Object { + "children": + + + +

+ custom status check description +

+
+
+ + + custom btn label + + +
+ + +
, + "key": "checkStatusStep", + "status": "complete", + "title": "custom title", + }, + ] + } + /> +
+`; + +exports[`statusCheckState failed status check - no data 1`] = ` +
+ + +
+ title1 +
+
+ +
+ + + OSX + + + Windows + + + + , + "key": 0, + "title": "step 1", + }, + Object { + "children": , + "key": 1, + "title": "step 2", + }, + Object { + "children": + + + +

+ custom status check description +

+
+
+ + + custom btn label + + +
+ + +
, + "key": "checkStatusStep", + "status": "complete", + "title": "custom title", + }, + ] + } + /> +
+`; + +exports[`statusCheckState initial state - no check has been attempted 1`] = ` +
+ + +
+ title1 +
+
+ +
+ + + OSX + + + Windows + + + + , + "key": 0, + "title": "step 1", + }, + Object { + "children": , + "key": 1, + "title": "step 2", + }, + Object { + "children": + + + +

+ custom status check description +

+
+
+ + + custom btn label + + +
+ +
, + "key": "checkStatusStep", + "status": "incomplete", + "title": "custom title", + }, + ] + } + /> +
+`; + +exports[`statusCheckState successful status check 1`] = ` +
+ + +
+ title1 +
+
+ +
+ + + OSX + + + Windows + + + + , + "key": 0, + "title": "step 1", + }, + Object { + "children": , + "key": 1, + "title": "step 2", + }, + Object { + "children": + + + +

+ custom status check description +

+
+
+ + + custom btn label + + +
+ + +
, + "key": "checkStatusStep", + "status": "complete", + "title": "custom title", + }, + ] + } + /> +
+`; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap index f765acc65c36c..807ab76357adc 100644 --- a/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap +++ b/src/core_plugins/kibana/public/home/components/tutorial/__snapshots__/tutorial.test.js.snap @@ -56,9 +56,11 @@ exports[`isCloudEnabled is false should not render instruction toggle when ON_PR } key="0" offset={1} + onStatusCheck={[Function]} paramValues={Object {}} replaceTemplateStrings={[Function]} setParameter={[Function]} + statusCheckState="NOT_CHECKED" title="Instruction title" /> @@ -141,9 +143,11 @@ exports[`isCloudEnabled is false should render ON_PREM instructions with instruc } key="0" offset={1} + onStatusCheck={[Function]} paramValues={Object {}} replaceTemplateStrings={[Function]} setParameter={[Function]} + statusCheckState="NOT_CHECKED" title="Instruction title" /> @@ -208,9 +212,11 @@ exports[`should render ELASTIC_CLOUD instructions when isCloudEnabled is true 1` } key="0" offset={1} + onStatusCheck={[Function]} paramValues={Object {}} replaceTemplateStrings={[Function]} setParameter={[Function]} + statusCheckState="NOT_CHECKED" title="Instruction title" /> diff --git a/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js b/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js index a8a1918506a19..b8e585bbbff9f 100644 --- a/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js +++ b/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.js @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { KuiBar, @@ -13,7 +13,13 @@ import { EuiTab, EuiSpacer, EuiSteps, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiButton, + EuiCallOut, } from '@elastic/eui'; +import * as StatusCheckStates from './status_check_states'; export class InstructionSet extends React.Component { @@ -58,6 +64,72 @@ export class InstructionSet extends React.Component { {tab.name} )); + }; + + renderStatusCheckMessage() { + let message; + let color; + switch (this.props.statusCheckState) { + case StatusCheckStates.NOT_CHECKED: + case StatusCheckStates.FETCHING: + return null; // Don't show any message while fetching or if you haven't yet checked. + case StatusCheckStates.HAS_DATA: + message = this.props.statusCheckConfig.success ? this.props.statusCheckConfig.success : 'Success'; + color = 'success'; + break; + case StatusCheckStates.ERROR: + case StatusCheckStates.NO_DATA: + message = this.props.statusCheckConfig.error ? this.props.statusCheckConfig.error : 'No data found'; + color = 'warning'; + break; + } + return ( + + ); + } + + renderStatusCheck() { + const { statusCheckState, statusCheckConfig, onStatusCheck } = this.props; + const checkStatusStep = ( + + + + +

+ {statusCheckConfig.text} +

+
+
+ + + + {statusCheckConfig.btnLabel || 'Check status'} + + +
+ + + + {this.renderStatusCheckMessage()} +
+ ); + + const stepStatus = statusCheckState === StatusCheckStates.NOT_CHECKED || + statusCheckState === StatusCheckStates.FETCHING ? 'incomplete' : 'complete'; + return { + title: statusCheckConfig.title || 'Status Check', + status: stepStatus, + children: checkStatusStep, + key: 'checkStatusStep' + }; } renderInstructions = () => { @@ -85,13 +157,17 @@ export class InstructionSet extends React.Component { }; }); + if (this.props.statusCheckConfig) { + steps.push(this.renderStatusCheck()); + } + return ( ); - } + }; renderHeader = () => { let paramsVisibilityToggle; @@ -129,7 +205,7 @@ export class InstructionSet extends React.Component { ); - } + }; render() { let paramsForm; @@ -175,9 +251,26 @@ const instructionVariantShape = PropTypes.shape({ instructions: PropTypes.arrayOf(instructionShape).isRequired, }); +const statusCheckConfigShape = PropTypes.shape({ + success: PropTypes.string, + error: PropTypes.string, + title: PropTypes.string, + text: PropTypes.string, + btnLabel: PropTypes.string, +}); + InstructionSet.propTypes = { title: PropTypes.string.isRequired, instructionVariants: PropTypes.arrayOf(instructionVariantShape).isRequired, + statusCheckConfig: statusCheckConfigShape, + statusCheckState: PropTypes.oneOf([ + StatusCheckStates.FETCHING, + StatusCheckStates.NOT_CHECKED, + StatusCheckStates.HAS_DATA, + StatusCheckStates.NO_DATA, + StatusCheckStates.ERROR, + ]), + onStatusCheck: PropTypes.func.isRequired, offset: PropTypes.number.isRequired, params: PropTypes.array, paramValues: PropTypes.object.isRequired, diff --git a/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js b/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js new file mode 100644 index 0000000000000..12769dc6f2c6d --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/instruction_set.test.js @@ -0,0 +1,125 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { + InstructionSet, +} from './instruction_set'; +import * as StatusCheckStates from './status_check_states'; + +const instructions = [ + { + title: 'step 1', + commands: [ + 'do stuff in command line', + ], + }, + { + title: 'step 2', + commands: [ + 'do more stuff in command line', + ], + } +]; + +const instructionVariants = [ + { + id: 'OSX', + instructions: instructions + }, + { + id: 'windows', + instructions: instructions, + } +]; + +test('render', () => { + const component = shallow( {}} + offset={1} + paramValues={{}} + replaceTemplateStrings={() => {}} + />); + expect(component).toMatchSnapshot(); // eslint-disable-line +}); + +describe('statusCheckState', () => { + const statusCheckConfig = { + success: 'custom success msg', + error: 'custom error msg', + title: 'custom title', + text: 'custom status check description', + btnLabel: 'custom btn label', + }; + + test('initial state - no check has been attempted', () => { + const component = shallow( {}} + offset={1} + paramValues={{}} + statusCheckConfig={statusCheckConfig} + replaceTemplateStrings={() => {}} + statusCheckState={StatusCheckStates.FETCHING} + />); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); + + test('checking status', () => { + const component = shallow( {}} + offset={1} + paramValues={{}} + statusCheckConfig={statusCheckConfig} + replaceTemplateStrings={() => {}} + statusCheckState={StatusCheckStates.FETCHING} + />); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); + + test('failed status check - error', () => { + const component = shallow( {}} + offset={1} + paramValues={{}} + statusCheckConfig={statusCheckConfig} + replaceTemplateStrings={() => {}} + statusCheckState={StatusCheckStates.ERROR} + />); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); + + test('failed status check - no data', () => { + const component = shallow( {}} + offset={1} + paramValues={{}} + statusCheckConfig={statusCheckConfig} + replaceTemplateStrings={() => {}} + statusCheckState={StatusCheckStates.NO_DATA} + />); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); + + test('successful status check', () => { + const component = shallow( {}} + offset={1} + paramValues={{}} + statusCheckConfig={statusCheckConfig} + replaceTemplateStrings={() => {}} + statusCheckState={StatusCheckStates.HAS_DATA} + />); + expect(component).toMatchSnapshot(); // eslint-disable-line + }); +}); diff --git a/src/core_plugins/kibana/public/home/components/tutorial/status_check_states.js b/src/core_plugins/kibana/public/home/components/tutorial/status_check_states.js new file mode 100644 index 0000000000000..5ae6f4a16faa3 --- /dev/null +++ b/src/core_plugins/kibana/public/home/components/tutorial/status_check_states.js @@ -0,0 +1,5 @@ +export const HAS_DATA = 'has_data'; +export const FETCHING = 'FETCHING'; +export const NO_DATA = 'NO_DATA'; +export const NOT_CHECKED = 'NOT_CHECKED'; +export const ERROR = 'ERROR'; diff --git a/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js index f2c5cc4d9e083..2ed2285929e4e 100644 --- a/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -7,6 +7,7 @@ import { Introduction } from './introduction'; import { InstructionSet } from './instruction_set'; import { RadioButtonGroup } from './radio_button_group'; import { EuiSpacer, EuiPage, EuiPanel, EuiLink, EuiText } from '@elastic/eui'; +import * as StatusCheckStates from './status_check_states'; const INSTRUCTIONS_TYPE = { ELASTIC_CLOUD: 'elasticCloud', @@ -22,6 +23,7 @@ export class Tutorial extends React.Component { this.state = { notFound: false, paramValues: {}, + statusCheckStates: [], tutorial: null }; @@ -51,7 +53,7 @@ export class Tutorial extends React.Component { // eslint-disable-next-line react/no-did-mount-set-state this.setState({ tutorial: tutorial - }, this.setParamDefaults); + }, this.initInstructionsState); } else { // eslint-disable-next-line react/no-did-mount-set-state this.setState({ @@ -75,26 +77,33 @@ export class Tutorial extends React.Component { default: throw new Error(`Unhandled instruction type ${this.state.visibleInstructions}`); } - } + }; + + getInstructionSets = () => this.getInstructions().instructionSets; - setParamDefaults = () => { + initInstructionsState = () => { const instructions = this.getInstructions(); + const paramValues = {}; if (instructions.params) { instructions.params.forEach((param => { paramValues[param.id] = param.defaultValue; })); } + + const statusCheckStates = new Array(instructions.instructionSets.length).fill(StatusCheckStates.NOT_CHECKED); + this.setState({ - paramValues: paramValues + paramValues, + statusCheckStates, }); - } + }; setVisibleInstructions = (instructionsType) => { this.setState({ visibleInstructions: instructionsType - }, this.setParamDefaults); - } + }, this.initInstructionsState); + }; setParameter = (paramId, newValue) => { this.setState(previousState => { @@ -102,15 +111,59 @@ export class Tutorial extends React.Component { paramValues[paramId] = newValue; return { paramValues: paramValues }; }); - } + }; + + checkInstructionSetStatus = async (instructionSetIndex) => { + const instructionSet = this.getInstructionSets()[instructionSetIndex]; + const esHitsCheckConfig = _.get(instructionSet, `statusCheck.esHitsCheck`); + + if (esHitsCheckConfig) { + const statusCheckState = await this.fetchEsHitsStatus(esHitsCheckConfig); + + this.setState((prevState) => ({ + statusCheckStates: { + ...prevState.statusCheckStates, + [instructionSetIndex]: statusCheckState, + } + })); + } + }; + + /** + * + * @param esHitsCheckConfig + * @return {Promise} + */ + fetchEsHitsStatus = async (esHitsCheckConfig) => { + const searchHeader = JSON.stringify({ index: esHitsCheckConfig.index }); + const searchBody = JSON.stringify({ query: esHitsCheckConfig.query, size: 1 }); + const response = await fetch(this.props.addBasePath('/elasticsearch/_msearch'), { + method: 'post', + body: `${searchHeader}\n${searchBody}\n`, + headers: { + accept: 'application/json', + 'content-type': 'application/x-ndjson', + 'kbn-xsrf': 'kibana', + }, + credentials: 'same-origin' + }); + + if (response.status > 300) { + return StatusCheckStates.ERROR; + } + + const results = await response.json(); + const numHits = _.get(results, 'responses.[0].hits.hits.length', 0); + return numHits === 0 ? StatusCheckStates.NO_DATA : StatusCheckStates.HAS_DATA; + }; onPrem = () => { this.setVisibleInstructions(INSTRUCTIONS_TYPE.ON_PREM); - } + }; onPremElasticCloud = () => { this.setVisibleInstructions(INSTRUCTIONS_TYPE.ON_PREM_ELASTIC_CLOUD); - } + }; renderInstructionSetsToggle = () => { if (!this.props.isCloudEnabled && this.state.tutorial.onPremElasticCloud) { @@ -125,17 +178,33 @@ export class Tutorial extends React.Component { /> ); } - } + }; + + onStatusCheck = (instructionSetIndex) => { + this.setState( + (prevState) => ({ + statusCheckStates: { + ...prevState.statusCheckStates, + [instructionSetIndex]: StatusCheckStates.FETCHING, + } + }), + this.checkInstructionSetStatus.bind(null, instructionSetIndex) + ); + }; renderInstructionSets = (instructions) => { let offset = 1; return instructions.instructionSets.map((instructionSet, index) => { const currentOffset = offset; offset += instructionSet.instructionVariants[0].instructions.length; + return ( { this.onStatusCheck(index); }} offset={currentOffset} params={instructions.params} paramValues={this.state.paramValues} @@ -145,7 +214,7 @@ export class Tutorial extends React.Component { /> ); }); - } + }; renderFooter = () => { let label; @@ -171,7 +240,7 @@ export class Tutorial extends React.Component { /> ); } - } + }; render() { let content; @@ -238,5 +307,5 @@ Tutorial.propTypes = { isCloudEnabled: PropTypes.bool.isRequired, getTutorial: PropTypes.func.isRequired, replaceTemplateStrings: PropTypes.func.isRequired, - tutorialId: PropTypes.string.isRequired + tutorialId: PropTypes.string.isRequired, }; diff --git a/src/core_plugins/kibana/server/tutorials/apm/index.js b/src/core_plugins/kibana/server/tutorials/apm/index.js index 2e4de77510598..b6bf922bdb458 100644 --- a/src/core_plugins/kibana/server/tutorials/apm/index.js +++ b/src/core_plugins/kibana/server/tutorials/apm/index.js @@ -1,5 +1,5 @@ import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; -import { ON_PREM_INSTRUCTIONS } from './on_prem'; +import { onPremInstructions } from './on_prem'; import { ELASTIC_CLOUD_INSTRUCTIONS } from './elastic_cloud'; const apmIntro = 'Collect in-depth performance metrics and errors from inside your applications.'; @@ -42,7 +42,7 @@ export function apmSpecProvider(server) { ' [Learn more]({config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html).', euiIconType: 'apmApp', artifacts: artifacts, - onPrem: ON_PREM_INSTRUCTIONS, + onPrem: onPremInstructions(server), elasticCloud: ELASTIC_CLOUD_INSTRUCTIONS, previewImagePath: '/plugins/kibana/home/tutorial_resources/apm/apm.png', }; diff --git a/src/core_plugins/kibana/server/tutorials/apm/on_prem.js b/src/core_plugins/kibana/server/tutorials/apm/on_prem.js index 34f517aed6788..42d12c3a321f1 100644 --- a/src/core_plugins/kibana/server/tutorials/apm/on_prem.js +++ b/src/core_plugins/kibana/server/tutorials/apm/on_prem.js @@ -17,72 +17,122 @@ import { JS_CLIENT_INSTRUCTIONS, } from './apm_client_instructions'; -export const ON_PREM_INSTRUCTIONS = { - instructionSets: [ - { - title: 'APM Server', - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.OSX, - instructions: [ - DOWNLOAD_SERVER_OSX, - IMPORT_DASHBOARD_UNIX, - EDIT_CONFIG, - START_SERVER_UNIX, - ], - }, - { - id: INSTRUCTION_VARIANT.DEB, - instructions: [ - DOWNLOAD_SERVER_DEB, - IMPORT_DASHBOARD_UNIX, - EDIT_CONFIG, - START_SERVER_UNIX, - ], - }, - { - id: INSTRUCTION_VARIANT.RPM, - instructions: [ - DOWNLOAD_SERVER_RPM, - IMPORT_DASHBOARD_UNIX, - EDIT_CONFIG, - START_SERVER_UNIX, - ], - }, - { - id: INSTRUCTION_VARIANT.WINDOWS, - instructions: WINDOWS_SERVER_INSTRUCTIONS, - }, - ], - }, - { - title: 'APM Agents', - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.NODE, - instructions: NODE_CLIENT_INSTRUCTIONS, - }, - { - id: INSTRUCTION_VARIANT.DJANGO, - instructions: DJANGO_CLIENT_INSTRUCTIONS, - }, - { - id: INSTRUCTION_VARIANT.FLASK, - instructions: FLASK_CLIENT_INSTRUCTIONS, - }, - { - id: INSTRUCTION_VARIANT.RAILS, - instructions: RAILS_CLIENT_INSTRUCTIONS, - }, - { - id: INSTRUCTION_VARIANT.RACK, - instructions: RACK_CLIENT_INSTRUCTIONS, +export function onPremInstructions(server) { + let apmIndexPattern = 'apm*'; + try { + apmIndexPattern = server.config().get('xpack.apm.indexPattern'); + } catch (error) { + // ignore error when config does not contain 'xpack.apm.indexPattern'. + // This is expected when APM plugin is not running. + } + + return { + instructionSets: [ + { + title: 'APM Server', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + DOWNLOAD_SERVER_OSX, + IMPORT_DASHBOARD_UNIX, + EDIT_CONFIG, + START_SERVER_UNIX, + ], + }, + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + DOWNLOAD_SERVER_DEB, + IMPORT_DASHBOARD_UNIX, + EDIT_CONFIG, + START_SERVER_UNIX, + ], + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + DOWNLOAD_SERVER_RPM, + IMPORT_DASHBOARD_UNIX, + EDIT_CONFIG, + START_SERVER_UNIX, + ], + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: WINDOWS_SERVER_INSTRUCTIONS, + }, + ], + statusCheck: { + title: 'APM Server status', + text: + 'Make sure APM Server is running before you start implementing the APM agents.', + btnLabel: 'Check APM Server status', + success: 'You have correctly setup APM-Server', + error: 'APM-Server has still not connected to Elasticsearch', + esHitsCheck: { + index: apmIndexPattern, + query: { + bool: { + filter: { + exists: { + field: 'listening', + }, + }, + }, + }, + }, }, - { - id: INSTRUCTION_VARIANT.JS, - instructions: JS_CLIENT_INSTRUCTIONS, + }, + { + title: 'APM Agents', + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.NODE, + instructions: NODE_CLIENT_INSTRUCTIONS, + }, + { + id: INSTRUCTION_VARIANT.DJANGO, + instructions: DJANGO_CLIENT_INSTRUCTIONS, + }, + { + id: INSTRUCTION_VARIANT.FLASK, + instructions: FLASK_CLIENT_INSTRUCTIONS, + }, + { + id: INSTRUCTION_VARIANT.RAILS, + instructions: RAILS_CLIENT_INSTRUCTIONS, + }, + { + id: INSTRUCTION_VARIANT.RACK, + instructions: RACK_CLIENT_INSTRUCTIONS, + }, + { + id: INSTRUCTION_VARIANT.JS, + instructions: JS_CLIENT_INSTRUCTIONS, + }, + ], + statusCheck: { + title: 'Agent status', + text: + 'Make sure you application is running, and the agents are sending data', + btnLabel: 'Check agent status', + success: 'Data succesfully received from one or more agents', + error: `No data has been received from agents yet`, + esHitsCheck: { + index: apmIndexPattern, + query: { + bool: { + filter: { + exists: { + field: 'processor.name', + }, + }, + }, + }, + }, }, - ], - }, - ], -}; + }, + ], + }; +}