[Infra UI] Replace redux source slice with constate container (#26121)
This commit is contained in:
parent
aaa809249e
commit
d8f487d331
|
@ -153,6 +153,7 @@
|
|||
"chroma-js": "^1.3.6",
|
||||
"classnames": "2.2.5",
|
||||
"concat-stream": "1.5.1",
|
||||
"constate": "^0.9.0",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"cronstrue": "^1.51.0",
|
||||
"d3": "3.5.6",
|
||||
|
|
|
@ -705,6 +705,52 @@ export namespace WaffleNodesQuery {
|
|||
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 type Variables = {
|
||||
sourceId?: string | null;
|
||||
|
@ -800,52 +846,6 @@ export namespace LogSummary {
|
|||
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 type Fragment = {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 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 React from 'react';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
|
@ -16,6 +16,7 @@ import { ThemeProvider } from 'styled-components';
|
|||
// TODO use theme provided from parentApp when kibana supports it
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { InfraFrontendLibs } from '../lib/lib';
|
||||
import { PageRouter } from '../routes';
|
||||
import { createStore } from '../store';
|
||||
|
@ -32,13 +33,15 @@ export async function startApp(libs: InfraFrontendLibs) {
|
|||
libs.framework.render(
|
||||
<I18nProvider>
|
||||
<EuiErrorBoundary>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ThemeProvider theme={{ eui: euiVars }}>
|
||||
<PageRouter history={history} />
|
||||
</ThemeProvider>
|
||||
</ApolloProvider>
|
||||
</ReduxStoreProvider>
|
||||
<ConstateProvider devtools>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ThemeProvider theme={{ eui: euiVars }}>
|
||||
<PageRouter history={history} />
|
||||
</ThemeProvider>
|
||||
</ApolloProvider>
|
||||
</ReduxStoreProvider>
|
||||
</ConstateProvider>
|
||||
</EuiErrorBoundary>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
|
56
x-pack/plugins/infra/public/components/error_page.tsx
Normal file
56
x-pack/plugins/infra/public/components/error_page.tsx
Normal 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;
|
||||
`;
|
|
@ -7,31 +7,49 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
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 { bindPlainActionCreators } from '../../utils/typed_redux';
|
||||
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';
|
||||
|
||||
interface WithLogFilterProps {
|
||||
indexPattern: StaticIndexPattern;
|
||||
}
|
||||
|
||||
const withLogFilter = connect(
|
||||
(state: State) => ({
|
||||
filterQuery: logFilterSelectors.selectLogFilterQuery(state),
|
||||
filterQueryDraft: logFilterSelectors.selectLogFilterQueryDraft(state),
|
||||
isFilterQueryDraftValid: logFilterSelectors.selectIsLogFilterQueryDraftValid(state),
|
||||
}),
|
||||
bindPlainActionCreators({
|
||||
applyFilterQuery: logFilterActions.applyLogFilterQuery,
|
||||
applyFilterQueryFromKueryExpression: (expression: string) =>
|
||||
logFilterActions.applyLogFilterQuery({
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}),
|
||||
setFilterQueryDraft: logFilterActions.setLogFilterQueryDraft,
|
||||
setFilterQueryDraftFromKueryExpression: (expression: string) =>
|
||||
logFilterActions.setLogFilterQueryDraft({
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}),
|
||||
})
|
||||
(dispatch, ownProps: WithLogFilterProps) =>
|
||||
bindPlainActionCreators({
|
||||
applyFilterQuery: (query: FilterQuery) =>
|
||||
logFilterActions.applyLogFilterQuery({
|
||||
query,
|
||||
serializedQuery: convertKueryToElasticSearchQuery(
|
||||
query.expression,
|
||||
ownProps.indexPattern
|
||||
),
|
||||
}),
|
||||
applyFilterQueryFromKueryExpression: (expression: string) =>
|
||||
logFilterActions.applyLogFilterQuery({
|
||||
query: {
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
},
|
||||
serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern),
|
||||
}),
|
||||
setFilterQueryDraft: logFilterActions.setLogFilterQueryDraft,
|
||||
setFilterQueryDraftFromKueryExpression: (expression: string) =>
|
||||
logFilterActions.setLogFilterQueryDraft({
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}),
|
||||
})(dispatch)
|
||||
);
|
||||
|
||||
export const WithLogFilter = asChildFunctionRenderer(withLogFilter);
|
||||
|
@ -42,8 +60,10 @@ export const WithLogFilter = asChildFunctionRenderer(withLogFilter);
|
|||
|
||||
type LogFilterUrlState = ReturnType<typeof logFilterSelectors.selectLogFilterQuery>;
|
||||
|
||||
export const WithLogFilterUrlState = () => (
|
||||
<WithLogFilter>
|
||||
type WithLogFilterUrlStateProps = WithLogFilterProps;
|
||||
|
||||
export const WithLogFilterUrlState: React.SFC<WithLogFilterUrlStateProps> = ({ indexPattern }) => (
|
||||
<WithLogFilter indexPattern={indexPattern}>
|
||||
{({ applyFilterQuery, filterQuery }) => (
|
||||
<UrlStateContainer
|
||||
urlState={filterQuery}
|
||||
|
|
|
@ -7,32 +7,50 @@
|
|||
import React from 'react';
|
||||
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 { bindPlainActionCreators } from '../../utils/typed_redux';
|
||||
import { UrlStateContainer } from '../../utils/url_state';
|
||||
|
||||
interface WithWaffleFilterProps {
|
||||
indexPattern: StaticIndexPattern;
|
||||
}
|
||||
|
||||
export const withWaffleFilter = connect(
|
||||
(state: State) => ({
|
||||
filterQuery: waffleFilterSelectors.selectWaffleFilterQuery(state),
|
||||
filterQueryDraft: waffleFilterSelectors.selectWaffleFilterQueryDraft(state),
|
||||
filterQueryAsJson: sharedSelectors.selectWaffleFilterQueryAsJson(state),
|
||||
filterQueryAsJson: waffleFilterSelectors.selectWaffleFilterQueryAsJson(state),
|
||||
isFilterQueryDraftValid: waffleFilterSelectors.selectIsWaffleFilterQueryDraftValid(state),
|
||||
}),
|
||||
bindPlainActionCreators({
|
||||
applyFilterQuery: waffleFilterActions.applyWaffleFilterQuery,
|
||||
applyFilterQueryFromKueryExpression: (expression: string) =>
|
||||
waffleFilterActions.applyWaffleFilterQuery({
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}),
|
||||
setFilterQueryDraft: waffleFilterActions.setWaffleFilterQueryDraft,
|
||||
setFilterQueryDraftFromKueryExpression: (expression: string) =>
|
||||
waffleFilterActions.setWaffleFilterQueryDraft({
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}),
|
||||
})
|
||||
(dispatch, ownProps: WithWaffleFilterProps) =>
|
||||
bindPlainActionCreators({
|
||||
applyFilterQuery: (query: FilterQuery) =>
|
||||
waffleFilterActions.applyWaffleFilterQuery({
|
||||
query,
|
||||
serializedQuery: convertKueryToElasticSearchQuery(
|
||||
query.expression,
|
||||
ownProps.indexPattern
|
||||
),
|
||||
}),
|
||||
applyFilterQueryFromKueryExpression: (expression: string) =>
|
||||
waffleFilterActions.applyWaffleFilterQuery({
|
||||
query: {
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
},
|
||||
serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern),
|
||||
}),
|
||||
setFilterQueryDraft: waffleFilterActions.setWaffleFilterQueryDraft,
|
||||
setFilterQueryDraftFromKueryExpression: (expression: string) =>
|
||||
waffleFilterActions.setWaffleFilterQueryDraft({
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
export const WithWaffleFilter = asChildFunctionRenderer(withWaffleFilter);
|
||||
|
@ -43,8 +61,12 @@ export const WithWaffleFilter = asChildFunctionRenderer(withWaffleFilter);
|
|||
|
||||
type WaffleFilterUrlState = ReturnType<typeof waffleFilterSelectors.selectWaffleFilterQuery>;
|
||||
|
||||
export const WithWaffleFilterUrlState = () => (
|
||||
<WithWaffleFilter>
|
||||
type WithWaffleFilterUrlStateProps = WithWaffleFilterProps;
|
||||
|
||||
export const WithWaffleFilterUrlState: React.SFC<WithWaffleFilterUrlStateProps> = ({
|
||||
indexPattern,
|
||||
}) => (
|
||||
<WithWaffleFilter indexPattern={indexPattern}>
|
||||
{({ applyFilterQuery, filterQuery }) => (
|
||||
<UrlStateContainer
|
||||
urlState={filterQuery}
|
||||
|
|
|
@ -5,18 +5,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { sourceSelectors, State } from '../store';
|
||||
import { RendererFunction } from '../utils/typed_react';
|
||||
|
||||
const withIndexPattern = connect((state: State) => ({
|
||||
indexPattern: sourceSelectors.selectDerivedIndexPattern(state),
|
||||
}));
|
||||
|
||||
interface WithKueryAutocompletionLifecycleProps {
|
||||
children: RendererFunction<{
|
||||
isLoadingSuggestions: boolean;
|
||||
|
@ -36,73 +30,71 @@ interface WithKueryAutocompletionLifecycleState {
|
|||
suggestions: AutocompleteSuggestion[];
|
||||
}
|
||||
|
||||
export const WithKueryAutocompletion = withIndexPattern(
|
||||
class WithKueryAutocompletionLifecycle extends React.Component<
|
||||
WithKueryAutocompletionLifecycleProps,
|
||||
WithKueryAutocompletionLifecycleState
|
||||
> {
|
||||
public readonly state: WithKueryAutocompletionLifecycleState = {
|
||||
currentRequest: null,
|
||||
suggestions: [],
|
||||
export class WithKueryAutocompletion extends React.Component<
|
||||
WithKueryAutocompletionLifecycleProps,
|
||||
WithKueryAutocompletionLifecycleState
|
||||
> {
|
||||
public readonly state: WithKueryAutocompletionLifecycleState = {
|
||||
currentRequest: null,
|
||||
suggestions: [],
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { currentRequest, suggestions } = this.state;
|
||||
|
||||
return this.props.children({
|
||||
isLoadingSuggestions: currentRequest !== null,
|
||||
loadSuggestions: this.loadSuggestions,
|
||||
suggestions,
|
||||
});
|
||||
}
|
||||
|
||||
private loadSuggestions = async (
|
||||
expression: string,
|
||||
cursorPosition: number,
|
||||
maxSuggestions?: number
|
||||
) => {
|
||||
const { indexPattern } = this.props;
|
||||
const autocompletionProvider = getAutocompleteProvider('kuery');
|
||||
const config = {
|
||||
get: () => true,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { currentRequest, suggestions } = this.state;
|
||||
|
||||
return this.props.children({
|
||||
isLoadingSuggestions: currentRequest !== null,
|
||||
loadSuggestions: this.loadSuggestions,
|
||||
suggestions,
|
||||
});
|
||||
if (!autocompletionProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
private loadSuggestions = async (
|
||||
expression: string,
|
||||
cursorPosition: number,
|
||||
maxSuggestions?: number
|
||||
) => {
|
||||
const { indexPattern } = this.props;
|
||||
const autocompletionProvider = getAutocompleteProvider('kuery');
|
||||
const config = {
|
||||
get: () => true,
|
||||
};
|
||||
const getSuggestions = autocompletionProvider({
|
||||
config,
|
||||
indexPatterns: [indexPattern],
|
||||
boolFilter: [],
|
||||
});
|
||||
|
||||
if (!autocompletionProvider) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
currentRequest: {
|
||||
expression,
|
||||
cursorPosition,
|
||||
},
|
||||
suggestions: [],
|
||||
});
|
||||
|
||||
const getSuggestions = autocompletionProvider({
|
||||
config,
|
||||
indexPatterns: [indexPattern],
|
||||
boolFilter: [],
|
||||
});
|
||||
const suggestions = await getSuggestions({
|
||||
query: expression,
|
||||
selectionStart: cursorPosition,
|
||||
selectionEnd: cursorPosition,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
currentRequest: {
|
||||
expression,
|
||||
cursorPosition,
|
||||
},
|
||||
suggestions: [],
|
||||
});
|
||||
|
||||
const suggestions = await getSuggestions({
|
||||
query: expression,
|
||||
selectionStart: cursorPosition,
|
||||
selectionEnd: cursorPosition,
|
||||
});
|
||||
|
||||
this.setState(
|
||||
state =>
|
||||
state.currentRequest &&
|
||||
state.currentRequest.expression !== expression &&
|
||||
state.currentRequest.cursorPosition !== cursorPosition
|
||||
? state // ignore this result, since a newer request is in flight
|
||||
: {
|
||||
...state,
|
||||
currentRequest: null,
|
||||
suggestions: maxSuggestions ? suggestions.slice(0, maxSuggestions) : suggestions,
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
this.setState(
|
||||
state =>
|
||||
state.currentRequest &&
|
||||
state.currentRequest.expression !== expression &&
|
||||
state.currentRequest.cursorPosition !== cursorPosition
|
||||
? state // ignore this result, since a newer request is in flight
|
||||
: {
|
||||
...state,
|
||||
currentRequest: null,
|
||||
suggestions: maxSuggestions ? suggestions.slice(0, maxSuggestions) : suggestions,
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -4,6 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { loadSourceActionCreators } from './operations/load';
|
||||
|
||||
export const loadSource = loadSourceActionCreators.resolve;
|
||||
export { SourceErrorPage } from './source_error_page';
|
||||
export { SourceLoadingPage } from './source_loading_page';
|
||||
export { WithSource } from './with_source';
|
|
@ -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}
|
||||
/>
|
||||
);
|
|
@ -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" />;
|
|
@ -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>
|
||||
);
|
|
@ -4,9 +4,10 @@
|
|||
* 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 { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
|
||||
import { HomePageContent } from './page_content';
|
||||
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 { WithWaffleTimeUrlState } from '../../containers/waffle/with_waffle_time';
|
||||
import { WithKibanaChrome } from '../../containers/with_kibana_chrome';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
import { SourceErrorPage, SourceLoadingPage, WithSource } from '../../containers/with_source';
|
||||
|
||||
interface HomePageProps {
|
||||
intl: InjectedIntl;
|
||||
}
|
||||
|
||||
export const HomePage = injectI18n(
|
||||
class extends React.PureComponent<HomePageProps, {}> {
|
||||
class extends React.Component<HomePageProps, {}> {
|
||||
public static displayName = 'HomePage';
|
||||
|
||||
public render() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<ColumnarPage>
|
||||
<Header appendSections={<InfrastructureBetaBadgeHeaderSection />} />
|
||||
<WithSource>
|
||||
{({ metricIndicesExist }) =>
|
||||
metricIndicesExist || metricIndicesExist === null ? (
|
||||
{({
|
||||
derivedIndexPattern,
|
||||
hasFailed,
|
||||
isLoading,
|
||||
lastFailureMessage,
|
||||
load,
|
||||
metricIndicesExist,
|
||||
}) =>
|
||||
metricIndicesExist ? (
|
||||
<>
|
||||
<WithWaffleTimeUrlState />
|
||||
<WithWaffleFilterUrlState />
|
||||
<WithWaffleFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithWaffleOptionsUrlState />
|
||||
<Header appendSections={<InfrastructureBetaBadgeHeaderSection />} />
|
||||
<HomeToolbar />
|
||||
<HomePageContent />
|
||||
</>
|
||||
) : isLoading ? (
|
||||
<SourceLoadingPage />
|
||||
) : hasFailed ? (
|
||||
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
|
||||
) : (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
|
|
|
@ -19,10 +19,10 @@ import { WithSource } from '../../containers/with_source';
|
|||
export const HomePageContent: React.SFC = () => (
|
||||
<PageContent>
|
||||
<WithSource>
|
||||
{({ configuredFields }) => (
|
||||
{({ configuredFields, derivedIndexPattern }) => (
|
||||
<WithOptions>
|
||||
{({ wafflemap, sourceId }) => (
|
||||
<WithWaffleFilter>
|
||||
<WithWaffleFilter indexPattern={derivedIndexPattern}>
|
||||
{({ filterQueryAsJson, applyFilterQuery }) => (
|
||||
<WithWaffleTime>
|
||||
{({ currentTimeRange, isAutoReloading }) => (
|
||||
|
|
|
@ -21,6 +21,7 @@ import { WithWaffleFilter } from '../../containers/waffle/with_waffle_filters';
|
|||
import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options';
|
||||
import { WithWaffleTime } from '../../containers/waffle/with_waffle_time';
|
||||
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
|
||||
const getTitle = (nodeType: string) => {
|
||||
const TITLES = {
|
||||
|
@ -78,32 +79,36 @@ export const HomeToolbar = injectI18n(({ intl }) => (
|
|||
</EuiFlexGroup>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<WithKueryAutocompletion>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<WithWaffleFilter>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder',
|
||||
defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)',
|
||||
})}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
<WithSource>
|
||||
{({ derivedIndexPattern }) => (
|
||||
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<WithWaffleFilter indexPattern={derivedIndexPattern}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder',
|
||||
defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)',
|
||||
})}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleFilter>
|
||||
)}
|
||||
</WithWaffleFilter>
|
||||
</WithKueryAutocompletion>
|
||||
)}
|
||||
</WithKueryAutocompletion>
|
||||
</WithSource>
|
||||
</EuiFlexItem>
|
||||
<WithWaffleOptions>
|
||||
{({ changeMetric, changeGroupBy, groupBy, metric, nodeType }) => (
|
||||
|
|
|
@ -20,40 +20,54 @@ 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 { WithSource } from '../../containers/with_source';
|
||||
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 (
|
||||
<ColumnarPage>
|
||||
<Header
|
||||
appendSections={<LogsBetaBadgeHeaderSection />}
|
||||
breadcrumbs={[
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<WithSource>
|
||||
{({ logIndicesExist }) =>
|
||||
logIndicesExist || logIndicesExist === null ? (
|
||||
{({
|
||||
derivedIndexPattern,
|
||||
hasFailed,
|
||||
isLoading,
|
||||
lastFailureMessage,
|
||||
load,
|
||||
logIndicesExist,
|
||||
}) =>
|
||||
logIndicesExist ? (
|
||||
<>
|
||||
<WithLogFilterUrlState />
|
||||
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
|
||||
<WithLogPositionUrlState />
|
||||
<WithLogMinimapUrlState />
|
||||
<WithLogTextviewUrlState />
|
||||
<Header
|
||||
appendSections={<LogsBetaBadgeHeaderSection />}
|
||||
breadcrumbs={[
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<LogsToolbar />
|
||||
<LogsPageContent />
|
||||
</>
|
||||
) : isLoading ? (
|
||||
<SourceLoadingPage />
|
||||
) : hasFailed ? (
|
||||
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
|
||||
) : (
|
||||
<WithKibanaChrome>
|
||||
{({ basePath }) => (
|
||||
|
|
|
@ -20,38 +20,42 @@ import { WithLogMinimap } from '../../containers/logs/with_log_minimap';
|
|||
import { WithLogPosition } from '../../containers/logs/with_log_position';
|
||||
import { WithLogTextview } from '../../containers/logs/with_log_textview';
|
||||
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
|
||||
import { WithSource } from '../../containers/with_source';
|
||||
|
||||
export const LogsToolbar = injectI18n(({ intl }) => (
|
||||
<Toolbar>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<WithKueryAutocompletion>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<WithLogFilter>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
/* filterQuery,*/
|
||||
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 : ''}
|
||||
/>
|
||||
<WithSource>
|
||||
{({ derivedIndexPattern }) => (
|
||||
<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>
|
||||
)}
|
||||
</WithLogFilter>
|
||||
</WithKueryAutocompletion>
|
||||
)}
|
||||
</WithKueryAutocompletion>
|
||||
</WithSource>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogCustomizationMenu>
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
import actionCreatorFactory from 'typescript-fsa';
|
||||
|
||||
import { FilterQuery } from './reducer';
|
||||
import { FilterQuery, SerializedFilterQuery } from './reducer';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/infra/local/log_filter');
|
||||
|
||||
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');
|
||||
|
|
|
@ -15,8 +15,13 @@ export interface KueryFilterQuery {
|
|||
|
||||
export type FilterQuery = KueryFilterQuery;
|
||||
|
||||
export interface SerializedFilterQuery {
|
||||
query: FilterQuery;
|
||||
serializedQuery: string;
|
||||
}
|
||||
|
||||
export interface LogFilterState {
|
||||
filterQuery: KueryFilterQuery | null;
|
||||
filterQuery: SerializedFilterQuery | null;
|
||||
filterQueryDraft: KueryFilterQuery | null;
|
||||
}
|
||||
|
||||
|
@ -33,6 +38,6 @@ export const logFilterReducer = reducerWithInitialState(initialLogFilterState)
|
|||
.case(applyLogFilterQuery, (state, filterQuery) => ({
|
||||
...state,
|
||||
filterQuery,
|
||||
filterQueryDraft: filterQuery,
|
||||
filterQueryDraft: filterQuery.query,
|
||||
}))
|
||||
.build();
|
||||
|
|
|
@ -10,7 +10,11 @@ import { fromKueryExpression } from '@kbn/es-query';
|
|||
|
||||
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;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import actionCreatorFactory from 'typescript-fsa';
|
||||
|
||||
import { FilterQuery } from './reducer';
|
||||
import { FilterQuery, SerializedFilterQuery } from './reducer';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_filter');
|
||||
|
||||
|
@ -14,4 +14,6 @@ export const setWaffleFilterQueryDraft = actionCreator<FilterQuery>(
|
|||
'SET_WAFFLE_FILTER_QUERY_DRAFT'
|
||||
);
|
||||
|
||||
export const applyWaffleFilterQuery = actionCreator<FilterQuery>('APPLY_WAFFLE_FILTER_QUERY');
|
||||
export const applyWaffleFilterQuery = actionCreator<SerializedFilterQuery>(
|
||||
'APPLY_WAFFLE_FILTER_QUERY'
|
||||
);
|
||||
|
|
|
@ -15,8 +15,13 @@ export interface KueryFilterQuery {
|
|||
|
||||
export type FilterQuery = KueryFilterQuery;
|
||||
|
||||
export interface SerializedFilterQuery {
|
||||
query: FilterQuery;
|
||||
serializedQuery: string;
|
||||
}
|
||||
|
||||
export interface WaffleFilterState {
|
||||
filterQuery: KueryFilterQuery | null;
|
||||
filterQuery: SerializedFilterQuery | null;
|
||||
filterQueryDraft: KueryFilterQuery | null;
|
||||
}
|
||||
|
||||
|
@ -33,6 +38,6 @@ export const waffleFilterReducer = reducerWithInitialState(initialWaffleFilterSt
|
|||
.case(applyWaffleFilterQuery, (state, filterQuery) => ({
|
||||
...state,
|
||||
filterQuery,
|
||||
filterQueryDraft: filterQuery,
|
||||
filterQueryDraft: filterQuery.query,
|
||||
}))
|
||||
.build();
|
||||
|
|
|
@ -10,7 +10,11 @@ import { fromKueryExpression } from '@kbn/es-query';
|
|||
|
||||
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;
|
||||
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
|
||||
export { logEntriesActions } from './log_entries';
|
||||
export { logSummaryActions } from './log_summary';
|
||||
export { sourceActions } from './source';
|
||||
|
|
|
@ -8,11 +8,6 @@ import { combineEpics } from 'redux-observable';
|
|||
|
||||
import { createLogEntriesEpic } from './log_entries';
|
||||
import { createLogSummaryEpic } from './log_summary';
|
||||
import { createSourceEpic } from './source';
|
||||
|
||||
export const createRemoteEpic = <State>() =>
|
||||
combineEpics(
|
||||
createLogEntriesEpic<State>(),
|
||||
createLogSummaryEpic<State>(),
|
||||
createSourceEpic<State>()
|
||||
);
|
||||
combineEpics(createLogEntriesEpic<State>(), createLogSummaryEpic<State>());
|
||||
|
|
|
@ -7,22 +7,18 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { initialLogEntriesState, logEntriesReducer, LogEntriesState } from './log_entries';
|
||||
import { initialLogSummaryState, logSummaryReducer, LogSummaryState } from './log_summary';
|
||||
import { initialSourceState, sourceReducer, SourceState } from './source';
|
||||
|
||||
export interface RemoteState {
|
||||
logEntries: LogEntriesState;
|
||||
logSummary: LogSummaryState;
|
||||
source: SourceState;
|
||||
}
|
||||
|
||||
export const initialRemoteState = {
|
||||
logEntries: initialLogEntriesState,
|
||||
logSummary: initialLogSummaryState,
|
||||
source: initialSourceState,
|
||||
};
|
||||
|
||||
export const remoteReducer = combineReducers<RemoteState>({
|
||||
logEntries: logEntriesReducer,
|
||||
logSummary: logSummaryReducer,
|
||||
source: sourceReducer,
|
||||
});
|
||||
|
|
|
@ -8,7 +8,6 @@ import { globalizeSelectors } from '../../utils/typed_redux';
|
|||
import { logEntriesSelectors as innerLogEntriesSelectors } from './log_entries';
|
||||
import { logSummarySelectors as innerLogSummarySelectors } from './log_summary';
|
||||
import { RemoteState } from './reducer';
|
||||
import { sourceSelectors as innerSourceSelectors } from './source';
|
||||
|
||||
export const logEntriesSelectors = globalizeSelectors(
|
||||
(state: RemoteState) => state.logEntries,
|
||||
|
@ -19,8 +18,3 @@ export const logSummarySelectors = globalizeSelectors(
|
|||
(state: RemoteState) => state.logSummary,
|
||||
innerLogSummarySelectors
|
||||
);
|
||||
|
||||
export const sourceSelectors = globalizeSelectors(
|
||||
(state: RemoteState) => state.source,
|
||||
innerSourceSelectors
|
||||
);
|
||||
|
|
|
@ -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' }));
|
||||
};
|
|
@ -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';
|
|
@ -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);
|
|
@ -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>;
|
|
@ -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}`,
|
||||
})
|
||||
);
|
|
@ -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>();
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
|
||||
import { getLogEntryAtTime } from '../utils/log_entry';
|
||||
import { globalizeSelectors } from '../utils/typed_redux';
|
||||
import {
|
||||
|
@ -24,7 +22,6 @@ import { State } from './reducer';
|
|||
import {
|
||||
logEntriesSelectors as remoteLogEntriesSelectors,
|
||||
logSummarySelectors as remoteLogSummarySelectors,
|
||||
sourceSelectors as remoteSourceSelectors,
|
||||
} from './remote';
|
||||
|
||||
/**
|
||||
|
@ -50,7 +47,6 @@ const selectRemote = (state: State) => state.remote;
|
|||
|
||||
export const logEntriesSelectors = globalizeSelectors(selectRemote, remoteLogEntriesSelectors);
|
||||
export const logSummarySelectors = globalizeSelectors(selectRemote, remoteLogSummarySelectors);
|
||||
export const sourceSelectors = globalizeSelectors(selectRemote, remoteSourceSelectors);
|
||||
|
||||
/**
|
||||
* shared selectors
|
||||
|
@ -75,34 +71,4 @@ export const sharedSelectors = {
|
|||
(entries, lastVisiblePosition) =>
|
||||
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;
|
||||
}
|
||||
}
|
||||
),
|
||||
};
|
||||
|
|
|
@ -13,10 +13,10 @@ import {
|
|||
createRootEpic,
|
||||
initialState,
|
||||
logEntriesSelectors,
|
||||
logFilterSelectors,
|
||||
logPositionSelectors,
|
||||
metricTimeSelectors,
|
||||
reducer,
|
||||
sharedSelectors,
|
||||
State,
|
||||
waffleTimeSelectors,
|
||||
} from '.';
|
||||
|
@ -45,7 +45,7 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies)
|
|||
selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd,
|
||||
selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart,
|
||||
selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading,
|
||||
selectLogFilterQueryAsJson: sharedSelectors.selectLogFilterQueryAsJson,
|
||||
selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson,
|
||||
selectLogTargetPosition: logPositionSelectors.selectTargetPosition,
|
||||
selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget,
|
||||
selectVisibleLogSummary: logPositionSelectors.selectVisibleSummary,
|
||||
|
|
21
x-pack/plugins/infra/public/utils/kuery.ts
Normal file
21
x-pack/plugins/infra/public/utils/kuery.ts
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 { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
export const convertKueryToElasticSearchQuery = (
|
||||
kueryExpression: string,
|
||||
indexPattern: StaticIndexPattern
|
||||
) => {
|
||||
try {
|
||||
return kueryExpression
|
||||
? JSON.stringify(toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern))
|
||||
: '';
|
||||
} catch (err) {
|
||||
return '';
|
||||
}
|
||||
};
|
90
x-pack/plugins/infra/public/utils/operation_status.ts
Normal file
90
x-pack/plugins/infra/public/utils/operation_status.ts
Normal 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(),
|
||||
},
|
||||
]),
|
||||
});
|
101
x-pack/plugins/infra/public/utils/typed_constate.tsx
Normal file
101
x-pack/plugins/infra/public/utils/typed_constate.tsx
Normal 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;
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import expect from 'expect.js';
|
||||
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';
|
||||
|
||||
|
|
|
@ -5739,6 +5739,11 @@ constants-browserify@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
|
||||
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:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
||||
|
|
Loading…
Reference in a new issue