[6.x] [Infra UI] Replace redux source slice with constate container (#26121) (#26721)

Backports the following commits to 6.x:
 - [Infra UI] Replace redux source slice with constate container  (#26121)
This commit is contained in:
Felix Stürmer 2018-12-14 00:04:14 +01:00 committed by GitHub
parent 18d41b6cde
commit 73e7ac28f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 797 additions and 472 deletions

View file

@ -152,6 +152,7 @@
"chroma-js": "^1.3.6", "chroma-js": "^1.3.6",
"classnames": "2.2.5", "classnames": "2.2.5",
"concat-stream": "1.5.1", "concat-stream": "1.5.1",
"constate": "^0.9.0",
"copy-to-clipboard": "^3.0.8", "copy-to-clipboard": "^3.0.8",
"cronstrue": "^1.51.0", "cronstrue": "^1.51.0",
"d3": "3.5.6", "d3": "3.5.6",

View file

@ -705,6 +705,52 @@ export namespace WaffleNodesQuery {
value: number; value: number;
}; };
} }
export namespace SourceQuery {
export type Variables = {
sourceId?: string | null;
};
export type Query = {
__typename?: 'Query';
source: Source;
};
export type Source = {
__typename?: 'InfraSource';
id: string;
configuration: Configuration;
status: Status;
};
export type Configuration = {
__typename?: 'InfraSourceConfiguration';
metricAlias: string;
logAlias: string;
fields: Fields;
};
export type Fields = {
__typename?: 'InfraSourceFields';
container: string;
host: string;
pod: string;
};
export type Status = {
__typename?: 'InfraSourceStatus';
indexFields: IndexFields[];
logIndicesExist: boolean;
metricIndicesExist: boolean;
};
export type IndexFields = {
__typename?: 'InfraIndexField';
name: string;
type: string;
searchable: boolean;
aggregatable: boolean;
};
}
export namespace LogEntries { export namespace LogEntries {
export type Variables = { export type Variables = {
sourceId?: string | null; sourceId?: string | null;
@ -800,52 +846,6 @@ export namespace LogSummary {
entriesCount: number; entriesCount: number;
}; };
} }
export namespace SourceQuery {
export type Variables = {
sourceId?: string | null;
};
export type Query = {
__typename?: 'Query';
source: Source;
};
export type Source = {
__typename?: 'InfraSource';
id: string;
configuration: Configuration;
status: Status;
};
export type Configuration = {
__typename?: 'InfraSourceConfiguration';
metricAlias: string;
logAlias: string;
fields: Fields;
};
export type Fields = {
__typename?: 'InfraSourceFields';
container: string;
host: string;
pod: string;
};
export type Status = {
__typename?: 'InfraSourceStatus';
indexFields: IndexFields[];
logIndicesExist: boolean;
metricIndicesExist: boolean;
};
export type IndexFields = {
__typename?: 'InfraIndexField';
name: string;
type: string;
searchable: boolean;
aggregatable: boolean;
};
}
export namespace InfraTimeKeyFields { export namespace InfraTimeKeyFields {
export type Fragment = { export type Fragment = {

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { I18nProvider } from '@kbn/i18n/react'; import { Provider as ConstateProvider } from 'constate';
import { createHashHistory } from 'history'; import { createHashHistory } from 'history';
import React from 'react'; import React from 'react';
import { ApolloProvider } from 'react-apollo'; import { ApolloProvider } from 'react-apollo';
@ -16,6 +16,7 @@ import { ThemeProvider } from 'styled-components';
// TODO use theme provided from parentApp when kibana supports it // TODO use theme provided from parentApp when kibana supports it
import { EuiErrorBoundary } from '@elastic/eui'; import { EuiErrorBoundary } from '@elastic/eui';
import euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; import euiVars from '@elastic/eui/dist/eui_theme_k6_light.json';
import { I18nProvider } from '@kbn/i18n/react';
import { InfraFrontendLibs } from '../lib/lib'; import { InfraFrontendLibs } from '../lib/lib';
import { PageRouter } from '../routes'; import { PageRouter } from '../routes';
import { createStore } from '../store'; import { createStore } from '../store';
@ -32,6 +33,7 @@ export async function startApp(libs: InfraFrontendLibs) {
libs.framework.render( libs.framework.render(
<I18nProvider> <I18nProvider>
<EuiErrorBoundary> <EuiErrorBoundary>
<ConstateProvider devtools>
<ReduxStoreProvider store={store}> <ReduxStoreProvider store={store}>
<ApolloProvider client={libs.apolloClient}> <ApolloProvider client={libs.apolloClient}>
<ThemeProvider theme={{ eui: euiVars }}> <ThemeProvider theme={{ eui: euiVars }}>
@ -39,6 +41,7 @@ export async function startApp(libs: InfraFrontendLibs) {
</ThemeProvider> </ThemeProvider>
</ApolloProvider> </ApolloProvider>
</ReduxStoreProvider> </ReduxStoreProvider>
</ConstateProvider>
</EuiErrorBoundary> </EuiErrorBoundary>
</I18nProvider> </I18nProvider>
); );

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import {
EuiButton,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
} from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import { FlexPage } from './page';
interface Props {
detailedMessage?: React.ReactNode;
retry?: () => void;
shortMessage: React.ReactNode;
}
export const ErrorPage: React.SFC<Props> = ({ detailedMessage, retry, shortMessage }) => (
<FlexPage>
<EuiPageBody>
<MinimumPageContent
horizontalPosition="center"
verticalPosition="center"
panelPaddingSize="none"
>
<EuiPageContentBody>
<EuiCallOut color="danger" iconType="cross" title="An error occurred">
<EuiFlexGroup>
<EuiFlexItem>{shortMessage}</EuiFlexItem>
{retry ? (
<EuiFlexItem grow={false}>
<EuiButton onClick={retry} iconType="refresh">
Try again
</EuiButton>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
{detailedMessage ? <div>{detailedMessage}</div> : null}
</EuiCallOut>
</EuiPageContentBody>
</MinimumPageContent>
</EuiPageBody>
</FlexPage>
);
const MinimumPageContent = styled(EuiPageContent)`
min-width: 50vh;
`;

View file

@ -7,23 +7,41 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { StaticIndexPattern } from 'ui/index_patterns';
import { logFilterActions, logFilterSelectors, State } from '../../store'; import { logFilterActions, logFilterSelectors, State } from '../../store';
import { FilterQuery } from '../../store/local/log_filter';
import { convertKueryToElasticSearchQuery } from '../../utils/kuery';
import { asChildFunctionRenderer } from '../../utils/typed_react'; import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux'; import { bindPlainActionCreators } from '../../utils/typed_redux';
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state'; import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';
interface WithLogFilterProps {
indexPattern: StaticIndexPattern;
}
const withLogFilter = connect( const withLogFilter = connect(
(state: State) => ({ (state: State) => ({
filterQuery: logFilterSelectors.selectLogFilterQuery(state), filterQuery: logFilterSelectors.selectLogFilterQuery(state),
filterQueryDraft: logFilterSelectors.selectLogFilterQueryDraft(state), filterQueryDraft: logFilterSelectors.selectLogFilterQueryDraft(state),
isFilterQueryDraftValid: logFilterSelectors.selectIsLogFilterQueryDraftValid(state), isFilterQueryDraftValid: logFilterSelectors.selectIsLogFilterQueryDraftValid(state),
}), }),
(dispatch, ownProps: WithLogFilterProps) =>
bindPlainActionCreators({ bindPlainActionCreators({
applyFilterQuery: logFilterActions.applyLogFilterQuery, applyFilterQuery: (query: FilterQuery) =>
logFilterActions.applyLogFilterQuery({
query,
serializedQuery: convertKueryToElasticSearchQuery(
query.expression,
ownProps.indexPattern
),
}),
applyFilterQueryFromKueryExpression: (expression: string) => applyFilterQueryFromKueryExpression: (expression: string) =>
logFilterActions.applyLogFilterQuery({ logFilterActions.applyLogFilterQuery({
query: {
kind: 'kuery', kind: 'kuery',
expression, expression,
},
serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern),
}), }),
setFilterQueryDraft: logFilterActions.setLogFilterQueryDraft, setFilterQueryDraft: logFilterActions.setLogFilterQueryDraft,
setFilterQueryDraftFromKueryExpression: (expression: string) => setFilterQueryDraftFromKueryExpression: (expression: string) =>
@ -31,7 +49,7 @@ const withLogFilter = connect(
kind: 'kuery', kind: 'kuery',
expression, expression,
}), }),
}) })(dispatch)
); );
export const WithLogFilter = asChildFunctionRenderer(withLogFilter); export const WithLogFilter = asChildFunctionRenderer(withLogFilter);
@ -42,8 +60,10 @@ export const WithLogFilter = asChildFunctionRenderer(withLogFilter);
type LogFilterUrlState = ReturnType<typeof logFilterSelectors.selectLogFilterQuery>; type LogFilterUrlState = ReturnType<typeof logFilterSelectors.selectLogFilterQuery>;
export const WithLogFilterUrlState = () => ( type WithLogFilterUrlStateProps = WithLogFilterProps;
<WithLogFilter>
export const WithLogFilterUrlState: React.SFC<WithLogFilterUrlStateProps> = ({ indexPattern }) => (
<WithLogFilter indexPattern={indexPattern}>
{({ applyFilterQuery, filterQuery }) => ( {({ applyFilterQuery, filterQuery }) => (
<UrlStateContainer <UrlStateContainer
urlState={filterQuery} urlState={filterQuery}

View file

@ -7,24 +7,42 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { sharedSelectors, State, waffleFilterActions, waffleFilterSelectors } from '../../store'; import { StaticIndexPattern } from 'ui/index_patterns';
import { State, waffleFilterActions, waffleFilterSelectors } from '../../store';
import { FilterQuery } from '../../store/local/waffle_filter';
import { convertKueryToElasticSearchQuery } from '../../utils/kuery';
import { asChildFunctionRenderer } from '../../utils/typed_react'; import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux'; import { bindPlainActionCreators } from '../../utils/typed_redux';
import { UrlStateContainer } from '../../utils/url_state'; import { UrlStateContainer } from '../../utils/url_state';
interface WithWaffleFilterProps {
indexPattern: StaticIndexPattern;
}
export const withWaffleFilter = connect( export const withWaffleFilter = connect(
(state: State) => ({ (state: State) => ({
filterQuery: waffleFilterSelectors.selectWaffleFilterQuery(state), filterQuery: waffleFilterSelectors.selectWaffleFilterQuery(state),
filterQueryDraft: waffleFilterSelectors.selectWaffleFilterQueryDraft(state), filterQueryDraft: waffleFilterSelectors.selectWaffleFilterQueryDraft(state),
filterQueryAsJson: sharedSelectors.selectWaffleFilterQueryAsJson(state), filterQueryAsJson: waffleFilterSelectors.selectWaffleFilterQueryAsJson(state),
isFilterQueryDraftValid: waffleFilterSelectors.selectIsWaffleFilterQueryDraftValid(state), isFilterQueryDraftValid: waffleFilterSelectors.selectIsWaffleFilterQueryDraftValid(state),
}), }),
(dispatch, ownProps: WithWaffleFilterProps) =>
bindPlainActionCreators({ bindPlainActionCreators({
applyFilterQuery: waffleFilterActions.applyWaffleFilterQuery, applyFilterQuery: (query: FilterQuery) =>
waffleFilterActions.applyWaffleFilterQuery({
query,
serializedQuery: convertKueryToElasticSearchQuery(
query.expression,
ownProps.indexPattern
),
}),
applyFilterQueryFromKueryExpression: (expression: string) => applyFilterQueryFromKueryExpression: (expression: string) =>
waffleFilterActions.applyWaffleFilterQuery({ waffleFilterActions.applyWaffleFilterQuery({
query: {
kind: 'kuery', kind: 'kuery',
expression, expression,
},
serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern),
}), }),
setFilterQueryDraft: waffleFilterActions.setWaffleFilterQueryDraft, setFilterQueryDraft: waffleFilterActions.setWaffleFilterQueryDraft,
setFilterQueryDraftFromKueryExpression: (expression: string) => setFilterQueryDraftFromKueryExpression: (expression: string) =>
@ -43,8 +61,12 @@ export const WithWaffleFilter = asChildFunctionRenderer(withWaffleFilter);
type WaffleFilterUrlState = ReturnType<typeof waffleFilterSelectors.selectWaffleFilterQuery>; type WaffleFilterUrlState = ReturnType<typeof waffleFilterSelectors.selectWaffleFilterQuery>;
export const WithWaffleFilterUrlState = () => ( type WithWaffleFilterUrlStateProps = WithWaffleFilterProps;
<WithWaffleFilter>
export const WithWaffleFilterUrlState: React.SFC<WithWaffleFilterUrlStateProps> = ({
indexPattern,
}) => (
<WithWaffleFilter indexPattern={indexPattern}>
{({ applyFilterQuery, filterQuery }) => ( {({ applyFilterQuery, filterQuery }) => (
<UrlStateContainer <UrlStateContainer
urlState={filterQuery} urlState={filterQuery}

View file

@ -5,18 +5,12 @@
*/ */
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers'; import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers';
import { StaticIndexPattern } from 'ui/index_patterns'; import { StaticIndexPattern } from 'ui/index_patterns';
import { sourceSelectors, State } from '../store';
import { RendererFunction } from '../utils/typed_react'; import { RendererFunction } from '../utils/typed_react';
const withIndexPattern = connect((state: State) => ({
indexPattern: sourceSelectors.selectDerivedIndexPattern(state),
}));
interface WithKueryAutocompletionLifecycleProps { interface WithKueryAutocompletionLifecycleProps {
children: RendererFunction<{ children: RendererFunction<{
isLoadingSuggestions: boolean; isLoadingSuggestions: boolean;
@ -36,11 +30,10 @@ interface WithKueryAutocompletionLifecycleState {
suggestions: AutocompleteSuggestion[]; suggestions: AutocompleteSuggestion[];
} }
export const WithKueryAutocompletion = withIndexPattern( export class WithKueryAutocompletion extends React.Component<
class WithKueryAutocompletionLifecycle extends React.Component<
WithKueryAutocompletionLifecycleProps, WithKueryAutocompletionLifecycleProps,
WithKueryAutocompletionLifecycleState WithKueryAutocompletionLifecycleState
> { > {
public readonly state: WithKueryAutocompletionLifecycleState = { public readonly state: WithKueryAutocompletionLifecycleState = {
currentRequest: null, currentRequest: null,
suggestions: [], suggestions: [],
@ -103,5 +96,4 @@ export const WithKueryAutocompletion = withIndexPattern(
} }
); );
}; };
} }
);

View file

@ -1,18 +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 { connect } from 'react-redux';
import { sourceSelectors, State } from '../store';
import { asChildFunctionRenderer } from '../utils/typed_react';
export const withSource = connect((state: State) => ({
configuredFields: sourceSelectors.selectSourceFields(state),
logIndicesExist: sourceSelectors.selectSourceLogIndicesExist(state),
metricIndicesExist: sourceSelectors.selectSourceMetricIndicesExist(state),
}));
export const WithSource = asChildFunctionRenderer(withSource);

View file

@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { loadSourceActionCreators } from './operations/load'; export { SourceErrorPage } from './source_error_page';
export { SourceLoadingPage } from './source_loading_page';
export const loadSource = loadSourceActionCreators.resolve; export { WithSource } from './with_source';

View file

@ -0,0 +1,22 @@
/*
* 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 { ErrorPage } from '../../components/error_page';
interface SourceErrorPageProps {
errorMessage: string;
retry: () => void;
}
export const SourceErrorPage: React.SFC<SourceErrorPageProps> = ({ errorMessage, retry }) => (
<ErrorPage
shortMessage="Failed to load data sources."
detailedMessage={<code>{errorMessage}</code>}
retry={retry}
/>
);

View file

@ -0,0 +1,11 @@
/*
* 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 { LoadingPage } from '../../components/loading_page';
export const SourceLoadingPage: React.SFC = () => <LoadingPage message="Loading data sources" />;

View file

@ -0,0 +1,150 @@
/*
* 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 { 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 { oc } from 'ts-optchain';
import { StaticIndexPattern } from 'ui/index_patterns';
import { memoizeLast } from 'ui/utils/memoize';
import { SourceQuery } from '../../../common/graphql/types';
import {
createStatusActions,
createStatusSelectors,
Operation,
OperationStatus,
StatusHistoryUpdater,
} from '../../utils/operation_status';
import { inferActionMap, inferEffectMap, inferSelectorMap } from '../../utils/typed_constate';
import { RendererFunction } from '../../utils/typed_react';
import { sourceQuery } from './query_source.gql_query';
type Operations = Operation<'load', SourceQuery.Variables>;
interface State {
operationStatusHistory: Array<OperationStatus<Operations>>;
source: SourceQuery.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) => oc(state).source.status.indexFields([]),
(state: State) => oc(state).source.configuration.logAlias(),
(state: State) => oc(state).source.configuration.metricAlias(),
(indexFields, logAlias, metricAlias) => ({
fields: indexFields,
title: `${logAlias},${metricAlias}`,
})
);
const selectors = inferSelectorMap<State>()({
...createStatusSelectors(({ operationStatusHistory }: State) => operationStatusHistory),
getConfiguredFields: () => state => oc(state).source.configuration.fields(),
getLogIndicesExist: () => state => oc(state).source.status.logIndicesExist(),
getMetricIndicesExist: () => state => oc(state).source.status.metricIndicesExist(),
getDerivedIndexPattern: () => getDerivedIndexPattern,
});
const effects = inferEffectMap<State>()({
load: () => ({ setState }) => {
const variables = {
sourceId,
};
setState(actions.startOperation({ name: 'load', parameters: variables }));
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,
})),
error =>
setState(state => ({
...actions.failOperation({ name: 'load', parameters: variables }, `${error}`)(state),
}))
);
},
});
const onMount: OnMount<State> = props => {
effects.load()(props);
};
return {
actions,
context: `source-${sourceId}`,
effects,
initialState,
key: `source-${sourceId}`,
onMount,
selectors,
};
});
interface WithSourceProps {
children: RendererFunction<{
configuredFields?: SourceQuery.Fields;
derivedIndexPattern: StaticIndexPattern;
hasFailed: boolean;
isLoading: boolean;
lastFailureMessage?: string;
load: () => void;
logIndicesExist?: boolean;
metricIndicesExist?: boolean;
}>;
}
export const WithSource: React.SFC<WithSourceProps> = ({ children }) => (
<ApolloConsumer>
{client => (
<ConstateContainer pure {...createContainerProps('default', client)}>
{({
getConfiguredFields,
getDerivedIndexPattern,
getHasFailed,
getIsInProgress,
getLastFailureMessage,
getLogIndicesExist,
getMetricIndicesExist,
load,
}) =>
children({
configuredFields: getConfiguredFields(),
derivedIndexPattern: getDerivedIndexPattern(),
hasFailed: getHasFailed(),
isLoading: getIsInProgress(),
lastFailureMessage: getLastFailureMessage(),
load,
logIndicesExist: getLogIndicesExist(),
metricIndicesExist: getMetricIndicesExist(),
})
}
</ConstateContainer>
)}
</ApolloConsumer>
);

View file

@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react'; import React from 'react';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { HomePageContent } from './page_content'; import { HomePageContent } from './page_content';
import { HomeToolbar } from './toolbar'; import { HomeToolbar } from './toolbar';
@ -19,30 +20,43 @@ import { WithWaffleFilterUrlState } from '../../containers/waffle/with_waffle_fi
import { WithWaffleOptionsUrlState } from '../../containers/waffle/with_waffle_options'; import { WithWaffleOptionsUrlState } from '../../containers/waffle/with_waffle_options';
import { WithWaffleTimeUrlState } from '../../containers/waffle/with_waffle_time'; import { WithWaffleTimeUrlState } from '../../containers/waffle/with_waffle_time';
import { WithKibanaChrome } from '../../containers/with_kibana_chrome'; import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
import { WithSource } from '../../containers/with_source'; import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../containers/with_source';
interface HomePageProps { interface HomePageProps {
intl: InjectedIntl; intl: InjectedIntl;
} }
export const HomePage = injectI18n( export const HomePage = injectI18n(
class extends React.PureComponent<HomePageProps, {}> { class extends React.Component<HomePageProps, {}> {
public static displayName = 'HomePage'; public static displayName = 'HomePage';
public render() { public render() {
const { intl } = this.props; const { intl } = this.props;
return ( return (
<ColumnarPage> <ColumnarPage>
<Header appendSections={<InfrastructureBetaBadgeHeaderSection />} />
<WithSource> <WithSource>
{({ metricIndicesExist }) => {({
metricIndicesExist || metricIndicesExist === null ? ( derivedIndexPattern,
hasFailed,
isLoading,
lastFailureMessage,
load,
metricIndicesExist,
}) =>
metricIndicesExist ? (
<> <>
<WithWaffleTimeUrlState /> <WithWaffleTimeUrlState />
<WithWaffleFilterUrlState /> <WithWaffleFilterUrlState indexPattern={derivedIndexPattern} />
<WithWaffleOptionsUrlState /> <WithWaffleOptionsUrlState />
<Header appendSections={<InfrastructureBetaBadgeHeaderSection />} />
<HomeToolbar /> <HomeToolbar />
<HomePageContent /> <HomePageContent />
</> </>
) : isLoading ? (
<SourceLoadingPage />
) : hasFailed ? (
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
) : ( ) : (
<WithKibanaChrome> <WithKibanaChrome>
{({ basePath }) => ( {({ basePath }) => (

View file

@ -19,10 +19,10 @@ import { WithSource } from '../../containers/with_source';
export const HomePageContent: React.SFC = () => ( export const HomePageContent: React.SFC = () => (
<PageContent> <PageContent>
<WithSource> <WithSource>
{({ configuredFields }) => ( {({ configuredFields, derivedIndexPattern }) => (
<WithOptions> <WithOptions>
{({ wafflemap, sourceId }) => ( {({ wafflemap, sourceId }) => (
<WithWaffleFilter> <WithWaffleFilter indexPattern={derivedIndexPattern}>
{({ filterQueryAsJson, applyFilterQuery }) => ( {({ filterQueryAsJson, applyFilterQuery }) => (
<WithWaffleTime> <WithWaffleTime>
{({ currentTimeRange, isAutoReloading }) => ( {({ currentTimeRange, isAutoReloading }) => (

View file

@ -21,6 +21,7 @@ import { WithWaffleFilter } from '../../containers/waffle/with_waffle_filters';
import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options'; import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options';
import { WithWaffleTime } from '../../containers/waffle/with_waffle_time'; import { WithWaffleTime } from '../../containers/waffle/with_waffle_time';
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
import { WithSource } from '../../containers/with_source';
const getTitle = (nodeType: string) => { const getTitle = (nodeType: string) => {
const TITLES = { const TITLES = {
@ -78,9 +79,11 @@ export const HomeToolbar = injectI18n(({ intl }) => (
</EuiFlexGroup> </EuiFlexGroup>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m"> <EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
<EuiFlexItem> <EuiFlexItem>
<WithKueryAutocompletion> <WithSource>
{({ derivedIndexPattern }) => (
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( {({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
<WithWaffleFilter> <WithWaffleFilter indexPattern={derivedIndexPattern}>
{({ {({
applyFilterQueryFromKueryExpression, applyFilterQueryFromKueryExpression,
filterQueryDraft, filterQueryDraft,
@ -104,6 +107,8 @@ export const HomeToolbar = injectI18n(({ intl }) => (
</WithWaffleFilter> </WithWaffleFilter>
)} )}
</WithKueryAutocompletion> </WithKueryAutocompletion>
)}
</WithSource>
</EuiFlexItem> </EuiFlexItem>
<WithWaffleOptions> <WithWaffleOptions>
{({ changeMetric, changeGroupBy, groupBy, metric, nodeType }) => ( {({ changeMetric, changeGroupBy, groupBy, metric, nodeType }) => (

View file

@ -20,26 +20,21 @@ import { WithLogMinimapUrlState } from '../../containers/logs/with_log_minimap';
import { WithLogPositionUrlState } from '../../containers/logs/with_log_position'; import { WithLogPositionUrlState } from '../../containers/logs/with_log_position';
import { WithLogTextviewUrlState } from '../../containers/logs/with_log_textview'; import { WithLogTextviewUrlState } from '../../containers/logs/with_log_textview';
import { WithKibanaChrome } from '../../containers/with_kibana_chrome'; import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
import { WithSource } from '../../containers/with_source'; import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../containers/with_source';
interface Props { interface Props {
intl: InjectedIntl; intl: InjectedIntl;
} }
export const LogsPage = injectI18n( export const LogsPage = injectI18n(
class extends React.Component<Props> { class extends React.Component<Props> {
public static displayName = 'LogsPage'; public static displayName = 'LogsPage';
public render() { public render() {
const { intl } = this.props; const { intl } = this.props;
return ( return (
<ColumnarPage> <ColumnarPage>
<WithSource>
{({ logIndicesExist }) =>
logIndicesExist || logIndicesExist === null ? (
<>
<WithLogFilterUrlState />
<WithLogPositionUrlState />
<WithLogMinimapUrlState />
<WithLogTextviewUrlState />
<Header <Header
appendSections={<LogsBetaBadgeHeaderSection />} appendSections={<LogsBetaBadgeHeaderSection />}
breadcrumbs={[ breadcrumbs={[
@ -51,9 +46,28 @@ export const LogsPage = injectI18n(
}, },
]} ]}
/> />
<WithSource>
{({
derivedIndexPattern,
hasFailed,
isLoading,
lastFailureMessage,
load,
logIndicesExist,
}) =>
logIndicesExist ? (
<>
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
<WithLogPositionUrlState />
<WithLogMinimapUrlState />
<WithLogTextviewUrlState />
<LogsToolbar /> <LogsToolbar />
<LogsPageContent /> <LogsPageContent />
</> </>
) : isLoading ? (
<SourceLoadingPage />
) : hasFailed ? (
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
) : ( ) : (
<WithKibanaChrome> <WithKibanaChrome>
{({ basePath }) => ( {({ basePath }) => (

View file

@ -20,17 +20,19 @@ import { WithLogMinimap } from '../../containers/logs/with_log_minimap';
import { WithLogPosition } from '../../containers/logs/with_log_position'; import { WithLogPosition } from '../../containers/logs/with_log_position';
import { WithLogTextview } from '../../containers/logs/with_log_textview'; import { WithLogTextview } from '../../containers/logs/with_log_textview';
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
import { WithSource } from '../../containers/with_source';
export const LogsToolbar = injectI18n(({ intl }) => ( export const LogsToolbar = injectI18n(({ intl }) => (
<Toolbar> <Toolbar>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="none"> <EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="none">
<EuiFlexItem> <EuiFlexItem>
<WithKueryAutocompletion> <WithSource>
{({ derivedIndexPattern }) => (
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( {({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
<WithLogFilter> <WithLogFilter indexPattern={derivedIndexPattern}>
{({ {({
applyFilterQueryFromKueryExpression, applyFilterQueryFromKueryExpression,
/* filterQuery,*/
filterQueryDraft, filterQueryDraft,
isFilterQueryDraftValid, isFilterQueryDraftValid,
setFilterQueryDraftFromKueryExpression, setFilterQueryDraftFromKueryExpression,
@ -52,6 +54,8 @@ export const LogsToolbar = injectI18n(({ intl }) => (
</WithLogFilter> </WithLogFilter>
)} )}
</WithKueryAutocompletion> </WithKueryAutocompletion>
)}
</WithSource>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<LogCustomizationMenu> <LogCustomizationMenu>

View file

@ -6,10 +6,10 @@
import actionCreatorFactory from 'typescript-fsa'; import actionCreatorFactory from 'typescript-fsa';
import { FilterQuery } from './reducer'; import { FilterQuery, SerializedFilterQuery } from './reducer';
const actionCreator = actionCreatorFactory('x-pack/infra/local/log_filter'); const actionCreator = actionCreatorFactory('x-pack/infra/local/log_filter');
export const setLogFilterQueryDraft = actionCreator<FilterQuery>('SET_LOG_FILTER_QUERY_DRAFT'); export const setLogFilterQueryDraft = actionCreator<FilterQuery>('SET_LOG_FILTER_QUERY_DRAFT');
export const applyLogFilterQuery = actionCreator<FilterQuery>('APPLY_LOG_FILTER_QUERY'); export const applyLogFilterQuery = actionCreator<SerializedFilterQuery>('APPLY_LOG_FILTER_QUERY');

View file

@ -15,8 +15,13 @@ export interface KueryFilterQuery {
export type FilterQuery = KueryFilterQuery; export type FilterQuery = KueryFilterQuery;
export interface SerializedFilterQuery {
query: FilterQuery;
serializedQuery: string;
}
export interface LogFilterState { export interface LogFilterState {
filterQuery: KueryFilterQuery | null; filterQuery: SerializedFilterQuery | null;
filterQueryDraft: KueryFilterQuery | null; filterQueryDraft: KueryFilterQuery | null;
} }
@ -33,6 +38,6 @@ export const logFilterReducer = reducerWithInitialState(initialLogFilterState)
.case(applyLogFilterQuery, (state, filterQuery) => ({ .case(applyLogFilterQuery, (state, filterQuery) => ({
...state, ...state,
filterQuery, filterQuery,
filterQueryDraft: filterQuery, filterQueryDraft: filterQuery.query,
})) }))
.build(); .build();

View file

@ -10,7 +10,11 @@ import { fromKueryExpression } from 'ui/kuery';
import { LogFilterState } from './reducer'; import { LogFilterState } from './reducer';
export const selectLogFilterQuery = (state: LogFilterState) => state.filterQuery; export const selectLogFilterQuery = (state: LogFilterState) =>
state.filterQuery ? state.filterQuery.query : null;
export const selectLogFilterQueryAsJson = (state: LogFilterState) =>
state.filterQuery ? state.filterQuery.serializedQuery : null;
export const selectLogFilterQueryDraft = (state: LogFilterState) => state.filterQueryDraft; export const selectLogFilterQueryDraft = (state: LogFilterState) => state.filterQueryDraft;

View file

@ -6,7 +6,7 @@
import actionCreatorFactory from 'typescript-fsa'; import actionCreatorFactory from 'typescript-fsa';
import { FilterQuery } from './reducer'; import { FilterQuery, SerializedFilterQuery } from './reducer';
const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_filter'); const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_filter');
@ -14,4 +14,6 @@ export const setWaffleFilterQueryDraft = actionCreator<FilterQuery>(
'SET_WAFFLE_FILTER_QUERY_DRAFT' 'SET_WAFFLE_FILTER_QUERY_DRAFT'
); );
export const applyWaffleFilterQuery = actionCreator<FilterQuery>('APPLY_WAFFLE_FILTER_QUERY'); export const applyWaffleFilterQuery = actionCreator<SerializedFilterQuery>(
'APPLY_WAFFLE_FILTER_QUERY'
);

View file

@ -15,8 +15,13 @@ export interface KueryFilterQuery {
export type FilterQuery = KueryFilterQuery; export type FilterQuery = KueryFilterQuery;
export interface SerializedFilterQuery {
query: FilterQuery;
serializedQuery: string;
}
export interface WaffleFilterState { export interface WaffleFilterState {
filterQuery: KueryFilterQuery | null; filterQuery: SerializedFilterQuery | null;
filterQueryDraft: KueryFilterQuery | null; filterQueryDraft: KueryFilterQuery | null;
} }
@ -33,6 +38,6 @@ export const waffleFilterReducer = reducerWithInitialState(initialWaffleFilterSt
.case(applyWaffleFilterQuery, (state, filterQuery) => ({ .case(applyWaffleFilterQuery, (state, filterQuery) => ({
...state, ...state,
filterQuery, filterQuery,
filterQueryDraft: filterQuery, filterQueryDraft: filterQuery.query,
})) }))
.build(); .build();

View file

@ -10,7 +10,11 @@ import { fromKueryExpression } from 'ui/kuery';
import { WaffleFilterState } from './reducer'; import { WaffleFilterState } from './reducer';
export const selectWaffleFilterQuery = (state: WaffleFilterState) => state.filterQuery; export const selectWaffleFilterQuery = (state: WaffleFilterState) =>
state.filterQuery ? state.filterQuery.query : null;
export const selectWaffleFilterQueryAsJson = (state: WaffleFilterState) =>
state.filterQuery ? state.filterQuery.serializedQuery : null;
export const selectWaffleFilterQueryDraft = (state: WaffleFilterState) => state.filterQueryDraft; export const selectWaffleFilterQueryDraft = (state: WaffleFilterState) => state.filterQueryDraft;

View file

@ -6,4 +6,3 @@
export { logEntriesActions } from './log_entries'; export { logEntriesActions } from './log_entries';
export { logSummaryActions } from './log_summary'; export { logSummaryActions } from './log_summary';
export { sourceActions } from './source';

View file

@ -8,11 +8,6 @@ import { combineEpics } from 'redux-observable';
import { createLogEntriesEpic } from './log_entries'; import { createLogEntriesEpic } from './log_entries';
import { createLogSummaryEpic } from './log_summary'; import { createLogSummaryEpic } from './log_summary';
import { createSourceEpic } from './source';
export const createRemoteEpic = <State>() => export const createRemoteEpic = <State>() =>
combineEpics( combineEpics(createLogEntriesEpic<State>(), createLogSummaryEpic<State>());
createLogEntriesEpic<State>(),
createLogSummaryEpic<State>(),
createSourceEpic<State>()
);

View file

@ -7,22 +7,18 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { initialLogEntriesState, logEntriesReducer, LogEntriesState } from './log_entries'; import { initialLogEntriesState, logEntriesReducer, LogEntriesState } from './log_entries';
import { initialLogSummaryState, logSummaryReducer, LogSummaryState } from './log_summary'; import { initialLogSummaryState, logSummaryReducer, LogSummaryState } from './log_summary';
import { initialSourceState, sourceReducer, SourceState } from './source';
export interface RemoteState { export interface RemoteState {
logEntries: LogEntriesState; logEntries: LogEntriesState;
logSummary: LogSummaryState; logSummary: LogSummaryState;
source: SourceState;
} }
export const initialRemoteState = { export const initialRemoteState = {
logEntries: initialLogEntriesState, logEntries: initialLogEntriesState,
logSummary: initialLogSummaryState, logSummary: initialLogSummaryState,
source: initialSourceState,
}; };
export const remoteReducer = combineReducers<RemoteState>({ export const remoteReducer = combineReducers<RemoteState>({
logEntries: logEntriesReducer, logEntries: logEntriesReducer,
logSummary: logSummaryReducer, logSummary: logSummaryReducer,
source: sourceReducer,
}); });

View file

@ -8,7 +8,6 @@ import { globalizeSelectors } from '../../utils/typed_redux';
import { logEntriesSelectors as innerLogEntriesSelectors } from './log_entries'; import { logEntriesSelectors as innerLogEntriesSelectors } from './log_entries';
import { logSummarySelectors as innerLogSummarySelectors } from './log_summary'; import { logSummarySelectors as innerLogSummarySelectors } from './log_summary';
import { RemoteState } from './reducer'; import { RemoteState } from './reducer';
import { sourceSelectors as innerSourceSelectors } from './source';
export const logEntriesSelectors = globalizeSelectors( export const logEntriesSelectors = globalizeSelectors(
(state: RemoteState) => state.logEntries, (state: RemoteState) => state.logEntries,
@ -19,8 +18,3 @@ export const logSummarySelectors = globalizeSelectors(
(state: RemoteState) => state.logSummary, (state: RemoteState) => state.logSummary,
innerLogSummarySelectors innerLogSummarySelectors
); );
export const sourceSelectors = globalizeSelectors(
(state: RemoteState) => state.source,
innerSourceSelectors
);

View file

@ -1,22 +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 { Action } from 'redux';
import { combineEpics, Epic, EpicWithState } from 'redux-observable';
import { of } from 'rxjs';
import { loadSource } from './actions';
import { loadSourceEpic } from './operations/load';
export const createSourceEpic = <State>() =>
combineEpics(createSourceEffectsEpic<State>(), loadSourceEpic as EpicWithState<
typeof loadSourceEpic,
State
>);
export const createSourceEffectsEpic = <State>(): Epic<Action, Action, State, {}> => action$ => {
return of(loadSource({ sourceId: 'default' }));
};

View file

@ -1,13 +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 * as sourceActions from './actions';
import * as sourceSelectors from './selectors';
export { sourceActions, sourceSelectors };
export * from './epic';
export * from './reducer';
export { initialSourceState, SourceState } from './state';

View file

@ -1,30 +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 { SourceQuery } from '../../../../../common/graphql/types';
import {
createGraphqlOperationActionCreators,
createGraphqlOperationReducer,
createGraphqlQueryEpic,
} from '../../../../utils/remote_state/remote_graphql_state';
import { initialSourceState } from '../state';
import { sourceQuery } from './query_source.gql_query';
const operationKey = 'load';
export const loadSourceActionCreators = createGraphqlOperationActionCreators<
SourceQuery.Query,
SourceQuery.Variables
>('source', operationKey);
export const loadSourceReducer = createGraphqlOperationReducer(
operationKey,
initialSourceState,
loadSourceActionCreators,
(state, action) => action.payload.result.data.source
);
export const loadSourceEpic = createGraphqlQueryEpic(sourceQuery, loadSourceActionCreators);

View file

@ -1,13 +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 reduceReducers from 'reduce-reducers';
import { Reducer } from 'redux';
import { loadSourceReducer } from './operations/load';
import { SourceState } from './state';
export const sourceReducer = reduceReducers(loadSourceReducer) as Reducer<SourceState>;

View file

@ -1,64 +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 { createSelector } from 'reselect';
import { createGraphqlStateSelectors } from '../../../utils/remote_state/remote_graphql_state';
import { SourceRemoteState } from './state';
const sourceStatusGraphqlStateSelectors = createGraphqlStateSelectors<SourceRemoteState>();
export const selectSource = sourceStatusGraphqlStateSelectors.selectData;
export const selectSourceConfiguration = createSelector(
selectSource,
source => (source ? source.configuration : null)
);
export const selectSourceLogAlias = createSelector(
selectSourceConfiguration,
configuration => (configuration ? configuration.logAlias : null)
);
export const selectSourceMetricAlias = createSelector(
selectSourceConfiguration,
configuration => (configuration ? configuration.metricAlias : null)
);
export const selectSourceFields = createSelector(
selectSourceConfiguration,
configuration => (configuration ? configuration.fields : null)
);
export const selectSourceStatus = createSelector(
selectSource,
source => (source ? source.status : null)
);
export const selectSourceLogIndicesExist = createSelector(
selectSourceStatus,
sourceStatus => (sourceStatus ? sourceStatus.logIndicesExist : null)
);
export const selectSourceMetricIndicesExist = createSelector(
selectSourceStatus,
sourceStatus => (sourceStatus ? sourceStatus.metricIndicesExist : null)
);
export const selectSourceIndexFields = createSelector(
selectSourceStatus,
sourceStatus => (sourceStatus ? sourceStatus.indexFields : [])
);
export const selectDerivedIndexPattern = createSelector(
selectSourceIndexFields,
selectSourceLogAlias,
selectSourceMetricAlias,
(indexFields, logAlias, metricAlias) => ({
fields: indexFields,
title: `${logAlias},${metricAlias}`,
})
);

View file

@ -1,16 +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 { SourceQuery } from '../../../../common/graphql/types';
import {
createGraphqlInitialState,
GraphqlState,
} from '../../../utils/remote_state/remote_graphql_state';
export type SourceRemoteState = SourceQuery.Source;
export type SourceState = GraphqlState<SourceRemoteState>;
export const initialSourceState = createGraphqlInitialState<SourceRemoteState>();

View file

@ -6,8 +6,6 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fromKueryExpression, toElasticsearchQuery } from 'ui/kuery';
import { getLogEntryAtTime } from '../utils/log_entry'; import { getLogEntryAtTime } from '../utils/log_entry';
import { globalizeSelectors } from '../utils/typed_redux'; import { globalizeSelectors } from '../utils/typed_redux';
import { import {
@ -24,7 +22,6 @@ import { State } from './reducer';
import { import {
logEntriesSelectors as remoteLogEntriesSelectors, logEntriesSelectors as remoteLogEntriesSelectors,
logSummarySelectors as remoteLogSummarySelectors, logSummarySelectors as remoteLogSummarySelectors,
sourceSelectors as remoteSourceSelectors,
} from './remote'; } from './remote';
/** /**
@ -50,7 +47,6 @@ const selectRemote = (state: State) => state.remote;
export const logEntriesSelectors = globalizeSelectors(selectRemote, remoteLogEntriesSelectors); export const logEntriesSelectors = globalizeSelectors(selectRemote, remoteLogEntriesSelectors);
export const logSummarySelectors = globalizeSelectors(selectRemote, remoteLogSummarySelectors); export const logSummarySelectors = globalizeSelectors(selectRemote, remoteLogSummarySelectors);
export const sourceSelectors = globalizeSelectors(selectRemote, remoteSourceSelectors);
/** /**
* shared selectors * shared selectors
@ -75,34 +71,4 @@ export const sharedSelectors = {
(entries, lastVisiblePosition) => (entries, lastVisiblePosition) =>
lastVisiblePosition ? getLogEntryAtTime(entries, lastVisiblePosition) : null lastVisiblePosition ? getLogEntryAtTime(entries, lastVisiblePosition) : null
), ),
selectLogFilterQueryAsJson: createSelector(
logFilterSelectors.selectLogFilterQuery,
sourceSelectors.selectDerivedIndexPattern,
(filterQuery, indexPattern) => {
try {
return filterQuery
? JSON.stringify(
toElasticsearchQuery(fromKueryExpression(filterQuery.expression), indexPattern)
)
: null;
} catch (err) {
return null;
}
}
),
selectWaffleFilterQueryAsJson: createSelector(
waffleFilterSelectors.selectWaffleFilterQuery,
sourceSelectors.selectDerivedIndexPattern,
(filterQuery, indexPattern) => {
try {
return filterQuery
? JSON.stringify(
toElasticsearchQuery(fromKueryExpression(filterQuery.expression), indexPattern)
)
: null;
} catch (err) {
return null;
}
}
),
}; };

View file

@ -13,10 +13,10 @@ import {
createRootEpic, createRootEpic,
initialState, initialState,
logEntriesSelectors, logEntriesSelectors,
logFilterSelectors,
logPositionSelectors, logPositionSelectors,
metricTimeSelectors, metricTimeSelectors,
reducer, reducer,
sharedSelectors,
State, State,
waffleTimeSelectors, waffleTimeSelectors,
} from '.'; } from '.';
@ -45,7 +45,7 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies)
selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd, selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd,
selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart, selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart,
selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading,
selectLogFilterQueryAsJson: sharedSelectors.selectLogFilterQueryAsJson, selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson,
selectLogTargetPosition: logPositionSelectors.selectTargetPosition, selectLogTargetPosition: logPositionSelectors.selectTargetPosition,
selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget, selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget,
selectVisibleLogSummary: logPositionSelectors.selectVisibleSummary, selectVisibleLogSummary: logPositionSelectors.selectVisibleSummary,

View 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 { StaticIndexPattern } from 'ui/index_patterns';
import { fromKueryExpression, toElasticsearchQuery } from 'ui/kuery';
export const convertKueryToElasticSearchQuery = (
kueryExpression: string,
indexPattern: StaticIndexPattern
) => {
try {
return kueryExpression
? JSON.stringify(toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern))
: '';
} catch (err) {
return '';
}
};

View file

@ -0,0 +1,90 @@
/*
* 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';
import { oc } from 'ts-optchain';
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) =>
oc(last(selectStatusHistory(state))).status() === 'in-progress',
getHasSucceeded: () => (state: S) =>
oc(last(selectStatusHistory(state))).status() === 'succeeded',
getHasFailed: () => (state: S) => oc(last(selectStatusHistory(state))).status() === 'failed',
getLastFailureMessage: () => (state: S) =>
oc(last(selectStatusHistory(state).filter(isFailedStatus))).message(),
});
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(),
},
]),
});

View file

@ -0,0 +1,101 @@
/*
* 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 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 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 const inferSelectorMap = <State extends any>() => <
Selectors extends {
[key: string]: (...args: any[]) => (state: State) => any;
}
>(
selectorMap: Selectors
): InferredSelectors<State, Selectors> => selectorMap as any;

View file

@ -6,7 +6,7 @@
import expect from 'expect.js'; import expect from 'expect.js';
import { SourceQuery } from '../../../../plugins/infra/common/graphql/types'; import { SourceQuery } from '../../../../plugins/infra/common/graphql/types';
import { sourceQuery } from '../../../../plugins/infra/public/store/remote/source/operations/query_source.gql_query'; import { sourceQuery } from '../../../../plugins/infra/public/containers/with_source/query_source.gql_query';
import { KbnTestProvider } from './types'; import { KbnTestProvider } from './types';

View file

@ -5710,6 +5710,11 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
constate@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/constate/-/constate-0.9.0.tgz#877197ef8fbcacee95672a7e98f7b21dec818891"
integrity sha512-Cgkqefi4GrepnA7gwqbrsU+Kf/xl0sPv3O1UNE/vUZtTgpWlkd+/rSZjjd7npICtExn6yuICro7Vb2zoFCnS/A==
contains-path@^0.1.0: contains-path@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"