Add status check to "add data" tutorials (#17732) (#19097)

* 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
This commit is contained in:
Nathan Reese 2018-05-15 19:41:13 -06:00 committed by GitHub
parent 5db2efdb43
commit 1dae5950af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1179 additions and 87 deletions

View file

@ -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({

View file

@ -0,0 +1,731 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`render 1`] = `
<div
className="kuiVerticalRhythmLarge"
>
<KuiBar
className="kuiVerticalRhythm"
>
<KuiBarSection>
<div
className="kuiTitle"
>
title1
</div>
</KuiBarSection>
<KuiBarSection />
</KuiBar>
<EuiTabs
className="kuiVerticalRhythm"
>
<EuiTab
disabled={false}
isSelected={true}
key="0"
onClick={[Function]}
>
OSX
</EuiTab>
<EuiTab
disabled={false}
isSelected={false}
key="1"
onClick={[Function]}
>
Windows
</EuiTab>
</EuiTabs>
<EuiSpacer
size="m"
/>
<EuiSteps
firstStepNumber={1}
headingElement="p"
steps={
Array [
Object {
"children": <Instruction
commands={
Array [
"do stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 0,
"title": "step 1",
},
Object {
"children": <Instruction
commands={
Array [
"do more stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
]
}
/>
</div>
`;
exports[`statusCheckState checking status 1`] = `
<div
className="kuiVerticalRhythmLarge"
>
<KuiBar
className="kuiVerticalRhythm"
>
<KuiBarSection>
<div
className="kuiTitle"
>
title1
</div>
</KuiBarSection>
<KuiBarSection />
</KuiBar>
<EuiTabs
className="kuiVerticalRhythm"
>
<EuiTab
disabled={false}
isSelected={true}
key="0"
onClick={[Function]}
>
OSX
</EuiTab>
<EuiTab
disabled={false}
isSelected={false}
key="1"
onClick={[Function]}
>
Windows
</EuiTab>
</EuiTabs>
<EuiSpacer
size="m"
/>
<EuiSteps
firstStepNumber={1}
headingElement="p"
steps={
Array [
Object {
"children": <Instruction
commands={
Array [
"do stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 0,
"title": "step 1",
},
Object {
"children": <Instruction
commands={
Array [
"do more stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
"children": <UNDEFINED>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="l"
justifyContent="spaceBetween"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiText
grow={true}
>
<p>
custom status check description
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isLoading={true}
onClick={[Function]}
type="button"
>
custom btn label
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
</UNDEFINED>,
"key": "checkStatusStep",
"status": "incomplete",
"title": "custom title",
},
]
}
/>
</div>
`;
exports[`statusCheckState failed status check - error 1`] = `
<div
className="kuiVerticalRhythmLarge"
>
<KuiBar
className="kuiVerticalRhythm"
>
<KuiBarSection>
<div
className="kuiTitle"
>
title1
</div>
</KuiBarSection>
<KuiBarSection />
</KuiBar>
<EuiTabs
className="kuiVerticalRhythm"
>
<EuiTab
disabled={false}
isSelected={true}
key="0"
onClick={[Function]}
>
OSX
</EuiTab>
<EuiTab
disabled={false}
isSelected={false}
key="1"
onClick={[Function]}
>
Windows
</EuiTab>
</EuiTabs>
<EuiSpacer
size="m"
/>
<EuiSteps
firstStepNumber={1}
headingElement="p"
steps={
Array [
Object {
"children": <Instruction
commands={
Array [
"do stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 0,
"title": "step 1",
},
Object {
"children": <Instruction
commands={
Array [
"do more stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
"children": <UNDEFINED>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="l"
justifyContent="spaceBetween"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiText
grow={true}
>
<p>
custom status check description
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isLoading={false}
onClick={[Function]}
type="button"
>
custom btn label
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<EuiCallOut
color="warning"
size="m"
title="custom error msg"
/>
</UNDEFINED>,
"key": "checkStatusStep",
"status": "complete",
"title": "custom title",
},
]
}
/>
</div>
`;
exports[`statusCheckState failed status check - no data 1`] = `
<div
className="kuiVerticalRhythmLarge"
>
<KuiBar
className="kuiVerticalRhythm"
>
<KuiBarSection>
<div
className="kuiTitle"
>
title1
</div>
</KuiBarSection>
<KuiBarSection />
</KuiBar>
<EuiTabs
className="kuiVerticalRhythm"
>
<EuiTab
disabled={false}
isSelected={true}
key="0"
onClick={[Function]}
>
OSX
</EuiTab>
<EuiTab
disabled={false}
isSelected={false}
key="1"
onClick={[Function]}
>
Windows
</EuiTab>
</EuiTabs>
<EuiSpacer
size="m"
/>
<EuiSteps
firstStepNumber={1}
headingElement="p"
steps={
Array [
Object {
"children": <Instruction
commands={
Array [
"do stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 0,
"title": "step 1",
},
Object {
"children": <Instruction
commands={
Array [
"do more stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
"children": <UNDEFINED>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="l"
justifyContent="spaceBetween"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiText
grow={true}
>
<p>
custom status check description
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isLoading={false}
onClick={[Function]}
type="button"
>
custom btn label
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<EuiCallOut
color="warning"
size="m"
title="custom error msg"
/>
</UNDEFINED>,
"key": "checkStatusStep",
"status": "complete",
"title": "custom title",
},
]
}
/>
</div>
`;
exports[`statusCheckState initial state - no check has been attempted 1`] = `
<div
className="kuiVerticalRhythmLarge"
>
<KuiBar
className="kuiVerticalRhythm"
>
<KuiBarSection>
<div
className="kuiTitle"
>
title1
</div>
</KuiBarSection>
<KuiBarSection />
</KuiBar>
<EuiTabs
className="kuiVerticalRhythm"
>
<EuiTab
disabled={false}
isSelected={true}
key="0"
onClick={[Function]}
>
OSX
</EuiTab>
<EuiTab
disabled={false}
isSelected={false}
key="1"
onClick={[Function]}
>
Windows
</EuiTab>
</EuiTabs>
<EuiSpacer
size="m"
/>
<EuiSteps
firstStepNumber={1}
headingElement="p"
steps={
Array [
Object {
"children": <Instruction
commands={
Array [
"do stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 0,
"title": "step 1",
},
Object {
"children": <Instruction
commands={
Array [
"do more stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
"children": <UNDEFINED>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="l"
justifyContent="spaceBetween"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiText
grow={true}
>
<p>
custom status check description
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isLoading={true}
onClick={[Function]}
type="button"
>
custom btn label
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
</UNDEFINED>,
"key": "checkStatusStep",
"status": "incomplete",
"title": "custom title",
},
]
}
/>
</div>
`;
exports[`statusCheckState successful status check 1`] = `
<div
className="kuiVerticalRhythmLarge"
>
<KuiBar
className="kuiVerticalRhythm"
>
<KuiBarSection>
<div
className="kuiTitle"
>
title1
</div>
</KuiBarSection>
<KuiBarSection />
</KuiBar>
<EuiTabs
className="kuiVerticalRhythm"
>
<EuiTab
disabled={false}
isSelected={true}
key="0"
onClick={[Function]}
>
OSX
</EuiTab>
<EuiTab
disabled={false}
isSelected={false}
key="1"
onClick={[Function]}
>
Windows
</EuiTab>
</EuiTabs>
<EuiSpacer
size="m"
/>
<EuiSteps
firstStepNumber={1}
headingElement="p"
steps={
Array [
Object {
"children": <Instruction
commands={
Array [
"do stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 0,
"title": "step 1",
},
Object {
"children": <Instruction
commands={
Array [
"do more stuff in command line",
]
}
paramValues={Object {}}
replaceTemplateStrings={[Function]}
textPost={undefined}
textPre={undefined}
/>,
"key": 1,
"title": "step 2",
},
Object {
"children": <UNDEFINED>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="l"
justifyContent="spaceBetween"
responsive={true}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiText
grow={true}
>
<p>
custom status check description
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiButton
color="primary"
fill={false}
iconSide="left"
isLoading={false}
onClick={[Function]}
type="button"
>
custom btn label
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer
size="s"
/>
<EuiCallOut
color="success"
size="m"
title="custom success msg"
/>
</UNDEFINED>,
"key": "checkStatusStep",
"status": "complete",
"title": "custom title",
},
]
}
/>
</div>
`;

View file

@ -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"
/>
</EuiPanel>
@ -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"
/>
</EuiPanel>
@ -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"
/>
</EuiPanel>

View file

@ -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}
</EuiTab>
));
};
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 (
<EuiCallOut
title={message}
color={color}
/>
);
}
renderStatusCheck() {
const { statusCheckState, statusCheckConfig, onStatusCheck } = this.props;
const checkStatusStep = (
<Fragment>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem>
<EuiText>
<p>
{statusCheckConfig.text}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButton
onClick={onStatusCheck}
isLoading={statusCheckState === StatusCheckStates.FETCHING}
>
{statusCheckConfig.btnLabel || 'Check status'}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
{this.renderStatusCheckMessage()}
</Fragment>
);
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 (
<EuiSteps
steps={steps}
firstStepNumber={this.props.offset}
/>
);
}
};
renderHeader = () => {
let paramsVisibilityToggle;
@ -129,7 +205,7 @@ export class InstructionSet extends React.Component {
</KuiBarSection>
</KuiBar>
);
}
};
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,

View file

@ -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(<InstructionSet
title="title1"
instructionVariants={instructionVariants}
onStatusCheck={() => {}}
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(<InstructionSet
title="title1"
instructionVariants={instructionVariants}
onStatusCheck={() => {}}
offset={1}
paramValues={{}}
statusCheckConfig={statusCheckConfig}
replaceTemplateStrings={() => {}}
statusCheckState={StatusCheckStates.FETCHING}
/>);
expect(component).toMatchSnapshot(); // eslint-disable-line
});
test('checking status', () => {
const component = shallow(<InstructionSet
title="title1"
instructionVariants={instructionVariants}
onStatusCheck={() => {}}
offset={1}
paramValues={{}}
statusCheckConfig={statusCheckConfig}
replaceTemplateStrings={() => {}}
statusCheckState={StatusCheckStates.FETCHING}
/>);
expect(component).toMatchSnapshot(); // eslint-disable-line
});
test('failed status check - error', () => {
const component = shallow(<InstructionSet
title="title1"
instructionVariants={instructionVariants}
onStatusCheck={() => {}}
offset={1}
paramValues={{}}
statusCheckConfig={statusCheckConfig}
replaceTemplateStrings={() => {}}
statusCheckState={StatusCheckStates.ERROR}
/>);
expect(component).toMatchSnapshot(); // eslint-disable-line
});
test('failed status check - no data', () => {
const component = shallow(<InstructionSet
title="title1"
instructionVariants={instructionVariants}
onStatusCheck={() => {}}
offset={1}
paramValues={{}}
statusCheckConfig={statusCheckConfig}
replaceTemplateStrings={() => {}}
statusCheckState={StatusCheckStates.NO_DATA}
/>);
expect(component).toMatchSnapshot(); // eslint-disable-line
});
test('successful status check', () => {
const component = shallow(<InstructionSet
title="title1"
instructionVariants={instructionVariants}
onStatusCheck={() => {}}
offset={1}
paramValues={{}}
statusCheckConfig={statusCheckConfig}
replaceTemplateStrings={() => {}}
statusCheckState={StatusCheckStates.HAS_DATA}
/>);
expect(component).toMatchSnapshot(); // eslint-disable-line
});
});

View file

@ -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';

View file

@ -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}`);
}
}
};
setParamDefaults = () => {
getInstructionSets = () => this.getInstructions().instructionSets;
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<string>}
*/
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 (
<InstructionSet
title={instructionSet.title}
instructionVariants={instructionSet.instructionVariants}
statusCheckConfig={instructionSet.statusCheck}
statusCheckState={this.state.statusCheckStates[index]}
onStatusCheck={() => { 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,
};

View file

@ -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',
};

View file

@ -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,
],
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.DEB,
instructions: [
DOWNLOAD_SERVER_DEB,
IMPORT_DASHBOARD_UNIX,
EDIT_CONFIG,
START_SERVER_UNIX,
],
},
{
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',
},
},
},
},
},
},
{
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,
},
{
id: INSTRUCTION_VARIANT.JS,
instructions: JS_CLIENT_INSTRUCTIONS,
},
],
},
],
};
},
],
};
}