[Uptime] Add client-side unit tests for remaining synthetics code (#80215)

* Test remaining branches in synthetics components.

* Fix TS errors.

* PR feedback.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Justin Kambic 2020-10-16 15:24:05 -04:00 committed by GitHub
parent 9edae0c84a
commit 403c4dac5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 655 additions and 14 deletions

View file

@ -28,7 +28,7 @@ describe('BrowserExpandedRowComponent', () => {
it('returns empty step state when no journey', () => {
expect(shallowWithIntl(<BrowserExpandedRowComponent />)).toMatchInlineSnapshot(
`<EmptyStepState />`
`<EmptyJourney />`
);
});
@ -43,7 +43,7 @@ describe('BrowserExpandedRowComponent', () => {
}}
/>
)
).toMatchInlineSnapshot(`<EmptyStepState />`);
).toMatchInlineSnapshot(`<EmptyJourney />`);
});
it('displays loading spinner when loading', () => {
@ -111,6 +111,27 @@ describe('BrowserExpandedRowComponent', () => {
`);
});
it('handles case where synth type is somehow missing', () => {
expect(
shallowWithIntl(
<BrowserExpandedRowComponent
journey={{
checkGroup: 'check_group',
loading: false,
steps: [
{
...defStep,
synthetics: {
type: undefined,
},
},
],
}}
/>
)
).toMatchInlineSnapshot(`""`);
});
it('renders console output step list when only console steps are present', () => {
expect(
shallowWithIntl(

View file

@ -0,0 +1,107 @@
/*
* 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 { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { ConsoleEvent } from '../console_event';
describe('ConsoleEvent component', () => {
it('renders danger color for errors', () => {
expect(
shallowWithIntl(
<ConsoleEvent
event={{
timestamp: '123',
docId: '1',
monitor: {
id: 'MONITOR_ID',
duration: {
us: 123,
},
type: 'browser',
status: 'down',
},
synthetics: {
payload: {
message: 'catastrophic error',
},
type: 'stderr',
},
}}
/>
)
).toMatchInlineSnapshot(`
<EuiFlexGroup>
<EuiFlexItem
grow={false}
>
123
</EuiFlexItem>
<EuiFlexItem
grow={false}
style={
Object {
"color": "#bd271e",
}
}
>
stderr
</EuiFlexItem>
<EuiFlexItem>
catastrophic error
</EuiFlexItem>
</EuiFlexGroup>
`);
});
it('uses default color for non-errors', () => {
expect(
shallowWithIntl(
<ConsoleEvent
event={{
timestamp: '123',
docId: '1',
monitor: {
id: 'MONITOR_ID',
duration: {
us: 123,
},
type: 'browser',
status: 'down',
},
synthetics: {
payload: {
message: 'not a catastrophic error',
},
type: 'cmd/status',
},
}}
/>
)
).toMatchInlineSnapshot(`
<EuiFlexGroup>
<EuiFlexItem
grow={false}
>
123
</EuiFlexItem>
<EuiFlexItem
grow={false}
style={
Object {
"color": undefined,
}
}
>
cmd/status
</EuiFlexItem>
<EuiFlexItem>
not a catastrophic error
</EuiFlexItem>
</EuiFlexGroup>
`);
});
});

View file

@ -0,0 +1,150 @@
/*
* 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 { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { ConsoleOutputEventList } from '../console_output_event_list';
describe('ConsoleOutputEventList component', () => {
it('renders a component per console event', () => {
expect(
shallowWithIntl(
<ConsoleOutputEventList
journey={{
checkGroup: 'check_group',
loading: false,
// 4 steps, three console, one step/end
steps: [
{
timestamp: '123',
docId: '1',
monitor: {
id: 'MON_ID',
duration: {
us: 10,
},
status: 'down',
type: 'browser',
},
synthetics: {
type: 'stderr',
},
},
{
timestamp: '124',
docId: '2',
monitor: {
id: 'MON_ID',
duration: {
us: 10,
},
status: 'down',
type: 'browser',
},
synthetics: {
type: 'cmd/status',
},
},
{
timestamp: '124',
docId: '2',
monitor: {
id: 'MON_ID',
duration: {
us: 10,
},
status: 'down',
type: 'browser',
},
synthetics: {
type: 'step/end',
},
},
{
timestamp: '125',
docId: '3',
monitor: {
id: 'MON_ID',
duration: {
us: 10,
},
status: 'down',
type: 'browser',
},
synthetics: {
type: 'stdout',
},
},
],
}}
/>
).find('EuiCodeBlock')
).toMatchInlineSnapshot(`
<EuiCodeBlock>
<ConsoleEvent
event={
Object {
"docId": "1",
"monitor": Object {
"duration": Object {
"us": 10,
},
"id": "MON_ID",
"status": "down",
"type": "browser",
},
"synthetics": Object {
"type": "stderr",
},
"timestamp": "123",
}
}
key="1_console-event-row"
/>
<ConsoleEvent
event={
Object {
"docId": "2",
"monitor": Object {
"duration": Object {
"us": 10,
},
"id": "MON_ID",
"status": "down",
"type": "browser",
},
"synthetics": Object {
"type": "cmd/status",
},
"timestamp": "124",
}
}
key="2_console-event-row"
/>
<ConsoleEvent
event={
Object {
"docId": "3",
"monitor": Object {
"duration": Object {
"us": 10,
},
"id": "MON_ID",
"status": "down",
"type": "browser",
},
"synthetics": Object {
"type": "stdout",
},
"timestamp": "125",
}
}
key="3_console-event-row"
/>
</EuiCodeBlock>
`);
});
});

View file

@ -0,0 +1,94 @@
/*
* 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 { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { EmptyJourney } from '../empty_journey';
describe('EmptyJourney component', () => {
it('omits check group element when undefined', () => {
expect(shallowWithIntl(<EmptyJourney />)).toMatchInlineSnapshot(`
<EuiEmptyPrompt
body={
<React.Fragment>
<p>
<FormattedMessage
defaultMessage="This journey did not contain any steps."
id="xpack.uptime.synthetics.emptyJourney.message.heading"
values={Object {}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="There is no further information to display."
id="xpack.uptime.synthetics.emptyJourney.message.footer"
values={Object {}}
/>
</p>
</React.Fragment>
}
iconType="cross"
title={
<h2>
<FormattedMessage
defaultMessage="There are no steps for this journey"
id="xpack.uptime.synthetics.emptyJourney.title"
values={Object {}}
/>
</h2>
}
/>
`);
});
it('includes check group element when present', () => {
expect(shallowWithIntl(<EmptyJourney checkGroup="check_group" />)).toMatchInlineSnapshot(`
<EuiEmptyPrompt
body={
<React.Fragment>
<p>
<FormattedMessage
defaultMessage="This journey did not contain any steps."
id="xpack.uptime.synthetics.emptyJourney.message.heading"
values={Object {}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="The journey's check group is {codeBlock}."
id="xpack.uptime.synthetics.emptyJourney.message.checkGroupField"
values={
Object {
"codeBlock": <code>
check_group
</code>,
}
}
/>
</p>
<p>
<FormattedMessage
defaultMessage="There is no further information to display."
id="xpack.uptime.synthetics.emptyJourney.message.footer"
values={Object {}}
/>
</p>
</React.Fragment>
}
iconType="cross"
title={
<h2>
<FormattedMessage
defaultMessage="There are no steps for this journey"
id="xpack.uptime.synthetics.emptyJourney.title"
values={Object {}}
/>
</h2>
}
/>
`);
});
});

View file

@ -0,0 +1,259 @@
/*
* 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 { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { ExecutedJourney } from '../executed_journey';
import { Ping } from '../../../../../common/runtime_types';
const MONITOR_BOILERPLATE = {
id: 'MON_ID',
duration: {
us: 10,
},
status: 'down',
type: 'browser',
};
describe('ExecutedJourney component', () => {
let steps: Ping[];
beforeEach(() => {
steps = [
{
docId: '1',
timestamp: '123',
monitor: MONITOR_BOILERPLATE,
synthetics: {
payload: {
status: 'failed',
},
type: 'step/end',
},
},
{
docId: '2',
timestamp: '124',
monitor: MONITOR_BOILERPLATE,
synthetics: {
payload: {
status: 'failed',
},
type: 'step/end',
},
},
];
});
it('creates expected message for all failed', () => {
const wrapper = shallowWithIntl(
<ExecutedJourney
journey={{
loading: false,
checkGroup: 'check_group',
steps,
}}
/>
);
expect(wrapper.find('EuiText')).toMatchInlineSnapshot(`
<EuiText>
<h3>
<FormattedMessage
defaultMessage="Summary information"
id="xpack.uptime.synthetics.executedJourney.heading"
values={Object {}}
/>
</h3>
<p>
2 Steps - all failed or skipped
</p>
</EuiText>
`);
});
it('creates expected message for all succeeded', () => {
steps[0].synthetics!.payload!.status = 'succeeded';
steps[1].synthetics!.payload!.status = 'succeeded';
const wrapper = shallowWithIntl(
<ExecutedJourney
journey={{
loading: false,
checkGroup: 'check_group',
steps,
}}
/>
);
expect(wrapper.find('EuiText')).toMatchInlineSnapshot(`
<EuiText>
<h3>
<FormattedMessage
defaultMessage="Summary information"
id="xpack.uptime.synthetics.executedJourney.heading"
values={Object {}}
/>
</h3>
<p>
2 Steps - all succeeded
</p>
</EuiText>
`);
});
it('creates appropriate message for mixed results', () => {
steps[0].synthetics!.payload!.status = 'succeeded';
const wrapper = shallowWithIntl(
<ExecutedJourney
journey={{
loading: false,
checkGroup: 'check_group',
steps,
}}
/>
);
expect(wrapper.find('EuiText')).toMatchInlineSnapshot(`
<EuiText>
<h3>
<FormattedMessage
defaultMessage="Summary information"
id="xpack.uptime.synthetics.executedJourney.heading"
values={Object {}}
/>
</h3>
<p>
2 Steps - 1 succeeded
</p>
</EuiText>
`);
});
it('tallies skipped steps', () => {
steps[0].synthetics!.payload!.status = 'succeeded';
steps[1].synthetics!.payload!.status = 'skipped';
const wrapper = shallowWithIntl(
<ExecutedJourney
journey={{
loading: false,
checkGroup: 'check_group',
steps,
}}
/>
);
expect(wrapper.find('EuiText')).toMatchInlineSnapshot(`
<EuiText>
<h3>
<FormattedMessage
defaultMessage="Summary information"
id="xpack.uptime.synthetics.executedJourney.heading"
values={Object {}}
/>
</h3>
<p>
2 Steps - 1 succeeded
</p>
</EuiText>
`);
});
it('uses appropriate count when non-step/end steps are included', () => {
steps[0].synthetics!.payload!.status = 'succeeded';
steps.push({
docId: '3',
timestamp: '125',
monitor: MONITOR_BOILERPLATE,
synthetics: {
type: 'stderr',
error: {
message: `there was an error, that's all we know`,
stack: 'your.error.happened.here',
},
},
});
const wrapper = shallowWithIntl(
<ExecutedJourney
journey={{
loading: false,
checkGroup: 'check_group',
steps,
}}
/>
);
expect(wrapper.find('EuiText')).toMatchInlineSnapshot(`
<EuiText>
<h3>
<FormattedMessage
defaultMessage="Summary information"
id="xpack.uptime.synthetics.executedJourney.heading"
values={Object {}}
/>
</h3>
<p>
2 Steps - 1 succeeded
</p>
</EuiText>
`);
});
it('renders a component per step', () => {
expect(
shallowWithIntl(
<ExecutedJourney journey={{ loading: false, checkGroup: 'check_group', steps }} />
).find('EuiFlexGroup')
).toMatchInlineSnapshot(`
<EuiFlexGroup
direction="column"
>
<ExecutedStep
index={0}
key="0"
step={
Object {
"docId": "1",
"monitor": Object {
"duration": Object {
"us": 10,
},
"id": "MON_ID",
"status": "down",
"type": "browser",
},
"synthetics": Object {
"payload": Object {
"status": "failed",
},
"type": "step/end",
},
"timestamp": "123",
}
}
/>
<ExecutedStep
index={1}
key="1"
step={
Object {
"docId": "2",
"monitor": Object {
"duration": Object {
"us": 10,
},
"id": "MON_ID",
"status": "down",
"type": "browser",
},
"synthetics": Object {
"payload": Object {
"status": "failed",
},
"type": "step/end",
},
"timestamp": "124",
}
}
/>
</EuiFlexGroup>
`);
});
});

View file

@ -11,7 +11,7 @@ import { Ping } from '../../../../common/runtime_types';
import { getJourneySteps } from '../../../state/actions/journey';
import { JourneyState } from '../../../state/reducers/journey';
import { journeySelector } from '../../../state/selectors';
import { EmptyStepState } from './empty_journey';
import { EmptyJourney } from './empty_journey';
import { ExecutedJourney } from './executed_journey';
import { ConsoleOutputEventList } from './console_output_event_list';
@ -51,7 +51,7 @@ export const BrowserExpandedRowComponent: FC<ComponentProps> = ({ checkGroup, jo
}
if (!journey || journey.steps.length === 0) {
return <EmptyStepState checkGroup={checkGroup} />;
return <EmptyJourney checkGroup={checkGroup} />;
}
if (journey.steps.some(stepEnd)) return <ExecutedJourney journey={journey} />;

View file

@ -7,6 +7,7 @@
import { EuiCodeBlock, EuiSpacer, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { FC } from 'react';
import { Ping } from '../../../../common/runtime_types';
import { JourneyState } from '../../../state/reducers/journey';
import { ConsoleEvent } from './console_event';
@ -14,6 +15,11 @@ interface Props {
journey: JourneyState;
}
const isConsoleStep = (step: Ping) =>
step.synthetics?.type === 'stderr' ||
step.synthetics?.type === 'stdout' ||
step.synthetics?.type === 'cmd/status';
export const ConsoleOutputEventList: FC<Props> = ({ journey }) => (
<div>
<EuiTitle>
@ -33,8 +39,8 @@ export const ConsoleOutputEventList: FC<Props> = ({ journey }) => (
</p>
<EuiSpacer />
<EuiCodeBlock>
{journey.steps.map((consoleEvent) => (
<ConsoleEvent event={consoleEvent} />
{journey.steps.filter(isConsoleStep).map((consoleEvent) => (
<ConsoleEvent event={consoleEvent} key={consoleEvent.docId + '_console-event-row'} />
))}
</EuiCodeBlock>
</div>

View file

@ -8,11 +8,11 @@ import { EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { FC } from 'react';
interface EmptyStepStateProps {
interface Props {
checkGroup?: string;
}
export const EmptyStepState: FC<EmptyStepStateProps> = ({ checkGroup }) => (
export const EmptyJourney: FC<Props> = ({ checkGroup }) => (
<EuiEmptyPrompt
iconType="cross"
title={

View file

@ -49,6 +49,10 @@ function reduceStepStatus(prev: StepStatusCount, cur: Ping): StepStatusCount {
return prev;
}
function isStepEnd(step: Ping) {
return step.synthetics?.type === 'step/end';
}
interface ExecutedJourneyProps {
journey: JourneyState;
}
@ -64,17 +68,17 @@ export const ExecutedJourney: FC<ExecutedJourneyProps> = ({ journey }) => (
</h3>
<p>
{statusMessage(
journey.steps.reduce(reduceStepStatus, { failed: 0, skipped: 0, succeeded: 0 })
journey.steps
.filter(isStepEnd)
.reduce(reduceStepStatus, { failed: 0, skipped: 0, succeeded: 0 })
)}
</p>
</EuiText>
<EuiSpacer />
<EuiFlexGroup direction="column">
{journey.steps
.filter((step) => step.synthetics?.type === 'step/end')
.map((step, index) => (
<ExecutedStep key={index} index={index} step={step} />
))}
{journey.steps.filter(isStepEnd).map((step, index) => (
<ExecutedStep key={index} index={index} step={step} />
))}
</EuiFlexGroup>
</div>
);