[Logs UI] Refactor source configuration as hook for consistent data flow (#34455)
This PR refactors the source configuration state and source configuration form state to hooks. Aside from slightly improved performance due to memoization, it should not lead to visible differences.
This commit is contained in:
parent
63ea74c43c
commit
dc6ecae7fa
|
@ -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…
Reference in a new issue