mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Backports the following commits to 7.x: - [Logs UI] Refactor source configuration as hook for consistent data flow (#34455)
This commit is contained in:
parent
d22b156812
commit
13e6a4ad72
58 changed files with 2144 additions and 1717 deletions
|
@ -13,4 +13,11 @@ export const sharedFragments = {
|
|||
tiebreaker
|
||||
}
|
||||
`,
|
||||
InfraSourceFields: gql`
|
||||
fragment InfraSourceFields on InfraSource {
|
||||
id
|
||||
version
|
||||
updatedAt
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
|
|
@ -718,6 +718,92 @@ export namespace MetricsQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace CreateSourceConfigurationMutation {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
sourceConfiguration: CreateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
createSource: CreateSource;
|
||||
};
|
||||
|
||||
export type CreateSource = {
|
||||
__typename?: 'CreateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
} & InfraSourceFields.Fragment;
|
||||
|
||||
export type Configuration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type Status = SourceStatusFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace SourceQuery {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
} & InfraSourceFields.Fragment;
|
||||
|
||||
export type Configuration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type Status = SourceStatusFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace UpdateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
changes: UpdateSourceInput[];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
updateSource: UpdateSource;
|
||||
};
|
||||
|
||||
export type UpdateSource = {
|
||||
__typename?: 'UpdateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
} & InfraSourceFields.Fragment;
|
||||
|
||||
export type Configuration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type Status = SourceStatusFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace WaffleNodesQuery {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
|
@ -776,62 +862,6 @@ export namespace WaffleNodesQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace CreateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
sourceConfiguration: CreateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
createSource: CreateSource;
|
||||
};
|
||||
|
||||
export type CreateSource = {
|
||||
__typename?: 'CreateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = SourceFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace SourceQuery {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = SourceFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace UpdateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
changes: UpdateSourceInput[];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
updateSource: UpdateSource;
|
||||
};
|
||||
|
||||
export type UpdateSource = {
|
||||
__typename?: 'UpdateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = SourceFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace LogEntries {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
|
@ -910,32 +940,18 @@ export namespace LogEntries {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace SourceFields {
|
||||
export namespace SourceConfigurationFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
id: string;
|
||||
|
||||
version?: string | null;
|
||||
|
||||
updatedAt?: number | null;
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
};
|
||||
|
||||
export type Configuration = {
|
||||
__typename?: 'InfraSourceConfiguration';
|
||||
|
||||
name: string;
|
||||
|
||||
description: string;
|
||||
|
||||
metricAlias: string;
|
||||
|
||||
logAlias: string;
|
||||
|
||||
metricAlias: string;
|
||||
|
||||
fields: Fields;
|
||||
};
|
||||
|
||||
|
@ -954,8 +970,10 @@ export namespace SourceFields {
|
|||
|
||||
timestamp: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Status = {
|
||||
export namespace SourceStatusFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraSourceStatus';
|
||||
|
||||
indexFields: IndexFields[];
|
||||
|
@ -987,3 +1005,15 @@ export namespace InfraTimeKeyFields {
|
|||
tiebreaker: number;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace InfraSourceFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
id: string;
|
||||
|
||||
version?: string | null;
|
||||
|
||||
updatedAt?: number | null;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent<
|
|||
onVisibleChildrenChange={this.handleVisibleChildrenChange}
|
||||
target={targetId}
|
||||
hideScrollbar={true}
|
||||
data-test-subj={'logStream'}
|
||||
>
|
||||
{registerChild => (
|
||||
<>
|
||||
|
|
|
@ -29,6 +29,7 @@ interface VerticalScrollPanelProps<Child> {
|
|||
height: number;
|
||||
width: number;
|
||||
hideScrollbar?: boolean;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
interface VerticalScrollPanelSnapshot<Child> {
|
||||
|
@ -208,11 +209,12 @@ export class VerticalScrollPanel<Child> extends React.PureComponent<
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { children, height, width, hideScrollbar } = this.props;
|
||||
const { children, height, width, hideScrollbar, 'data-test-subj': dataTestSubj } = this.props;
|
||||
const scrollbarOffset = hideScrollbar ? ASSUMED_SCROLLBAR_WIDTH : 0;
|
||||
|
||||
return (
|
||||
<ScrollPanelWrapper
|
||||
data-test-subj={dataTestSubj}
|
||||
style={{ height, width: width + scrollbarOffset }}
|
||||
scrollbarOffset={scrollbarOffset}
|
||||
onScroll={this.handleScroll}
|
||||
|
|
|
@ -6,71 +6,71 @@
|
|||
|
||||
import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import euiStyled from '../../../../../common/eui_styled_components';
|
||||
import { WithSourceConfigurationFlyoutState } from '../../components/source_configuration/source_configuration_flyout_state';
|
||||
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
|
||||
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
|
||||
|
||||
interface InvalidNodeErrorProps {
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
export const InvalidNodeError: React.SFC<InvalidNodeErrorProps> = ({ nodeName }) => (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
<CenteredEmptyPrompt
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.invalidNodeErrorTitle"
|
||||
defaultMessage="Looks like {nodeName} isn't collecting any metrics data"
|
||||
values={{
|
||||
nodeName,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.invalidNodeErrorDescription"
|
||||
defaultMessage="Double check your configuration"
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel"
|
||||
defaultMessage="View setup instructions"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<WithSourceConfigurationFlyoutState>
|
||||
{({ enable }) => (
|
||||
<EuiButton color="primary" onClick={enable}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.configureSourceActionLabel"
|
||||
defaultMessage="Change source configuration"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</WithSourceConfigurationFlyoutState>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</WithKibanaChrome>
|
||||
);
|
||||
export const InvalidNodeError: React.FunctionComponent<InvalidNodeErrorProps> = ({ nodeName }) => {
|
||||
const { show } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
|
||||
return (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
<CenteredEmptyPrompt
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.invalidNodeErrorTitle"
|
||||
defaultMessage="Looks like {nodeName} isn't collecting any metrics data"
|
||||
values={{
|
||||
nodeName,
|
||||
}}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.invalidNodeErrorDescription"
|
||||
defaultMessage="Double check your configuration"
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel"
|
||||
defaultMessage="View setup instructions"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton color="primary" onClick={show}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.configureSourceActionLabel"
|
||||
defaultMessage="Change source configuration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</WithKibanaChrome>
|
||||
);
|
||||
};
|
||||
|
||||
const CenteredEmptyPrompt = euiStyled(EuiEmptyPrompt)`
|
||||
align-self: center;
|
||||
|
|
|
@ -6,3 +6,7 @@
|
|||
|
||||
export { SourceConfigurationButton } from './source_configuration_button';
|
||||
export { SourceConfigurationFlyout } from './source_configuration_flyout';
|
||||
export {
|
||||
SourceConfigurationFlyoutState,
|
||||
useSourceConfigurationFlyoutState,
|
||||
} from './source_configuration_flyout_state';
|
||||
|
|
|
@ -52,6 +52,7 @@ export const IndicesConfigurationPanel = ({
|
|||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="metricIndicesInput"
|
||||
fullWidth
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
|
@ -78,7 +79,13 @@ export const IndicesConfigurationPanel = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText fullWidth disabled={isLoading} isLoading={isLoading} {...logAliasFieldProps} />
|
||||
<EuiFieldText
|
||||
data-test-subj="logIndicesInput"
|
||||
fullWidth
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
{...logAliasFieldProps}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
|
|
|
@ -37,7 +37,13 @@ export const NameConfigurationPanel = ({
|
|||
<FormattedMessage id="xpack.infra.sourceConfiguration.nameLabel" defaultMessage="Name" />
|
||||
}
|
||||
>
|
||||
<EuiFieldText fullWidth disabled={isLoading} isLoading={isLoading} {...nameFieldProps} />
|
||||
<EuiFieldText
|
||||
data-test-subj="nameInput"
|
||||
fullWidth
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
{...nameFieldProps}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
|
|
|
@ -5,26 +5,24 @@
|
|||
*/
|
||||
|
||||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
import { WithSourceConfigurationFlyoutState } from './source_configuration_flyout_state';
|
||||
import { Source } from '../../containers/source';
|
||||
import { SourceConfigurationFlyoutState } from './source_configuration_flyout_state';
|
||||
|
||||
export const SourceConfigurationButton: React.SFC = () => (
|
||||
<WithSourceConfigurationFlyoutState>
|
||||
{({ toggle }) => (
|
||||
<WithSource>
|
||||
{({ configuration }) => (
|
||||
<EuiButtonEmpty
|
||||
aria-label="Configure source"
|
||||
color="text"
|
||||
iconType="gear"
|
||||
onClick={toggle}
|
||||
>
|
||||
{configuration && configuration.name}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</WithSource>
|
||||
)}
|
||||
</WithSourceConfigurationFlyoutState>
|
||||
);
|
||||
export const SourceConfigurationButton: React.FunctionComponent = () => {
|
||||
const { toggleIsVisible } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
const { source } = useContext(Source.Context);
|
||||
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
aria-label="Configure source"
|
||||
color="text"
|
||||
data-test-subj="configureSourceButton"
|
||||
iconType="gear"
|
||||
onClick={toggleIsVisible}
|
||||
>
|
||||
{source && source.configuration && source.configuration.name}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,155 +16,184 @@ import {
|
|||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
import { FormattedMessage } from '@kbn/i18n/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 { WithSourceConfigurationFlyoutState } from './source_configuration_flyout_state';
|
||||
import { WithSourceConfigurationFormState } from './source_configuration_form_state';
|
||||
import { SourceConfigurationFlyoutState } from './source_configuration_flyout_state';
|
||||
import { useSourceConfigurationFormState } from './source_configuration_form_state';
|
||||
|
||||
const noop = () => undefined;
|
||||
|
||||
interface SourceConfigurationFlyoutProps {
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
export const SourceConfigurationFlyout: React.FunctionComponent = () => {
|
||||
const { isVisible, hide } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
|
||||
export const SourceConfigurationFlyout = injectI18n(({ intl }: SourceConfigurationFlyoutProps) => (
|
||||
<WithSourceConfigurationFlyoutState>
|
||||
{({ disable: close, value: isVisible }) =>
|
||||
isVisible ? (
|
||||
<WithSource>
|
||||
{({ create, configuration, exists, isLoading, update }) =>
|
||||
configuration ? (
|
||||
<WithSourceConfigurationFormState
|
||||
initialFormState={{
|
||||
name: configuration.name,
|
||||
description: configuration.description,
|
||||
fields: {
|
||||
container: configuration.fields.container,
|
||||
host: configuration.fields.host,
|
||||
message: configuration.fields.message,
|
||||
pod: configuration.fields.pod,
|
||||
tiebreaker: configuration.fields.tiebreaker,
|
||||
timestamp: configuration.fields.timestamp,
|
||||
},
|
||||
logAlias: configuration.logAlias,
|
||||
metricAlias: configuration.metricAlias,
|
||||
const {
|
||||
createSourceConfiguration,
|
||||
source,
|
||||
sourceExists,
|
||||
isLoading,
|
||||
updateSourceConfiguration,
|
||||
} = useContext(Source.Context);
|
||||
|
||||
const configuration = source && source.configuration;
|
||||
const initialFormState = useMemo(
|
||||
() =>
|
||||
configuration
|
||||
? {
|
||||
name: configuration.name,
|
||||
description: configuration.description,
|
||||
fields: {
|
||||
container: configuration.fields.container,
|
||||
host: configuration.fields.host,
|
||||
message: configuration.fields.message,
|
||||
pod: configuration.fields.pod,
|
||||
tiebreaker: configuration.fields.tiebreaker,
|
||||
timestamp: configuration.fields.timestamp,
|
||||
},
|
||||
logAlias: configuration.logAlias,
|
||||
metricAlias: configuration.metricAlias,
|
||||
}
|
||||
: defaultFormState,
|
||||
[configuration]
|
||||
);
|
||||
|
||||
const {
|
||||
fieldProps,
|
||||
formState,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
resetForm,
|
||||
updates,
|
||||
} = useSourceConfigurationFormState({
|
||||
initialFormState,
|
||||
});
|
||||
|
||||
const persistUpdates = useCallback(
|
||||
async () => {
|
||||
if (sourceExists) {
|
||||
await updateSourceConfiguration(updates);
|
||||
} else {
|
||||
await createSourceConfiguration(formState);
|
||||
}
|
||||
resetForm();
|
||||
},
|
||||
[sourceExists, updateSourceConfiguration, createSourceConfiguration, resetForm, formState]
|
||||
);
|
||||
|
||||
if (!isVisible || !configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
aria-labelledby="sourceConfigurationTitle"
|
||||
data-test-subj="sourceConfigurationFlyout"
|
||||
hideCloseButton
|
||||
onClose={noop}
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle>
|
||||
<h2 id="sourceConfigurationTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationTitle"
|
||||
defaultMessage="Configure source"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<NameConfigurationPanel isLoading={isLoading} nameFieldProps={fieldProps.name} />
|
||||
<EuiSpacer />
|
||||
<IndicesConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
logAliasFieldProps={fieldProps.logAlias}
|
||||
metricAliasFieldProps={fieldProps.metricAlias}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<FieldsConfigurationPanel
|
||||
containerFieldProps={fieldProps.containerField}
|
||||
hostFieldProps={fieldProps.hostField}
|
||||
isLoading={isLoading}
|
||||
podFieldProps={fieldProps.podField}
|
||||
tiebreakerFieldProps={fieldProps.tiebreakerField}
|
||||
timestampFieldProps={fieldProps.timestampField}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<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();
|
||||
}}
|
||||
>
|
||||
{({
|
||||
getCurrentFormState,
|
||||
getNameFieldProps,
|
||||
getLogAliasFieldProps,
|
||||
getMetricAliasFieldProps,
|
||||
getFieldFieldProps,
|
||||
isFormValid,
|
||||
resetForm,
|
||||
updates,
|
||||
}) => (
|
||||
<EuiFlyout
|
||||
aria-labelledby="sourceConfigurationTitle"
|
||||
hideCloseButton
|
||||
onClose={noop}
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle>
|
||||
<h2 id="sourceConfigurationTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationTitle"
|
||||
defaultMessage="Configure source"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<NameConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
nameFieldProps={getNameFieldProps()}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<IndicesConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
logAliasFieldProps={getLogAliasFieldProps()}
|
||||
metricAliasFieldProps={getMetricAliasFieldProps()}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<FieldsConfigurationPanel
|
||||
containerFieldProps={getFieldFieldProps('container')}
|
||||
hostFieldProps={getFieldFieldProps('host')}
|
||||
isLoading={isLoading}
|
||||
podFieldProps={getFieldFieldProps('pod')}
|
||||
tiebreakerFieldProps={getFieldFieldProps('tiebreaker')}
|
||||
timestampFieldProps={getFieldFieldProps('timestamp')}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
{updates.length === 0 ? (
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => close()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButtonEmpty
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
close();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.discardAndCloseButtonLabel"
|
||||
defaultMessage="Discard and Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? (
|
||||
<EuiButton color="primary" isLoading fill>
|
||||
Loading
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton
|
||||
color="primary"
|
||||
isDisabled={updates.length === 0 || !isFormValid()}
|
||||
fill
|
||||
onClick={() =>
|
||||
(exists ? update(updates) : create(getCurrentFormState())).then(
|
||||
() => resetForm()
|
||||
)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.updateSourceConfigurationButtonLabel"
|
||||
defaultMessage="Update Source"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
</WithSourceConfigurationFormState>
|
||||
) : null
|
||||
}
|
||||
</WithSource>
|
||||
) : null
|
||||
}
|
||||
</WithSourceConfigurationFlyoutState>
|
||||
));
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.discardAndCloseButtonLabel"
|
||||
defaultMessage="Discard and Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultFormState = {
|
||||
name: '',
|
||||
description: '',
|
||||
fields: {
|
||||
container: '',
|
||||
host: '',
|
||||
message: [],
|
||||
pod: '',
|
||||
tiebreaker: '',
|
||||
timestamp: '',
|
||||
},
|
||||
logAlias: '',
|
||||
metricAlias: '',
|
||||
};
|
||||
|
|
|
@ -4,10 +4,30 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createContainer from 'constate-latest';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { WithBinary, WithBinaryProps } from '../../containers/primitives/with_binary';
|
||||
export const useSourceConfigurationFlyoutState = ({
|
||||
initialVisibility = false,
|
||||
}: {
|
||||
initialVisibility?: boolean;
|
||||
} = {}) => {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(initialVisibility);
|
||||
|
||||
export const WithSourceConfigurationFlyoutState: React.SFC<WithBinaryProps> = props => (
|
||||
<WithBinary {...props} context="source-configuration-flyout" />
|
||||
);
|
||||
const toggleIsVisible = useCallback(
|
||||
() => setIsVisible(isCurrentlyVisible => !isCurrentlyVisible),
|
||||
[setIsVisible]
|
||||
);
|
||||
|
||||
const show = useCallback(() => setIsVisible(true), [setIsVisible]);
|
||||
const hide = useCallback(() => setIsVisible(false), [setIsVisible]);
|
||||
|
||||
return {
|
||||
hide,
|
||||
isVisible,
|
||||
show,
|
||||
toggleIsVisible,
|
||||
};
|
||||
};
|
||||
|
||||
export const SourceConfigurationFlyoutState = createContainer(useSourceConfigurationFlyoutState);
|
||||
|
|
|
@ -4,15 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ActionMap, Container as ConstateContainer, OnMount, SelectorMap } from 'constate';
|
||||
import mergeAll from 'lodash/fp/mergeAll';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { memoizeLast } from 'ui/utils/memoize';
|
||||
import { convertChangeToUpdater } from '../../../common/source_configuration';
|
||||
import { UpdateSourceInput } from '../../graphql/types';
|
||||
import { RendererFunction } from '../../utils/typed_react';
|
||||
|
||||
export interface InputFieldProps<
|
||||
Value extends string = string,
|
||||
|
@ -27,8 +24,6 @@ export interface InputFieldProps<
|
|||
|
||||
type FieldErrorMessage = string | JSX.Element;
|
||||
|
||||
type EditableFieldName = 'container' | 'host' | 'pod' | 'tiebreaker' | 'timestamp';
|
||||
|
||||
interface FormState {
|
||||
name: string;
|
||||
description: string;
|
||||
|
@ -44,154 +39,152 @@ interface FormState {
|
|||
};
|
||||
}
|
||||
|
||||
interface State {
|
||||
updates: UpdateSourceInput[];
|
||||
}
|
||||
export const useSourceConfigurationFormState = ({
|
||||
initialFormState,
|
||||
}: {
|
||||
initialFormState: FormState;
|
||||
}) => {
|
||||
const [updates, setUpdates] = useState<UpdateSourceInput[]>([]);
|
||||
|
||||
interface Actions {
|
||||
resetForm: () => void;
|
||||
updateName: (name: string) => void;
|
||||
updateLogAlias: (value: string) => void;
|
||||
updateMetricAlias: (value: string) => void;
|
||||
updateField: (field: EditableFieldName, value: string) => void;
|
||||
}
|
||||
|
||||
interface Selectors {
|
||||
getCurrentFormState: () => FormState;
|
||||
getNameFieldValidationErrors: () => FieldErrorMessage[];
|
||||
getLogAliasFieldValidationErrors: () => FieldErrorMessage[];
|
||||
getMetricAliasFieldValidationErrors: () => FieldErrorMessage[];
|
||||
getFieldFieldValidationErrors: (field: EditableFieldName) => FieldErrorMessage[];
|
||||
isFormValid: () => boolean;
|
||||
}
|
||||
|
||||
const createContainerProps = memoizeLast((initialFormState: FormState) => {
|
||||
const actions: ActionMap<State, Actions> = {
|
||||
resetForm: () => state => ({
|
||||
...state,
|
||||
updates: [],
|
||||
}),
|
||||
updateName: name => state => ({
|
||||
...state,
|
||||
updates: addOrCombineLastUpdate(state.updates, { setName: { name } }),
|
||||
}),
|
||||
updateLogAlias: logAlias => state => ({
|
||||
...state,
|
||||
updates: addOrCombineLastUpdate(state.updates, { setAliases: { logAlias } }),
|
||||
}),
|
||||
updateMetricAlias: metricAlias => state => ({
|
||||
...state,
|
||||
updates: addOrCombineLastUpdate(state.updates, { setAliases: { metricAlias } }),
|
||||
}),
|
||||
updateField: (field, value) => state => ({
|
||||
...state,
|
||||
updates: addOrCombineLastUpdate(state.updates, { setFields: { [field]: value } }),
|
||||
}),
|
||||
};
|
||||
|
||||
const getCurrentFormState = memoizeLast(
|
||||
(previousFormState: FormState, updates: UpdateSourceInput[]) =>
|
||||
updates
|
||||
.map(convertChangeToUpdater)
|
||||
.reduce((state, updater) => updater(state), previousFormState)
|
||||
const addOrCombineLastUpdate = useCallback(
|
||||
(newUpdate: UpdateSourceInput) =>
|
||||
setUpdates(currentUpdates => [
|
||||
...currentUpdates.slice(0, -1),
|
||||
...maybeCombineUpdates(currentUpdates[currentUpdates.length - 1], newUpdate),
|
||||
]),
|
||||
[setUpdates]
|
||||
);
|
||||
|
||||
const selectors: SelectorMap<State, Selectors> = {
|
||||
getCurrentFormState: () => ({ updates }) => getCurrentFormState(initialFormState, updates),
|
||||
getNameFieldValidationErrors: () => state =>
|
||||
validateInputFieldNotEmpty(selectors.getCurrentFormState()(state).name),
|
||||
getLogAliasFieldValidationErrors: () => state =>
|
||||
validateInputFieldNotEmpty(selectors.getCurrentFormState()(state).logAlias),
|
||||
getMetricAliasFieldValidationErrors: () => state =>
|
||||
validateInputFieldNotEmpty(selectors.getCurrentFormState()(state).metricAlias),
|
||||
getFieldFieldValidationErrors: field => state =>
|
||||
validateInputFieldNotEmpty(selectors.getCurrentFormState()(state).fields[field]),
|
||||
isFormValid: () => state =>
|
||||
[
|
||||
selectors.getNameFieldValidationErrors()(state),
|
||||
selectors.getLogAliasFieldValidationErrors()(state),
|
||||
selectors.getMetricAliasFieldValidationErrors()(state),
|
||||
selectors.getFieldFieldValidationErrors('container')(state),
|
||||
selectors.getFieldFieldValidationErrors('host')(state),
|
||||
selectors.getFieldFieldValidationErrors('pod')(state),
|
||||
selectors.getFieldFieldValidationErrors('tiebreaker')(state),
|
||||
selectors.getFieldFieldValidationErrors('timestamp')(state),
|
||||
].every(errors => errors.length === 0),
|
||||
};
|
||||
const resetForm = useCallback(() => setUpdates([]), []);
|
||||
|
||||
const formState = useMemo(
|
||||
() =>
|
||||
updates
|
||||
.map(convertChangeToUpdater)
|
||||
.reduce((state, updater) => updater(state), initialFormState),
|
||||
[updates, initialFormState]
|
||||
);
|
||||
|
||||
const nameFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.name),
|
||||
name: 'name',
|
||||
onChange: name => addOrCombineLastUpdate({ setName: { name } }),
|
||||
value: formState.name,
|
||||
}),
|
||||
[formState.name, addOrCombineLastUpdate]
|
||||
);
|
||||
const logAliasFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.logAlias),
|
||||
name: 'logAlias',
|
||||
onChange: logAlias => addOrCombineLastUpdate({ setAliases: { logAlias } }),
|
||||
value: formState.logAlias,
|
||||
}),
|
||||
[formState.logAlias, addOrCombineLastUpdate]
|
||||
);
|
||||
const metricAliasFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.metricAlias),
|
||||
name: 'metricAlias',
|
||||
onChange: metricAlias => addOrCombineLastUpdate({ setAliases: { metricAlias } }),
|
||||
value: formState.metricAlias,
|
||||
}),
|
||||
[formState.metricAlias, addOrCombineLastUpdate]
|
||||
);
|
||||
const containerFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.container),
|
||||
name: `containerField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { container: value } }),
|
||||
value: formState.fields.container,
|
||||
}),
|
||||
[formState.fields.container, addOrCombineLastUpdate]
|
||||
);
|
||||
const hostFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.host),
|
||||
name: `hostField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { host: value } }),
|
||||
value: formState.fields.host,
|
||||
}),
|
||||
[formState.fields.host, addOrCombineLastUpdate]
|
||||
);
|
||||
const podFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.pod),
|
||||
name: `podField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { pod: value } }),
|
||||
value: formState.fields.pod,
|
||||
}),
|
||||
[formState.fields.pod, addOrCombineLastUpdate]
|
||||
);
|
||||
const tiebreakerFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.tiebreaker),
|
||||
name: `tiebreakerField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { tiebreaker: value } }),
|
||||
value: formState.fields.tiebreaker,
|
||||
}),
|
||||
[formState.fields.tiebreaker, addOrCombineLastUpdate]
|
||||
);
|
||||
const timestampFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.timestamp),
|
||||
name: `timestampField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { timestamp: value } }),
|
||||
value: formState.fields.timestamp,
|
||||
}),
|
||||
[formState.fields.timestamp, addOrCombineLastUpdate]
|
||||
);
|
||||
|
||||
const fieldProps = useMemo(
|
||||
() => ({
|
||||
name: nameFieldProps,
|
||||
logAlias: logAliasFieldProps,
|
||||
metricAlias: metricAliasFieldProps,
|
||||
containerField: containerFieldFieldProps,
|
||||
hostField: hostFieldFieldProps,
|
||||
podField: podFieldFieldProps,
|
||||
tiebreakerField: tiebreakerFieldFieldProps,
|
||||
timestampField: timestampFieldFieldProps,
|
||||
}),
|
||||
[
|
||||
nameFieldProps,
|
||||
logAliasFieldProps,
|
||||
metricAliasFieldProps,
|
||||
containerFieldFieldProps,
|
||||
hostFieldFieldProps,
|
||||
podFieldFieldProps,
|
||||
tiebreakerFieldFieldProps,
|
||||
timestampFieldFieldProps,
|
||||
]
|
||||
);
|
||||
|
||||
const isFormValid = useMemo(
|
||||
() => Object.values(fieldProps).every(({ error }) => error.length <= 0),
|
||||
[fieldProps]
|
||||
);
|
||||
|
||||
const isFormDirty = useMemo(() => updates.length > 0, [updates]);
|
||||
|
||||
return {
|
||||
actions,
|
||||
initialState: { updates: [] } as State,
|
||||
selectors,
|
||||
fieldProps,
|
||||
formState,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
resetForm,
|
||||
updates,
|
||||
};
|
||||
});
|
||||
|
||||
interface WithSourceConfigurationFormStateProps {
|
||||
children: RendererFunction<
|
||||
State &
|
||||
Actions &
|
||||
Selectors & {
|
||||
getFieldFieldProps: (field: EditableFieldName) => InputFieldProps;
|
||||
getLogAliasFieldProps: () => InputFieldProps;
|
||||
getMetricAliasFieldProps: () => InputFieldProps;
|
||||
getNameFieldProps: () => InputFieldProps;
|
||||
}
|
||||
>;
|
||||
initialFormState: FormState;
|
||||
onMount?: OnMount<State>;
|
||||
}
|
||||
|
||||
export const WithSourceConfigurationFormState: React.SFC<WithSourceConfigurationFormStateProps> = ({
|
||||
children,
|
||||
initialFormState,
|
||||
onMount,
|
||||
}) => (
|
||||
<ConstateContainer
|
||||
{...createContainerProps(initialFormState)}
|
||||
context="source-configuration-form"
|
||||
onMount={onMount}
|
||||
>
|
||||
{args => {
|
||||
const currentFormState = args.getCurrentFormState();
|
||||
return children({
|
||||
...args,
|
||||
getNameFieldProps: () =>
|
||||
createInputFieldProps({
|
||||
errors: args.getNameFieldValidationErrors(),
|
||||
name: 'name',
|
||||
onChange: args.updateName,
|
||||
value: currentFormState.name,
|
||||
}),
|
||||
getLogAliasFieldProps: () =>
|
||||
createInputFieldProps({
|
||||
errors: args.getLogAliasFieldValidationErrors(),
|
||||
name: 'logAlias',
|
||||
onChange: args.updateLogAlias,
|
||||
value: currentFormState.logAlias,
|
||||
}),
|
||||
getMetricAliasFieldProps: () =>
|
||||
createInputFieldProps({
|
||||
errors: args.getMetricAliasFieldValidationErrors(),
|
||||
name: 'metricAlias',
|
||||
onChange: args.updateMetricAlias,
|
||||
value: currentFormState.metricAlias,
|
||||
}),
|
||||
getFieldFieldProps: field =>
|
||||
createInputFieldProps({
|
||||
errors: args.getFieldFieldValidationErrors(field),
|
||||
name: `${field}Field`,
|
||||
onChange: newValue => args.updateField(field, newValue),
|
||||
value: currentFormState.fields[field],
|
||||
}),
|
||||
});
|
||||
}}
|
||||
</ConstateContainer>
|
||||
);
|
||||
|
||||
const addOrCombineLastUpdate = (updates: UpdateSourceInput[], newUpdate: UpdateSourceInput) => [
|
||||
...updates.slice(0, -1),
|
||||
...maybeCombineUpdates(updates[updates.length - 1], newUpdate),
|
||||
];
|
||||
};
|
||||
|
||||
const createInputFieldProps = <
|
||||
Value extends string = string,
|
||||
|
|
|
@ -7,14 +7,17 @@
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { ErrorPage } from '../../components/error_page';
|
||||
import { ErrorPage } from './error_page';
|
||||
|
||||
interface SourceErrorPageProps {
|
||||
errorMessage: string;
|
||||
retry: () => void;
|
||||
}
|
||||
|
||||
export const SourceErrorPage: React.SFC<SourceErrorPageProps> = ({ errorMessage, retry }) => (
|
||||
export const SourceErrorPage: React.FunctionComponent<SourceErrorPageProps> = ({
|
||||
errorMessage,
|
||||
retry,
|
||||
}) => (
|
||||
<ErrorPage
|
||||
shortMessage={
|
||||
<FormattedMessage
|
|
@ -7,9 +7,9 @@
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { LoadingPage } from '../../components/loading_page';
|
||||
import { LoadingPage } from './loading_page';
|
||||
|
||||
export const SourceLoadingPage: React.SFC = () => (
|
||||
export const SourceLoadingPage: React.FunctionComponent = () => (
|
||||
<LoadingPage
|
||||
message={
|
||||
<FormattedMessage
|
|
@ -1,40 +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 {
|
||||
ActionMap,
|
||||
Container as ConstateContainer,
|
||||
ContainerProps as ConstateContainerProps,
|
||||
Omit,
|
||||
} from 'constate';
|
||||
import React from 'react';
|
||||
|
||||
interface State {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface Actions {
|
||||
disable: () => void;
|
||||
enable: () => void;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
const actions: ActionMap<State, Actions> = {
|
||||
disable: () => state => ({ ...state, value: false }),
|
||||
enable: () => state => ({ ...state, value: true }),
|
||||
toggle: () => state => ({ ...state, value: !state.value }),
|
||||
};
|
||||
|
||||
export type WithBinaryProps = Omit<
|
||||
ConstateContainerProps<State, Actions>,
|
||||
'actions' | 'initialState' | 'pure'
|
||||
> & {
|
||||
initialValue?: boolean;
|
||||
};
|
||||
|
||||
export const WithBinary: React.SFC<WithBinaryProps> = ({ initialValue = false, ...props }) => (
|
||||
<ConstateContainer {...props} actions={actions} initialState={{ value: initialValue }} pure />
|
||||
);
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { sharedFragments } from '../../../common/graphql/shared';
|
||||
import {
|
||||
sourceConfigurationFieldsFragment,
|
||||
sourceStatusFieldsFragment,
|
||||
} from './source_fields_fragment.gql_query';
|
||||
|
||||
export const createSourceMutation = gql`
|
||||
mutation CreateSourceConfigurationMutation(
|
||||
$sourceId: ID!
|
||||
$sourceConfiguration: CreateSourceInput!
|
||||
) {
|
||||
createSource(id: $sourceId, source: $sourceConfiguration) {
|
||||
source {
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
...SourceConfigurationFields
|
||||
}
|
||||
status {
|
||||
...SourceStatusFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraSourceFields}
|
||||
${sourceConfigurationFieldsFragment}
|
||||
${sourceStatusFieldsFragment}
|
||||
`;
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './with_binary';
|
||||
export { Source } from './source';
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 gql from 'graphql-tag';
|
||||
|
||||
import { sharedFragments } from '../../../common/graphql/shared';
|
||||
import {
|
||||
sourceConfigurationFieldsFragment,
|
||||
sourceStatusFieldsFragment,
|
||||
} from './source_fields_fragment.gql_query';
|
||||
|
||||
export const sourceQuery = gql`
|
||||
query SourceQuery($sourceId: ID = "default") {
|
||||
source(id: $sourceId) {
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
...SourceConfigurationFields
|
||||
}
|
||||
status {
|
||||
...SourceStatusFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraSourceFields}
|
||||
${sourceConfigurationFieldsFragment}
|
||||
${sourceStatusFieldsFragment}
|
||||
`;
|
191
x-pack/plugins/infra/public/containers/source/source.tsx
Normal file
191
x-pack/plugins/infra/public/containers/source/source.tsx
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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 { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
CreateSourceConfigurationMutation,
|
||||
CreateSourceInput,
|
||||
SourceQuery,
|
||||
UpdateSourceInput,
|
||||
UpdateSourceMutation,
|
||||
} from '../../graphql/types';
|
||||
import { useApolloClient } from '../../utils/apollo_context';
|
||||
import { useTrackedPromise } from '../../utils/use_tracked_promise';
|
||||
import { createSourceMutation } from './create_source.gql_query';
|
||||
import { sourceQuery } from './query_source.gql_query';
|
||||
import { updateSourceMutation } from './update_source.gql_query';
|
||||
|
||||
type Source = SourceQuery.Query['source'];
|
||||
|
||||
export const useSource = ({ sourceId }: { sourceId: string }) => {
|
||||
const apolloClient = useApolloClient();
|
||||
const [source, setSource] = useState<Source | undefined>(undefined);
|
||||
|
||||
const [loadSourceRequest, loadSource] = useTrackedPromise(
|
||||
{
|
||||
cancelPreviousOn: 'resolution',
|
||||
createPromise: async () => {
|
||||
if (!apolloClient) {
|
||||
throw new DependencyError('Failed to load source: No apollo client available.');
|
||||
}
|
||||
|
||||
return await apolloClient.query<SourceQuery.Query, SourceQuery.Variables>({
|
||||
fetchPolicy: 'no-cache',
|
||||
query: sourceQuery,
|
||||
variables: {
|
||||
sourceId,
|
||||
},
|
||||
});
|
||||
},
|
||||
onResolve: response => {
|
||||
setSource(response.data.source);
|
||||
},
|
||||
},
|
||||
[apolloClient, sourceId]
|
||||
);
|
||||
|
||||
const [createSourceConfigurationRequest, createSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
createPromise: async (newSourceConfiguration: CreateSourceInput) => {
|
||||
if (!apolloClient) {
|
||||
throw new DependencyError(
|
||||
'Failed to create source configuration: No apollo client available.'
|
||||
);
|
||||
}
|
||||
|
||||
return await apolloClient.mutate<
|
||||
CreateSourceConfigurationMutation.Mutation,
|
||||
CreateSourceConfigurationMutation.Variables
|
||||
>({
|
||||
mutation: createSourceMutation,
|
||||
fetchPolicy: 'no-cache',
|
||||
variables: {
|
||||
sourceId,
|
||||
sourceConfiguration: {
|
||||
name: newSourceConfiguration.name,
|
||||
description: newSourceConfiguration.description,
|
||||
metricAlias: newSourceConfiguration.metricAlias,
|
||||
logAlias: newSourceConfiguration.logAlias,
|
||||
fields: newSourceConfiguration.fields
|
||||
? {
|
||||
container: newSourceConfiguration.fields.container,
|
||||
host: newSourceConfiguration.fields.host,
|
||||
pod: newSourceConfiguration.fields.pod,
|
||||
tiebreaker: newSourceConfiguration.fields.tiebreaker,
|
||||
timestamp: newSourceConfiguration.fields.timestamp,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
onResolve: response => {
|
||||
if (response.data) {
|
||||
setSource(response.data.createSource.source);
|
||||
}
|
||||
},
|
||||
},
|
||||
[apolloClient, sourceId]
|
||||
);
|
||||
|
||||
const [updateSourceConfigurationRequest, updateSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
createPromise: async (changes: UpdateSourceInput[]) => {
|
||||
if (!apolloClient) {
|
||||
throw new DependencyError(
|
||||
'Failed to update source configuration: No apollo client available.'
|
||||
);
|
||||
}
|
||||
|
||||
return await apolloClient.mutate<
|
||||
UpdateSourceMutation.Mutation,
|
||||
UpdateSourceMutation.Variables
|
||||
>({
|
||||
mutation: updateSourceMutation,
|
||||
fetchPolicy: 'no-cache',
|
||||
variables: {
|
||||
sourceId,
|
||||
changes,
|
||||
},
|
||||
});
|
||||
},
|
||||
onResolve: response => {
|
||||
if (response.data) {
|
||||
setSource(response.data.updateSource.source);
|
||||
}
|
||||
},
|
||||
},
|
||||
[apolloClient, sourceId]
|
||||
);
|
||||
|
||||
const derivedIndexPattern = useMemo(
|
||||
() => ({
|
||||
fields: source ? source.status.indexFields : [],
|
||||
title: source ? `${source.configuration.logAlias}` : 'unknown-index',
|
||||
}),
|
||||
[source]
|
||||
);
|
||||
|
||||
const isLoading = useMemo(
|
||||
() =>
|
||||
[
|
||||
loadSourceRequest.state,
|
||||
createSourceConfigurationRequest.state,
|
||||
updateSourceConfigurationRequest.state,
|
||||
].some(state => state === 'pending'),
|
||||
[
|
||||
loadSourceRequest.state,
|
||||
createSourceConfigurationRequest.state,
|
||||
updateSourceConfigurationRequest.state,
|
||||
]
|
||||
);
|
||||
|
||||
const sourceExists = useMemo(() => (source ? !!source.version : undefined), [source]);
|
||||
|
||||
const logIndicesExist = useMemo(() => source && source.status && source.status.logIndicesExist, [
|
||||
source,
|
||||
]);
|
||||
const metricIndicesExist = useMemo(
|
||||
() => source && source.status && source.status.metricIndicesExist,
|
||||
[source]
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
loadSource();
|
||||
},
|
||||
[loadSource]
|
||||
);
|
||||
|
||||
return {
|
||||
createSourceConfiguration,
|
||||
derivedIndexPattern,
|
||||
logIndicesExist,
|
||||
isLoading,
|
||||
isLoadingSource: loadSourceRequest.state === 'pending',
|
||||
hasFailedLoadingSource: loadSourceRequest.state === 'rejected',
|
||||
loadSource,
|
||||
loadSourceFailureMessage:
|
||||
loadSourceRequest.state === 'rejected' ? `${loadSourceRequest.value}` : undefined,
|
||||
metricIndicesExist,
|
||||
source,
|
||||
sourceExists,
|
||||
sourceId,
|
||||
updateSourceConfiguration,
|
||||
version: source && source.version ? source.version : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const Source = createContainer(useSource);
|
||||
|
||||
class DependencyError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 gql from 'graphql-tag';
|
||||
|
||||
export const sourceConfigurationFieldsFragment = gql`
|
||||
fragment SourceConfigurationFields on InfraSourceConfiguration {
|
||||
name
|
||||
description
|
||||
logAlias
|
||||
metricAlias
|
||||
fields {
|
||||
container
|
||||
host
|
||||
message
|
||||
pod
|
||||
tiebreaker
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const sourceStatusFieldsFragment = gql`
|
||||
fragment SourceStatusFields on InfraSourceStatus {
|
||||
indexFields {
|
||||
name
|
||||
type
|
||||
searchable
|
||||
aggregatable
|
||||
}
|
||||
logIndicesExist
|
||||
metricIndicesExist
|
||||
}
|
||||
`;
|
|
@ -6,16 +6,28 @@
|
|||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { sourceFieldsFragment } from './source_fields_fragment.gql_query';
|
||||
import { sharedFragments } from '../../../common/graphql/shared';
|
||||
import {
|
||||
sourceConfigurationFieldsFragment,
|
||||
sourceStatusFieldsFragment,
|
||||
} from './source_fields_fragment.gql_query';
|
||||
|
||||
export const updateSourceMutation = gql`
|
||||
mutation UpdateSourceMutation($sourceId: ID = "default", $changes: [UpdateSourceInput!]!) {
|
||||
updateSource(id: $sourceId, changes: $changes) {
|
||||
source {
|
||||
...SourceFields
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
...SourceConfigurationFields
|
||||
}
|
||||
status {
|
||||
...SourceStatusFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sourceFieldsFragment}
|
||||
${sharedFragments.InfraSourceFields}
|
||||
${sourceConfigurationFieldsFragment}
|
||||
${sourceStatusFieldsFragment}
|
||||
`;
|
|
@ -1,21 +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 gql from 'graphql-tag';
|
||||
|
||||
import { sourceFieldsFragment } from './source_fields_fragment.gql_query';
|
||||
|
||||
export const createSourceMutation = gql`
|
||||
mutation createSourceMutation($sourceId: ID!, $sourceConfiguration: CreateSourceInput!) {
|
||||
createSource(id: $sourceId, source: $sourceConfiguration) {
|
||||
source {
|
||||
...SourceFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sourceFieldsFragment}
|
||||
`;
|
|
@ -4,6 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { SourceErrorPage } from './source_error_page';
|
||||
export { SourceLoadingPage } from './source_loading_page';
|
||||
export { WithSource } from './with_source';
|
||||
|
|
|
@ -1,19 +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 gql from 'graphql-tag';
|
||||
|
||||
import { sourceFieldsFragment } from './source_fields_fragment.gql_query';
|
||||
|
||||
export const sourceQuery = gql`
|
||||
query SourceQuery($sourceId: ID = "default") {
|
||||
source(id: $sourceId) {
|
||||
...SourceFields
|
||||
}
|
||||
}
|
||||
|
||||
${sourceFieldsFragment}
|
||||
`;
|
|
@ -1,39 +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 gql from 'graphql-tag';
|
||||
|
||||
export const sourceFieldsFragment = gql`
|
||||
fragment SourceFields on InfraSource {
|
||||
id
|
||||
version
|
||||
updatedAt
|
||||
configuration {
|
||||
name
|
||||
description
|
||||
metricAlias
|
||||
logAlias
|
||||
fields {
|
||||
container
|
||||
host
|
||||
message
|
||||
pod
|
||||
tiebreaker
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
status {
|
||||
indexFields {
|
||||
name
|
||||
type
|
||||
searchable
|
||||
aggregatable
|
||||
}
|
||||
logIndicesExist
|
||||
metricIndicesExist
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -4,274 +4,62 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
import { Container as ConstateContainer, OnMount } from 'constate';
|
||||
import React from 'react';
|
||||
import { ApolloConsumer } from 'react-apollo';
|
||||
import { createSelector } from 'reselect';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
import { memoizeLast } from 'ui/utils/memoize';
|
||||
import {
|
||||
CreateSourceInput,
|
||||
CreateSourceMutation,
|
||||
SourceQuery,
|
||||
UpdateSourceInput,
|
||||
UpdateSourceMutation,
|
||||
} from '../../graphql/types';
|
||||
import {
|
||||
createStatusActions,
|
||||
createStatusSelectors,
|
||||
Operation,
|
||||
OperationStatus,
|
||||
StatusHistoryUpdater,
|
||||
} from '../../utils/operation_status';
|
||||
import { inferActionMap, inferEffectMap, inferSelectorMap } from '../../utils/typed_constate';
|
||||
import { CreateSourceInput, SourceQuery, UpdateSourceInput } from '../../graphql/types';
|
||||
import { RendererFunction } from '../../utils/typed_react';
|
||||
import { createSourceMutation } from './create_source.gql_query';
|
||||
import { sourceQuery } from './query_source.gql_query';
|
||||
import { updateSourceMutation } from './update_source.gql_query';
|
||||
|
||||
type Operations =
|
||||
| Operation<'create', CreateSourceMutation.Variables>
|
||||
| Operation<'load', SourceQuery.Variables>
|
||||
| Operation<'update', UpdateSourceMutation.Variables>;
|
||||
|
||||
interface State {
|
||||
operationStatusHistory: Array<OperationStatus<Operations>>;
|
||||
source: SourceQuery.Query['source'] | undefined;
|
||||
}
|
||||
|
||||
const createContainerProps = memoizeLast((sourceId: string, apolloClient: ApolloClient<any>) => {
|
||||
const initialState: State = {
|
||||
operationStatusHistory: [],
|
||||
source: undefined,
|
||||
};
|
||||
|
||||
const actions = inferActionMap<State>()({
|
||||
...createStatusActions((updater: StatusHistoryUpdater<Operations>) => (state: State) => ({
|
||||
...state,
|
||||
operationStatusHistory: updater(state.operationStatusHistory),
|
||||
})),
|
||||
});
|
||||
|
||||
const getDerivedIndexPattern = createSelector(
|
||||
(state: State) =>
|
||||
(state && state.source && state.source.status && state.source.status.indexFields) || [],
|
||||
(state: State) =>
|
||||
(state &&
|
||||
state.source &&
|
||||
state.source.configuration &&
|
||||
state.source.configuration.logAlias) ||
|
||||
undefined,
|
||||
(state: State) =>
|
||||
(state &&
|
||||
state.source &&
|
||||
state.source.configuration &&
|
||||
state.source.configuration.metricAlias) ||
|
||||
undefined,
|
||||
(indexFields, logAlias, metricAlias) => ({
|
||||
fields: indexFields,
|
||||
title: `${logAlias},${metricAlias}`,
|
||||
})
|
||||
);
|
||||
|
||||
const selectors = inferSelectorMap<State>()({
|
||||
...createStatusSelectors(({ operationStatusHistory }: State) => operationStatusHistory),
|
||||
getConfiguration: () => state =>
|
||||
(state && state.source && state.source.configuration) || undefined,
|
||||
getSourceId: () => () => sourceId,
|
||||
getLogIndicesExist: () => state =>
|
||||
(state && state.source && state.source.status && state.source.status.logIndicesExist) ||
|
||||
undefined,
|
||||
getMetricIndicesExist: () => state =>
|
||||
(state && state.source && state.source.status && state.source.status.metricIndicesExist) ||
|
||||
undefined,
|
||||
getDerivedIndexPattern: () => getDerivedIndexPattern,
|
||||
getVersion: () => state => (state && state.source && state.source.version) || undefined,
|
||||
getExists: () => state => (state && state.source && !!state.source.version) || false,
|
||||
});
|
||||
|
||||
const effects = inferEffectMap<State>()({
|
||||
create: (sourceConfiguration: CreateSourceInput) => ({ setState }) => {
|
||||
const variables = {
|
||||
sourceId,
|
||||
sourceConfiguration: {
|
||||
name: sourceConfiguration.name,
|
||||
description: sourceConfiguration.description,
|
||||
metricAlias: sourceConfiguration.metricAlias,
|
||||
logAlias: sourceConfiguration.logAlias,
|
||||
fields: sourceConfiguration.fields
|
||||
? {
|
||||
container: sourceConfiguration.fields.container,
|
||||
host: sourceConfiguration.fields.host,
|
||||
pod: sourceConfiguration.fields.pod,
|
||||
tiebreaker: sourceConfiguration.fields.tiebreaker,
|
||||
timestamp: sourceConfiguration.fields.timestamp,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
setState(actions.startOperation({ name: 'create', parameters: variables }));
|
||||
|
||||
return apolloClient
|
||||
.mutate<CreateSourceMutation.Mutation, CreateSourceMutation.Variables>({
|
||||
mutation: createSourceMutation,
|
||||
fetchPolicy: 'no-cache',
|
||||
variables,
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
setState(state => ({
|
||||
...actions.finishOperation({ name: 'create', parameters: variables })(state),
|
||||
source: result.data ? result.data.createSource.source : state.source,
|
||||
}));
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
setState(state => ({
|
||||
...actions.failOperation({ name: 'create', parameters: variables }, `${error}`)(
|
||||
state
|
||||
),
|
||||
}));
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
},
|
||||
load: () => ({ setState }) => {
|
||||
const variables = {
|
||||
sourceId,
|
||||
};
|
||||
|
||||
setState(actions.startOperation({ name: 'load', parameters: variables }));
|
||||
|
||||
return apolloClient
|
||||
.query<SourceQuery.Query, SourceQuery.Variables>({
|
||||
query: sourceQuery,
|
||||
fetchPolicy: 'no-cache',
|
||||
variables,
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
setState(state => ({
|
||||
...actions.finishOperation({ name: 'load', parameters: variables })(state),
|
||||
source: result.data.source,
|
||||
}));
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
setState(state => ({
|
||||
...actions.failOperation({ name: 'load', parameters: variables }, `${error}`)(state),
|
||||
}));
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
},
|
||||
update: (changes: UpdateSourceInput[]) => ({ setState }) => {
|
||||
const variables = {
|
||||
sourceId,
|
||||
changes,
|
||||
};
|
||||
|
||||
setState(actions.startOperation({ name: 'update', parameters: variables }));
|
||||
|
||||
return apolloClient
|
||||
.mutate<UpdateSourceMutation.Mutation, UpdateSourceMutation.Variables>({
|
||||
mutation: updateSourceMutation,
|
||||
fetchPolicy: 'no-cache',
|
||||
variables,
|
||||
})
|
||||
.then(
|
||||
result => {
|
||||
setState(state => ({
|
||||
...actions.finishOperation({ name: 'update', parameters: variables })(state),
|
||||
source: result.data ? result.data.updateSource.source : state.source,
|
||||
}));
|
||||
return result;
|
||||
},
|
||||
error => {
|
||||
setState(state => ({
|
||||
...actions.failOperation({ name: 'update', parameters: variables }, `${error}`)(
|
||||
state
|
||||
),
|
||||
}));
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const onMount: OnMount<State> = props => {
|
||||
effects.load()(props);
|
||||
};
|
||||
|
||||
return {
|
||||
actions,
|
||||
context: `source-${sourceId}`,
|
||||
effects,
|
||||
initialState,
|
||||
key: `source-${sourceId}`,
|
||||
onMount,
|
||||
selectors,
|
||||
};
|
||||
});
|
||||
import { Source } from '../source';
|
||||
|
||||
interface WithSourceProps {
|
||||
children: RendererFunction<{
|
||||
configuration?: SourceQuery.Query['source']['configuration'];
|
||||
create: (sourceConfiguration: CreateSourceInput) => Promise<any>;
|
||||
create: (sourceConfiguration: CreateSourceInput) => Promise<any> | undefined;
|
||||
derivedIndexPattern: StaticIndexPattern;
|
||||
exists: boolean;
|
||||
exists?: boolean;
|
||||
hasFailed: boolean;
|
||||
isLoading: boolean;
|
||||
lastFailureMessage?: string;
|
||||
load: () => Promise<any>;
|
||||
load: () => Promise<any> | undefined;
|
||||
logIndicesExist?: boolean;
|
||||
metricAlias?: string;
|
||||
metricIndicesExist?: boolean;
|
||||
sourceId: string;
|
||||
update: (changes: UpdateSourceInput[]) => Promise<any>;
|
||||
update: (changes: UpdateSourceInput[]) => Promise<any> | undefined;
|
||||
version?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const WithSource: React.SFC<WithSourceProps> = ({ children }) => (
|
||||
<ApolloConsumer>
|
||||
{client => (
|
||||
<ConstateContainer {...createContainerProps('default', client)}>
|
||||
{({
|
||||
create,
|
||||
getConfiguration,
|
||||
getDerivedIndexPattern,
|
||||
getExists,
|
||||
getHasFailed,
|
||||
getIsInProgress,
|
||||
getLastFailureMessage,
|
||||
getLogIndicesExist,
|
||||
getMetricIndicesExist,
|
||||
getSourceId,
|
||||
getVersion,
|
||||
load,
|
||||
update,
|
||||
}) =>
|
||||
children({
|
||||
create,
|
||||
configuration: getConfiguration(),
|
||||
derivedIndexPattern: getDerivedIndexPattern(),
|
||||
exists: getExists(),
|
||||
hasFailed: getHasFailed(),
|
||||
isLoading: getIsInProgress(),
|
||||
lastFailureMessage: getLastFailureMessage(),
|
||||
load,
|
||||
logIndicesExist: getLogIndicesExist(),
|
||||
metricIndicesExist: getMetricIndicesExist(),
|
||||
sourceId: getSourceId(),
|
||||
update,
|
||||
version: getVersion(),
|
||||
})
|
||||
}
|
||||
</ConstateContainer>
|
||||
)}
|
||||
</ApolloConsumer>
|
||||
);
|
||||
export const WithSource: React.FunctionComponent<WithSourceProps> = ({ children }) => {
|
||||
const {
|
||||
createSourceConfiguration,
|
||||
derivedIndexPattern,
|
||||
source,
|
||||
sourceExists,
|
||||
sourceId,
|
||||
metricIndicesExist,
|
||||
logIndicesExist,
|
||||
isLoading,
|
||||
loadSource,
|
||||
hasFailedLoadingSource,
|
||||
loadSourceFailureMessage,
|
||||
updateSourceConfiguration,
|
||||
version,
|
||||
} = useContext(Source.Context);
|
||||
|
||||
return children({
|
||||
create: createSourceConfiguration,
|
||||
configuration: source && source.configuration,
|
||||
derivedIndexPattern,
|
||||
exists: sourceExists,
|
||||
hasFailed: hasFailedLoadingSource,
|
||||
isLoading,
|
||||
lastFailureMessage: loadSourceFailureMessage,
|
||||
load: loadSource,
|
||||
logIndicesExist,
|
||||
metricIndicesExist,
|
||||
sourceId,
|
||||
update: updateSourceConfiguration,
|
||||
version,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -718,6 +718,92 @@ export namespace MetricsQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace CreateSourceConfigurationMutation {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
sourceConfiguration: CreateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
createSource: CreateSource;
|
||||
};
|
||||
|
||||
export type CreateSource = {
|
||||
__typename?: 'CreateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
} & InfraSourceFields.Fragment;
|
||||
|
||||
export type Configuration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type Status = SourceStatusFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace SourceQuery {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
} & InfraSourceFields.Fragment;
|
||||
|
||||
export type Configuration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type Status = SourceStatusFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace UpdateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
changes: UpdateSourceInput[];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
updateSource: UpdateSource;
|
||||
};
|
||||
|
||||
export type UpdateSource = {
|
||||
__typename?: 'UpdateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
} & InfraSourceFields.Fragment;
|
||||
|
||||
export type Configuration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type Status = SourceStatusFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace WaffleNodesQuery {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
|
@ -776,62 +862,6 @@ export namespace WaffleNodesQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace CreateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
sourceConfiguration: CreateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
createSource: CreateSource;
|
||||
};
|
||||
|
||||
export type CreateSource = {
|
||||
__typename?: 'CreateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = SourceFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace SourceQuery {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = SourceFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace UpdateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
changes: UpdateSourceInput[];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
|
||||
updateSource: UpdateSource;
|
||||
};
|
||||
|
||||
export type UpdateSource = {
|
||||
__typename?: 'UpdateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = SourceFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace LogEntries {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
|
@ -910,32 +940,18 @@ export namespace LogEntries {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace SourceFields {
|
||||
export namespace SourceConfigurationFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
id: string;
|
||||
|
||||
version?: string | null;
|
||||
|
||||
updatedAt?: number | null;
|
||||
|
||||
configuration: Configuration;
|
||||
|
||||
status: Status;
|
||||
};
|
||||
|
||||
export type Configuration = {
|
||||
__typename?: 'InfraSourceConfiguration';
|
||||
|
||||
name: string;
|
||||
|
||||
description: string;
|
||||
|
||||
metricAlias: string;
|
||||
|
||||
logAlias: string;
|
||||
|
||||
metricAlias: string;
|
||||
|
||||
fields: Fields;
|
||||
};
|
||||
|
||||
|
@ -954,8 +970,10 @@ export namespace SourceFields {
|
|||
|
||||
timestamp: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Status = {
|
||||
export namespace SourceStatusFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraSourceStatus';
|
||||
|
||||
indexFields: IndexFields[];
|
||||
|
@ -987,3 +1005,15 @@ export namespace InfraTimeKeyFields {
|
|||
tiebreaker: number;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace InfraSourceFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraSource';
|
||||
|
||||
id: string;
|
||||
|
||||
version?: string | null;
|
||||
|
||||
updatedAt?: number | null;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
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 { SourceConfigurationFlyoutState } from '../../components/source_configuration';
|
||||
import { Source } from '../../containers/source';
|
||||
import { MetricsExplorerPage } from './metrics_explorer';
|
||||
import { SnapshotPage } from './snapshot';
|
||||
|
||||
|
@ -19,38 +22,42 @@ interface InfrastructurePageProps extends RouteComponentProps {
|
|||
}
|
||||
|
||||
export const InfrastructurePage = injectI18n(({ match, intl }: InfrastructurePageProps) => (
|
||||
<ColumnarPage>
|
||||
<DocumentTitle
|
||||
title={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.documentTitle',
|
||||
defaultMessage: 'Infrastructure',
|
||||
})}
|
||||
/>
|
||||
<Source.Provider sourceId="default">
|
||||
<SourceConfigurationFlyoutState.Provider>
|
||||
<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',
|
||||
})}
|
||||
/>
|
||||
|
||||
<RoutedTabs
|
||||
tabs={[
|
||||
{
|
||||
title: 'Snapshot',
|
||||
path: `${match.path}/snapshot`,
|
||||
},
|
||||
// {
|
||||
// title: 'Metrics explorer',
|
||||
// path: `${match.path}/metrics-explorer`,
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
<RoutedTabs
|
||||
tabs={[
|
||||
{
|
||||
title: 'Snapshot',
|
||||
path: `${match.path}/snapshot`,
|
||||
},
|
||||
// {
|
||||
// title: 'Metrics explorer',
|
||||
// path: `${match.path}/metrics-explorer`,
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Switch>
|
||||
<Route path={`${match.path}/snapshot`} component={SnapshotPage} />
|
||||
<Route path={`${match.path}/metrics-explorer`} component={MetricsExplorerPage} />
|
||||
</Switch>
|
||||
</ColumnarPage>
|
||||
<Switch>
|
||||
<Route path={`${match.path}/snapshot`} component={SnapshotPage} />
|
||||
<Route path={`${match.path}/metrics-explorer`} component={MetricsExplorerPage} />
|
||||
</Switch>
|
||||
</ColumnarPage>
|
||||
</SourceConfigurationFlyoutState.Provider>
|
||||
</Source.Provider>
|
||||
));
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
*/
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { SnapshotPageContent } from './page_content';
|
||||
import { SnapshotToolbar } from './toolbar';
|
||||
|
@ -18,121 +17,110 @@ import { Header } from '../../../components/header';
|
|||
import { ColumnarPage } from '../../../components/page';
|
||||
|
||||
import { SourceConfigurationFlyout } from '../../../components/source_configuration';
|
||||
import { WithSourceConfigurationFlyoutState } from '../../../components/source_configuration/source_configuration_flyout_state';
|
||||
import { SourceConfigurationFlyoutState } from '../../../components/source_configuration';
|
||||
import { SourceErrorPage } from '../../../components/source_error_page';
|
||||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { Source } from '../../../containers/source';
|
||||
import { WithWaffleFilterUrlState } from '../../../containers/waffle/with_waffle_filters';
|
||||
import { WithWaffleOptionsUrlState } from '../../../containers/waffle/with_waffle_options';
|
||||
import { WithWaffleTimeUrlState } from '../../../containers/waffle/with_waffle_time';
|
||||
import { WithKibanaChrome } from '../../../containers/with_kibana_chrome';
|
||||
import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../../containers/with_source';
|
||||
|
||||
interface SnapshotPageProps extends RouteComponentProps {
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
export const SnapshotPage = injectI18n(({ intl }) => {
|
||||
const { show } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
const {
|
||||
derivedIndexPattern,
|
||||
hasFailedLoadingSource,
|
||||
isLoading,
|
||||
loadSourceFailureMessage,
|
||||
loadSource,
|
||||
metricIndicesExist,
|
||||
} = useContext(Source.Context);
|
||||
|
||||
export const SnapshotPage = injectI18n(
|
||||
class extends React.Component<SnapshotPageProps, {}> {
|
||||
public static displayName = 'SnapshotPage';
|
||||
|
||||
public render() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<ColumnarPage>
|
||||
<DocumentTitle
|
||||
title={(previousTitle: string) =>
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.infrastructureSnapshotPage.documentTitle',
|
||||
defaultMessage: '{previousTitle} | Snapshot',
|
||||
},
|
||||
{
|
||||
previousTitle,
|
||||
}
|
||||
)
|
||||
return (
|
||||
<ColumnarPage>
|
||||
<DocumentTitle
|
||||
title={(previousTitle: string) =>
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'xpack.infra.infrastructureSnapshotPage.documentTitle',
|
||||
defaultMessage: '{previousTitle} | Snapshot',
|
||||
},
|
||||
{
|
||||
previousTitle,
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
breadcrumbs={[
|
||||
{
|
||||
href: '#/',
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.header.infrastructureTitle',
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SourceConfigurationFlyout />
|
||||
<WithSource>
|
||||
{({
|
||||
derivedIndexPattern,
|
||||
hasFailed,
|
||||
isLoading,
|
||||
lastFailureMessage,
|
||||
load,
|
||||
metricIndicesExist,
|
||||
}) =>
|
||||
isLoading ? (
|
||||
<SourceLoadingPage />
|
||||
) : metricIndicesExist ? (
|
||||
<>
|
||||
<WithWaffleTimeUrlState />
|
||||
<WithWaffleFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithWaffleOptionsUrlState />
|
||||
<SnapshotToolbar />
|
||||
<SnapshotPageContent />
|
||||
</>
|
||||
) : hasFailed ? (
|
||||
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
|
||||
) : (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
<NoIndices
|
||||
title={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.noMetricsIndicesTitle',
|
||||
defaultMessage: "Looks like you don't have any metrics indices.",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
breadcrumbs={[
|
||||
{
|
||||
href: '#/',
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.header.infrastructureTitle',
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SourceConfigurationFlyout />
|
||||
{isLoading ? (
|
||||
<SourceLoadingPage />
|
||||
) : metricIndicesExist ? (
|
||||
<>
|
||||
<WithWaffleTimeUrlState />
|
||||
<WithWaffleFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithWaffleOptionsUrlState />
|
||||
<SnapshotToolbar />
|
||||
<SnapshotPageContent />
|
||||
</>
|
||||
) : hasFailedLoadingSource ? (
|
||||
<SourceErrorPage errorMessage={loadSourceFailureMessage || ''} retry={loadSource} />
|
||||
) : (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
<NoIndices
|
||||
title={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.noMetricsIndicesTitle',
|
||||
defaultMessage: "Looks like you don't have any metrics indices.",
|
||||
})}
|
||||
message={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.noMetricsIndicesDescription',
|
||||
defaultMessage: "Let's add some!",
|
||||
})}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel',
|
||||
defaultMessage: 'View setup instructions',
|
||||
})}
|
||||
message={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.noMetricsIndicesDescription',
|
||||
defaultMessage: "Let's add some!",
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
data-test-subj="configureSourceButton"
|
||||
color="primary"
|
||||
onClick={show}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.configureSourceActionLabel',
|
||||
defaultMessage: 'Change source configuration',
|
||||
})}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`${basePath}/app/kibana#/home/tutorial_directory/metrics`}
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel',
|
||||
defaultMessage: 'View setup instructions',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<WithSourceConfigurationFlyoutState>
|
||||
{({ enable }) => (
|
||||
<EuiButton color="primary" onClick={enable}>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.configureSourceActionLabel',
|
||||
defaultMessage: 'Change source configuration',
|
||||
})}
|
||||
</EuiButton>
|
||||
)}
|
||||
</WithSourceConfigurationFlyoutState>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
data-test-subj="noMetricsIndicesPrompt"
|
||||
/>
|
||||
)}
|
||||
</WithKibanaChrome>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
</ColumnarPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
data-test-subj="noMetricsIndicesPrompt"
|
||||
/>
|
||||
)}
|
||||
</WithKibanaChrome>
|
||||
)}
|
||||
</ColumnarPage>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React from 'react';
|
||||
import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { Source } from '../../containers/source';
|
||||
import { RedirectToLogs } from './redirect_to_logs';
|
||||
import { RedirectToNodeDetail } from './redirect_to_node_detail';
|
||||
import { RedirectToNodeLogs } from './redirect_to_node_logs';
|
||||
|
@ -20,18 +21,20 @@ export class LinkToPage extends React.Component<LinkToPageProps> {
|
|||
const { match } = this.props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-logs/:nodeId`}
|
||||
component={RedirectToNodeLogs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-detail/:nodeId`}
|
||||
component={RedirectToNodeDetail}
|
||||
/>
|
||||
<Route path={`${match.url}/logs`} component={RedirectToLogs} />
|
||||
<Redirect to="/infrastructure" />
|
||||
</Switch>
|
||||
<Source.Provider sourceId="default">
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-logs/:nodeId`}
|
||||
component={RedirectToNodeLogs}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.url}/:nodeType(host|container|pod)-detail/:nodeId`}
|
||||
component={RedirectToNodeDetail}
|
||||
/>
|
||||
<Route path={`${match.url}/logs`} component={RedirectToLogs} />
|
||||
<Redirect to="/infrastructure" />
|
||||
</Switch>
|
||||
</Source.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { LogsPage } from './logs';
|
||||
export { LogsPage } from './page';
|
||||
|
|
|
@ -1,174 +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, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { LogsPageContent } from './page_content';
|
||||
import { LogsToolbar } from './toolbar';
|
||||
|
||||
import { DocumentTitle } from '../../components/document_title';
|
||||
import { NoIndices } from '../../components/empty_states/no_indices';
|
||||
|
||||
import { Header } from '../../components/header';
|
||||
import { HelpCenterContent } from '../../components/help_center_content';
|
||||
import { LogFlyout } from '../../components/logging/log_flyout';
|
||||
import { ColumnarPage } from '../../components/page';
|
||||
|
||||
import { SourceConfigurationFlyout } from '../../components/source_configuration';
|
||||
import { WithSourceConfigurationFlyoutState } from '../../components/source_configuration/source_configuration_flyout_state';
|
||||
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
|
||||
import { WithLogFilter, WithLogFilterUrlState } from '../../containers/logs/with_log_filter';
|
||||
import { WithLogFlyout } from '../../containers/logs/with_log_flyout';
|
||||
import { WithFlyoutOptions } from '../../containers/logs/with_log_flyout_options';
|
||||
import { WithFlyoutOptionsUrlState } from '../../containers/logs/with_log_flyout_options';
|
||||
import { WithLogMinimapUrlState } from '../../containers/logs/with_log_minimap';
|
||||
import { WithLogPositionUrlState } from '../../containers/logs/with_log_position';
|
||||
import { WithLogTextviewUrlState } from '../../containers/logs/with_log_textview';
|
||||
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
|
||||
import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../containers/with_source';
|
||||
|
||||
interface Props {
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
export const LogsPage = injectI18n(
|
||||
class extends React.Component<Props> {
|
||||
public static displayName = 'LogsPage';
|
||||
|
||||
public render() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<LogViewConfiguration.Provider>
|
||||
<ColumnarPage>
|
||||
<Header
|
||||
breadcrumbs={[
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<WithSource>
|
||||
{({
|
||||
derivedIndexPattern,
|
||||
hasFailed,
|
||||
isLoading,
|
||||
lastFailureMessage,
|
||||
load,
|
||||
logIndicesExist,
|
||||
sourceId,
|
||||
}) => (
|
||||
<>
|
||||
<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 />
|
||||
{isLoading ? (
|
||||
<SourceLoadingPage />
|
||||
) : logIndicesExist ? (
|
||||
<>
|
||||
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithLogPositionUrlState />
|
||||
<WithLogMinimapUrlState />
|
||||
<WithLogTextviewUrlState />
|
||||
<WithFlyoutOptionsUrlState />
|
||||
<LogsToolbar />
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({ applyFilterQueryFromKueryExpression }) => (
|
||||
<React.Fragment>
|
||||
<WithFlyoutOptions>
|
||||
{({ showFlyout, setFlyoutItem }) => (
|
||||
<LogsPageContent
|
||||
showFlyout={showFlyout}
|
||||
setFlyoutItem={setFlyoutItem}
|
||||
/>
|
||||
)}
|
||||
</WithFlyoutOptions>
|
||||
<WithLogFlyout sourceId={sourceId}>
|
||||
{({ flyoutItem, hideFlyout, loading }) => (
|
||||
<LogFlyout
|
||||
setFilter={applyFilterQueryFromKueryExpression}
|
||||
flyoutItem={flyoutItem}
|
||||
hideFlyout={hideFlyout}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
</WithLogFlyout>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</WithLogFilter>
|
||||
</>
|
||||
) : hasFailed ? (
|
||||
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
|
||||
) : (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
<NoIndices
|
||||
title={intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.noLoggingIndicesTitle',
|
||||
defaultMessage: "Looks like you don't have any logging indices.",
|
||||
})}
|
||||
message={intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.noLoggingIndicesDescription',
|
||||
defaultMessage: "Let's add some!",
|
||||
})}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`${basePath}/app/kibana#/home/tutorial_directory/logging`}
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id:
|
||||
'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel',
|
||||
defaultMessage: 'View setup instructions',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<WithSourceConfigurationFlyoutState>
|
||||
{({ enable }) => (
|
||||
<EuiButton color="primary" onClick={enable}>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.configureSourceActionLabel',
|
||||
defaultMessage: 'Change source configuration',
|
||||
})}
|
||||
</EuiButton>
|
||||
)}
|
||||
</WithSourceConfigurationFlyoutState>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</WithKibanaChrome>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</WithSource>
|
||||
</ColumnarPage>
|
||||
</LogViewConfiguration.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
21
x-pack/plugins/infra/public/pages/logs/page.tsx
Normal file
21
x-pack/plugins/infra/public/pages/logs/page.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { ColumnarPage } from '../../components/page';
|
||||
import { LogsPageContent } from './page_content';
|
||||
import { LogsPageHeader } from './page_header';
|
||||
import { LogsPageProviders } from './page_providers';
|
||||
|
||||
export const LogsPage = () => (
|
||||
<LogsPageProviders>
|
||||
<ColumnarPage>
|
||||
<LogsPageHeader />
|
||||
<LogsPageContent />
|
||||
</ColumnarPage>
|
||||
</LogsPageProviders>
|
||||
);
|
|
@ -6,113 +6,32 @@
|
|||
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import euiStyled from '../../../../../common/eui_styled_components';
|
||||
import { AutoSizer } from '../../components/auto_sizer';
|
||||
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 { WithLogPosition } from '../../containers/logs/with_log_position';
|
||||
import { WithStreamItems } from '../../containers/logs/with_stream_items';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
setFlyoutItem: (id: string) => void;
|
||||
showFlyout: () => void;
|
||||
}
|
||||
|
||||
export const LogsPageContent: React.FunctionComponent<Props> = ({ showFlyout, setFlyoutItem }) => {
|
||||
const { intervalSize, textScale, textWrap } = useContext(LogViewConfiguration.Context);
|
||||
export const LogsPageContent: React.FunctionComponent = () => {
|
||||
const {
|
||||
hasFailedLoadingSource,
|
||||
isLoadingSource,
|
||||
logIndicesExist,
|
||||
loadSource,
|
||||
loadSourceFailureMessage,
|
||||
} = useContext(Source.Context);
|
||||
|
||||
return (
|
||||
<PageContent>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => (
|
||||
<LogPageEventStreamColumn innerRef={measureRef}>
|
||||
<WithLogPosition>
|
||||
{({
|
||||
isAutoReloading,
|
||||
jumpToTargetPosition,
|
||||
reportVisiblePositions,
|
||||
targetPosition,
|
||||
}) => (
|
||||
<WithStreamItems initializeOnMount={!isAutoReloading}>
|
||||
{({
|
||||
hasMoreAfterEnd,
|
||||
hasMoreBeforeStart,
|
||||
isLoadingMore,
|
||||
isReloading,
|
||||
items,
|
||||
lastLoadedTime,
|
||||
loadNewerEntries,
|
||||
}) => (
|
||||
<ScrollableLogTextStreamView
|
||||
hasMoreAfterEnd={hasMoreAfterEnd}
|
||||
hasMoreBeforeStart={hasMoreBeforeStart}
|
||||
height={height}
|
||||
isLoadingMore={isLoadingMore}
|
||||
isReloading={isReloading}
|
||||
isStreaming={isAutoReloading}
|
||||
items={items}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
lastLoadedTime={lastLoadedTime}
|
||||
loadNewerItems={loadNewerEntries}
|
||||
reportVisibleInterval={reportVisiblePositions}
|
||||
scale={textScale}
|
||||
target={targetPosition}
|
||||
width={width}
|
||||
wrap={textWrap}
|
||||
setFlyoutItem={setFlyoutItem}
|
||||
showFlyout={showFlyout}
|
||||
/>
|
||||
)}
|
||||
</WithStreamItems>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
</LogPageEventStreamColumn>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => {
|
||||
return (
|
||||
<LogPageMinimapColumn innerRef={measureRef}>
|
||||
<WithSummary>
|
||||
{({ buckets }) => (
|
||||
<WithLogPosition>
|
||||
{({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => (
|
||||
<LogMinimap
|
||||
height={height}
|
||||
width={width}
|
||||
highlightedInterval={visibleTimeInterval}
|
||||
intervalSize={intervalSize}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
summaryBuckets={buckets}
|
||||
target={visibleMidpointTime}
|
||||
/>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
)}
|
||||
</WithSummary>
|
||||
</LogPageMinimapColumn>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
</PageContent>
|
||||
<>
|
||||
{isLoadingSource ? (
|
||||
<SourceLoadingPage />
|
||||
) : logIndicesExist ? (
|
||||
<LogsPageLogsContent />
|
||||
) : hasFailedLoadingSource ? (
|
||||
<SourceErrorPage errorMessage={loadSourceFailureMessage || ''} retry={loadSource} />
|
||||
) : (
|
||||
<LogsPageNoIndicesContent />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const LogPageEventStreamColumn = euiStyled.div`
|
||||
flex: 1 0 0%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const LogPageMinimapColumn = euiStyled.div`
|
||||
flex: 1 0 0%;
|
||||
overflow: hidden;
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
|
44
x-pack/plugins/infra/public/pages/logs/page_header.tsx
Normal file
44
x-pack/plugins/infra/public/pages/logs/page_header.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 } from '@kbn/i18n/react';
|
||||
import React from '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';
|
||||
|
||||
export const LogsPageHeader = injectI18n(({ intl }) => {
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
breadcrumbs={[
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<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 />
|
||||
</>
|
||||
);
|
||||
});
|
152
x-pack/plugins/infra/public/pages/logs/page_logs_content.tsx
Normal file
152
x-pack/plugins/infra/public/pages/logs/page_logs_content.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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, { useContext } from 'react';
|
||||
|
||||
import euiStyled from '../../../../../common/eui_styled_components';
|
||||
import { AutoSizer } from '../../components/auto_sizer';
|
||||
import { LogFlyout } from '../../components/logging/log_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 { WithLogFlyout } from '../../containers/logs/with_log_flyout';
|
||||
import { WithFlyoutOptionsUrlState } from '../../containers/logs/with_log_flyout_options';
|
||||
import { WithFlyoutOptions } from '../../containers/logs/with_log_flyout_options';
|
||||
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 { WithStreamItems } from '../../containers/logs/with_stream_items';
|
||||
import { Source } from '../../containers/source';
|
||||
|
||||
import { LogsToolbar } from './page_toolbar';
|
||||
|
||||
export const LogsPageLogsContent: React.FunctionComponent = () => {
|
||||
const { derivedIndexPattern, sourceId } = useContext(Source.Context);
|
||||
const { intervalSize, textScale, textWrap } = useContext(LogViewConfiguration.Context);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithLogPositionUrlState />
|
||||
<WithLogMinimapUrlState />
|
||||
<WithLogTextviewUrlState />
|
||||
<WithFlyoutOptionsUrlState />
|
||||
<LogsToolbar />
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({ applyFilterQueryFromKueryExpression }) => (
|
||||
<WithLogFlyout sourceId={sourceId}>
|
||||
{({ flyoutItem, hideFlyout, loading }) => (
|
||||
<LogFlyout
|
||||
setFilter={applyFilterQueryFromKueryExpression}
|
||||
flyoutItem={flyoutItem}
|
||||
hideFlyout={hideFlyout}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
</WithLogFlyout>
|
||||
)}
|
||||
</WithLogFilter>
|
||||
<WithFlyoutOptions>
|
||||
{({ showFlyout, setFlyoutItem }) => (
|
||||
<PageContent>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => (
|
||||
<LogPageEventStreamColumn innerRef={measureRef}>
|
||||
<WithLogPosition>
|
||||
{({
|
||||
isAutoReloading,
|
||||
jumpToTargetPosition,
|
||||
reportVisiblePositions,
|
||||
targetPosition,
|
||||
}) => (
|
||||
<WithStreamItems initializeOnMount={!isAutoReloading}>
|
||||
{({
|
||||
hasMoreAfterEnd,
|
||||
hasMoreBeforeStart,
|
||||
isLoadingMore,
|
||||
isReloading,
|
||||
items,
|
||||
lastLoadedTime,
|
||||
loadNewerEntries,
|
||||
}) => (
|
||||
<ScrollableLogTextStreamView
|
||||
hasMoreAfterEnd={hasMoreAfterEnd}
|
||||
hasMoreBeforeStart={hasMoreBeforeStart}
|
||||
height={height}
|
||||
isLoadingMore={isLoadingMore}
|
||||
isReloading={isReloading}
|
||||
isStreaming={isAutoReloading}
|
||||
items={items}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
lastLoadedTime={lastLoadedTime}
|
||||
loadNewerItems={loadNewerEntries}
|
||||
reportVisibleInterval={reportVisiblePositions}
|
||||
scale={textScale}
|
||||
target={targetPosition}
|
||||
width={width}
|
||||
wrap={textWrap}
|
||||
setFlyoutItem={setFlyoutItem}
|
||||
showFlyout={showFlyout}
|
||||
/>
|
||||
)}
|
||||
</WithStreamItems>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
</LogPageEventStreamColumn>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => {
|
||||
return (
|
||||
<LogPageMinimapColumn innerRef={measureRef}>
|
||||
<WithSummary>
|
||||
{({ buckets }) => (
|
||||
<WithLogPosition>
|
||||
{({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => (
|
||||
<LogMinimap
|
||||
height={height}
|
||||
width={width}
|
||||
highlightedInterval={visibleTimeInterval}
|
||||
intervalSize={intervalSize}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
summaryBuckets={buckets}
|
||||
target={visibleMidpointTime}
|
||||
/>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
)}
|
||||
</WithSummary>
|
||||
</LogPageMinimapColumn>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
</PageContent>
|
||||
)}
|
||||
</WithFlyoutOptions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const LogPageEventStreamColumn = euiStyled.div`
|
||||
flex: 1 0 0%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const LogPageMinimapColumn = euiStyled.div`
|
||||
flex: 1 0 0%;
|
||||
overflow: hidden;
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { NoIndices } from '../../components/empty_states/no_indices';
|
||||
import { SourceConfigurationFlyoutState } from '../../components/source_configuration';
|
||||
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
|
||||
|
||||
export const LogsPageNoIndicesContent = injectI18n(({ intl }) => {
|
||||
const { show } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
|
||||
return (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
<NoIndices
|
||||
data-test-subj="noLogsIndicesPrompt"
|
||||
title={intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.noLoggingIndicesTitle',
|
||||
defaultMessage: "Looks like you don't have any logging indices.",
|
||||
})}
|
||||
message={intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.noLoggingIndicesDescription',
|
||||
defaultMessage: "Let's add some!",
|
||||
})}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
href={`${basePath}/app/kibana#/home/tutorial_directory/logging`}
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel',
|
||||
defaultMessage: 'View setup instructions',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButton data-test-subj="configureSourceButton" color="primary" onClick={show}>
|
||||
{intl.formatMessage({
|
||||
id: 'xpack.infra.configureSourceActionLabel',
|
||||
defaultMessage: 'Change source configuration',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</WithKibanaChrome>
|
||||
);
|
||||
});
|
19
x-pack/plugins/infra/public/pages/logs/page_providers.tsx
Normal file
19
x-pack/plugins/infra/public/pages/logs/page_providers.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
|
||||
import { Source } from '../../containers/source';
|
||||
|
||||
export const LogsPageProviders: React.FunctionComponent = ({ children }) => (
|
||||
<Source.Provider sourceId="default">
|
||||
<SourceConfigurationFlyoutState.Provider>
|
||||
<LogViewConfiguration.Provider>{children}</LogViewConfiguration.Provider>
|
||||
</SourceConfigurationFlyoutState.Provider>
|
||||
</Source.Provider>
|
||||
);
|
109
x-pack/plugins/infra/public/pages/logs/page_toolbar.tsx
Normal file
109
x-pack/plugins/infra/public/pages/logs/page_toolbar.tsx
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 { 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 { 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 { 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 { derivedIndexPattern } = useContext(Source.Context);
|
||||
const {
|
||||
availableIntervalSizes,
|
||||
availableTextScales,
|
||||
intervalSize,
|
||||
setIntervalSize,
|
||||
setTextScale,
|
||||
setTextWrap,
|
||||
textScale,
|
||||
textWrap,
|
||||
} = useContext(LogViewConfiguration.Context);
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder',
|
||||
defaultMessage: 'Search for log entries… (e.g. host.name:host-1)',
|
||||
})}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</WithLogFilter>
|
||||
)}
|
||||
</WithKueryAutocompletion>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SourceConfigurationButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogCustomizationMenu>
|
||||
<LogMinimapScaleControls
|
||||
availableIntervalSizes={availableIntervalSizes}
|
||||
setIntervalSize={setIntervalSize}
|
||||
intervalSize={intervalSize}
|
||||
/>
|
||||
<LogTextWrapControls wrap={textWrap} setTextWrap={setTextWrap} />
|
||||
<LogTextScaleControls
|
||||
availableTextScales={availableTextScales}
|
||||
textScale={textScale}
|
||||
setTextScale={setTextScale}
|
||||
/>
|
||||
</LogCustomizationMenu>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<WithLogPosition resetOnUnmount>
|
||||
{({
|
||||
visibleMidpointTime,
|
||||
isAutoReloading,
|
||||
jumpToTargetPositionTime,
|
||||
startLiveStreaming,
|
||||
stopLiveStreaming,
|
||||
}) => (
|
||||
<LogTimeControls
|
||||
currentTime={visibleMidpointTime}
|
||||
isLiveStreaming={isAutoReloading}
|
||||
jumpToTime={jumpToTargetPositionTime}
|
||||
startLiveStreaming={startLiveStreaming}
|
||||
stopLiveStreaming={stopLiveStreaming}
|
||||
/>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
);
|
||||
});
|
|
@ -1,112 +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 { 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 { 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 { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
|
||||
import { WithLogFilter } from '../../containers/logs/with_log_filter';
|
||||
import { WithLogPosition } from '../../containers/logs/with_log_position';
|
||||
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
|
||||
export const LogsToolbar = injectI18n(({ intl }) => {
|
||||
const {
|
||||
availableIntervalSizes,
|
||||
availableTextScales,
|
||||
intervalSize,
|
||||
setIntervalSize,
|
||||
setTextScale,
|
||||
setTextWrap,
|
||||
textScale,
|
||||
textWrap,
|
||||
} = useContext(LogViewConfiguration.Context);
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
<WithSource>
|
||||
{({ derivedIndexPattern }) => (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder',
|
||||
defaultMessage: 'Search for log entries… (e.g. host.name:host-1)',
|
||||
})}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</WithLogFilter>
|
||||
)}
|
||||
</WithKueryAutocompletion>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SourceConfigurationButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogCustomizationMenu>
|
||||
<LogMinimapScaleControls
|
||||
availableIntervalSizes={availableIntervalSizes}
|
||||
setIntervalSize={setIntervalSize}
|
||||
intervalSize={intervalSize}
|
||||
/>
|
||||
<LogTextWrapControls wrap={textWrap} setTextWrap={setTextWrap} />
|
||||
<LogTextScaleControls
|
||||
availableTextScales={availableTextScales}
|
||||
textScale={textScale}
|
||||
setTextScale={setTextScale}
|
||||
/>
|
||||
</LogCustomizationMenu>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<WithLogPosition resetOnUnmount>
|
||||
{({
|
||||
visibleMidpointTime,
|
||||
isAutoReloading,
|
||||
jumpToTargetPositionTime,
|
||||
startLiveStreaming,
|
||||
stopLiveStreaming,
|
||||
}) => (
|
||||
<LogTimeControls
|
||||
currentTime={visibleMidpointTime}
|
||||
isLiveStreaming={isAutoReloading}
|
||||
jumpToTime={jumpToTargetPositionTime}
|
||||
startLiveStreaming={startLiveStreaming}
|
||||
stopLiveStreaming={stopLiveStreaming}
|
||||
/>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</WithSource>
|
||||
</Toolbar>
|
||||
);
|
||||
});
|
|
@ -31,7 +31,6 @@ import { SourceConfigurationFlyout } from '../../components/source_configuration
|
|||
import { WithMetadata } from '../../containers/metadata/with_metadata';
|
||||
import { WithMetrics } from '../../containers/metrics/with_metrics';
|
||||
import {
|
||||
MetricsTimeContainer,
|
||||
WithMetricsTime,
|
||||
WithMetricsTimeUrlState,
|
||||
} from '../../containers/metrics/with_metrics_time';
|
||||
|
@ -40,6 +39,7 @@ import { InfraNodeType, InfraTimerangeInput } from '../../graphql/types';
|
|||
import { Error, ErrorPageBody } from '../error';
|
||||
import { layoutCreators } from './layouts';
|
||||
import { InfraMetricLayoutSection } from './layouts/types';
|
||||
import { MetricDetailPageProviders } from './page_providers';
|
||||
|
||||
const DetailPageContent = euiStyled(PageContent)`
|
||||
overflow: auto;
|
||||
|
@ -89,7 +89,7 @@ export const MetricDetail = withTheme(
|
|||
const layouts = layoutCreator(this.props.theme);
|
||||
|
||||
return (
|
||||
<MetricsTimeContainer.Provider>
|
||||
<MetricDetailPageProviders>
|
||||
<WithSource>
|
||||
{({ sourceId }) => (
|
||||
<WithMetricsTime>
|
||||
|
@ -241,7 +241,7 @@ export const MetricDetail = withTheme(
|
|||
</WithMetricsTime>
|
||||
)}
|
||||
</WithSource>
|
||||
</MetricsTimeContainer.Provider>
|
||||
</MetricDetailPageProviders>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
19
x-pack/plugins/infra/public/pages/metrics/page_providers.tsx
Normal file
19
x-pack/plugins/infra/public/pages/metrics/page_providers.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { MetricsTimeContainer } from '../../containers/metrics/with_metrics_time';
|
||||
import { Source } from '../../containers/source';
|
||||
|
||||
export const MetricDetailPageProviders: React.FunctionComponent = ({ children }) => (
|
||||
<Source.Provider sourceId="default">
|
||||
<SourceConfigurationFlyoutState.Provider>
|
||||
<MetricsTimeContainer.Provider>{children}</MetricsTimeContainer.Provider>
|
||||
</SourceConfigurationFlyoutState.Provider>
|
||||
</Source.Provider>
|
||||
);
|
|
@ -1,47 +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.
|
||||
*/
|
||||
|
||||
interface MemoizedCall {
|
||||
args: any[];
|
||||
returnValue: any;
|
||||
this: any;
|
||||
}
|
||||
|
||||
// A symbol expressing, that the memoized function has never been called
|
||||
const neverCalled: unique symbol = Symbol();
|
||||
type NeverCalled = typeof neverCalled;
|
||||
|
||||
/**
|
||||
* A simple memoize function, that only stores the last returned value
|
||||
* and uses the identity of all passed parameters as a cache key.
|
||||
*/
|
||||
function memoizeLast<T extends (...args: any[]) => any>(func: T): T {
|
||||
let prevCall: MemoizedCall | NeverCalled = neverCalled;
|
||||
|
||||
// We need to use a `function` here for proper this passing.
|
||||
const memoizedFunction = function(this: any, ...args: any[]) {
|
||||
if (
|
||||
prevCall !== neverCalled &&
|
||||
prevCall.this === this &&
|
||||
prevCall.args.length === args.length &&
|
||||
prevCall.args.every((arg, index) => arg === args[index])
|
||||
) {
|
||||
return prevCall.returnValue;
|
||||
}
|
||||
|
||||
prevCall = {
|
||||
args,
|
||||
this: this,
|
||||
returnValue: func.apply(this, args),
|
||||
};
|
||||
|
||||
return prevCall.returnValue;
|
||||
} as T;
|
||||
|
||||
return memoizedFunction;
|
||||
}
|
||||
|
||||
export { memoizeLast };
|
|
@ -1,98 +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 last from 'lodash/fp/last';
|
||||
|
||||
export interface InProgressStatus<O extends Operation<string, any>> {
|
||||
operation: O;
|
||||
status: 'in-progress';
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface SucceededStatus<O extends Operation<string, any>> {
|
||||
operation: O;
|
||||
status: 'succeeded';
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface FailedStatus<O extends Operation<string, any>> {
|
||||
message: string;
|
||||
operation: O;
|
||||
status: 'failed';
|
||||
time: number;
|
||||
}
|
||||
|
||||
const isFailedStatus = <O extends Operation<string, any>>(
|
||||
status: OperationStatus<O>
|
||||
): status is FailedStatus<O> => status.status === 'failed';
|
||||
|
||||
export type OperationStatus<O extends Operation<string, any>> =
|
||||
| InProgressStatus<O>
|
||||
| SucceededStatus<O>
|
||||
| FailedStatus<O>;
|
||||
|
||||
export interface Operation<Name extends string, Parameters> {
|
||||
name: Name;
|
||||
parameters: Parameters;
|
||||
}
|
||||
|
||||
export const createStatusSelectors = <S extends {}>(
|
||||
selectStatusHistory: (state: S) => Array<OperationStatus<any>>
|
||||
) => ({
|
||||
getIsInProgress: () => (state: S) => {
|
||||
const lastStatus = last(selectStatusHistory(state));
|
||||
return lastStatus ? lastStatus.status === 'in-progress' : false;
|
||||
},
|
||||
getHasSucceeded: () => (state: S) => {
|
||||
const lastStatus = last(selectStatusHistory(state));
|
||||
return lastStatus ? lastStatus.status === 'succeeded' : false;
|
||||
},
|
||||
getHasFailed: () => (state: S) => {
|
||||
const lastStatus = last(selectStatusHistory(state));
|
||||
return lastStatus ? lastStatus.status === 'failed' : false;
|
||||
},
|
||||
getLastFailureMessage: () => (state: S) => {
|
||||
const lastStatus = last(selectStatusHistory(state).filter(isFailedStatus));
|
||||
return lastStatus ? lastStatus.message : undefined;
|
||||
},
|
||||
});
|
||||
|
||||
export type StatusHistoryUpdater<Operations extends Operation<string, any>> = (
|
||||
statusHistory: Array<OperationStatus<Operations>>
|
||||
) => Array<OperationStatus<Operations>>;
|
||||
|
||||
export const createStatusActions = <S extends {}, Operations extends Operation<string, any>>(
|
||||
updateStatusHistory: (updater: StatusHistoryUpdater<Operations>) => (state: S) => S
|
||||
) => ({
|
||||
startOperation: (operation: Operations) =>
|
||||
updateStatusHistory(statusHistory => [
|
||||
...statusHistory,
|
||||
{
|
||||
operation,
|
||||
status: 'in-progress',
|
||||
time: Date.now(),
|
||||
},
|
||||
]),
|
||||
finishOperation: (operation: Operations) =>
|
||||
updateStatusHistory(statusHistory => [
|
||||
...statusHistory,
|
||||
{
|
||||
operation,
|
||||
status: 'succeeded',
|
||||
time: Date.now(),
|
||||
},
|
||||
]),
|
||||
failOperation: (operation: Operations, message: string) =>
|
||||
updateStatusHistory(statusHistory => [
|
||||
...statusHistory,
|
||||
{
|
||||
message,
|
||||
operation,
|
||||
status: 'failed',
|
||||
time: Date.now(),
|
||||
},
|
||||
]),
|
||||
});
|
|
@ -1,107 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The helper types and functions below are designed to be used with constate
|
||||
* v0.9. From version 1.0 the use of react hooks probably makes them
|
||||
* unnecessary.
|
||||
*
|
||||
* The `inferActionMap`, `inferEffectMap` and `inferSelectorMap` functions
|
||||
* remove the necessity to type out the child-facing interfaces as suggested in
|
||||
* the constate typescript documentation by inferring the `ActionMap`,
|
||||
* `EffectMap` and `SelectorMap` types from the object passed as an argument.
|
||||
* At runtime these functions just return their first argument without
|
||||
* modification.
|
||||
*
|
||||
* Until partial type argument inference is (hopefully) introduced with
|
||||
* TypeScript 3.3, the functions are split into two nested functions to allow
|
||||
* for specifying the `State` type argument while leaving the other type
|
||||
* arguments for inference by the compiler.
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* ```typescript
|
||||
* const actions = inferActionMap<State>()({
|
||||
* increment: (amount: number) => state => ({ ...state, count: state.count + amount }),
|
||||
* });
|
||||
* // actions has type ActionMap<State, { increment: (amount: number) => void; }>
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { ActionMap, EffectMap, EffectProps, SelectorMap } from 'constate';
|
||||
|
||||
/**
|
||||
* actions
|
||||
*/
|
||||
|
||||
type InferredAction<State, Action> = Action extends (...args: infer A) => (state: State) => State
|
||||
? (...args: A) => void
|
||||
: never;
|
||||
|
||||
type InferredActions<State, Actions> = ActionMap<
|
||||
State,
|
||||
{ [K in keyof Actions]: InferredAction<State, Actions[K]> }
|
||||
>;
|
||||
|
||||
export type ActionsFromMap<M> = M extends ActionMap<any, infer A> ? A : never;
|
||||
|
||||
export const inferActionMap = <State extends any>() => <
|
||||
Actions extends {
|
||||
[key: string]: (...args: any[]) => (state: State) => State;
|
||||
}
|
||||
>(
|
||||
actionMap: Actions
|
||||
): InferredActions<State, Actions> => actionMap as any;
|
||||
|
||||
/**
|
||||
* effects
|
||||
*/
|
||||
|
||||
type InferredEffect<State, Effect> = Effect extends (
|
||||
...args: infer A
|
||||
) => (props: EffectProps<State>) => infer R
|
||||
? (...args: A) => R
|
||||
: never;
|
||||
|
||||
type InferredEffects<State, Effects> = EffectMap<
|
||||
State,
|
||||
{ [K in keyof Effects]: InferredEffect<State, Effects[K]> }
|
||||
>;
|
||||
|
||||
export type EffectsFromMap<M> = M extends EffectMap<any, infer E> ? E : never;
|
||||
|
||||
export const inferEffectMap = <State extends any>() => <
|
||||
Effects extends {
|
||||
[key: string]: (...args: any[]) => (props: EffectProps<State>) => any;
|
||||
}
|
||||
>(
|
||||
effectMap: Effects
|
||||
): InferredEffects<State, Effects> => effectMap as any;
|
||||
|
||||
/**
|
||||
* selectors
|
||||
*/
|
||||
|
||||
type InferredSelector<State, Selector> = Selector extends (
|
||||
...args: infer A
|
||||
) => (state: State) => infer R
|
||||
? (...args: A) => R
|
||||
: never;
|
||||
|
||||
type InferredSelectors<State, Selectors> = SelectorMap<
|
||||
State,
|
||||
{ [K in keyof Selectors]: InferredSelector<State, Selectors[K]> }
|
||||
>;
|
||||
|
||||
export type SelectorsFromMap<M> = M extends SelectorMap<any, infer S> ? S : never;
|
||||
|
||||
export const inferSelectorMap = <State extends any>() => <
|
||||
Selectors extends {
|
||||
[key: string]: (...args: any[]) => (state: State) => any;
|
||||
}
|
||||
>(
|
||||
selectorMap: Selectors
|
||||
): InferredSelectors<State, Selectors> => selectorMap as any;
|
260
x-pack/plugins/infra/public/utils/use_tracked_promise.ts
Normal file
260
x-pack/plugins/infra/public/utils/use_tracked_promise.ts
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { DependencyList, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
interface UseTrackedPromiseArgs<Arguments extends any[], Result> {
|
||||
createPromise: (...args: Arguments) => Promise<Result>;
|
||||
onResolve?: (result: Result) => void;
|
||||
onReject?: (value: unknown) => void;
|
||||
cancelPreviousOn?: 'creation' | 'settlement' | 'resolution' | 'rejection' | 'never';
|
||||
}
|
||||
|
||||
/**
|
||||
* This hook manages a Promise factory and can create new Promises from it. The
|
||||
* state of these Promises is tracked and they can be canceled when superseded
|
||||
* to avoid race conditions.
|
||||
*
|
||||
* ```
|
||||
* const [requestState, performRequest] = useTrackedPromise(
|
||||
* {
|
||||
* cancelPreviousOn: 'resolution',
|
||||
* createPromise: async (url: string) => {
|
||||
* return await fetchSomething(url)
|
||||
* },
|
||||
* onResolve: response => {
|
||||
* setSomeState(response.data);
|
||||
* },
|
||||
* onReject: response => {
|
||||
* setSomeError(response);
|
||||
* },
|
||||
* },
|
||||
* [fetchSomething]
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* The `onResolve` and `onReject` handlers are registered separately, because
|
||||
* the hook will inject a rejection when in case of a canellation. The
|
||||
* `cancelPreviousOn` attribute can be used to indicate when the preceding
|
||||
* pending promises should be canceled:
|
||||
*
|
||||
* 'never': No preceding promises will be canceled.
|
||||
*
|
||||
* 'creation': Any preceding promises will be canceled as soon as a new one is
|
||||
* created.
|
||||
*
|
||||
* 'settlement': Any preceding promise will be canceled when a newer promise is
|
||||
* resolved or rejected.
|
||||
*
|
||||
* 'resolution': Any preceding promise will be canceled when a newer promise is
|
||||
* resolved.
|
||||
*
|
||||
* 'rejection': Any preceding promise will be canceled when a newer promise is
|
||||
* rejected.
|
||||
*
|
||||
* Any pending promises will be canceled when the component using the hook is
|
||||
* unmounted, but their status will not be tracked to avoid React warnings
|
||||
* about memory leaks.
|
||||
*
|
||||
* The last argument is a normal React hook dependency list that indicates
|
||||
* under which conditions a new reference to the configuration object should be
|
||||
* used.
|
||||
*/
|
||||
export const useTrackedPromise = <Arguments extends any[], Result>(
|
||||
{
|
||||
createPromise,
|
||||
onResolve = noOp,
|
||||
onReject = noOp,
|
||||
cancelPreviousOn = 'never',
|
||||
}: UseTrackedPromiseArgs<Arguments, Result>,
|
||||
dependencies: DependencyList
|
||||
) => {
|
||||
/**
|
||||
* If a promise is currently pending, this holds a reference to it and its
|
||||
* cancellation function.
|
||||
*/
|
||||
const pendingPromises = useRef<ReadonlyArray<CancelablePromise<Result>>>([]);
|
||||
|
||||
/**
|
||||
* The state of the promise most recently created by the `createPromise`
|
||||
* factory. It could be uninitialized, pending, resolved or rejected.
|
||||
*/
|
||||
const [promiseState, setPromiseState] = useState<PromiseState<Result>>({
|
||||
state: 'uninitialized',
|
||||
});
|
||||
|
||||
const execute = useMemo(
|
||||
() => (...args: Arguments) => {
|
||||
let rejectCancellationPromise!: (value: any) => void;
|
||||
const cancellationPromise = new Promise<any>((_, reject) => {
|
||||
rejectCancellationPromise = reject;
|
||||
});
|
||||
|
||||
// remember the list of prior pending promises for cancellation
|
||||
const previousPendingPromises = pendingPromises.current;
|
||||
|
||||
const cancelPreviousPendingPromises = () => {
|
||||
previousPendingPromises.forEach(promise => promise.cancel());
|
||||
};
|
||||
|
||||
const newPromise = createPromise(...args);
|
||||
const newCancelablePromise = Promise.race([newPromise, cancellationPromise]);
|
||||
|
||||
// track this new state
|
||||
setPromiseState({
|
||||
state: 'pending',
|
||||
promise: newCancelablePromise,
|
||||
});
|
||||
|
||||
if (cancelPreviousOn === 'creation') {
|
||||
cancelPreviousPendingPromises();
|
||||
}
|
||||
|
||||
const newPendingPromise: CancelablePromise<Result> = {
|
||||
cancel: () => {
|
||||
rejectCancellationPromise(new CanceledPromiseError());
|
||||
},
|
||||
cancelSilently: () => {
|
||||
rejectCancellationPromise(new SilentCanceledPromiseError());
|
||||
},
|
||||
promise: newCancelablePromise.then(
|
||||
value => {
|
||||
setPromiseState(previousPromiseState =>
|
||||
previousPromiseState.state === 'pending' &&
|
||||
previousPromiseState.promise === newCancelablePromise
|
||||
? {
|
||||
state: 'resolved',
|
||||
promise: newPendingPromise.promise,
|
||||
value,
|
||||
}
|
||||
: previousPromiseState
|
||||
);
|
||||
|
||||
if (['settlement', 'resolution'].includes(cancelPreviousOn)) {
|
||||
cancelPreviousPendingPromises();
|
||||
}
|
||||
|
||||
// remove itself from the list of pending promises
|
||||
pendingPromises.current = pendingPromises.current.filter(
|
||||
pendingPromise => pendingPromise.promise !== newPendingPromise.promise
|
||||
);
|
||||
|
||||
if (onResolve) {
|
||||
onResolve(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
value => {
|
||||
if (!(value instanceof SilentCanceledPromiseError)) {
|
||||
setPromiseState(previousPromiseState =>
|
||||
previousPromiseState.state === 'pending' &&
|
||||
previousPromiseState.promise === newCancelablePromise
|
||||
? {
|
||||
state: 'rejected',
|
||||
promise: newCancelablePromise,
|
||||
value,
|
||||
}
|
||||
: previousPromiseState
|
||||
);
|
||||
}
|
||||
|
||||
if (['settlement', 'rejection'].includes(cancelPreviousOn)) {
|
||||
cancelPreviousPendingPromises();
|
||||
}
|
||||
|
||||
// remove itself from the list of pending promises
|
||||
pendingPromises.current = pendingPromises.current.filter(
|
||||
pendingPromise => pendingPromise.promise !== newPendingPromise.promise
|
||||
);
|
||||
|
||||
if (onReject) {
|
||||
onReject(value);
|
||||
}
|
||||
|
||||
throw value;
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
// add the new promise to the list of pending promises
|
||||
pendingPromises.current = [...pendingPromises.current, newPendingPromise];
|
||||
|
||||
// silence "unhandled rejection" warnings
|
||||
newPendingPromise.promise.catch(noOp);
|
||||
|
||||
return newPendingPromise.promise;
|
||||
},
|
||||
dependencies
|
||||
);
|
||||
|
||||
/**
|
||||
* Cancel any pending promises silently to avoid memory leaks and race
|
||||
* conditions.
|
||||
*/
|
||||
useEffect(
|
||||
() => () => {
|
||||
pendingPromises.current.forEach(promise => promise.cancelSilently());
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return [promiseState, execute] as [typeof promiseState, typeof execute];
|
||||
};
|
||||
|
||||
interface UninitializedPromiseState {
|
||||
state: 'uninitialized';
|
||||
}
|
||||
|
||||
interface PendingPromiseState<ResolvedValue> {
|
||||
state: 'pending';
|
||||
promise: Promise<ResolvedValue>;
|
||||
}
|
||||
|
||||
interface ResolvedPromiseState<ResolvedValue> {
|
||||
state: 'resolved';
|
||||
promise: Promise<ResolvedValue>;
|
||||
value: ResolvedValue;
|
||||
}
|
||||
|
||||
interface RejectedPromiseState<ResolvedValue, RejectedValue> {
|
||||
state: 'rejected';
|
||||
promise: Promise<ResolvedValue>;
|
||||
value: RejectedValue;
|
||||
}
|
||||
|
||||
type SettledPromise<ResolvedValue, RejectedValue> =
|
||||
| ResolvedPromiseState<ResolvedValue>
|
||||
| RejectedPromiseState<ResolvedValue, RejectedValue>;
|
||||
|
||||
type PromiseState<ResolvedValue, RejectedValue = unknown> =
|
||||
| UninitializedPromiseState
|
||||
| PendingPromiseState<ResolvedValue>
|
||||
| SettledPromise<ResolvedValue, RejectedValue>;
|
||||
|
||||
interface CancelablePromise<ResolvedValue> {
|
||||
// reject the promise prematurely with a CanceledPromiseError
|
||||
cancel: () => void;
|
||||
// reject the promise prematurely with a SilentCanceledPromiseError
|
||||
cancelSilently: () => void;
|
||||
// the tracked promise
|
||||
promise: Promise<ResolvedValue>;
|
||||
}
|
||||
|
||||
class CanceledPromiseError extends Error {
|
||||
public isCanceled = true;
|
||||
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
class SilentCanceledPromiseError extends CanceledPromiseError {}
|
||||
|
||||
const noOp = () => undefined;
|
|
@ -7,7 +7,7 @@
|
|||
import expect from '@kbn/expect';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { sourceQuery } from '../../../../plugins/infra/public/containers/with_source/query_source.gql_query';
|
||||
import { sourceQuery } from '../../../../plugins/infra/public/containers/source/query_source.gql_query';
|
||||
import { SourceQuery } from '../../../../plugins/infra/public/graphql/types';
|
||||
import { KbnTestProvider } from './types';
|
||||
|
||||
|
|
26
x-pack/test/functional/apps/infra/constants.ts
Normal file
26
x-pack/test/functional/apps/infra/constants.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const DATES = {
|
||||
'7.0.0': {
|
||||
hosts: {
|
||||
min: 1547571261002,
|
||||
max: 1547571831033,
|
||||
},
|
||||
},
|
||||
'6.6.0': {
|
||||
docker: {
|
||||
min: 1547578132289,
|
||||
max: 1547579090048,
|
||||
},
|
||||
},
|
||||
metricsAndLogs: {
|
||||
hosts: {
|
||||
withData: 1539806283000,
|
||||
withoutData: 1539122400000,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -5,9 +5,10 @@
|
|||
*/
|
||||
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import { DATES } from './constants';
|
||||
|
||||
const DATE_WITH_DATA = new Date(1539806283000);
|
||||
const DATE_WITHOUT_DATA = new Date(1539122400000);
|
||||
const DATE_WITH_DATA = new Date(DATES.metricsAndLogs.hosts.withData);
|
||||
const DATE_WITHOUT_DATA = new Date(DATES.metricsAndLogs.hosts.withoutData);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) => {
|
||||
|
|
|
@ -12,5 +12,7 @@ export default ({ loadTestFile }: KibanaFunctionalTestDefaultProviders) => {
|
|||
this.tags('ciGroup7');
|
||||
|
||||
loadTestFile(require.resolve('./home_page'));
|
||||
loadTestFile(require.resolve('./logs_source_configuration'));
|
||||
loadTestFile(require.resolve('./metrics_source_configuration'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const infraSourceConfigurationFlyout = getService('infraSourceConfigurationFlyout');
|
||||
const pageObjects = getPageObjects(['common', 'infraLogs']);
|
||||
|
||||
describe('Logs Page', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('empty_kibana');
|
||||
});
|
||||
after(async () => {
|
||||
await esArchiver.unload('empty_kibana');
|
||||
});
|
||||
|
||||
describe('with logs present', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('infra/metrics_and_logs');
|
||||
});
|
||||
after(async () => {
|
||||
await esArchiver.unload('infra/metrics_and_logs');
|
||||
});
|
||||
|
||||
it('renders the log stream', async () => {
|
||||
await pageObjects.common.navigateToApp('infraLogs');
|
||||
await pageObjects.infraLogs.getLogStream();
|
||||
});
|
||||
|
||||
it('can change the log indices to a pattern that matches nothing', async () => {
|
||||
await pageObjects.infraLogs.openSourceConfigurationFlyout();
|
||||
|
||||
const nameInput = await infraSourceConfigurationFlyout.getNameInput();
|
||||
await nameInput.clearValue();
|
||||
await nameInput.type('Modified Source');
|
||||
|
||||
const logIndicesInput = await infraSourceConfigurationFlyout.getLogIndicesInput();
|
||||
await logIndicesInput.clearValue();
|
||||
await logIndicesInput.type('does-not-exist-*');
|
||||
|
||||
await infraSourceConfigurationFlyout.saveConfiguration();
|
||||
await infraSourceConfigurationFlyout.closeFlyout();
|
||||
});
|
||||
|
||||
it('renders the no indices screen when no indices match the pattern', async () => {
|
||||
await pageObjects.infraLogs.getNoLogsIndicesPrompt();
|
||||
});
|
||||
|
||||
it('can change the log indices back to a pattern that matches something', async () => {
|
||||
await pageObjects.infraLogs.openSourceConfigurationFlyout();
|
||||
|
||||
const logIndicesInput = await infraSourceConfigurationFlyout.getLogIndicesInput();
|
||||
await logIndicesInput.clearValue();
|
||||
await logIndicesInput.type('filebeat-*');
|
||||
|
||||
await infraSourceConfigurationFlyout.saveConfiguration();
|
||||
await infraSourceConfigurationFlyout.closeFlyout();
|
||||
});
|
||||
|
||||
it('renders the log stream again', async () => {
|
||||
await pageObjects.infraLogs.getLogStream();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
import { DATES } from './constants';
|
||||
|
||||
const DATE_WITH_DATA = new Date(DATES.metricsAndLogs.hosts.withData);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const infraSourceConfigurationFlyout = getService('infraSourceConfigurationFlyout');
|
||||
const pageObjects = getPageObjects(['common', 'infraHome']);
|
||||
|
||||
describe('Infrastructure Snapshot Page', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('empty_kibana');
|
||||
});
|
||||
after(async () => {
|
||||
await esArchiver.unload('empty_kibana');
|
||||
});
|
||||
|
||||
describe('with metrics present', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('infra/metrics_and_logs');
|
||||
});
|
||||
after(async () => {
|
||||
await esArchiver.unload('infra/metrics_and_logs');
|
||||
});
|
||||
|
||||
it('renders the waffle map', async () => {
|
||||
await pageObjects.common.navigateToApp('infraOps');
|
||||
await pageObjects.infraHome.goToTime(DATE_WITH_DATA);
|
||||
await pageObjects.infraHome.getWaffleMap();
|
||||
});
|
||||
|
||||
it('can change the metric indices to a pattern that matches nothing', async () => {
|
||||
await pageObjects.infraHome.openSourceConfigurationFlyout();
|
||||
|
||||
const nameInput = await infraSourceConfigurationFlyout.getNameInput();
|
||||
await nameInput.clearValue();
|
||||
await nameInput.type('Modified Source');
|
||||
|
||||
const metricIndicesInput = await infraSourceConfigurationFlyout.getMetricIndicesInput();
|
||||
await metricIndicesInput.clearValue();
|
||||
await metricIndicesInput.type('does-not-exist-*');
|
||||
|
||||
await infraSourceConfigurationFlyout.saveConfiguration();
|
||||
await infraSourceConfigurationFlyout.closeFlyout();
|
||||
});
|
||||
|
||||
it('renders the no indices screen when no indices match the pattern', async () => {
|
||||
await pageObjects.infraHome.getNoMetricsIndicesPrompt();
|
||||
});
|
||||
|
||||
it('can change the log indices back to a pattern that matches something', async () => {
|
||||
await pageObjects.infraHome.openSourceConfigurationFlyout();
|
||||
|
||||
const metricIndicesInput = await infraSourceConfigurationFlyout.getMetricIndicesInput();
|
||||
await metricIndicesInput.clearValue();
|
||||
await metricIndicesInput.type('metricbeat-*');
|
||||
|
||||
await infraSourceConfigurationFlyout.saveConfiguration();
|
||||
await infraSourceConfigurationFlyout.closeFlyout();
|
||||
});
|
||||
|
||||
it('renders the log stream again', async () => {
|
||||
await pageObjects.infraHome.getWaffleMap();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -19,12 +19,12 @@ import {
|
|||
SpaceSelectorPageProvider,
|
||||
AccountSettingProvider,
|
||||
InfraHomePageProvider,
|
||||
InfraLogsPageProvider,
|
||||
GisPageProvider,
|
||||
StatusPagePageProvider,
|
||||
UpgradeAssistantProvider,
|
||||
RollupPageProvider,
|
||||
UptimePageProvider,
|
||||
|
||||
} from './page_objects';
|
||||
|
||||
import {
|
||||
|
@ -56,7 +56,7 @@ import {
|
|||
GrokDebuggerProvider,
|
||||
UserMenuProvider,
|
||||
UptimeProvider,
|
||||
|
||||
InfraSourceConfigurationFlyoutProvider,
|
||||
} from './services';
|
||||
|
||||
// the default export of config files must be a config provider
|
||||
|
@ -89,7 +89,7 @@ export default async function ({ readConfigFile }) {
|
|||
resolve(__dirname, './apps/maps'),
|
||||
resolve(__dirname, './apps/status_page'),
|
||||
resolve(__dirname, './apps/upgrade_assistant'),
|
||||
resolve(__dirname, './apps/uptime')
|
||||
resolve(__dirname, './apps/uptime'),
|
||||
],
|
||||
|
||||
// define the name and providers for services that should be
|
||||
|
@ -127,6 +127,7 @@ export default async function ({ readConfigFile }) {
|
|||
userMenu: UserMenuProvider,
|
||||
uptime: UptimeProvider,
|
||||
rollup: RollupPageProvider,
|
||||
infraSourceConfigurationFlyout: InfraSourceConfigurationFlyoutProvider,
|
||||
},
|
||||
|
||||
// just like services, PageObjects are defined as a map of
|
||||
|
@ -143,11 +144,12 @@ export default async function ({ readConfigFile }) {
|
|||
reporting: ReportingPageProvider,
|
||||
spaceSelector: SpaceSelectorPageProvider,
|
||||
infraHome: InfraHomePageProvider,
|
||||
infraLogs: InfraLogsPageProvider,
|
||||
maps: GisPageProvider,
|
||||
statusPage: StatusPagePageProvider,
|
||||
upgradeAssistant: UpgradeAssistantProvider,
|
||||
uptime: UptimePageProvider,
|
||||
rollup: RollupPageProvider
|
||||
rollup: RollupPageProvider,
|
||||
},
|
||||
|
||||
servers: kibanaFunctionalConfig.get('servers'),
|
||||
|
@ -206,6 +208,10 @@ export default async function ({ readConfigFile }) {
|
|||
infraOps: {
|
||||
pathname: '/app/infra',
|
||||
},
|
||||
infraLogs: {
|
||||
pathname: '/app/infra',
|
||||
hash: '/logs',
|
||||
},
|
||||
canvas: {
|
||||
pathname: '/app/canvas',
|
||||
hash: '/',
|
||||
|
@ -215,8 +221,8 @@ export default async function ({ readConfigFile }) {
|
|||
},
|
||||
rollupJob: {
|
||||
pathname: '/app/kibana',
|
||||
hash: '/management/elasticsearch/rollup_jobs/'
|
||||
}
|
||||
hash: '/management/elasticsearch/rollup_jobs/',
|
||||
},
|
||||
},
|
||||
|
||||
// choose where esArchiver should load archives from
|
||||
|
@ -233,5 +239,4 @@ export default async function ({ readConfigFile }) {
|
|||
reportName: 'X-Pack Functional Tests',
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export { ReportingPageProvider } from './reporting_page';
|
|||
export { SpaceSelectorPageProvider } from './space_selector_page';
|
||||
export { AccountSettingProvider } from './accountsetting_page';
|
||||
export { InfraHomePageProvider } from './infra_home_page';
|
||||
export { InfraLogsPageProvider } from './infra_logs_page';
|
||||
export { GisPageProvider } from './gis_page';
|
||||
export { StatusPagePageProvider } from './status_page';
|
||||
export { UpgradeAssistantProvider } from './upgrade_assistant';
|
||||
|
|
|
@ -35,5 +35,10 @@ export function InfraHomePageProvider({ getService }: KibanaFunctionalTestDefaul
|
|||
async getNoMetricsDataPrompt() {
|
||||
return await testSubjects.find('noMetricsDataPrompt');
|
||||
},
|
||||
|
||||
async openSourceConfigurationFlyout() {
|
||||
await testSubjects.click('configureSourceButton');
|
||||
await testSubjects.exists('sourceConfigurationFlyout');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
31
x-pack/test/functional/page_objects/infra_logs_page.ts
Normal file
31
x-pack/test/functional/page_objects/infra_logs_page.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 testSubjSelector from '@kbn/test-subj-selector';
|
||||
// import moment from 'moment';
|
||||
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../types/providers';
|
||||
|
||||
export function InfraLogsPageProvider({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
// const find = getService('find');
|
||||
// const browser = getService('browser');
|
||||
|
||||
return {
|
||||
async getLogStream() {
|
||||
return await testSubjects.find('logStream');
|
||||
},
|
||||
|
||||
async getNoLogsIndicesPrompt() {
|
||||
return await testSubjects.find('noLogsIndicesPrompt');
|
||||
},
|
||||
|
||||
async openSourceConfigurationFlyout() {
|
||||
await testSubjects.click('configureSourceButton');
|
||||
await testSubjects.exists('sourceConfigurationFlyout');
|
||||
},
|
||||
};
|
||||
}
|
|
@ -12,3 +12,4 @@ export { AceEditorProvider } from './ace_editor';
|
|||
export { GrokDebuggerProvider } from './grok_debugger';
|
||||
export { UserMenuProvider } from './user_menu';
|
||||
export { UptimeProvider } from './uptime';
|
||||
export { InfraSourceConfigurationFlyoutProvider } from './infra_source_configuration_flyout';
|
||||
|
|
|
@ -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 { KibanaFunctionalTestDefaultProviders } from '../../types/providers';
|
||||
|
||||
export function InfraSourceConfigurationFlyoutProvider({
|
||||
getService,
|
||||
}: KibanaFunctionalTestDefaultProviders) {
|
||||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async getNameInput() {
|
||||
return await testSubjects.find('nameInput');
|
||||
},
|
||||
|
||||
async getLogIndicesInput() {
|
||||
return await testSubjects.find('logIndicesInput');
|
||||
},
|
||||
|
||||
async getMetricIndicesInput() {
|
||||
return await testSubjects.find('metricIndicesInput');
|
||||
},
|
||||
|
||||
async saveConfiguration() {
|
||||
await testSubjects.click('updateSourceConfigurationButton');
|
||||
|
||||
await retry.try(async () => {
|
||||
const element = await testSubjects.find('updateSourceConfigurationButton');
|
||||
return !(await element.isEnabled());
|
||||
});
|
||||
},
|
||||
|
||||
async closeFlyout() {
|
||||
const flyout = await testSubjects.find('sourceConfigurationFlyout');
|
||||
await testSubjects.click('closeFlyoutButton');
|
||||
await testSubjects.waitForDeleted(flyout);
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue