[Uptime] Ping List Disable expand row if no body present (#54898) (#55724)

* update ping list

* update snap

* updated body

* update snaps

* fix i18n

* updated translation

* updated tests
This commit is contained in:
Shahzad 2020-01-23 20:40:31 +01:00 committed by GitHub
parent d7e58fe17a
commit 071e41a7df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 388 additions and 110 deletions

View file

@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PingListExpandedRow renders expected elements for valid props 1`] = `
<EuiText>
<FormattedMessage
defaultMessage="Body not recorded. Read our {docsLink} for more information on recording response bodies."
id="xpack.uptime.pingList.expandedRow.response_body.notRecorded"
values={
Object {
"docsLink": <ForwardRef
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuration-heartbeat-options.html#monitor-http-response"
target="_blank"
>
docs
 
<EuiIcon
size="s"
type="popout"
/>
</ForwardRef>,
}
}
/>
</EuiText>
`;

View file

@ -1,77 +1,228 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PingListExpandedRow doesn't render list items if the body field is undefined 1`] = `
<EuiDescriptionList
listItems={Array []}
/>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut
color="primary"
>
<EuiDescriptionList
listItems={Array []}
/>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`PingListExpandedRow doesn't render list items if the http field is undefined 1`] = `
<EuiDescriptionList
listItems={Array []}
/>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut
color="primary"
>
<EuiDescriptionList
listItems={Array []}
/>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`PingListExpandedRow doesn't render list items if the response field is undefined 1`] = `
<EuiDescriptionList
listItems={Array []}
/>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut
color="primary"
>
<EuiDescriptionList
listItems={Array []}
/>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`PingListExpandedRow renders error information when an error field is present 1`] = `
<EuiDescriptionList
listItems={
Array [
Object {
"description": <EuiText>
Forbidden
</EuiText>,
"title": "Error",
},
Object {
"description": <React.Fragment>
<BodyDescription
body={
Object {
"bytes": 1200000,
"content": "<http><head><title>The Title</title></head><body></body></http>",
"hash": "testhash",
}
}
/>
<BodyExcerpt
content="<http><head><title>The Title</title></head><body></body></http>"
/>
</React.Fragment>,
"title": "Response Body",
},
]
}
/>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut
color="danger"
>
<EuiDescriptionList
listItems={
Array [
Object {
"description": <EuiText>
Forbidden
</EuiText>,
"title": "Error",
},
Object {
"description": <React.Fragment>
<BodyDescription
body={
Object {
"bytes": 1200000,
"content": "<http><head><title>The Title</title></head><body></body></http>",
"hash": "testhash",
}
}
/>
<EuiSpacer
size="s"
/>
<BodyExcerpt
content="<http><head><title>The Title</title></head><body></body></http>"
/>
</React.Fragment>,
"title": "Response Body",
},
]
}
/>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`PingListExpandedRow renders expected elements for valid props 1`] = `
<EuiDescriptionList
listItems={
Array [
Object {
"description": <React.Fragment>
<BodyDescription
body={
Object {
"bytes": 1200000,
"content": "<http><head><title>The Title</title></head><body></body></http>",
"hash": "testhash",
}
}
/>
<BodyExcerpt
content="<http><head><title>The Title</title></head><body></body></http>"
/>
</React.Fragment>,
"title": "Response Body",
},
]
}
/>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut
color="primary"
>
<EuiDescriptionList
listItems={
Array [
Object {
"description": <React.Fragment>
<BodyDescription
body={
Object {
"bytes": 1200000,
"content": "<http><head><title>The Title</title></head><body></body></http>",
"hash": "testhash",
}
}
/>
<EuiSpacer
size="s"
/>
<BodyExcerpt
content="<http><head><title>The Title</title></head><body></body></http>"
/>
</React.Fragment>,
"title": "Response Body",
},
]
}
/>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`PingListExpandedRow renders link to docs if body is not recorded but it is present 1`] = `
<div
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<div
class="euiFlexItem"
>
<div
class="euiCallOut euiCallOut--primary"
>
<div
class="euiCallOutHeader"
>
<span
class="euiCallOutHeader__title"
/>
</div>
<div
class="euiText euiText--small"
>
<dl
class="euiDescriptionList euiDescriptionList--row"
>
<dt
class="euiDescriptionList__title"
>
Response Body
</dt>
<dd
class="euiDescriptionList__description"
>
<div
class="euiText euiText--medium"
>
Body size is 1MB.
</div>
<div
class="euiSpacer euiSpacer--s"
/>
<div
class="euiText euiText--medium"
>
Body not recorded. Read our
<a
class="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/beats/heartbeat/current/configuration-heartbeat-options.html#monitor-http-response"
rel="noopener"
target="_blank"
>
docs 
<svg
aria-hidden="true"
class="euiIcon euiIcon--small euiIcon-isLoading"
focusable="false"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
/>
</a>
for more information on recording response bodies.
</div>
</dd>
</dl>
</div>
</div>
</div>
</div>
`;
exports[`PingListExpandedRow shallow renders link to docs if body is not recorded but it is present 1`] = `
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut
color="primary"
>
<EuiDescriptionList
listItems={
Array [
Object {
"description": <React.Fragment>
<BodyDescription
body={
Object {
"bytes": 1200000,
"hash": "testhash",
}
}
/>
<EuiSpacer
size="s"
/>
<DocLinkForBody />
</React.Fragment>,
"title": "Response Body",
},
]
}
/>
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
`;

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { DocLinkForBody } from '../doc_link_body';
describe('PingListExpandedRow', () => {
it('renders expected elements for valid props', () => {
expect(shallowWithIntl(<DocLinkForBody />)).toMatchSnapshot();
});
});

View file

@ -4,10 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { mountWithIntl, renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { PingListExpandedRowComponent } from '../expanded_row';
import { Ping } from '../../../../../common/graphql/types';
import { DocLinkForBody } from '../doc_link_body';
describe('PingListExpandedRow', () => {
let ping: Ping;
@ -56,4 +57,26 @@ describe('PingListExpandedRow', () => {
delete ping.http;
expect(shallowWithIntl(<PingListExpandedRowComponent ping={ping} />)).toMatchSnapshot();
});
it(`shallow renders link to docs if body is not recorded but it is present`, () => {
// @ts-ignore this shouldn't be undefined unless the beforeEach block is modified
delete ping.http.response.body.content;
expect(shallowWithIntl(<PingListExpandedRowComponent ping={ping} />)).toMatchSnapshot();
});
it(`renders link to docs if body is not recorded but it is present`, () => {
// @ts-ignore this shouldn't be undefined unless the beforeEach block is modified
delete ping.http.response.body.content;
expect(renderWithIntl(<PingListExpandedRowComponent ping={ping} />)).toMatchSnapshot();
});
it(`mount component to find link to docs if body is not recorded but it is present`, () => {
// @ts-ignore this shouldn't be undefined unless the beforeEach block is modified
delete ping.http.response.body.content;
const component = mountWithIntl(<PingListExpandedRowComponent ping={ping} />);
const docLinkComponent = component.find(DocLinkForBody);
expect(docLinkComponent).toHaveLength(1);
});
});

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiIcon, EuiLink, EuiText } from '@elastic/eui';
const bodyDocsLink =
'https://www.elastic.co/guide/en/beats/heartbeat/current/configuration-heartbeat-options.html#monitor-http-response';
export const DocLinkForBody = () => {
const docsLink = (
<EuiLink href={bodyDocsLink} target="_blank">
{i18n.translate('xpack.uptime.pingList.drawer.body.docsLink', {
defaultMessage: 'docs',
description: 'Docs link to set response body',
})}
&nbsp;
<EuiIcon size="s" type="popout" />
</EuiLink>
);
return (
<EuiText>
<FormattedMessage
id="xpack.uptime.pingList.expandedRow.response_body.notRecorded"
defaultMessage="Body not recorded. Read our {docsLink} for more information on recording response bodies."
values={{ docsLink }}
/>
</EuiText>
);
};

View file

@ -5,10 +5,19 @@
*/
// @ts-ignore formatNumber
import { formatNumber } from '@elastic/eui/lib/services/format';
import { EuiCodeBlock, EuiDescriptionList, EuiText } from '@elastic/eui';
import React, { Fragment } from 'react';
import {
EuiCallOut,
EuiCodeBlock,
EuiDescriptionList,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Ping, HttpBody } from '../../../../common/graphql/types';
import { DocLinkForBody } from './doc_link_body';
interface Props {
ping: Ping;
@ -52,7 +61,7 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => {
}
// Show the body, if present
if (ping.http && ping.http.response && ping.http.response.body) {
if (ping.http?.response?.body) {
const body = ping.http.response.body;
listItems.push({
@ -60,12 +69,21 @@ export const PingListExpandedRowComponent = ({ ping }: Props) => {
defaultMessage: 'Response Body',
}),
description: (
<Fragment>
<>
<BodyDescription body={body} />
<BodyExcerpt content={body.content || ''} />
</Fragment>
<EuiSpacer size={'s'} />
{body.content ? <BodyExcerpt content={body.content || ''} /> : <DocLinkForBody />}
</>
),
});
}
return <EuiDescriptionList listItems={listItems} />;
return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut color={ping?.error ? 'danger' : 'primary'}>
<EuiDescriptionList listItems={listItems} />
</EuiCallOut>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -115,6 +115,14 @@ export const PingListComponent = ({
})
);
const pings: Ping[] = data?.allPings?.pings ?? [];
const hasStatus: boolean = pings.reduce(
(hasHttpStatus: boolean, currentPing: Ping) =>
hasHttpStatus || !!currentPing.http?.response?.status_code,
false
);
const columns: any[] = [
{
field: 'monitor.status',
@ -172,55 +180,57 @@ export const PingListComponent = ({
}),
},
{
align: 'right',
align: hasStatus ? 'right' : 'center',
field: 'error.type',
name: i18n.translate('xpack.uptime.pingList.errorTypeColumnLabel', {
defaultMessage: 'Error type',
}),
render: (error: string) => error ?? '-',
},
];
const pings: Ping[] = data?.allPings?.pings ?? [];
const hasStatus: boolean = pings.reduce(
(hasHttpStatus: boolean, currentPing: Ping) =>
hasHttpStatus || !!currentPing.http?.response?.status_code,
false
);
if (hasStatus) {
columns.push({
field: 'http.response.status_code',
// Only add this column is there is any status present in list
...(hasStatus
? [
{
field: 'http.response.status_code',
align: 'right',
name: (
<SpanWithMargin>
{i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', {
defaultMessage: 'Response code',
})}
</SpanWithMargin>
),
render: (statusCode: string) => (
<SpanWithMargin>
<EuiBadge>{statusCode}</EuiBadge>
</SpanWithMargin>
),
},
]
: []),
{
align: 'right',
name: (
<SpanWithMargin>
{i18n.translate('xpack.uptime.pingList.responseCodeColumnLabel', {
defaultMessage: 'Response code',
})}
</SpanWithMargin>
),
render: (statusCode: string) => (
<SpanWithMargin>
<EuiBadge>{statusCode}</EuiBadge>{' '}
</SpanWithMargin>
),
});
}
width: '24px',
isExpander: true,
render: (item: Ping) => {
return (
<EuiButtonIcon
onClick={() => toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)}
disabled={!item.error && !(item.http?.response?.body?.bytes > 0)}
aria-label={
itemIdToExpandedRowMap[item.id]
? i18n.translate('xpack.uptime.pingList.collapseRow', {
defaultMessage: 'Collapse',
})
: i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' })
}
iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
/>
);
},
},
];
columns.push({
align: 'right',
width: '24px',
isExpander: true,
render: (item: Ping) => (
<EuiButtonIcon
onClick={() => toggleDetails(item, itemIdToExpandedRowMap, setItemIdToExpandedRowMap)}
aria-label={
itemIdToExpandedRowMap[item.id]
? i18n.translate('xpack.uptime.pingList.collapseRow', { defaultMessage: 'Collapse' })
: i18n.translate('xpack.uptime.pingList.expandRow', { defaultMessage: 'Expand' })
}
iconType={itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'}
/>
),
});
const pagination: Pagination = {
initialPageSize: 20,
pageIndex: 0,