[LogsUI] [InfraUI] Turn source configuration into a tab and standardize the main navigation (#42243) (#42887)

* Setup Logs routing for multiple pages

* Adds nested routing for logs
* Adds an index page to handle shared concerns
* Adds the Stream page at /logs/stream

* Introduce shared settings page

* Introduces shared/settings page
* Adds shared/settings page as a tab in the Logs routes
* Removes previous source configuration flyout traces from Logs pages

* Begin styling adjustments to settings page

* Implements use of EUI Panels
* Centers page content

* Add discard button

* Add discard button to allow resetting the form

* Fix button alignment

* Align Apply and Discard buttons to the right
* Align Loading button to the right

* Add EuiDescribedFormGroup for all form fields

* Add EuiDescribedFormGroup for name panel
* Add EuiDescribedFormGroup for indices panel
* Add EuiDescribedFormGroup for fields panel

* Remove all SourceConfigurationFlyout traces from the Infrastructure UI

* Add a ViewSourceConfigurationButton

* Adds a ViewSourceConfigurationButton component that will route to the /settings page
* Replace all instances of "View Configuration" buttons that were opening the flyout with the new button

* Enable settings tab amongst Infrastructure routes

* Change navigation to mimic SIEM

* Introduces an AppNavigation component
* Amends styling / handling of RoutedTabs to match SIEM implementation
* Adds new AppNavigation component to Infrastructure and Logs indexe pages

* Functional test amendments (WIP)

* Temporarily disable certain functional tests

* Remove unused imports

* Disable imports so build can pass

* Amend I18N errors

* I18N

* Automatically fix issues with i18n (node scripts/i18n_check --fix result)

* Functional tests

* Amend tests so they pass locally. Pending CI test.

* Amend RoutedTabs (without link focus style)

* Tweak RoutedTabs and AppNavigation for better performance / visuals

* Ensure outline isn't cut off
* Ensure only the react-router instance is hit for performance
* Ensure links still have href attributes for things like "Open in a new tab" even if history.push ultimately navigates

* Fix i18n usages

* node scripts/i18n_check --fix

* Amend functional test config (post Master merge fix)

* Remove unused function and fix unused import

* Add a Prompt to notify users when form changes will be lost

* Add aria-label to Button
This commit is contained in:
Kerry Gallagher 2019-08-08 10:41:53 +01:00 committed by GitHub
parent e08bcd9a5a
commit ff80382daa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1019 additions and 935 deletions

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiButtonIcon } from '@elastic/eui';
import { injectI18n } from '@kbn/i18n/react';
import React from 'react';
@ -20,20 +19,13 @@ import {
LogEntryColumnContent,
LogEntryColumnWidth,
LogEntryColumnWidths,
iconColumnId,
} from './log_entry_column';
import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel';
export const LogColumnHeaders = injectI18n<{
columnConfigurations: LogColumnConfiguration[];
columnWidths: LogEntryColumnWidths;
showColumnConfiguration: () => void;
}>(({ columnConfigurations, columnWidths, intl, showColumnConfiguration }) => {
const showColumnConfigurationLabel = intl.formatMessage({
id: 'xpack.infra.logColumnHeaders.configureColumnsLabel',
defaultMessage: 'Configure source',
});
}>(({ columnConfigurations, columnWidths, intl }) => {
return (
<LogColumnHeadersWrapper>
{columnConfigurations.map(columnConfiguration => {
@ -69,19 +61,6 @@ export const LogColumnHeaders = injectI18n<{
);
}
})}
<LogColumnHeader
columnWidth={columnWidths[iconColumnId]}
data-test-subj="logColumnHeader iconLogColumnHeader"
key="iconColumnHeader"
>
<EuiButtonIcon
aria-label={showColumnConfigurationLabel}
color="text"
iconType="gear"
onClick={showColumnConfiguration}
title={showColumnConfigurationLabel}
/>
</LogColumnHeader>
</LogColumnHeadersWrapper>
);
});

View file

@ -48,7 +48,6 @@ interface ScrollableLogTextStreamViewProps {
loadNewerItems: () => void;
setFlyoutItem: (id: string) => void;
setFlyoutVisibility: (visible: boolean) => void;
showColumnConfiguration: () => void;
intl: InjectedIntl;
highlightedItem: string | null;
currentHighlightKey: UniqueTimeKey | null;
@ -109,7 +108,6 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent<
items,
lastLoadedTime,
scale,
showColumnConfiguration,
wrap,
} = this.props;
const { targetId } = this.state;
@ -153,7 +151,6 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent<
<LogColumnHeaders
columnConfigurations={columnConfigurations}
columnWidths={columnWidths}
showColumnConfiguration={showColumnConfiguration}
/>
<AutoSizer bounds content detectAnyWindowResize="height">
{({ measureRef, bounds: { height = 0 }, content: { width = 0 } }) => (

View file

@ -6,19 +6,20 @@
import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useContext } from 'react';
import React from 'react';
import euiStyled from '../../../../../common/eui_styled_components';
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
import {
ViewSourceConfigurationButton,
ViewSourceConfigurationButtonHrefBase,
} from '../../components/source_configuration';
interface InvalidNodeErrorProps {
nodeName: string;
}
export const InvalidNodeError: React.FunctionComponent<InvalidNodeErrorProps> = ({ nodeName }) => {
const { showIndicesConfiguration } = useContext(SourceConfigurationFlyoutState.Context);
return (
<WithKibanaChrome>
{({ basePath }) => (
@ -57,12 +58,15 @@ export const InvalidNodeError: React.FunctionComponent<InvalidNodeErrorProps> =
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<EuiButton color="primary" onClick={showIndicesConfiguration}>
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
hrefBase={ViewSourceConfigurationButtonHrefBase.infrastructure}
>
<FormattedMessage
id="xpack.infra.configureSourceActionLabel"
defaultMessage="Change source configuration"
/>
</EuiButton>
</ViewSourceConfigurationButton>
</EuiFlexItem>
</EuiFlexGroup>
}

View file

@ -0,0 +1,33 @@
/*
* 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 } from '@elastic/eui';
import React from 'react';
import euiStyled from '../../../../../common/eui_styled_components';
interface AppNavigationProps {
children: React.ReactNode;
}
export const AppNavigation = ({ children }: AppNavigationProps) => (
<Nav>
<EuiFlexGroup gutterSize="m" alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>{children}</EuiFlexItem>
</EuiFlexGroup>
</Nav>
);
const Nav = euiStyled.nav`
background: ${props => props.theme.eui.euiColorEmptyShade};
border-bottom: ${props => props.theme.eui.euiBorderThin};
padding: ${props =>
`${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL} ${props.theme.eui.euiSize} ${props.theme.eui.euiSizeL}`}
.euiTabs {
padding-left: 3px;
margin-left: -3px;
}
`;

View file

@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiTab, EuiTabs } from '@elastic/eui';
import { EuiTab, EuiTabs, EuiLink } from '@elastic/eui';
import React from 'react';
import { Route } from 'react-router-dom';
import euiStyled from '../../../../../common/eui_styled_components';
interface TabConfiguration {
title: string;
@ -17,27 +18,42 @@ interface RoutedTabsProps {
tabs: TabConfiguration[];
}
const noop = () => {};
export class RoutedTabs extends React.Component<RoutedTabsProps> {
public render() {
return <EuiTabs>{this.renderTabs()}</EuiTabs>;
return <EuiTabs display="condensed">{this.renderTabs()}</EuiTabs>;
}
private renderTabs() {
return this.props.tabs.map(tab => {
return (
<Route
key={`${tab.path}${tab.title}`}
key={`${tab.path}-${tab.title}`}
path={tab.path}
children={({ match, history }) => (
<EuiTab
onClick={() => (match ? undefined : history.push(tab.path))}
isSelected={match !== null}
>
{tab.title}
</EuiTab>
<TabContainer className="euiTab">
<EuiLink
href={`#${tab.path}`}
onClick={e => {
e.preventDefault();
history.push(tab.path);
}}
>
<EuiTab onClick={noop} isSelected={match !== null}>
{tab.title}
</EuiTab>
</EuiLink>
</TabContainer>
)}
/>
);
});
}
}
const TabContainer = euiStyled.div`
.euiLink {
color: inherit !important;
}
`;

View file

@ -4,7 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiCode, EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
import {
EuiDescribedFormGroup,
EuiCode,
EuiFieldText,
EuiForm,
EuiFormRow,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
@ -39,145 +47,230 @@ export const FieldsConfigurationPanel = ({
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFormRow
error={timestampFieldProps.error}
fullWidth
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.timestampFieldDescription"
defaultMessage="Timestamp used to sort log entries. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>@timestamp</EuiCode>,
}}
/>
}
isInvalid={timestampFieldProps.isInvalid}
label={
<EuiDescribedFormGroup
idAria="timestampField"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.timestampFieldLabel"
defaultMessage="Timestamp"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...timestampFieldProps}
/>
</EuiFormRow>
<EuiFormRow
error={tiebreakerFieldProps.error}
fullWidth
helpText={
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.tiebreakerFieldDescription"
defaultMessage="Field used to break ties between two entries with the same timestamp. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>_doc</EuiCode>,
}}
id="xpack.infra.sourceConfiguration.timestampFieldDescription"
defaultMessage="Timestamp used to sort log entries"
/>
}
isInvalid={tiebreakerFieldProps.isInvalid}
label={
>
<EuiFormRow
describedByIds={['timestampField']}
error={timestampFieldProps.error}
fullWidth
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.timestampFieldRecommendedValue"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>@timestamp</EuiCode>,
}}
/>
}
isInvalid={timestampFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.timestampFieldLabel"
defaultMessage="Timestamp"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...timestampFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiDescribedFormGroup
idAria="tiebreakerField"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.tiebreakerFieldLabel"
defaultMessage="Tiebreaker"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...tiebreakerFieldProps}
/>
</EuiFormRow>
<EuiFormRow
error={containerFieldProps.error}
fullWidth
helpText={
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.containerFieldDescription"
defaultMessage="Field used to identify Docker containers. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>container.id</EuiCode>,
}}
id="xpack.infra.sourceConfiguration.tiebreakerFieldDescription"
defaultMessage="Field used to break ties between two entries with the same timestamp"
/>
}
isInvalid={containerFieldProps.isInvalid}
label={
>
<EuiFormRow
describedByIds={['tiebreakerField']}
error={tiebreakerFieldProps.error}
fullWidth
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.tiebreakerFieldRecommendedValue"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>_doc</EuiCode>,
}}
/>
}
isInvalid={tiebreakerFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.tiebreakerFieldLabel"
defaultMessage="Tiebreaker"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...tiebreakerFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiDescribedFormGroup
idAria="containerField"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.containerFieldLabel"
defaultMessage="Container ID"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...containerFieldProps}
/>
</EuiFormRow>
<EuiFormRow
error={hostFieldProps.error}
fullWidth
helpText={
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.hostFieldDescription"
defaultMessage="Field used to identify hosts. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>host.name</EuiCode>,
}}
id="xpack.infra.sourceConfiguration.containerFieldDescription"
defaultMessage="Field used to identify Docker containers"
/>
}
isInvalid={hostFieldProps.isInvalid}
label={
>
<EuiFormRow
describedByIds={['containerField']}
error={containerFieldProps.error}
fullWidth
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.containerFieldRecommendedValue"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>container.id</EuiCode>,
}}
/>
}
isInvalid={containerFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.containerFieldLabel"
defaultMessage="Container ID"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...containerFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiDescribedFormGroup
idAria="hostNameField"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.hostFieldLabel"
id="xpack.infra.sourceConfiguration.hostNameFieldLabel"
defaultMessage="Host name"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...hostFieldProps}
/>
</EuiFormRow>
<EuiFormRow
error={podFieldProps.error}
fullWidth
helpText={
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.podFieldDescription"
defaultMessage="Field used to identify Kubernetes pods. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>kubernetes.pod.uid</EuiCode>,
}}
id="xpack.infra.sourceConfiguration.hostNameFieldDescription"
defaultMessage="Field used to identify hosts"
/>
}
isInvalid={podFieldProps.isInvalid}
label={
>
<EuiFormRow
describedByIds={['hostNameField']}
error={hostFieldProps.error}
fullWidth
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.hostFieldDescription"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>host.name</EuiCode>,
}}
/>
}
isInvalid={hostFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.hostFieldLabel"
defaultMessage="Host name"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...hostFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiDescribedFormGroup
idAria="podField"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.podFieldLabel"
defaultMessage="Pod ID"
/>
}
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.podFieldDescription"
defaultMessage="Field used to identify Kubernetes pods"
/>
}
>
<EuiFieldText
<EuiFormRow
describedByIds={['podField']}
error={podFieldProps.error}
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...podFieldProps}
/>
</EuiFormRow>
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.podFieldRecommendedValue"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>kubernetes.pod.uid</EuiCode>,
}}
/>
}
isInvalid={podFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.podFieldLabel"
defaultMessage="Pod ID"
/>
}
>
<EuiFieldText
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...podFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
</EuiForm>
);

View file

@ -4,9 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { SourceConfigurationButton } from './source_configuration_button';
export { SourceConfigurationFlyout } from './source_configuration_flyout';
export { SourceConfigurationSettings } from './source_configuration_settings';
export {
SourceConfigurationFlyoutState,
useSourceConfigurationFlyoutState,
} from './source_configuration_flyout_state';
ViewSourceConfigurationButton,
ViewSourceConfigurationButtonHrefBase,
} from './view_source_configuration_button';

View file

@ -4,7 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiCode, EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
import {
EuiCode,
EuiDescribedFormGroup,
EuiFieldText,
EuiForm,
EuiFormRow,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
@ -33,63 +41,97 @@ export const IndicesConfigurationPanel = ({
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFormRow
error={metricAliasFieldProps.error}
fullWidth
helpText={
<EuiDescribedFormGroup
idAria="matricIndices"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.metricIndicesTitle"
defaultMessage="Metric Indices"
/>
}
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.metricIndicesDescription"
defaultMessage="Index pattern for matching indices that contain Metricbeat data. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>metricbeat-*</EuiCode>,
}}
/>
}
isInvalid={metricAliasFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.metricIndicesLabel"
defaultMessage="Metric indices"
defaultMessage="Index pattern for matching indices that contain Metricbeat data"
/>
}
>
<EuiFieldText
data-test-subj="metricIndicesInput"
<EuiFormRow
describedByIds={['metricIndices']}
error={metricAliasFieldProps.error}
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...metricAliasFieldProps}
/>
</EuiFormRow>
<EuiFormRow
error={logAliasFieldProps.error}
fullWidth
helpText={
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.metricIndicesRecommendedValue"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>metricbeat-*</EuiCode>,
}}
/>
}
isInvalid={metricAliasFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.metricIndicesLabel"
defaultMessage="Metric indices"
/>
}
>
<EuiFieldText
data-test-subj="metricIndicesInput"
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...metricAliasFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
<EuiDescribedFormGroup
idAria="logIndices"
title={
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesTitle"
defaultMessage="Log Indices"
/>
}
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesDescription"
defaultMessage="Index pattern for matching indices that contain log data. The recommended value is {defaultValue}."
values={{
defaultValue: <EuiCode>filebeat-*</EuiCode>,
}}
/>
}
isInvalid={logAliasFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesLabel"
defaultMessage="Log indices"
defaultMessage="Index pattern for matching indices that contain log data"
/>
}
>
<EuiFieldText
data-test-subj="logIndicesInput"
<EuiFormRow
describedByIds={['logIndices']}
error={logAliasFieldProps.error}
fullWidth
disabled={isLoading}
isLoading={isLoading}
readOnly={readOnly}
{...logAliasFieldProps}
/>
</EuiFormRow>
helpText={
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesRecommendedValue"
defaultMessage="The recommended value is {defaultValue}"
values={{
defaultValue: <EuiCode>filebeat-*</EuiCode>,
}}
/>
}
isInvalid={logAliasFieldProps.isInvalid}
label={
<FormattedMessage
id="xpack.infra.sourceConfiguration.logIndicesLabel"
defaultMessage="Log indices"
/>
}
>
<EuiFieldText
data-test-subj="logIndicesInput"
fullWidth
disabled={isLoading}
isLoading={isLoading}
readOnly={readOnly}
{...logAliasFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
</EuiForm>
);

View file

@ -56,7 +56,7 @@ export const LogColumnsConfigurationPanel: React.FunctionComponent<
<h3>
<FormattedMessage
id="xpack.infra.sourceConfiguration.logColumnsSectionTitle"
defaultMessage="Columns"
defaultMessage="Log Columns"
/>
</h3>
</EuiTitle>
@ -245,6 +245,7 @@ const RemoveLogColumnButton: React.FunctionComponent<{
iconType="trash"
onClick={onClick}
title={removeColumnLabel}
aria-label={removeColumnLabel}
/>
);
};

View file

@ -4,7 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
import {
EuiDescribedFormGroup,
EuiFieldText,
EuiForm,
EuiFormRow,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
@ -31,22 +38,36 @@ export const NameConfigurationPanel = ({
</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFormRow
error={nameFieldProps.error}
fullWidth
isInvalid={nameFieldProps.isInvalid}
label={
<EuiDescribedFormGroup
idAria="name"
title={
<FormattedMessage id="xpack.infra.sourceConfiguration.nameLabel" defaultMessage="Name" />
}
description={
<FormattedMessage
id="xpack.infra.sourceConfiguration.nameDescription"
defaultMessage="A descriptive name for the source configuration"
/>
}
>
<EuiFieldText
data-test-subj="nameInput"
<EuiFormRow
describedByIds={['name']}
error={nameFieldProps.error}
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...nameFieldProps}
/>
</EuiFormRow>
isInvalid={nameFieldProps.isInvalid}
label={
<FormattedMessage id="xpack.infra.sourceConfiguration.nameLabel" defaultMessage="Name" />
}
>
<EuiFieldText
data-test-subj="nameInput"
fullWidth
disabled={isLoading}
readOnly={readOnly}
isLoading={isLoading}
{...nameFieldProps}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
</EuiForm>
);

View file

@ -1,31 +0,0 @@
/*
* 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 { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useContext } from 'react';
import { SourceConfigurationFlyoutState } from './source_configuration_flyout_state';
export const SourceConfigurationButton: React.FunctionComponent = () => {
const { toggleIsVisible } = useContext(SourceConfigurationFlyoutState.Context);
return (
<EuiButtonEmpty
aria-label="Configure source"
color="text"
data-test-subj="configureSourceButton"
iconType="gear"
onClick={toggleIsVisible}
size="xs"
>
<FormattedMessage
id="xpack.infra.sourceConfiguration.sourceConfigurationButtonLabel"
defaultMessage="Configuration"
/>
</EuiButtonEmpty>
);
};

View file

@ -1,281 +0,0 @@
/*
* 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 {
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiTabbedContent,
EuiTabbedContentTab,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react';
import React, { useCallback, useContext, useMemo } from 'react';
import { Source } from '../../containers/source';
import { FieldsConfigurationPanel } from './fields_configuration_panel';
import { IndicesConfigurationPanel } from './indices_configuration_panel';
import { NameConfigurationPanel } from './name_configuration_panel';
import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel';
import { isValidTabId, SourceConfigurationFlyoutState } from './source_configuration_flyout_state';
import { useSourceConfigurationFormState } from './source_configuration_form_state';
const noop = () => undefined;
interface SourceConfigurationFlyoutProps {
intl: InjectedIntl;
shouldAllowEdit: boolean;
}
export const SourceConfigurationFlyout = injectI18n(
({ intl, shouldAllowEdit }: SourceConfigurationFlyoutProps) => {
const { activeTabId, hide, isVisible, setActiveTab } = useContext(
SourceConfigurationFlyoutState.Context
);
const {
createSourceConfiguration,
source,
sourceExists,
isLoading,
updateSourceConfiguration,
} = useContext(Source.Context);
const availableFields = useMemo(
() => (source && source.status ? source.status.indexFields.map(field => field.name) : []),
[source]
);
const {
addLogColumn,
moveLogColumn,
indicesConfigurationProps,
logColumnConfigurationProps,
errors,
resetForm,
isFormDirty,
isFormValid,
formState,
formStateChanges,
} = useSourceConfigurationFormState(source && source.configuration);
const persistUpdates = useCallback(async () => {
if (sourceExists) {
await updateSourceConfiguration(formStateChanges);
} else {
await createSourceConfiguration(formState);
}
resetForm();
}, [
sourceExists,
updateSourceConfiguration,
createSourceConfiguration,
resetForm,
formState,
formStateChanges,
]);
const isWriteable = useMemo(() => shouldAllowEdit && source && source.origin !== 'internal', [
shouldAllowEdit,
source,
]);
const tabs: EuiTabbedContentTab[] = useMemo(
() =>
isVisible
? [
{
id: 'indicesAndFieldsTab',
name: intl.formatMessage({
id: 'xpack.infra.sourceConfiguration.sourceConfigurationIndicesTabTitle',
defaultMessage: 'Indices and fields',
}),
content: (
<>
<EuiSpacer />
<NameConfigurationPanel
isLoading={isLoading}
nameFieldProps={indicesConfigurationProps.name}
readOnly={!isWriteable}
/>
<EuiSpacer />
<IndicesConfigurationPanel
isLoading={isLoading}
logAliasFieldProps={indicesConfigurationProps.logAlias}
metricAliasFieldProps={indicesConfigurationProps.metricAlias}
readOnly={!isWriteable}
/>
<EuiSpacer />
<FieldsConfigurationPanel
containerFieldProps={indicesConfigurationProps.containerField}
hostFieldProps={indicesConfigurationProps.hostField}
isLoading={isLoading}
podFieldProps={indicesConfigurationProps.podField}
readOnly={!isWriteable}
tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField}
timestampFieldProps={indicesConfigurationProps.timestampField}
/>
</>
),
},
{
id: 'logsTab',
name: intl.formatMessage({
id: 'xpack.infra.sourceConfiguration.sourceConfigurationLogColumnsTabTitle',
defaultMessage: 'Log Columns',
}),
content: (
<>
<EuiSpacer />
<LogColumnsConfigurationPanel
addLogColumn={addLogColumn}
moveLogColumn={moveLogColumn}
availableFields={availableFields}
isLoading={isLoading}
logColumnConfiguration={logColumnConfigurationProps}
/>
</>
),
},
]
: [],
[
addLogColumn,
moveLogColumn,
availableFields,
indicesConfigurationProps,
intl.formatMessage,
isLoading,
isVisible,
logColumnConfigurationProps,
isWriteable,
]
);
const activeTab = useMemo(() => tabs.filter(tab => tab.id === activeTabId)[0] || tabs[0], [
activeTabId,
tabs,
]);
const activateTab = useCallback(
(tab: EuiTabbedContentTab) => {
const tabId = tab.id;
if (isValidTabId(tabId)) {
setActiveTab(tabId);
}
},
[setActiveTab, isValidTabId]
);
if (!isVisible || !source || !source.configuration) {
return null;
}
return (
<EuiFlyout
aria-labelledby="sourceConfigurationTitle"
data-test-subj="sourceConfigurationFlyout"
hideCloseButton
onClose={noop}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle>
<h2 id="sourceConfigurationTitle">
{isWriteable ? (
<FormattedMessage
id="xpack.infra.sourceConfiguration.sourceConfigurationTitle"
defaultMessage="Configure source"
/>
) : (
<FormattedMessage
id="xpack.infra.sourceConfiguration.sourceConfigurationReadonlyTitle"
defaultMessage="View source configuration"
/>
)}
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiTabbedContent onTabClick={activateTab} selectedTab={activeTab} tabs={tabs} />
</EuiFlyoutBody>
<EuiFlyoutFooter>
{errors.length > 0 ? (
<>
<EuiCallOut color="danger">
<ul>
{errors.map((error, errorIndex) => (
<li key={errorIndex}>{error}</li>
))}
</ul>
</EuiCallOut>
<EuiSpacer size="m" />
</>
) : null}
<EuiFlexGroup>
<EuiFlexItem grow={false}>
{!isFormDirty ? (
<EuiButtonEmpty
data-test-subj="closeFlyoutButton"
iconType="cross"
isDisabled={isLoading}
onClick={() => hide()}
>
<FormattedMessage
id="xpack.infra.sourceConfiguration.closeButtonLabel"
defaultMessage="Close"
/>
</EuiButtonEmpty>
) : (
<EuiButtonEmpty
data-test-subj="discardAndCloseFlyoutButton"
color="danger"
iconType="cross"
isDisabled={isLoading}
onClick={() => {
resetForm();
hide();
}}
>
<FormattedMessage
id="xpack.infra.sourceConfiguration.discardAndCloseButtonLabel"
defaultMessage="Discard and Close"
/>
</EuiButtonEmpty>
)}
</EuiFlexItem>
<EuiFlexItem />
{isWriteable && (
<EuiFlexItem grow={false}>
{isLoading ? (
<EuiButton color="primary" isLoading fill>
Loading
</EuiButton>
) : (
<EuiButton
data-test-subj="updateSourceConfigurationButton"
color="primary"
isDisabled={!isFormDirty || !isFormValid}
fill
onClick={persistUpdates}
>
<FormattedMessage
id="xpack.infra.sourceConfiguration.updateSourceConfigurationButtonLabel"
defaultMessage="Update Source"
/>
</EuiButton>
)}
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
);
}
);

View file

@ -1,51 +0,0 @@
/*
* 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 createContainer from 'constate-latest';
import { useCallback, useState } from 'react';
import { useVisibilityState } from '../../utils/use_visibility_state';
type TabId = 'indicesAndFieldsTab' | 'logsTab';
const validTabIds: TabId[] = ['indicesAndFieldsTab', 'logsTab'];
export const useSourceConfigurationFlyoutState = ({
initialVisibility = false,
initialTab = 'indicesAndFieldsTab',
}: {
initialVisibility?: boolean;
initialTab?: TabId;
} = {}) => {
const { isVisible, show, hide, toggle: toggleIsVisible } = useVisibilityState(initialVisibility);
const [activeTabId, setActiveTab] = useState(initialTab);
const showWithTab = useCallback(
(tabId?: TabId) => {
if (tabId != null) {
setActiveTab(tabId);
}
show();
},
[show]
);
const showIndicesConfiguration = useCallback(() => showWithTab('indicesAndFieldsTab'), [show]);
const showLogsConfiguration = useCallback(() => showWithTab('logsTab'), [show]);
return {
activeTabId,
hide,
isVisible,
setActiveTab,
show: showWithTab,
showIndicesConfiguration,
showLogsConfiguration,
toggleIsVisible,
};
};
export const isValidTabId = (value: any): value is TabId => validTabIds.includes(value);
export const SourceConfigurationFlyoutState = createContainer(useSourceConfigurationFlyoutState);

View file

@ -0,0 +1,213 @@
/*
* 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 {
EuiButton,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiPage,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react';
import React, { useCallback, useContext, useMemo } from 'react';
import { Prompt } from 'react-router-dom';
import { Source } from '../../containers/source';
import { FieldsConfigurationPanel } from './fields_configuration_panel';
import { IndicesConfigurationPanel } from './indices_configuration_panel';
import { NameConfigurationPanel } from './name_configuration_panel';
import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel';
import { useSourceConfigurationFormState } from './source_configuration_form_state';
interface SourceConfigurationSettingsProps {
intl: InjectedIntl;
shouldAllowEdit: boolean;
}
export const SourceConfigurationSettings = injectI18n(
({ shouldAllowEdit }: SourceConfigurationSettingsProps) => {
const {
createSourceConfiguration,
source,
sourceExists,
isLoading,
updateSourceConfiguration,
} = useContext(Source.Context);
const availableFields = useMemo(
() => (source && source.status ? source.status.indexFields.map(field => field.name) : []),
[source]
);
const {
addLogColumn,
moveLogColumn,
indicesConfigurationProps,
logColumnConfigurationProps,
errors,
resetForm,
isFormDirty,
isFormValid,
formState,
formStateChanges,
} = useSourceConfigurationFormState(source && source.configuration);
const persistUpdates = useCallback(async () => {
if (sourceExists) {
await updateSourceConfiguration(formStateChanges);
} else {
await createSourceConfiguration(formState);
}
resetForm();
}, [
sourceExists,
updateSourceConfiguration,
createSourceConfiguration,
resetForm,
formState,
formStateChanges,
]);
const isWriteable = useMemo(() => shouldAllowEdit && source && source.origin !== 'internal', [
shouldAllowEdit,
source,
]);
if (!source || !source.configuration) {
return null;
}
return (
<>
<EuiPage>
<EuiPageBody>
<EuiPageContent
verticalPosition="center"
horizontalPosition="center"
data-test-subj="sourceConfigurationContent"
>
<EuiPageContentBody>
<Prompt
when={isFormDirty}
message={i18n.translate('xpack.infra.sourceConfiguration.unsavedFormPrompt', {
defaultMessage: 'Are you sure you want to leave? Changes will be lost',
})}
/>
<EuiPanel paddingSize="l">
<NameConfigurationPanel
isLoading={isLoading}
nameFieldProps={indicesConfigurationProps.name}
readOnly={!isWriteable}
/>
</EuiPanel>
<EuiSpacer />
<EuiPanel paddingSize="l">
<IndicesConfigurationPanel
isLoading={isLoading}
logAliasFieldProps={indicesConfigurationProps.logAlias}
metricAliasFieldProps={indicesConfigurationProps.metricAlias}
readOnly={!isWriteable}
/>
</EuiPanel>
<EuiSpacer />
<EuiPanel paddingSize="l">
<FieldsConfigurationPanel
containerFieldProps={indicesConfigurationProps.containerField}
hostFieldProps={indicesConfigurationProps.hostField}
isLoading={isLoading}
podFieldProps={indicesConfigurationProps.podField}
readOnly={!isWriteable}
tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField}
timestampFieldProps={indicesConfigurationProps.timestampField}
/>
</EuiPanel>
<EuiSpacer />
<EuiPanel paddingSize="l">
<LogColumnsConfigurationPanel
addLogColumn={addLogColumn}
moveLogColumn={moveLogColumn}
availableFields={availableFields}
isLoading={isLoading}
logColumnConfiguration={logColumnConfigurationProps}
/>
</EuiPanel>
{errors.length > 0 ? (
<>
<EuiCallOut color="danger">
<ul>
{errors.map((error, errorIndex) => (
<li key={errorIndex}>{error}</li>
))}
</ul>
</EuiCallOut>
<EuiSpacer size="m" />
</>
) : null}
<EuiSpacer size="m" />
<EuiFlexGroup>
{isWriteable && (
<EuiFlexItem>
{isLoading ? (
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButton color="primary" isLoading fill>
Loading
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
) : (
<>
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="discardSettingsButton"
color="danger"
iconType="cross"
isDisabled={isLoading || !isFormDirty}
onClick={() => {
resetForm();
}}
>
<FormattedMessage
id="xpack.infra.sourceConfiguration.discardSettingsButtonLabel"
defaultMessage="Discard"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="applySettingsButton"
color="primary"
isDisabled={!isFormDirty || !isFormValid}
fill
onClick={persistUpdates}
>
<FormattedMessage
id="xpack.infra.sourceConfiguration.applySettingsButtonLabel"
defaultMessage="Apply"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</>
)}
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</>
);
}
);

View file

@ -0,0 +1,40 @@
/*
* 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 { EuiButton } from '@elastic/eui';
import React from 'react';
import { Route } from 'react-router-dom';
export enum ViewSourceConfigurationButtonHrefBase {
infrastructure = 'infrastructure',
logs = 'logs',
}
interface ViewSourceConfigurationButtonProps {
'data-test-subj'?: string;
hrefBase: ViewSourceConfigurationButtonHrefBase;
children: React.ReactNode;
}
export const ViewSourceConfigurationButton = ({
'data-test-subj': dataTestSubj,
hrefBase,
children,
}: ViewSourceConfigurationButtonProps) => {
const href = `/${hrefBase}/settings`;
return (
<Route
key={href}
path={href}
children={({ match, history }) => (
<EuiButton data-test-subj={dataTestSubj} color="primary" onClick={() => history.push(href)}>
{children}
</EuiButton>
)}
/>
);
};

View file

@ -15,10 +15,11 @@ import { ColumnarPage } from '../../components/page';
import { MetricsExplorerOptionsContainer } from '../../containers/metrics_explorer/use_metrics_explorer_options';
import { WithMetricsExplorerOptionsUrlState } from '../../containers/metrics_explorer/with_metrics_explorer_options_url_state';
import { WithSource } from '../../containers/with_source';
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
import { Source } from '../../containers/source';
import { MetricsExplorerPage } from './metrics_explorer';
import { SnapshotPage } from './snapshot';
import { SettingsPage } from '../shared/settings';
import { AppNavigation } from '../../components/navigation/app_navigation';
interface InfrastructurePageProps extends RouteComponentProps {
intl: InjectedIntl;
@ -26,23 +27,23 @@ interface InfrastructurePageProps extends RouteComponentProps {
export const InfrastructurePage = injectI18n(({ match, intl }: InfrastructurePageProps) => (
<Source.Provider sourceId="default">
<SourceConfigurationFlyoutState.Provider>
<ColumnarPage>
<DocumentTitle
title={intl.formatMessage({
id: 'xpack.infra.homePage.documentTitle',
defaultMessage: 'Infrastructure',
})}
/>
<ColumnarPage>
<DocumentTitle
title={intl.formatMessage({
id: 'xpack.infra.homePage.documentTitle',
defaultMessage: 'Infrastructure',
})}
/>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/infrastructure"
feedbackLinkText={intl.formatMessage({
id: 'xpack.infra.infrastructure.infrastructureHelpContent.feedbackLinkText',
defaultMessage: 'Provide feedback for Infrastructure',
})}
/>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/infrastructure"
feedbackLinkText={intl.formatMessage({
id: 'xpack.infra.infrastructure.infrastructureHelpContent.feedbackLinkText',
defaultMessage: 'Provide feedback for Infrastructure',
})}
/>
<AppNavigation>
<RoutedTabs
tabs={[
{
@ -59,30 +60,38 @@ export const InfrastructurePage = injectI18n(({ match, intl }: InfrastructurePag
}),
path: `${match.path}/metrics-explorer`,
},
{
title: intl.formatMessage({
id: 'xpack.infra.homePage.settingsTabTitle',
defaultMessage: 'Settings',
}),
path: `${match.path}/settings`,
},
]}
/>
</AppNavigation>
<Switch>
<Route path={`${match.path}/inventory`} component={SnapshotPage} />
<Route
path={`${match.path}/metrics-explorer`}
render={props => (
<WithSource>
{({ configuration, createDerivedIndexPattern }) => (
<MetricsExplorerOptionsContainer.Provider>
<WithMetricsExplorerOptionsUrlState />
<MetricsExplorerPage
derivedIndexPattern={createDerivedIndexPattern('metrics')}
source={configuration}
{...props}
/>
</MetricsExplorerOptionsContainer.Provider>
)}
</WithSource>
)}
/>
</Switch>
</ColumnarPage>
</SourceConfigurationFlyoutState.Provider>
<Switch>
<Route path={`${match.path}/inventory`} component={SnapshotPage} />
<Route
path={`${match.path}/metrics-explorer`}
render={props => (
<WithSource>
{({ configuration, createDerivedIndexPattern }) => (
<MetricsExplorerOptionsContainer.Provider>
<WithMetricsExplorerOptionsUrlState />
<MetricsExplorerPage
derivedIndexPattern={createDerivedIndexPattern('metrics')}
source={configuration}
{...props}
/>
</MetricsExplorerOptionsContainer.Provider>
)}
</WithSource>
)}
/>
<Route path={`${match.path}/settings`} component={SettingsPage} />
</Switch>
</ColumnarPage>
</Source.Provider>
));

View file

@ -17,10 +17,12 @@ import { NoIndices } from '../../../components/empty_states/no_indices';
import { Header } from '../../../components/header';
import { ColumnarPage } from '../../../components/page';
import { SourceConfigurationFlyout } from '../../../components/source_configuration';
import { SourceConfigurationFlyoutState } from '../../../components/source_configuration';
import { SourceErrorPage } from '../../../components/source_error_page';
import { SourceLoadingPage } from '../../../components/source_loading_page';
import {
ViewSourceConfigurationButton,
ViewSourceConfigurationButtonHrefBase,
} from '../../../components/source_configuration';
import { Source } from '../../../containers/source';
import { WithWaffleFilterUrlState } from '../../../containers/waffle/with_waffle_filters';
import { WithWaffleOptionsUrlState } from '../../../containers/waffle/with_waffle_options';
@ -36,7 +38,6 @@ interface SnapshotPageProps {
export const SnapshotPage = injectUICapabilities(
injectI18n((props: SnapshotPageProps) => {
const { intl, uiCapabilities } = props;
const { showIndicesConfiguration } = useContext(SourceConfigurationFlyoutState.Context);
const {
createDerivedIndexPattern,
hasFailedLoadingSource,
@ -76,9 +77,6 @@ export const SnapshotPage = injectUICapabilities(
]}
readOnlyBadge={!uiCapabilities.infrastructure.save}
/>
<SourceConfigurationFlyout
shouldAllowEdit={uiCapabilities.infrastructure.configureSource as boolean}
/>
{isLoading ? (
<SourceLoadingPage />
) : metricIndicesExist ? (
@ -120,16 +118,15 @@ export const SnapshotPage = injectUICapabilities(
</EuiFlexItem>
{uiCapabilities.infrastructure.configureSource ? (
<EuiFlexItem>
<EuiButton
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
color="primary"
onClick={showIndicesConfiguration}
hrefBase={ViewSourceConfigurationButtonHrefBase.infrastructure}
>
{intl.formatMessage({
id: 'xpack.infra.configureSourceActionLabel',
defaultMessage: 'Change source configuration',
})}
</EuiButton>
</ViewSourceConfigurationButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>

View file

@ -10,7 +10,6 @@ import React from 'react';
import { AutocompleteField } from '../../../components/autocomplete_field';
import { Toolbar } from '../../../components/eui/toolbar';
import { SourceConfigurationButton } from '../../../components/source_configuration';
import { WaffleGroupByControls } from '../../../components/waffle/waffle_group_by_controls';
import { WaffleMetricControls } from '../../../components/waffle/waffle_metric_controls';
import { WaffleNodeTypeSwitcher } from '../../../components/waffle/waffle_node_type_switcher';
@ -111,9 +110,6 @@ export const SnapshotToolbar = injectI18n(({ intl }) => (
customOptions={customOptions}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SourceConfigurationButton />
</EuiFlexItem>
</React.Fragment>
)}
</WithWaffleOptions>

View file

@ -0,0 +1,69 @@
/*
* 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 { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
import { DocumentTitle } from '../../components/document_title';
import { HelpCenterContent } from '../../components/help_center_content';
import { RoutedTabs } from '../../components/navigation/routed_tabs';
import { ColumnarPage } from '../../components/page';
import { Source } from '../../containers/source';
import { StreamPage } from './stream';
import { SettingsPage } from '../shared/settings';
import { AppNavigation } from '../../components/navigation/app_navigation';
interface LogsPageProps extends RouteComponentProps {
intl: InjectedIntl;
}
export const LogsPage = injectI18n(({ match, intl }: LogsPageProps) => (
<Source.Provider sourceId="default">
<ColumnarPage>
<DocumentTitle
title={intl.formatMessage({
id: 'xpack.infra.logs.index.documentTitle',
defaultMessage: 'Logs',
})}
/>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/logs"
feedbackLinkText={intl.formatMessage({
id: 'xpack.infra.logsPage.logsHelpContent.feedbackLinkText',
defaultMessage: 'Provide feedback for Logs',
})}
/>
<AppNavigation>
<RoutedTabs
tabs={[
{
title: intl.formatMessage({
id: 'xpack.infra.logs.index.streamTabTitle',
defaultMessage: 'Stream',
}),
path: `${match.path}/stream`,
},
{
title: intl.formatMessage({
id: 'xpack.infra.logs.index.settingsTabTitle',
defaultMessage: 'Settings',
}),
path: `${match.path}/settings`,
},
]}
/>
</AppNavigation>
<Switch>
<Route path={`${match.path}/stream`} component={StreamPage} />
<Route path={`${match.path}/settings`} component={SettingsPage} />
</Switch>
</ColumnarPage>
</Source.Provider>
));

View file

@ -1,57 +0,0 @@
/*
* 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 { injectI18n, InjectedIntl } from '@kbn/i18n/react';
import React from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { DocumentTitle } from '../../components/document_title';
import { Header } from '../../components/header';
import { HelpCenterContent } from '../../components/help_center_content';
import { SourceConfigurationFlyout } from '../../components/source_configuration';
interface LogsPageHeaderProps {
intl: InjectedIntl;
uiCapabilities: UICapabilities;
}
export const LogsPageHeader = injectUICapabilities(
injectI18n((props: LogsPageHeaderProps) => {
const { intl, uiCapabilities } = props;
return (
<>
<Header
breadcrumbs={[
{
text: intl.formatMessage({
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
defaultMessage: 'Logs',
}),
},
]}
readOnlyBadge={!uiCapabilities.logs.save}
/>
<DocumentTitle
title={intl.formatMessage({
id: 'xpack.infra.logsPage.documentTitle',
defaultMessage: 'Logs',
})}
/>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/logs"
feedbackLinkText={intl.formatMessage({
id: 'xpack.infra.logsPage.logsHelpContent.feedbackLinkText',
defaultMessage: 'Provide feedback for Logs',
})}
/>
<SourceConfigurationFlyout
shouldAllowEdit={uiCapabilities.logs.configureSource as boolean}
/>
</>
);
})
);

View file

@ -1,33 +0,0 @@
/*
* 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 { SourceConfigurationFlyoutState } from '../../components/source_configuration';
import { LogFlyout } from '../../containers/logs/log_flyout';
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
import { LogHighlightsState } from '../../containers/logs/log_highlights/log_highlights';
import { Source, useSource } from '../../containers/source';
import { useSourceId } from '../../containers/source_id';
export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
const [sourceId] = useSourceId();
const source = useSource({ sourceId });
return (
<Source.Context.Provider value={source}>
<SourceConfigurationFlyoutState.Provider>
<LogViewConfiguration.Provider>
<LogFlyout.Provider>
<LogHighlightsState.Provider sourceId={sourceId} sourceVersion={source.version}>
{children}
</LogHighlightsState.Provider>
</LogFlyout.Provider>
</LogViewConfiguration.Provider>
</SourceConfigurationFlyoutState.Provider>
</Source.Context.Provider>
);
};

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { LogsPage } from './page';
export { StreamPage } from './page';

View file

@ -6,20 +6,20 @@
import React from 'react';
import { ColumnarPage } from '../../components/page';
import { LogsPageContent } from './page_content';
import { LogsPageHeader } from './page_header';
import { ColumnarPage } from '../../../components/page';
import { StreamPageContent } from './page_content';
import { StreamPageHeader } from './page_header';
import { LogsPageProviders } from './page_providers';
import { useTrackPageview } from '../../hooks/use_track_metric';
import { useTrackPageview } from '../../../hooks/use_track_metric';
export const LogsPage = () => {
export const StreamPage = () => {
useTrackPageview({ app: 'infra_logs', path: 'stream' });
useTrackPageview({ app: 'infra_logs', path: 'stream', delay: 15000 });
return (
<LogsPageProviders>
<ColumnarPage data-test-subj="infraLogsPage">
<LogsPageHeader />
<LogsPageContent />
<StreamPageHeader />
<StreamPageContent />
</ColumnarPage>
</LogsPageProviders>
);

View file

@ -6,13 +6,13 @@
import React, { useContext } from 'react';
import { SourceErrorPage } from '../../components/source_error_page';
import { SourceLoadingPage } from '../../components/source_loading_page';
import { Source } from '../../containers/source';
import { SourceErrorPage } from '../../../components/source_error_page';
import { SourceLoadingPage } from '../../../components/source_loading_page';
import { Source } from '../../../containers/source';
import { LogsPageLogsContent } from './page_logs_content';
import { LogsPageNoIndicesContent } from './page_no_indices_content';
export const LogsPageContent: React.FunctionComponent = () => {
export const StreamPageContent: React.FunctionComponent = () => {
const {
hasFailedLoadingSource,
isLoadingSource,

View file

@ -0,0 +1,45 @@
/*
* 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 { i18n } from '@kbn/i18n';
import React from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { DocumentTitle } from '../../../components/document_title';
import { Header } from '../../../components/header';
interface StreamPageHeaderProps {
uiCapabilities: UICapabilities;
}
export const StreamPageHeader = injectUICapabilities((props: StreamPageHeaderProps) => {
const { uiCapabilities } = props;
return (
<>
<Header
breadcrumbs={[
{
text: i18n.translate('xpack.infra.logs.streamPage.logsBreadcrumbsText', {
defaultMessage: 'Logs',
}),
},
]}
readOnlyBadge={!uiCapabilities.logs.save}
/>
<DocumentTitle
title={(previousTitle: string) =>
i18n.translate('xpack.infra.logs.streamPage.documentTitle', {
defaultMessage: '{previousTitle} | Stream',
values: {
previousTitle,
},
})
}
/>
</>
);
});

View file

@ -6,30 +6,29 @@
import React, { useContext } from 'react';
import euiStyled from '../../../../../common/eui_styled_components';
import { AutoSizer } from '../../components/auto_sizer';
import { LogEntryFlyout } from '../../components/logging/log_entry_flyout';
import { LogMinimap } from '../../components/logging/log_minimap';
import { ScrollableLogTextStreamView } from '../../components/logging/log_text_stream';
import { PageContent } from '../../components/page';
import euiStyled from '../../../../../../common/eui_styled_components';
import { AutoSizer } from '../../../components/auto_sizer';
import { LogEntryFlyout } from '../../../components/logging/log_entry_flyout';
import { LogMinimap } from '../../../components/logging/log_minimap';
import { ScrollableLogTextStreamView } from '../../../components/logging/log_text_stream';
import { PageContent } from '../../../components/page';
import { WithSummary } from '../../containers/logs/log_summary';
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
import { WithLogFilter, WithLogFilterUrlState } from '../../containers/logs/with_log_filter';
import { WithSummary } from '../../../containers/logs/log_summary';
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
import { WithLogFilter, WithLogFilterUrlState } from '../../../containers/logs/with_log_filter';
import {
LogFlyout as LogFlyoutState,
WithFlyoutOptionsUrlState,
} from '../../containers/logs/log_flyout';
import { WithLogMinimapUrlState } from '../../containers/logs/with_log_minimap';
import { WithLogPositionUrlState } from '../../containers/logs/with_log_position';
import { WithLogPosition } from '../../containers/logs/with_log_position';
import { WithLogTextviewUrlState } from '../../containers/logs/with_log_textview';
import { ReduxSourceIdBridge, WithStreamItems } from '../../containers/logs/with_stream_items';
import { Source } from '../../containers/source';
} from '../../../containers/logs/log_flyout';
import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minimap';
import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position';
import { WithLogPosition } from '../../../containers/logs/with_log_position';
import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview';
import { ReduxSourceIdBridge, WithStreamItems } from '../../../containers/logs/with_stream_items';
import { Source } from '../../../containers/source';
import { LogsToolbar } from './page_toolbar';
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
import { LogHighlightsBridge } from '../../containers/logs/log_highlights';
import { LogHighlightsBridge } from '../../../containers/logs/log_highlights';
export const LogsPageLogsContent: React.FunctionComponent = () => {
const { createDerivedIndexPattern, source, sourceId, version } = useContext(Source.Context);
@ -43,7 +42,6 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
flyoutItem,
isLoading,
} = useContext(LogFlyoutState.Context);
const { showLogsConfiguration } = useContext(SourceConfigurationFlyoutState.Context);
const derivedIndexPattern = createDerivedIndexPattern('logs');
@ -105,7 +103,6 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
loadNewerItems={loadNewerEntries}
reportVisibleInterval={reportVisiblePositions}
scale={textScale}
showColumnConfiguration={showLogsConfiguration}
target={targetPosition}
wrap={textWrap}
setFlyoutItem={setFlyoutId}

View file

@ -6,13 +6,16 @@
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { injectI18n, InjectedIntl } from '@kbn/i18n/react';
import React, { useContext } from 'react';
import React from 'react';
import { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { NoIndices } from '../../components/empty_states/no_indices';
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
import { NoIndices } from '../../../components/empty_states/no_indices';
import { WithKibanaChrome } from '../../../containers/with_kibana_chrome';
import {
ViewSourceConfigurationButton,
ViewSourceConfigurationButtonHrefBase,
} from '../../../components/source_configuration';
interface LogsPageNoIndicesContentProps {
intl: InjectedIntl;
@ -22,7 +25,6 @@ interface LogsPageNoIndicesContentProps {
export const LogsPageNoIndicesContent = injectUICapabilities(
injectI18n((props: LogsPageNoIndicesContentProps) => {
const { intl, uiCapabilities } = props;
const { showIndicesConfiguration } = useContext(SourceConfigurationFlyoutState.Context);
return (
<WithKibanaChrome>
@ -54,16 +56,15 @@ export const LogsPageNoIndicesContent = injectUICapabilities(
</EuiFlexItem>
{uiCapabilities.logs.configureSource ? (
<EuiFlexItem>
<EuiButton
<ViewSourceConfigurationButton
data-test-subj="configureSourceButton"
color="primary"
onClick={showIndicesConfiguration}
hrefBase={ViewSourceConfigurationButtonHrefBase.logs}
>
{intl.formatMessage({
id: 'xpack.infra.configureSourceActionLabel',
defaultMessage: 'Change source configuration',
})}
</EuiButton>
</ViewSourceConfigurationButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>

View file

@ -0,0 +1,30 @@
/*
* 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 { LogFlyout } from '../../../containers/logs/log_flyout';
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights';
import { Source, useSource } from '../../../containers/source';
import { useSourceId } from '../../../containers/source_id';
export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
const [sourceId] = useSourceId();
const source = useSource({ sourceId });
return (
<Source.Context.Provider value={source}>
<LogViewConfiguration.Provider>
<LogFlyout.Provider>
<LogHighlightsState.Provider sourceId={sourceId} sourceVersion={source.version}>
{children}
</LogHighlightsState.Provider>
</LogFlyout.Provider>
</LogViewConfiguration.Provider>
</Source.Context.Provider>
);
};

View file

@ -8,22 +8,21 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { injectI18n } from '@kbn/i18n/react';
import React, { useContext } from 'react';
import { AutocompleteField } from '../../components/autocomplete_field';
import { Toolbar } from '../../components/eui';
import { LogCustomizationMenu } from '../../components/logging/log_customization_menu';
import { LogHighlightsMenu } from '../../components/logging/log_highlights_menu';
import { LogHighlightsState } from '../../containers/logs/log_highlights/log_highlights';
import { LogMinimapScaleControls } from '../../components/logging/log_minimap_scale_controls';
import { LogTextScaleControls } from '../../components/logging/log_text_scale_controls';
import { LogTextWrapControls } from '../../components/logging/log_text_wrap_controls';
import { LogTimeControls } from '../../components/logging/log_time_controls';
import { SourceConfigurationButton } from '../../components/source_configuration';
import { LogFlyout } from '../../containers/logs/log_flyout';
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
import { WithLogFilter } from '../../containers/logs/with_log_filter';
import { WithLogPosition } from '../../containers/logs/with_log_position';
import { Source } from '../../containers/source';
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
import { AutocompleteField } from '../../../components/autocomplete_field';
import { Toolbar } from '../../../components/eui';
import { LogCustomizationMenu } from '../../../components/logging/log_customization_menu';
import { LogHighlightsMenu } from '../../../components/logging/log_highlights_menu';
import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights';
import { LogMinimapScaleControls } from '../../../components/logging/log_minimap_scale_controls';
import { LogTextScaleControls } from '../../../components/logging/log_text_scale_controls';
import { LogTextWrapControls } from '../../../components/logging/log_text_wrap_controls';
import { LogTimeControls } from '../../../components/logging/log_time_controls';
import { LogFlyout } from '../../../containers/logs/log_flyout';
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
import { WithLogFilter } from '../../../containers/logs/with_log_filter';
import { WithLogPosition } from '../../../containers/logs/with_log_position';
import { Source } from '../../../containers/source';
import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion';
export const LogsToolbar = injectI18n(({ intl }) => {
const { createDerivedIndexPattern } = useContext(Source.Context);
@ -91,9 +90,6 @@ export const LogsToolbar = injectI18n(({ intl }) => {
)}
</WithKueryAutocompletion>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SourceConfigurationButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LogCustomizationMenu>
<LogMinimapScaleControls

View file

@ -28,7 +28,6 @@ import { InvalidNodeError } from '../../components/metrics/invalid_node';
import { MetricsSideNav } from '../../components/metrics/side_nav';
import { MetricsTimeControls } from '../../components/metrics/time_controls';
import { ColumnarPage, PageContent } from '../../components/page';
import { SourceConfigurationFlyout } from '../../components/source_configuration';
import { WithMetrics } from '../../containers/metrics/with_metrics';
import {
WithMetricsTime,
@ -130,9 +129,6 @@ export const MetricDetail = withMetricPageProviders(
breadcrumbs={breadcrumbs}
readOnlyBadge={!uiCapabilities.infrastructure.save}
/>
<SourceConfigurationFlyout
shouldAllowEdit={uiCapabilities.infrastructure.configureSource as boolean}
/>
<WithMetricsTimeUrlState />
<DocumentTitle
title={intl.formatMessage(

View file

@ -6,7 +6,6 @@
import React from 'react';
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
import { MetricsTimeContainer } from '../../containers/metrics/with_metrics_time';
import { Source } from '../../containers/source';
@ -14,10 +13,8 @@ export const withMetricPageProviders = <T extends object>(Component: React.Compo
props: T
) => (
<Source.Provider sourceId="default">
<SourceConfigurationFlyoutState.Provider>
<MetricsTimeContainer.Provider>
<Component {...props} />
</MetricsTimeContainer.Provider>
</SourceConfigurationFlyoutState.Provider>
<MetricsTimeContainer.Provider>
<Component {...props} />
</MetricsTimeContainer.Provider>
</Source.Provider>
);

View file

@ -0,0 +1,18 @@
/*
* 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 { UICapabilities } from 'ui/capabilities';
import { injectUICapabilities } from 'ui/capabilities/react';
import { SourceConfigurationSettings } from '../../../components/source_configuration/source_configuration_settings';
interface SettingsPageProps {
uiCapabilities: UICapabilities;
}
export const SettingsPage = injectUICapabilities(({ uiCapabilities }: SettingsPageProps) => (
<SourceConfigurationSettings shouldAllowEdit={uiCapabilities.logs.configureSource as boolean} />
));

View file

@ -37,6 +37,7 @@ const PageRouterComponent: React.SFC<RouterProps> = ({ history, uiCapabilities }
{uiCapabilities.infrastructure.show && (
<Redirect from="/home" exact={true} to="/infrastructure/inventory" />
)}
{uiCapabilities.logs.show && <Redirect from="/logs" exact={true} to="/logs/stream" />}
{uiCapabilities.logs.show && <Route path="/logs" component={LogsPage} />}
{uiCapabilities.infrastructure.show && (
<Route path="/infrastructure" component={InfrastructurePage} />

View file

@ -484,11 +484,6 @@
"common.ui.indexPattern.unknownFieldHeader": "不明なフィールドタイプ {type}",
"common.ui.indexPattern.warningText": "現在 {index} に一致するすべてのインデックスにクエリを実行しています。{title} はワイルドカードベースのインデックスパターンに移行されるはずです。",
"common.ui.indexPattern.warningTitle": "時間間隔インデックスパターンのサポートは廃止されました",
"inspector.closeButton": "インスペクターを閉じる",
"inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です",
"inspector.reqTimestampKey": "リクエストのタイムスタンプ",
"inspector.title": "インスペクター",
"inspector.view": "{viewName} を表示",
"common.ui.legacyBrowserMessage": "この Kibana インストレーションは、現在ご使用のブラウザが満たしていない厳格なセキュリティ要件が有効になっています。",
"common.ui.legacyBrowserTitle": "ブラウザをアップグレードしてください",
"common.ui.management.breadcrumb": "管理",
@ -615,6 +610,11 @@
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "バウンドを取得できませんでした",
"common.ui.welcomeErrorMessage": "Kibana が正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。",
"common.ui.welcomeMessage": "Kibana を読み込み中",
"inspector.closeButton": "インスペクターを閉じる",
"inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です",
"inspector.reqTimestampKey": "リクエストのタイムスタンプ",
"inspector.title": "インスペクター",
"inspector.view": "{viewName} を表示",
"core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "ホームページに移動",
"core.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "ヘルプメニュー",
"core.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation": "ドキュメントに移動",
@ -5078,7 +5078,6 @@
"xpack.infra.linkLogsTitle": "ログ",
"xpack.infra.linkTo.hostWithIp.error": "IP アドレス「{hostIp}」でホストが見つかりません.",
"xpack.infra.linkTo.hostWithIp.loading": "IP アドレス「{hostIp}」のホストを読み込み中",
"xpack.infra.logColumnHeaders.configureColumnsLabel": "列を構成",
"xpack.infra.logEntryActionsMenu.buttonLabel": "アクション",
"xpack.infra.logEntryActionsMenu.uptimeActionLabel": "監視ステータスを表示",
"xpack.infra.logEntryItemView.viewDetailsToolTip": "詳細を表示",
@ -5111,8 +5110,6 @@
"xpack.infra.logs.stopStreamingButtonLabel": "ストリーム停止",
"xpack.infra.logs.streamingDescription": "新しいエントリーをストリーム中...",
"xpack.infra.logs.streamingNewEntriesText": "新しいエントリーをストリーム中",
"xpack.infra.logsPage.documentTitle": "ログ",
"xpack.infra.logsPage.logsBreadcrumbsText": "ログ",
"xpack.infra.logsPage.logsHelpContent.feedbackLinkText": "ログのフィードバックを提供",
"xpack.infra.logsPage.noLoggingIndicesDescription": "追加しましょう!",
"xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel": "セットアップの手順を表示",
@ -5234,10 +5231,7 @@
"xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリーするか、コンソール式の UI で履歴ビューをスクロールします。",
"xpack.infra.registerFeatures.logsTitle": "ログ",
"xpack.infra.sourceConfiguration.addLogColumnButtonLabel": "列を追加",
"xpack.infra.sourceConfiguration.closeButtonLabel": "閉じる",
"xpack.infra.sourceConfiguration.containerFieldDescription": "Docker コンテナーの識別に使用されるフィールドです。推奨値は {defaultValue} です。",
"xpack.infra.sourceConfiguration.containerFieldLabel": "コンテナー ID",
"xpack.infra.sourceConfiguration.discardAndCloseButtonLabel": "破棄して閉じる",
"xpack.infra.sourceConfiguration.fieldEmptyErrorMessage": "このフィールドは未入力のままにできません。",
"xpack.infra.sourceConfiguration.fieldLogColumnTitle": "フィールド",
"xpack.infra.sourceConfiguration.fieldsSectionTitle": "フィールド",
@ -5246,29 +5240,18 @@
"xpack.infra.sourceConfiguration.indicesSectionTitle": "インデックス",
"xpack.infra.sourceConfiguration.logColumnListEmptyErrorMessage": "ログ列リストは未入力のままにできません。",
"xpack.infra.sourceConfiguration.logColumnsSectionTitle": "列",
"xpack.infra.sourceConfiguration.logIndicesDescription": "ログデータを含む一致するインデックスのインデックスパターンです。推奨値は {defaultValue} です。",
"xpack.infra.sourceConfiguration.logIndicesLabel": "ログインデックス",
"xpack.infra.sourceConfiguration.messageLogColumnDescription": "このシステムフィールドは、ドキュメントフィールドから取得されたログエントリーメッセージを表示します。",
"xpack.infra.sourceConfiguration.metricIndicesDescription": "Metricbeat データを含む一致するインデックスのインデックスパターンです。推奨値は {defaultValue} です。",
"xpack.infra.sourceConfiguration.metricIndicesLabel": "メトリックインデックス",
"xpack.infra.sourceConfiguration.nameLabel": "名前",
"xpack.infra.sourceConfiguration.nameSectionTitle": "名前",
"xpack.infra.sourceConfiguration.noLogColumnsDescription": "上のボタンでこのリストに列を追加します。",
"xpack.infra.sourceConfiguration.noLogColumnsTitle": "列がありません",
"xpack.infra.sourceConfiguration.podFieldDescription": "Kubernetes ポッドの識別に使用されるフィールドです。推奨値は {defaultValue} です。",
"xpack.infra.sourceConfiguration.podFieldLabel": "ポッド ID",
"xpack.infra.sourceConfiguration.sourceConfigurationButtonLabel": "構成",
"xpack.infra.sourceConfiguration.sourceConfigurationIndicesTabTitle": "インデックスとフィールド",
"xpack.infra.sourceConfiguration.sourceConfigurationLogColumnsTabTitle": "ログ列",
"xpack.infra.sourceConfiguration.sourceConfigurationReadonlyTitle": "ソース構成を表示",
"xpack.infra.sourceConfiguration.sourceConfigurationTitle": "ソースの構成",
"xpack.infra.sourceConfiguration.systemColumnBadgeLabel": "システム",
"xpack.infra.sourceConfiguration.tiebreakerFieldDescription": "同じタイムスタンプの 2 つのエントリーを識別するのに使用されるフィールドです。推奨値は {defaultValue} です。",
"xpack.infra.sourceConfiguration.tiebreakerFieldLabel": "タイブレーカー",
"xpack.infra.sourceConfiguration.timestampFieldDescription": "ログエントリーの並べ替えに使用されるタイムスタンプです。推奨値は {defaultValue} です。",
"xpack.infra.sourceConfiguration.timestampFieldLabel": "タイムスタンプ",
"xpack.infra.sourceConfiguration.timestampLogColumnDescription": "このシステムフィールドは、{timestampSetting} フィールド設定から判断されたログエントリーの時刻を表示します。",
"xpack.infra.sourceConfiguration.updateSourceConfigurationButtonLabel": "ソースを更新",
"xpack.infra.sourceErrorPage.failedToLoadDataSourcesMessage": "データソースの読み込みに失敗しました。",
"xpack.infra.sourceLoadingPage.loadingDataSourcesMessage": "データソースを読み込み中",
"xpack.infra.tableView.columnName.avg": "平均",
@ -10643,4 +10626,4 @@
"xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "ログテキストが必要です。",
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
}
}
}

View file

@ -484,11 +484,6 @@
"common.ui.indexPattern.unknownFieldHeader": "未知字段类型 {type}",
"common.ui.indexPattern.warningText": "当前正在查询所有匹配 {index} 的索引。{title} 应迁移到基于通配符的索引模式。",
"common.ui.indexPattern.warningTitle": "已移除对时间间隔索引模式的支持",
"inspector.closeButton": "关闭检查器",
"inspector.reqTimestampDescription": "记录请求启动的时间",
"inspector.reqTimestampKey": "请求时间戳",
"inspector.title": "检查器",
"inspector.view": "视图:{viewName}",
"common.ui.legacyBrowserMessage": "此 Kibana 安装启用了当前浏览器未满足的严格安全要求。",
"common.ui.legacyBrowserTitle": "请升级您的浏览器",
"common.ui.management.breadcrumb": "管理",
@ -616,6 +611,11 @@
"common.ui.visualize.queryGeohashBounds.unableToGetBoundErrorTitle": "无法获取边界",
"common.ui.welcomeErrorMessage": "Kibana 未正确加载。检查服务器输出以了解详情。",
"common.ui.welcomeMessage": "正在加载 Kibana",
"inspector.closeButton": "关闭检查器",
"inspector.reqTimestampDescription": "记录请求启动的时间",
"inspector.reqTimestampKey": "请求时间戳",
"inspector.title": "检查器",
"inspector.view": "视图:{viewName}",
"core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "前往主页",
"core.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "帮助菜单",
"core.ui.chrome.headerGlobalNav.helpMenuGoToDocumentation": "前往文档",
@ -5221,7 +5221,6 @@
"xpack.infra.linkLogsTitle": "Logs",
"xpack.infra.linkTo.hostWithIp.error": "未找到 IP 地址为“{hostIp}”的主机。",
"xpack.infra.linkTo.hostWithIp.loading": "正在加载 IP 地址为“{hostIp}”的主机。",
"xpack.infra.logColumnHeaders.configureColumnsLabel": "配置列",
"xpack.infra.logEntryActionsMenu.buttonLabel": "操作",
"xpack.infra.logEntryActionsMenu.uptimeActionLabel": "查看监测状态",
"xpack.infra.logEntryItemView.viewDetailsToolTip": "查看详情",
@ -5254,8 +5253,6 @@
"xpack.infra.logs.stopStreamingButtonLabel": "停止流式传输",
"xpack.infra.logs.streamingDescription": "正在流式传输新条目……",
"xpack.infra.logs.streamingNewEntriesText": "正在流式传输新条目",
"xpack.infra.logsPage.documentTitle": "Logs",
"xpack.infra.logsPage.logsBreadcrumbsText": "Logs",
"xpack.infra.logsPage.logsHelpContent.feedbackLinkText": "提供 Logs 的反馈",
"xpack.infra.logsPage.noLoggingIndicesDescription": "让我们添加一些!",
"xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel": "查看设置说明",
@ -5377,10 +5374,7 @@
"xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。",
"xpack.infra.registerFeatures.logsTitle": "Logs",
"xpack.infra.sourceConfiguration.addLogColumnButtonLabel": "添加列",
"xpack.infra.sourceConfiguration.closeButtonLabel": "关闭",
"xpack.infra.sourceConfiguration.containerFieldDescription": "用于标识 Docker 容器的字段。推荐值为 {defaultValue}。",
"xpack.infra.sourceConfiguration.containerFieldLabel": "容器 ID",
"xpack.infra.sourceConfiguration.discardAndCloseButtonLabel": "丢弃并关闭",
"xpack.infra.sourceConfiguration.fieldEmptyErrorMessage": "字段不得为空。",
"xpack.infra.sourceConfiguration.fieldLogColumnTitle": "字段",
"xpack.infra.sourceConfiguration.fieldsSectionTitle": "字段",
@ -5389,29 +5383,18 @@
"xpack.infra.sourceConfiguration.indicesSectionTitle": "索引",
"xpack.infra.sourceConfiguration.logColumnListEmptyErrorMessage": "日志列列表不得为空。",
"xpack.infra.sourceConfiguration.logColumnsSectionTitle": "列",
"xpack.infra.sourceConfiguration.logIndicesDescription": "用于匹配包含日志数据的索引的索引模式。推荐值为 {defaultValue}。",
"xpack.infra.sourceConfiguration.logIndicesLabel": "日志索引",
"xpack.infra.sourceConfiguration.messageLogColumnDescription": "此系统字段显示派生自文档字段的日志条目消息。",
"xpack.infra.sourceConfiguration.metricIndicesDescription": "用于匹配包含 Metricbeat 数据的索引的索引模式。推荐值为 {defaultValue}。",
"xpack.infra.sourceConfiguration.metricIndicesLabel": "指标索引",
"xpack.infra.sourceConfiguration.nameLabel": "名称",
"xpack.infra.sourceConfiguration.nameSectionTitle": "名称",
"xpack.infra.sourceConfiguration.noLogColumnsDescription": "使用上面的按钮将列添加到此列表。",
"xpack.infra.sourceConfiguration.noLogColumnsTitle": "无列",
"xpack.infra.sourceConfiguration.podFieldDescription": "用于标识 Kubernetes Pod 的字段。推荐值为 {defaultValue}。",
"xpack.infra.sourceConfiguration.podFieldLabel": "Pod ID",
"xpack.infra.sourceConfiguration.sourceConfigurationButtonLabel": "配置",
"xpack.infra.sourceConfiguration.sourceConfigurationIndicesTabTitle": "索引和字段",
"xpack.infra.sourceConfiguration.sourceConfigurationLogColumnsTabTitle": "日志列",
"xpack.infra.sourceConfiguration.sourceConfigurationReadonlyTitle": "查看源配置",
"xpack.infra.sourceConfiguration.sourceConfigurationTitle": "配置源",
"xpack.infra.sourceConfiguration.systemColumnBadgeLabel": "系统",
"xpack.infra.sourceConfiguration.tiebreakerFieldDescription": "用于时间戳相同的两个条目间决胜的字段。推荐值为 {defaultValue}。",
"xpack.infra.sourceConfiguration.tiebreakerFieldLabel": "决胜属性",
"xpack.infra.sourceConfiguration.timestampFieldDescription": "用于排序日志条目的时间戳。推荐值为 {defaultValue}。",
"xpack.infra.sourceConfiguration.timestampFieldLabel": "时间戳",
"xpack.infra.sourceConfiguration.timestampLogColumnDescription": "此系统字段显示 {timestampSetting} 字段设置所确定的日志条目时间。",
"xpack.infra.sourceConfiguration.updateSourceConfigurationButtonLabel": "更新源",
"xpack.infra.sourceErrorPage.failedToLoadDataSourcesMessage": "无法加载数据源。",
"xpack.infra.sourceLoadingPage.loadingDataSourcesMessage": "正在加载数据源",
"xpack.infra.tableView.columnName.avg": "平均值",
@ -10785,4 +10768,4 @@
"xpack.watcher.watchActions.logging.logTextIsRequiredValidationMessage": "“日志文本”必填。",
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
}
}
}

View file

@ -11,10 +11,10 @@ import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const infraLogStream = getService('infraLogStream');
const infraSourceConfigurationFlyout = getService('infraSourceConfigurationFlyout');
const pageObjects = getPageObjects(['infraLogs']);
const infraSourceConfigurationForm = getService('infraSourceConfigurationForm');
const pageObjects = getPageObjects(['common', 'infraLogs']);
describe('Logs Page', function() {
describe('Logs Source Configuration', function() {
this.tags('smoke');
before(async () => {
await esArchiver.load('empty_kibana');
@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await esArchiver.unload('empty_kibana');
});
describe('with logs present', () => {
describe('Allows indices configuration', () => {
before(async () => {
await esArchiver.load('infra/metrics_and_logs');
});
@ -31,54 +31,44 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await esArchiver.unload('infra/metrics_and_logs');
});
it('renders the log stream', async () => {
await pageObjects.infraLogs.navigateTo();
await pageObjects.infraLogs.getLogStream();
});
it('can change the log indices to a pattern that matches nothing', async () => {
await pageObjects.infraLogs.openSourceConfigurationFlyout();
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/settings');
await infraSourceConfigurationForm.getForm();
const nameInput = await infraSourceConfigurationFlyout.getNameInput();
const nameInput = await infraSourceConfigurationForm.getNameInput();
await nameInput.clearValueWithKeyboard({ charByChar: true });
await nameInput.type('Modified Source');
const logIndicesInput = await infraSourceConfigurationFlyout.getLogIndicesInput();
const logIndicesInput = await infraSourceConfigurationForm.getLogIndicesInput();
await logIndicesInput.clearValueWithKeyboard({ charByChar: true });
await logIndicesInput.type('does-not-exist-*');
await infraSourceConfigurationFlyout.saveConfiguration();
await infraSourceConfigurationFlyout.closeFlyout();
await infraSourceConfigurationForm.saveConfiguration();
});
it('renders the no indices screen when no indices match the pattern', async () => {
await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream');
await pageObjects.infraLogs.getNoLogsIndicesPrompt();
});
it('can change the log indices back to a pattern that matches something', async () => {
await pageObjects.infraLogs.openSourceConfigurationFlyout();
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/settings');
await infraSourceConfigurationForm.getForm();
const logIndicesInput = await infraSourceConfigurationFlyout.getLogIndicesInput();
const logIndicesInput = await infraSourceConfigurationForm.getLogIndicesInput();
await logIndicesInput.clearValueWithKeyboard({ charByChar: true });
await logIndicesInput.type('filebeat-*');
await infraSourceConfigurationFlyout.saveConfiguration();
await infraSourceConfigurationFlyout.closeFlyout();
});
it('renders the log stream again', async () => {
await pageObjects.infraLogs.getLogStream();
await infraSourceConfigurationForm.saveConfiguration();
});
it('renders the default log columns with their headers', async () => {
await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream');
const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels();
expect(columnHeaderLabels).to.eql(['Timestamp', 'event.dataset', 'Message', '']);
expect(columnHeaderLabels).to.eql(['Timestamp', 'event.dataset', 'Message']);
const logStreamEntries = await infraLogStream.getStreamEntries();
expect(logStreamEntries.length).to.be.greaterThan(0);
const firstLogStreamEntry = logStreamEntries[0];
@ -90,26 +80,25 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('can change the log columns', async () => {
await pageObjects.infraLogs.openSourceConfigurationFlyout();
await infraSourceConfigurationFlyout.switchToLogsTab();
await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/settings');
await infraSourceConfigurationForm.getForm();
await infraSourceConfigurationFlyout.removeAllLogColumns();
await infraSourceConfigurationFlyout.addTimestampLogColumn();
await infraSourceConfigurationFlyout.addFieldLogColumn('host.name');
await infraSourceConfigurationForm.removeAllLogColumns();
await infraSourceConfigurationForm.addTimestampLogColumn();
await infraSourceConfigurationForm.addFieldLogColumn('host.name');
// TODO: make test more robust
// await infraSourceConfigurationFlyout.moveLogColumn(0, 1);
// await infraSourceConfigurationForm.moveLogColumn(0, 1);
await infraSourceConfigurationFlyout.saveConfiguration();
await infraSourceConfigurationFlyout.closeFlyout();
await infraSourceConfigurationForm.saveConfiguration();
});
it('renders the changed log columns with their headers', async () => {
await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream');
const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels();
// TODO: make test more robust
// expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp', '']);
expect(columnHeaderLabels).to.eql(['Timestamp', 'host.name', '']);
// expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp']);
expect(columnHeaderLabels).to.eql(['Timestamp', 'host.name']);
const logStreamEntries = await infraLogStream.getStreamEntries();

View file

@ -11,10 +11,10 @@ const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData;
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const infraSourceConfigurationFlyout = getService('infraSourceConfigurationFlyout');
const infraSourceConfigurationForm = getService('infraSourceConfigurationForm');
const pageObjects = getPageObjects(['common', 'infraHome']);
describe('Infrastructure Snapshot Page', function() {
describe('Infrastructure Source Configuration', function() {
this.tags('smoke');
before(async () => {
await esArchiver.load('empty_kibana');
@ -38,38 +38,37 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('can change the metric indices to a pattern that matches nothing', async () => {
await pageObjects.infraHome.openSourceConfigurationFlyout();
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
await pageObjects.common.navigateToActualUrl('infraOps', 'infrastructure/settings');
const nameInput = await infraSourceConfigurationFlyout.getNameInput();
const nameInput = await infraSourceConfigurationForm.getNameInput();
await nameInput.clearValueWithKeyboard({ charByChar: true });
await nameInput.type('Modified Source');
const metricIndicesInput = await infraSourceConfigurationFlyout.getMetricIndicesInput();
const metricIndicesInput = await infraSourceConfigurationForm.getMetricIndicesInput();
await metricIndicesInput.clearValueWithKeyboard({ charByChar: true });
await metricIndicesInput.type('does-not-exist-*');
await infraSourceConfigurationFlyout.saveConfiguration();
await infraSourceConfigurationFlyout.closeFlyout();
await infraSourceConfigurationForm.saveConfiguration();
});
it('renders the no indices screen when no indices match the pattern', async () => {
await pageObjects.common.navigateToApp('infraOps');
await pageObjects.infraHome.getNoMetricsIndicesPrompt();
});
it('can change the log indices back to a pattern that matches something', async () => {
await pageObjects.infraHome.openSourceConfigurationFlyout();
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
it('can change the metric indices back to a pattern that matches something', async () => {
await pageObjects.common.navigateToActualUrl('infraOps', 'infrastructure/settings');
const metricIndicesInput = await infraSourceConfigurationFlyout.getMetricIndicesInput();
const metricIndicesInput = await infraSourceConfigurationForm.getMetricIndicesInput();
await metricIndicesInput.clearValueWithKeyboard({ charByChar: true });
await metricIndicesInput.type('metricbeat-*');
await infraSourceConfigurationFlyout.saveConfiguration();
await infraSourceConfigurationFlyout.closeFlyout();
await infraSourceConfigurationForm.saveConfiguration();
});
it('renders the log stream again', async () => {
it('renders the waffle map again', async () => {
await pageObjects.common.navigateToApp('infraOps');
await pageObjects.infraHome.goToTime(DATE_WITH_DATA);
await pageObjects.infraHome.getWaffleMap();
});
});

View file

@ -10,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function InfraHomePageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const find = getService('find');
const browser = getService('browser');
@ -23,6 +24,12 @@ export function InfraHomePageProvider({ getService }: FtrProviderContext) {
},
async getWaffleMap() {
await retry.try(async () => {
const element = await testSubjects.find('waffleMap');
if (!element) {
throw new Error();
}
});
return await testSubjects.find('waffleMap');
},

View file

@ -25,10 +25,5 @@ export function InfraLogsPageProvider({ getPageObjects, getService }: FtrProvide
async getNoLogsIndicesPrompt() {
return await testSubjects.find('noLogsIndicesPrompt');
},
async openSourceConfigurationFlyout() {
await testSubjects.click('configureSourceButton');
await testSubjects.exists('sourceConfigurationFlyout');
},
};
}

View file

@ -44,7 +44,7 @@ import { GrokDebuggerProvider } from './grok_debugger';
// @ts-ignore not ts yet
import { UserMenuProvider } from './user_menu';
import { UptimeProvider } from './uptime';
import { InfraSourceConfigurationFlyoutProvider } from './infra_source_configuration_flyout';
import { InfraSourceConfigurationFormProvider } from './infra_source_configuration_form';
import { InfraLogStreamProvider } from './infra_log_stream';
import { MachineLearningProvider } from './ml';
@ -86,7 +86,7 @@ export const services = {
spaces: SpacesServiceProvider,
userMenu: UserMenuProvider,
uptime: UptimeProvider,
infraSourceConfigurationFlyout: InfraSourceConfigurationFlyoutProvider,
infraSourceConfigurationForm: InfraSourceConfigurationFormProvider,
infraLogStream: InfraLogStreamProvider,
ml: MachineLearningProvider,
};

View file

@ -9,6 +9,7 @@ import { WebElementWrapper } from '../../../../test/functional/services/lib/web_
export function InfraLogStreamProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
return {
async getColumnHeaderLabels(): Promise<string[]> {
@ -18,7 +19,14 @@ export function InfraLogStreamProvider({ getService }: FtrProviderContext) {
return await Promise.all(columnHeaderElements.map(element => element.getVisibleText()));
},
async getStreamEntries(): Promise<WebElementWrapper[]> {
async getStreamEntries(minimumItems = 1): Promise<WebElementWrapper[]> {
await retry.try(async () => {
const elements = await testSubjects.findAll('streamEntry');
if (!elements || elements.length < minimumItems) {
throw new Error();
}
});
return await testSubjects.findAll('streamEntry');
},

View file

@ -7,68 +7,56 @@
import { FtrProviderContext } from '../ftr_provider_context';
import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
export function InfraSourceConfigurationFlyoutProvider({ getService }: FtrProviderContext) {
const find = getService('find');
export function InfraSourceConfigurationFormProvider({ getService }: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const browser = getService('browser');
return {
/**
* Tab navigation
*/
async switchToIndicesAndFieldsTab() {
await (await find.descendantDisplayedByCssSelector(
'#indicesAndFieldsTab',
await this.getFlyout()
)).click();
await testSubjects.find('sourceConfigurationNameSectionTitle');
},
async switchToLogsTab() {
await (await find.descendantDisplayedByCssSelector(
'#logsTab',
await this.getFlyout()
)).click();
await testSubjects.find('sourceConfigurationLogColumnsSectionTitle');
},
/**
* Indices and fields
*/
async getNameInput(): Promise<WebElementWrapper> {
return await testSubjects.findDescendant('nameInput', await this.getFlyout());
return await testSubjects.findDescendant('nameInput', await this.getForm());
},
async getLogIndicesInput(): Promise<WebElementWrapper> {
return await testSubjects.findDescendant('logIndicesInput', await this.getFlyout());
return await testSubjects.findDescendant('logIndicesInput', await this.getForm());
},
async getMetricIndicesInput(): Promise<WebElementWrapper> {
return await testSubjects.findDescendant('metricIndicesInput', await this.getFlyout());
return await testSubjects.findDescendant('metricIndicesInput', await this.getForm());
},
/**
* Logs
*/
async getAddLogColumnButton(): Promise<WebElementWrapper> {
return await testSubjects.findDescendant('addLogColumnButton', await this.getFlyout());
return await testSubjects.findDescendant('addLogColumnButton', await this.getForm());
},
async getAddLogColumnPopover(): Promise<WebElementWrapper> {
return await testSubjects.find('addLogColumnPopover');
},
async addTimestampLogColumn() {
await (await this.getAddLogColumnButton()).click();
await (await testSubjects.findDescendant(
'addTimestampLogColumn',
await this.getAddLogColumnPopover()
)).click();
await retry.try(async () => {
await (await testSubjects.findDescendant(
'addTimestampLogColumn',
await this.getAddLogColumnPopover()
)).click();
});
},
async addFieldLogColumn(fieldName: string) {
await (await this.getAddLogColumnButton()).click();
const popover = await this.getAddLogColumnPopover();
await (await testSubjects.findDescendant('fieldSearchInput', popover)).type(fieldName);
await (await testSubjects.findDescendant(`addFieldLogColumn:${fieldName}`, popover)).click();
await retry.try(async () => {
const popover = await this.getAddLogColumnPopover();
await (await testSubjects.findDescendant('fieldSearchInput', popover)).type(fieldName);
await (await testSubjects.findDescendant(
`addFieldLogColumn:${fieldName}`,
popover
)).click();
});
},
async getLogColumnPanels(): Promise<WebElementWrapper[]> {
return await testSubjects.findAllDescendant('logColumnPanel', await this.getFlyout());
return await testSubjects.findAllDescendant('logColumnPanel', await this.getForm());
},
async removeLogColumn(columnIndex: number) {
const logColumnPanel = (await this.getLogColumnPanels())[columnIndex];
@ -104,29 +92,24 @@ export function InfraSourceConfigurationFlyoutProvider({ getService }: FtrProvid
},
/**
* Form and flyout
* Form
*/
async getFlyout(): Promise<WebElementWrapper> {
return await testSubjects.find('sourceConfigurationFlyout');
async getForm(): Promise<WebElementWrapper> {
return await testSubjects.find('sourceConfigurationContent');
},
async saveConfiguration() {
await (await testSubjects.findDescendant(
'updateSourceConfigurationButton',
await this.getFlyout()
'applySettingsButton',
await this.getForm()
)).click();
await retry.try(async () => {
const element = await testSubjects.findDescendant(
'updateSourceConfigurationButton',
await this.getFlyout()
'applySettingsButton',
await this.getForm()
);
return !(await element.isEnabled());
});
},
async closeFlyout() {
const flyout = await this.getFlyout();
await (await testSubjects.findDescendant('closeFlyoutButton', flyout)).click();
await testSubjects.waitForDeleted(flyout);
},
};
}