[SIEM] Map Docs Link & Intrinsic Ratios (#49267)

* rough out markup and intrinsic ratio poc

* reorganize comps

* media queries for aspect ratios

* disable ratio when error; add translations

* unit tests, translations & cleanup

* update copy per ben’s suggestions

* snapshots and translations

* move paddingSize prop inline

* change panel selector

* fix FormattedMessage id

* update snapshots
This commit is contained in:
Michael Marcialis 2019-10-25 17:43:10 -04:00 committed by GitHub
parent ed07fb4c06
commit 7003c2c6d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 376 additions and 134 deletions

View file

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Embeddable it renders 1`] = `
<Component>
<Embeddable>
<p>
Test content
</p>
</Embeddable>
</Component>
`;

View file

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmbeddableHeader it renders 1`] = `
<Component>
<EmbeddableHeader
title="Test title"
/>
</Component>
`;

View file

@ -1,19 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmbeddedMap renders correctly against snapshot 1`] = `
<Fragment>
<Embeddable>
<EmbeddableHeader
title="Network map"
>
<EuiText
size="xs"
>
<EuiLink
href="undefinedguide/en/siem/guide/undefined/conf-map-ui.html"
target="_blank"
>
Map configuration help
</EuiLink>
</EuiText>
</EmbeddableHeader>
<InPortal
node={<div />}
>
<MapToolTip />
</InPortal>
<Styled(EuiFlexGroup)>
<EmbeddableMap
maintainRatio={true}
>
<Loader
data-test-subj="loading-panel"
overlay={true}
size="xl"
/>
</Styled(EuiFlexGroup)>
<EuiSpacer />
</Fragment>
</EmbeddableMap>
</Embeddable>
`;

View file

@ -15,43 +15,41 @@ exports[`IndexPatternsMissingPrompt renders correctly against snapshot 1`] = `
body={
<React.Fragment>
<p>
An ECS compliant Kibana index pattern must be configured to view event data on the map. When using beats, you can run the following setup commands to create the required Kibana index patterns, otherwise you can configure them manually within Kibana settings.
<FormattedMessage
defaultMessage="An ECS compliant Kibana index pattern must be configured to view events on the map. When using {beats}, you can run the {setup} command on your hosts to automatically create the index patterns. For example: {example}."
id="xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription1"
values={
Object {
"beats": <a
href="https://www.elastic.coguide/en/beats/libbeat/current/getting-started.html"
rel="noopener noreferrer"
target="_blank"
>
beats
</a>,
"example": <EuiCode>
./packetbeat setup
</EuiCode>,
"setup": <EuiCode>
setup
</EuiCode>,
}
}
/>
</p>
<p>
<ForwardRef
href="https://www.elastic.co/guide/en/beats/auditbeat/current/load-kibana-dashboards.html"
target="blank"
>
auditbeat-*
</ForwardRef>
,
<ForwardRef
href="https://www.elastic.co/guide/en/beats/filebeat/current/load-kibana-dashboards.html"
target="blank"
>
filebeat-*
</ForwardRef>
,
<ForwardRef
href="https://www.elastic.co/guide/en/beats/packetbeat/current/load-kibana-dashboards.html"
target="blank"
>
packetbeat-*
</ForwardRef>
,
<ForwardRef
href="https://www.elastic.co/guide/en/beats/winlogbeat/current/load-kibana-dashboards.html"
target="blank"
>
winlogbeat-*
</ForwardRef>
<FormattedMessage
defaultMessage="You can also configure them manually in Kibana."
id="xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription2"
values={Object {}}
/>
</p>
</React.Fragment>
}
iconType="gisApp"
title={
<h2>
Required Index Patterns Not Configured
Required index patterns not configured
</h2>
}
titleSize="xs"

View file

@ -0,0 +1,29 @@
/*
* 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 { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import React from 'react';
import '../../mock/ui_settings';
import { TestProviders } from '../../mock';
import { Embeddable } from './embeddable';
jest.mock('../../lib/settings/use_kibana_ui_setting');
describe('Embeddable', () => {
test('it renders', () => {
const wrapper = shallow(
<TestProviders>
<Embeddable>
<p>{'Test content'}</p>
</Embeddable>
</TestProviders>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
});

View file

@ -0,0 +1,25 @@
/*
* 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 { EuiPanel } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
const Panel = styled(EuiPanel)`
overflow: hidden;
`;
Panel.displayName = 'Panel';
export interface EmbeddableProps {
children: React.ReactNode;
}
export const Embeddable = React.memo<EmbeddableProps>(({ children }) => (
<section className="siemEmbeddable">
<Panel paddingSize="none">{children}</Panel>
</section>
));
Embeddable.displayName = 'Embeddable';

View file

@ -0,0 +1,74 @@
/*
* 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 { mount, shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import React from 'react';
import '../../mock/ui_settings';
import { TestProviders } from '../../mock';
import { EmbeddableHeader } from './embeddable_header';
jest.mock('../../lib/settings/use_kibana_ui_setting');
describe('EmbeddableHeader', () => {
test('it renders', () => {
const wrapper = shallow(
<TestProviders>
<EmbeddableHeader title="Test title" />
</TestProviders>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
test('it renders the title', () => {
const wrapper = mount(
<TestProviders>
<EmbeddableHeader title="Test title" />
</TestProviders>
);
expect(
wrapper
.find('[data-test-subj="header-embeddable-title"]')
.first()
.exists()
).toBe(true);
});
test('it renders supplements when children provided', () => {
const wrapper = mount(
<TestProviders>
<EmbeddableHeader title="Test title">
<p>{'Test children'}</p>
</EmbeddableHeader>
</TestProviders>
);
expect(
wrapper
.find('[data-test-subj="header-embeddable-supplements"]')
.first()
.exists()
).toBe(true);
});
test('it DOES NOT render supplements when children not provided', () => {
const wrapper = mount(
<TestProviders>
<EmbeddableHeader title="Test title" />
</TestProviders>
);
expect(
wrapper
.find('[data-test-subj="header-embeddable-supplements"]')
.first()
.exists()
).toBe(false);
});
});

View file

@ -0,0 +1,43 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import React from 'react';
import styled, { css } from 'styled-components';
const Header = styled.header.attrs({
className: 'siemEmbeddable__header',
})`
${({ theme }) => css`
border-bottom: ${theme.eui.euiBorderThin};
padding: ${theme.eui.paddingSizes.m};
`}
`;
Header.displayName = 'Header';
export interface EmbeddableHeaderProps {
children?: React.ReactNode;
title: string | React.ReactNode;
}
export const EmbeddableHeader = React.memo<EmbeddableHeaderProps>(({ children, title }) => (
<Header>
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem>
<EuiTitle size="xxxs">
<h6 data-test-subj="header-embeddable-title">{title}</h6>
</EuiTitle>
</EuiFlexItem>
{children && (
<EuiFlexItem data-test-subj="header-embeddable-supplements" grow={false}>
{children}
</EuiFlexItem>
)}
</EuiFlexGroup>
</Header>
));
EmbeddableHeader.displayName = 'EmbeddableHeader';

View file

@ -4,41 +4,74 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGroup, EuiSpacer } from '@elastic/eui';
import { EuiLink, EuiText } from '@elastic/eui';
import { Filter } from '@kbn/es-query';
import React, { useEffect, useState } from 'react';
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
import { createPortalNode, InPortal } from 'react-reverse-portal';
import { Query } from 'src/plugins/data/common';
import styled, { css } from 'styled-components';
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
import styled from 'styled-components';
import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
import { EmbeddablePanel } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers';
import { useIndexPatterns } from '../../hooks/use_index_patterns';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
import { useKibanaPlugins } from '../../lib/compose/kibana_plugins';
import { useKibanaCore } from '../../lib/compose/kibana_core';
import { useStateToaster } from '../toasters';
import { useKibanaPlugins } from '../../lib/compose/kibana_plugins';
import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting';
import { Loader } from '../loader';
import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapEmbeddable, SetQuery } from './types';
import * as i18n from './translations';
import { useStateToaster } from '../toasters';
import { Embeddable } from './embeddable';
import { EmbeddableHeader } from './embeddable_header';
import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers';
import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapToolTip } from './map_tool_tip/map_tool_tip';
import * as i18n from './translations';
import { MapEmbeddable, SetQuery } from './types';
const EmbeddableWrapper = styled(EuiFlexGroup)`
position: relative;
height: 400px;
margin: 0;
interface EmbeddableMapProps {
maintainRatio?: boolean;
}
.mapToolbarOverlay__button {
display: none;
}
const EmbeddableMap = styled.div.attrs({
className: 'siemEmbeddable__map',
})<EmbeddableMapProps>`
${({ maintainRatio, theme }) => css`
.embPanel {
border: none;
box-shadow: none;
}
.mapToolbarOverlay__button {
display: none;
}
${maintainRatio &&
css`
padding-top: calc(3 / 4 * 100%); //4:3 (standard) ratio
position: relative;
@media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) {
padding-top: calc(9 / 32 * 100%); //32:9 (ultra widescreen) ratio
}
@media only screen and (min-width: 1441px) and (min-height: 901px) {
padding-top: calc(9 / 21 * 100%); //21:9 (ultrawide) ratio
}
.embPanel {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
`}
`}
`;
EmbeddableMap.displayName = 'EmbeddableMap';
export interface EmbeddedMapProps {
query: Query;
@ -152,11 +185,23 @@ export const EmbeddedMap = React.memo<EmbeddedMapProps>(
}, [startDate, endDate]);
return isError ? null : (
<>
<Embeddable>
<EmbeddableHeader title={i18n.EMBEDDABLE_HEADER_TITLE}>
<EuiText size="xs">
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/conf-map-ui.html`}
target="_blank"
>
{i18n.EMBEDDABLE_HEADER_HELP}
</EuiLink>
</EuiText>
</EmbeddableHeader>
<InPortal node={portalNode}>
<MapToolTip />
</InPortal>
<EmbeddableWrapper>
<EmbeddableMap maintainRatio={!isIndexError}>
{embeddable != null ? (
<EmbeddablePanel
data-test-subj="embeddable-panel"
@ -174,9 +219,8 @@ export const EmbeddedMap = React.memo<EmbeddedMapProps>(
) : (
<Loader data-test-subj="loading-panel" overlay size="xl" />
)}
</EmbeddableWrapper>
<EuiSpacer />
</>
</EmbeddableMap>
</Embeddable>
);
}
);

View file

@ -4,69 +4,58 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
import { EuiButton, EuiCode, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import * as React from 'react';
import chrome from 'ui/chrome';
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
import * as i18n from './translations';
interface DocMapping {
beat: string;
docLink: string;
}
export const IndexPatternsMissingPrompt = React.memo(() => (
<EuiEmptyPrompt
iconType="gisApp"
title={<h2>{i18n.ERROR_TITLE}</h2>}
titleSize="xs"
body={
<>
<p>
<FormattedMessage
defaultMessage="An ECS compliant Kibana index pattern must be configured to view events on the map. When using {beats}, you can run the {setup} command on your hosts to automatically create the index patterns. For example: {example}."
id="xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription1"
values={{
beats: (
<a
href={`${ELASTIC_WEBSITE_URL}guide/en/beats/libbeat/${DOC_LINK_VERSION}/getting-started.html`}
rel="noopener noreferrer"
target="_blank"
>
{'beats'}
</a>
),
setup: <EuiCode>{'setup'}</EuiCode>,
example: <EuiCode>{'./packetbeat setup'}</EuiCode>,
}}
/>
</p>
export const IndexPatternsMissingPrompt = React.memo(() => {
const beatsSetupDocMapping: DocMapping[] = [
{
beat: 'auditbeat',
docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/auditbeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`,
},
{
beat: 'filebeat',
docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/filebeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`,
},
{
beat: 'packetbeat',
docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/packetbeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`,
},
{
beat: 'winlogbeat',
docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/winlogbeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`,
},
];
return (
<EuiEmptyPrompt
iconType="gisApp"
title={<h2>{i18n.ERROR_TITLE}</h2>}
titleSize="xs"
body={
<>
<p>{i18n.ERROR_DESCRIPTION}</p>
<p>
{beatsSetupDocMapping
.map<React.ReactNode>(v => (
<EuiLink key={v.beat} href={v.docLink} target="blank">
{`${v.beat}-*`}
</EuiLink>
))
.reduce((acc, v) => [acc, ', ', v])}
</p>
</>
}
actions={
<EuiButton
href={`${chrome.getBasePath()}/app/kibana#/management/kibana/index_patterns`}
color="primary"
target="_blank"
fill
>
{i18n.ERROR_BUTTON}
</EuiButton>
}
/>
);
});
<p>
<FormattedMessage
defaultMessage="You can also configure them manually in Kibana."
id="xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription2"
/>
</p>
</>
}
actions={
<EuiButton
href={`${chrome.getBasePath()}/app/kibana#/management/kibana/index_patterns`}
color="primary"
target="_blank"
fill
>
{i18n.ERROR_BUTTON}
</EuiButton>
}
/>
));

View file

@ -6,6 +6,20 @@
import { i18n } from '@kbn/i18n';
export const EMBEDDABLE_HEADER_TITLE = i18n.translate(
'xpack.siem.components.embeddables.embeddedMap.embeddableHeaderTitle',
{
defaultMessage: 'Network map',
}
);
export const EMBEDDABLE_HEADER_HELP = i18n.translate(
'xpack.siem.components.embeddables.embeddedMap.embeddableHeaderHelp',
{
defaultMessage: 'Map configuration help',
}
);
export const MAP_TITLE = i18n.translate(
'xpack.siem.components.embeddables.embeddedMap.embeddablePanelTitle',
{
@ -51,15 +65,7 @@ export const ERROR_CREATING_EMBEDDABLE = i18n.translate(
export const ERROR_TITLE = i18n.translate(
'xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorTitle',
{
defaultMessage: 'Required Index Patterns Not Configured',
}
);
export const ERROR_DESCRIPTION = i18n.translate(
'xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription',
{
defaultMessage:
'An ECS compliant Kibana index pattern must be configured to view event data on the map. When using beats, you can run the following setup commands to create the required Kibana index patterns, otherwise you can configure them manually within Kibana settings.',
defaultMessage: 'Required index patterns not configured',
}
);

View file

@ -14,9 +14,9 @@ import { EmbeddedMap } from '../../components/embeddables/embedded_map';
import { FiltersGlobal } from '../../components/filters_global';
import { HeaderPage } from '../../components/header_page';
import { LastEventTime } from '../../components/last_event_time';
import { SiemNavigation } from '../../components/navigation';
import { manageQuery } from '../../components/page/manage_query';
import { KpiNetworkComponent } from '../../components/page/network';
import { SiemNavigation } from '../../components/navigation';
import { SiemSearchBar } from '../../components/search_bar';
import { KpiNetworkQuery } from '../../containers/kpi_network';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
@ -26,7 +26,6 @@ import { convertToBuildEsQuery } from '../../lib/keury';
import { networkModel, State, inputsSelectors } from '../../store';
import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { SpyRoute } from '../../utils/route/spy_routes';
import { navTabsNetwork, NetworkRoutes, NetworkRoutesLoading } from './navigation';
import { NetworkEmptyPage } from './network_empty_page';
import * as i18n from './translations';
@ -78,6 +77,8 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
setQuery={setQuery}
/>
<EuiSpacer />
<KpiNetworkQuery
endDate={to}
filterQuery={filterQuery}

View file

@ -9318,7 +9318,6 @@
"xpack.siem.anomaliesTable.table.unit": "{totalCount, plural, =1 {anomaly} other {anomalies}}",
"xpack.siem.clipboard.copy.to.the.clipboard": "クリップボードにコピー",
"xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorButtonLabel": "インデックスパターンを編集",
"xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription": "マップにイベントデータを表示するには、ECS に準拠した Kibana インデックスパターンを構成する必要があります。ビートを使用すると、次のセットアップコマンドを実行して必要な Kibana インデックスパターンを作成するか、Kibana の設定で手動により構成することができます。",
"xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorTitle": "必要なインデックスパターンが構成されていません",
"xpack.siem.components.ml.anomaly.errors.anomaliesTableFetchFailureTitle": "異常表の取得に失敗",
"xpack.siem.components.ml.api.errors.networkErrorFailureTitle": "ネットワークエラー",
@ -10470,4 +10469,4 @@
"xpack.fileUpload.fileParser.errorReadingFile": "ファイルの読み込み中にエラーが発生しました",
"xpack.fileUpload.fileParser.noFileProvided": "エラー、ファイルが提供されていません"
}
}
}

View file

@ -9475,7 +9475,6 @@
"xpack.siem.anomaliesTable.table.unit": "{totalCount, plural, =1 {anomaly} other {anomalies}}",
"xpack.siem.clipboard.copy.to.the.clipboard": "复制到剪贴板",
"xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorButtonLabel": "配置索引模式",
"xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription": "必须配置符合 ECS 的 Kibana 索引模式,才能查看地图上的数据。使用 Beats 时,您可以运行以下设置命令来创建所需的 Kibana 索引模式,否则只能在 Kibana 设置内手动配置。",
"xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorTitle": "未配置所需的索引模式",
"xpack.siem.components.ml.anomaly.errors.anomaliesTableFetchFailureTitle": "异常表提取失败",
"xpack.siem.components.ml.api.errors.networkErrorFailureTitle": "网络错误:",
@ -10627,4 +10626,4 @@
"xpack.fileUpload.fileParser.errorReadingFile": "读取文件时出错",
"xpack.fileUpload.fileParser.noFileProvided": "错误,未提供任何文件"
}
}
}