mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* [Metrics UI] Refactor containers to hooks * clean up depends; move useInterval out of useWaffleTime; * converting WithWaffleFilters to useWaffleFilters * Removing WithWaffleOptions * Refactor WithWaffleViewState to useWaffleViewState * Removing obsolete files * Fixing race condition with complext state * Adding undefined to RisonValue; unwinding changes trying to work around bad type * Switching to context * Change assertion to ignore the length of the current URL * Fixing test frameork to accept urls longer then 230 characters * Fixes #59395; Refactor WithMetricsTime to hook; Fixes brushing on metric detail page; fixes refresh button on metric detail page * Fixing tests with adding timeRange Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
de2099e8d0
commit
2ba01e1263
87 changed files with 1086 additions and 2148 deletions
|
@ -247,25 +247,11 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
}
|
||||
|
||||
currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//');
|
||||
const maxAdditionalLengthOnNavUrl = 230;
|
||||
|
||||
// On several test failures at the end of the TileMap test we try to navigate back to
|
||||
// Visualize so we can create the next Vertical Bar Chart, but we can see from the
|
||||
// logging and the screenshot that it's still on the TileMap page. Why didn't the "get"
|
||||
// with a new timestamped URL go? I thought that sleep(700) between the get and the
|
||||
// refresh would solve the problem but didn't seem to always work.
|
||||
// So this hack fails the navSuccessful check if the currentUrl doesn't match the
|
||||
// appUrl plus up to 230 other chars.
|
||||
// Navigating to Settings when there is a default index pattern has a URL length of 196
|
||||
// (from debug output). Some other tabs may also be long. But a rather simple configured
|
||||
// visualization is about 1000 chars long. So at least we catch that case.
|
||||
|
||||
// Browsers don't show the ':port' if it's 80 or 443 so we have to
|
||||
// remove that part so we can get a match in the tests.
|
||||
const navSuccessful = new RegExp(
|
||||
appUrl.replace(':80/', '/').replace(':443/', '/') +
|
||||
`.{0,${maxAdditionalLengthOnNavUrl}}$`
|
||||
).test(currentUrl);
|
||||
const navSuccessful = currentUrl
|
||||
.replace(':80/', '/')
|
||||
.replace(':443/', '/')
|
||||
.startsWith(appUrl);
|
||||
|
||||
if (!navSuccessful) {
|
||||
const msg = `App failed to load: ${appName} in ${defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`;
|
||||
|
|
2
test/typings/rison_node.d.ts
vendored
2
test/typings/rison_node.d.ts
vendored
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
declare module 'rison-node' {
|
||||
export type RisonValue = null | boolean | number | string | RisonObject | RisonArray;
|
||||
export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface RisonArray extends Array<RisonValue> {}
|
||||
|
|
2
typings/rison_node.d.ts
vendored
2
typings/rison_node.d.ts
vendored
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
declare module 'rison-node' {
|
||||
export type RisonValue = null | boolean | number | string | RisonObject | RisonArray;
|
||||
export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface RisonArray extends Array<RisonValue> {}
|
||||
|
|
|
@ -11,6 +11,10 @@ export const InfraMetadataRequestRT = rt.type({
|
|||
nodeId: rt.string,
|
||||
nodeType: ItemTypeRT,
|
||||
sourceId: rt.string,
|
||||
timeRange: rt.type({
|
||||
from: rt.number,
|
||||
to: rt.number,
|
||||
}),
|
||||
});
|
||||
|
||||
export const InfraMetadataFeatureRT = rt.type({
|
||||
|
|
|
@ -20,7 +20,7 @@ import { withTheme } from '../../../../observability/public';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { MetadataDetails } from '../../../public/pages/metrics/components/metadata_details';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, theme, onChangeRangeTime }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<MetadataDetails
|
||||
fields={[
|
||||
|
@ -42,6 +42,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection
|
||||
id="awsEC2CpuUtilization"
|
||||
|
|
|
@ -18,7 +18,7 @@ import { withTheme } from '../../../../observability/public';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<LayoutContent>
|
||||
<Section
|
||||
|
@ -30,6 +30,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection
|
||||
id="awsRDSCpuTotal"
|
||||
|
|
|
@ -18,7 +18,7 @@ import { withTheme } from '../../../../observability/public';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<LayoutContent>
|
||||
<Section
|
||||
|
@ -30,6 +30,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection
|
||||
id="awsS3BucketSize"
|
||||
|
|
|
@ -18,7 +18,7 @@ import { withTheme } from '../../../../observability/public';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<LayoutContent>
|
||||
<Section
|
||||
|
@ -30,6 +30,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection
|
||||
id="awsSQSMessagesVisible"
|
||||
|
|
|
@ -22,7 +22,7 @@ import { LayoutContent } from '../../../public/pages/metrics/components/layout_c
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { MetadataDetails } from '../../../public/pages/metrics/components/metadata_details';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<MetadataDetails />
|
||||
<LayoutContent>
|
||||
|
@ -40,6 +40,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection id="containerOverview">
|
||||
<GaugesSectionVis
|
||||
|
|
|
@ -24,7 +24,7 @@ import { MetadataDetails } from '../../../public/pages/metrics/components/metada
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<MetadataDetails
|
||||
fields={[
|
||||
|
@ -52,6 +52,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection id="hostSystemOverview">
|
||||
<GaugesSectionVis
|
||||
|
@ -242,6 +243,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection id="hostK8sOverview">
|
||||
<GaugesSectionVis
|
||||
|
@ -371,8 +373,8 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
/>
|
||||
</SubSection>
|
||||
</Section>
|
||||
<Aws.Layout metrics={metrics} />
|
||||
<Ngnix.Layout metrics={metrics} />
|
||||
<Aws.Layout metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
|
||||
<Ngnix.Layout metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
|
||||
</LayoutContent>
|
||||
</React.Fragment>
|
||||
));
|
||||
|
|
|
@ -23,7 +23,7 @@ import { MetadataDetails } from '../../../public/pages/metrics/components/metada
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<MetadataDetails />
|
||||
<LayoutContent>
|
||||
|
@ -38,6 +38,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection id="podOverview">
|
||||
<GaugesSectionVis
|
||||
|
@ -161,7 +162,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
/>
|
||||
</SubSection>
|
||||
</Section>
|
||||
<Nginx.Layout metrics={metrics} />
|
||||
<Nginx.Layout metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
|
||||
</LayoutContent>
|
||||
</React.Fragment>
|
||||
));
|
||||
|
|
|
@ -18,7 +18,7 @@ import { ChartSectionVis } from '../../../../public/pages/metrics/components/cha
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { withTheme } from '../../../../../observability/public';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<Section
|
||||
navLabel="AWS"
|
||||
|
@ -29,6 +29,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
|||
}
|
||||
)}
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection id="awsOverview">
|
||||
<GaugesSectionVis
|
||||
|
|
|
@ -16,9 +16,14 @@ import { ChartSectionVis } from '../../../../public/pages/metrics/components/cha
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { withTheme } from '../../../../../observability/public';
|
||||
|
||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||
<React.Fragment>
|
||||
<Section navLabel="Nginx" sectionLabel="Nginx" metrics={metrics}>
|
||||
<Section
|
||||
navLabel="Nginx"
|
||||
sectionLabel="Nginx"
|
||||
metrics={metrics}
|
||||
onChangeRangeTime={onChangeRangeTime}
|
||||
>
|
||||
<SubSection
|
||||
id="nginxHits"
|
||||
label={i18n.translate(
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ElasticsearchMappingOf } from '../../server/utils/typed_elasticsearch_mappings';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { WaffleViewState } from '../../public/containers/waffle/with_waffle_view_state';
|
||||
import { WaffleViewState } from '../../public/pages/inventory_view/hooks/use_waffle_view_state';
|
||||
|
||||
export const inventoryViewSavedObjectType = 'inventory-view';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
|
@ -102,7 +102,7 @@ export const inventoryViewSavedObjectMappings: {
|
|||
type: 'boolean',
|
||||
},
|
||||
time: {
|
||||
type: 'integer',
|
||||
type: 'long',
|
||||
},
|
||||
autoReload: {
|
||||
type: 'boolean',
|
||||
|
@ -117,6 +117,12 @@ export const inventoryViewSavedObjectMappings: {
|
|||
},
|
||||
},
|
||||
},
|
||||
accountId: {
|
||||
type: 'keyword',
|
||||
},
|
||||
region: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,9 +8,6 @@ import { createBrowserHistory } from 'history';
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
import { CoreStart, AppMountParameters } from 'kibana/public';
|
||||
|
||||
// TODO use theme provided from parentApp when kibana supports it
|
||||
|
@ -18,9 +15,7 @@ import { EuiErrorBoundary } from '@elastic/eui';
|
|||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { EuiThemeProvider } from '../../../observability/public/typings/eui_styled_components';
|
||||
import { InfraFrontendLibs } from '../lib/lib';
|
||||
import { createStore } from '../store';
|
||||
import { ApolloClientContext } from '../utils/apollo_context';
|
||||
import { ReduxStateContextProvider } from '../utils/redux_context';
|
||||
import { HistoryContext } from '../utils/history_context';
|
||||
import {
|
||||
useUiSetting$,
|
||||
|
@ -43,12 +38,6 @@ export async function startApp(
|
|||
) {
|
||||
const { element, appBasePath } = params;
|
||||
const history = createBrowserHistory({ basename: appBasePath });
|
||||
const libs$ = new BehaviorSubject(libs);
|
||||
const store = createStore({
|
||||
apolloClient: libs$.pipe(pluck('apolloClient')),
|
||||
observableApi: libs$.pipe(pluck('observableApi')),
|
||||
});
|
||||
|
||||
const InfraPluginRoot: React.FunctionComponent = () => {
|
||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||
|
||||
|
@ -56,19 +45,15 @@ export async function startApp(
|
|||
<core.i18n.Context>
|
||||
<EuiErrorBoundary>
|
||||
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ReduxStateContextProvider>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<HistoryContext.Provider value={history}>
|
||||
<Router history={history} />
|
||||
</HistoryContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</ApolloProvider>
|
||||
</ReduxStateContextProvider>
|
||||
</ReduxStoreProvider>
|
||||
<ApolloProvider client={libs.apolloClient}>
|
||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<HistoryContext.Provider value={history}>
|
||||
<Router history={history} />
|
||||
</HistoryContext.Provider>
|
||||
</EuiThemeProvider>
|
||||
</ApolloClientContext.Provider>
|
||||
</ApolloProvider>
|
||||
</TriggersActionsProvider>
|
||||
</EuiErrorBoundary>
|
||||
</core.i18n.Context>
|
||||
|
|
|
@ -5,64 +5,89 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { InfraWaffleMapOptions, InfraWaffleMapBounds } from '../../lib/lib';
|
||||
import { KueryFilterQuery } from '../../store/local/waffle_filter';
|
||||
import { useInterval } from 'react-use';
|
||||
|
||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||
import { NodesOverview } from '../nodes_overview';
|
||||
import { Toolbar } from './toolbars/toolbar';
|
||||
import { PageContent } from '../page';
|
||||
import { useSnapshot } from '../../containers/waffle/use_snaphot';
|
||||
import { useInventoryMeta } from '../../containers/inventory_metadata/use_inventory_meta';
|
||||
import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time';
|
||||
import { useWaffleFiltersContext } from '../../pages/inventory_view/hooks/use_waffle_filters';
|
||||
import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options';
|
||||
import { useSourceContext } from '../../containers/source';
|
||||
import { InfraFormatterType, InfraWaffleMapGradientLegend } from '../../lib/lib';
|
||||
|
||||
export interface LayoutProps {
|
||||
options: InfraWaffleMapOptions;
|
||||
nodeType: InventoryItemType;
|
||||
onDrilldown: (filter: KueryFilterQuery) => void;
|
||||
currentTime: number;
|
||||
onViewChange: (view: string) => void;
|
||||
view: string;
|
||||
boundsOverride: InfraWaffleMapBounds;
|
||||
autoBounds: boolean;
|
||||
const euiVisColorPalette = euiPaletteColorBlind();
|
||||
|
||||
filterQuery: string | null | undefined;
|
||||
metric: SnapshotMetricInput;
|
||||
groupBy: SnapshotGroupBy;
|
||||
sourceId: string;
|
||||
accountId: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
export const Layout = (props: LayoutProps) => {
|
||||
const { accounts, regions } = useInventoryMeta(props.sourceId, props.nodeType);
|
||||
export const Layout = () => {
|
||||
const { sourceId, source } = useSourceContext();
|
||||
const {
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
accountId,
|
||||
region,
|
||||
changeView,
|
||||
view,
|
||||
autoBounds,
|
||||
boundsOverride,
|
||||
} = useWaffleOptionsContext();
|
||||
const { accounts, regions } = useInventoryMeta(sourceId, nodeType);
|
||||
const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext();
|
||||
const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext();
|
||||
const { loading, nodes, reload, interval } = useSnapshot(
|
||||
props.filterQuery,
|
||||
props.metric,
|
||||
props.groupBy,
|
||||
props.nodeType,
|
||||
props.sourceId,
|
||||
props.currentTime,
|
||||
props.accountId,
|
||||
props.region
|
||||
filterQueryAsJson,
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
sourceId,
|
||||
currentTime,
|
||||
accountId,
|
||||
region
|
||||
);
|
||||
|
||||
const options = {
|
||||
formatter: InfraFormatterType.percent,
|
||||
formatTemplate: '{{value}}',
|
||||
legend: {
|
||||
type: 'gradient',
|
||||
rules: [
|
||||
{ value: 0, color: '#D3DAE6' },
|
||||
{ value: 1, color: euiVisColorPalette[1] },
|
||||
],
|
||||
} as InfraWaffleMapGradientLegend,
|
||||
metric,
|
||||
fields: source?.configuration?.fields,
|
||||
groupBy,
|
||||
};
|
||||
|
||||
useInterval(
|
||||
() => {
|
||||
if (!loading) {
|
||||
jumpToTime(Date.now());
|
||||
}
|
||||
},
|
||||
isAutoReloading ? 5000 : null
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar accounts={accounts} regions={regions} nodeType={props.nodeType} />
|
||||
<Toolbar accounts={accounts} regions={regions} nodeType={nodeType} />
|
||||
<PageContent>
|
||||
<NodesOverview
|
||||
nodes={nodes}
|
||||
options={props.options}
|
||||
nodeType={props.nodeType}
|
||||
options={options}
|
||||
nodeType={nodeType}
|
||||
loading={loading}
|
||||
reload={reload}
|
||||
onDrilldown={props.onDrilldown}
|
||||
currentTime={props.currentTime}
|
||||
onViewChange={props.onViewChange}
|
||||
view={props.view}
|
||||
autoBounds={props.autoBounds}
|
||||
boundsOverride={props.boundsOverride}
|
||||
onDrilldown={applyFilterQuery}
|
||||
currentTime={currentTime}
|
||||
onViewChange={changeView}
|
||||
view={view}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
interval={interval}
|
||||
/>
|
||||
</PageContent>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SavedViewsToolbarControls } from '../../saved_views/toolbar_control';
|
||||
import { inventoryViewSavedObjectType } from '../../../../common/saved_objects/inventory_view';
|
||||
import { useWaffleViewState } from '../../../pages/inventory_view/hooks/use_waffle_view_state';
|
||||
|
||||
export const SavedViews = () => {
|
||||
const { viewState, defaultViewState, onViewChange } = useWaffleViewState();
|
||||
return (
|
||||
<SavedViewsToolbarControls
|
||||
defaultViewState={defaultViewState}
|
||||
viewState={viewState}
|
||||
onViewChange={onViewChange}
|
||||
viewType={inventoryViewSavedObjectType}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Action } from 'typescript-fsa';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
|
@ -16,33 +15,23 @@ import { InventoryCloudAccount } from '../../../../common/http_api/inventory_met
|
|||
import { findToolbar } from '../../../../common/inventory_models/toolbars';
|
||||
import { ToolbarWrapper } from './toolbar_wrapper';
|
||||
|
||||
import { waffleOptionsSelectors } from '../../../store';
|
||||
import { InfraGroupByOptions } from '../../../lib/lib';
|
||||
import { WithWaffleViewState } from '../../../containers/waffle/with_waffle_view_state';
|
||||
import { SavedViewsToolbarControls } from '../../saved_views/toolbar_control';
|
||||
import { inventoryViewSavedObjectType } from '../../../../common/saved_objects/inventory_view';
|
||||
import { IIndexPattern } from '../../../../../../../src/plugins/data/public';
|
||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
||||
import { WaffleOptionsState } from '../../../pages/inventory_view/hooks/use_waffle_options';
|
||||
import { SavedViews } from './save_views';
|
||||
|
||||
export interface ToolbarProps {
|
||||
export interface ToolbarProps
|
||||
extends Omit<WaffleOptionsState, 'view' | 'boundsOverride' | 'autoBounds'> {
|
||||
createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern;
|
||||
changeMetric: (payload: SnapshotMetricInput) => Action<SnapshotMetricInput>;
|
||||
changeGroupBy: (payload: SnapshotGroupBy) => Action<SnapshotGroupBy>;
|
||||
changeCustomOptions: (payload: InfraGroupByOptions[]) => Action<InfraGroupByOptions[]>;
|
||||
changeAccount: (id: string) => Action<string>;
|
||||
changeRegion: (name: string) => Action<string>;
|
||||
customOptions: ReturnType<typeof waffleOptionsSelectors.selectCustomOptions>;
|
||||
groupBy: ReturnType<typeof waffleOptionsSelectors.selectGroupBy>;
|
||||
metric: ReturnType<typeof waffleOptionsSelectors.selectMetric>;
|
||||
nodeType: ReturnType<typeof waffleOptionsSelectors.selectNodeType>;
|
||||
accountId: ReturnType<typeof waffleOptionsSelectors.selectAccountId>;
|
||||
region: ReturnType<typeof waffleOptionsSelectors.selectRegion>;
|
||||
changeMetric: (payload: SnapshotMetricInput) => void;
|
||||
changeGroupBy: (payload: SnapshotGroupBy) => void;
|
||||
changeCustomOptions: (payload: InfraGroupByOptions[]) => void;
|
||||
changeAccount: (id: string) => void;
|
||||
changeRegion: (name: string) => void;
|
||||
accounts: InventoryCloudAccount[];
|
||||
regions: string[];
|
||||
customMetrics: ReturnType<typeof waffleOptionsSelectors.selectCustomMetrics>;
|
||||
changeCustomMetrics: (
|
||||
payload: SnapshotCustomMetricInput[]
|
||||
) => Action<SnapshotCustomMetricInput[]>;
|
||||
changeCustomMetrics: (payload: SnapshotCustomMetricInput[]) => void;
|
||||
}
|
||||
|
||||
const wrapToolbarItems = (
|
||||
|
@ -57,16 +46,7 @@ const wrapToolbarItems = (
|
|||
<ToolbarItems {...props} accounts={accounts} regions={regions} />
|
||||
<EuiFlexItem grow={true} />
|
||||
<EuiFlexItem grow={false}>
|
||||
<WithWaffleViewState indexPattern={props.createDerivedIndexPattern('metrics')}>
|
||||
{({ defaultViewState, viewState, onViewChange }) => (
|
||||
<SavedViewsToolbarControls
|
||||
defaultViewState={defaultViewState}
|
||||
viewState={viewState}
|
||||
onViewChange={onViewChange}
|
||||
viewType={inventoryViewSavedObjectType}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleViewState>
|
||||
<SavedViews />
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -8,58 +8,52 @@ import React from 'react';
|
|||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { WithSource } from '../../../containers/with_source';
|
||||
import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options';
|
||||
import { Toolbar } from '../../eui/toolbar';
|
||||
import { ToolbarProps } from './toolbar';
|
||||
import { fieldToName } from '../../waffle/lib/field_to_display_name';
|
||||
import { useSourceContext } from '../../../containers/source';
|
||||
import { useWaffleOptionsContext } from '../../../pages/inventory_view/hooks/use_waffle_options';
|
||||
|
||||
interface Props {
|
||||
children: (props: Omit<ToolbarProps, 'accounts' | 'regions'>) => React.ReactElement;
|
||||
}
|
||||
|
||||
export const ToolbarWrapper = (props: Props) => {
|
||||
const {
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeCustomOptions,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
customOptions,
|
||||
groupBy,
|
||||
metric,
|
||||
nodeType,
|
||||
accountId,
|
||||
region,
|
||||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
} = useWaffleOptionsContext();
|
||||
const { createDerivedIndexPattern } = useSourceContext();
|
||||
return (
|
||||
<Toolbar>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<WithSource>
|
||||
{({ createDerivedIndexPattern }) => (
|
||||
<WithWaffleOptions>
|
||||
{({
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeCustomOptions,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
customOptions,
|
||||
groupBy,
|
||||
metric,
|
||||
nodeType,
|
||||
accountId,
|
||||
region,
|
||||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
}) =>
|
||||
props.children({
|
||||
createDerivedIndexPattern,
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomOptions,
|
||||
customOptions,
|
||||
groupBy,
|
||||
metric,
|
||||
nodeType,
|
||||
region,
|
||||
accountId,
|
||||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
})
|
||||
}
|
||||
</WithWaffleOptions>
|
||||
)}
|
||||
</WithSource>
|
||||
{props.children({
|
||||
createDerivedIndexPattern,
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomOptions,
|
||||
customOptions,
|
||||
groupBy,
|
||||
metric,
|
||||
nodeType,
|
||||
region,
|
||||
accountId,
|
||||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
);
|
||||
|
|
|
@ -12,7 +12,6 @@ import React from 'react';
|
|||
|
||||
import { euiStyled } from '../../../../observability/public';
|
||||
import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib';
|
||||
import { KueryFilterQuery } from '../../store/local/waffle_filter';
|
||||
import { createFormatter } from '../../utils/formatters';
|
||||
import { NoData } from '../empty_states';
|
||||
import { InfraLoadingPanel } from '../loading';
|
||||
|
@ -24,6 +23,11 @@ import { convertIntervalToString } from '../../utils/convert_interval_to_string'
|
|||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { createFormatterForMetric } from '../metrics_explorer/helpers/create_formatter_for_metric';
|
||||
|
||||
export interface KueryFilterQuery {
|
||||
kind: 'kuery';
|
||||
expression: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
options: InfraWaffleMapOptions;
|
||||
nodeType: InventoryItemType;
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import { euiStyled } from '../../../../observability/public';
|
||||
import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options';
|
||||
import { InfraFormatter, InfraWaffleMapBounds, InfraWaffleMapLegend } from '../../lib/lib';
|
||||
import { GradientLegend } from './gradient_legend';
|
||||
import { LegendControls } from './legend_controls';
|
||||
import { isInfraWaffleMapGradientLegend, isInfraWaffleMapStepLegend } from './lib/type_guards';
|
||||
import { StepLegend } from './steps_legend';
|
||||
import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options';
|
||||
interface Props {
|
||||
legend: InfraWaffleMapLegend;
|
||||
bounds: InfraWaffleMapBounds;
|
||||
|
@ -25,22 +25,24 @@ interface LegendControlOptions {
|
|||
}
|
||||
|
||||
export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter }) => {
|
||||
const {
|
||||
changeBoundsOverride,
|
||||
changeAutoBounds,
|
||||
autoBounds,
|
||||
boundsOverride,
|
||||
} = useWaffleOptionsContext();
|
||||
return (
|
||||
<LegendContainer>
|
||||
<WithWaffleOptions>
|
||||
{({ changeBoundsOverride, changeAutoBounds, autoBounds, boundsOverride }) => (
|
||||
<LegendControls
|
||||
dataBounds={dataBounds}
|
||||
bounds={bounds}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
onChange={(options: LegendControlOptions) => {
|
||||
changeBoundsOverride(options.bounds);
|
||||
changeAutoBounds(options.auto);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleOptions>
|
||||
<LegendControls
|
||||
dataBounds={dataBounds}
|
||||
bounds={bounds}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
onChange={(options: LegendControlOptions) => {
|
||||
changeBoundsOverride(options.bounds);
|
||||
changeAutoBounds(options.auto);
|
||||
}}
|
||||
/>
|
||||
{isInfraWaffleMapGradientLegend(legend) && (
|
||||
<GradientLegend formatter={formatter} legend={legend} bounds={bounds} />
|
||||
)}
|
||||
|
|
|
@ -5,11 +5,7 @@
|
|||
*/
|
||||
|
||||
import { createUptimeLink } from './create_uptime_link';
|
||||
import {
|
||||
InfraWaffleMapOptions,
|
||||
InfraWaffleMapLegendMode,
|
||||
InfraFormatterType,
|
||||
} from '../../../lib/lib';
|
||||
import { InfraWaffleMapOptions, InfraFormatterType } from '../../../lib/lib';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
|
||||
const options: InfraWaffleMapOptions = {
|
||||
|
@ -26,7 +22,7 @@ const options: InfraWaffleMapOptions = {
|
|||
metric: { type: 'cpu' },
|
||||
groupBy: [],
|
||||
legend: {
|
||||
type: InfraWaffleMapLegendMode.gradient,
|
||||
type: 'gradient',
|
||||
rules: [],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
InfraWaffleMapGradientLegend,
|
||||
InfraWaffleMapLegendMode,
|
||||
InfraWaffleMapStepLegend,
|
||||
} from '../../../lib/lib';
|
||||
import { InfraWaffleMapGradientLegend, InfraWaffleMapStepLegend } from '../../../lib/lib';
|
||||
|
||||
export function isInfraWaffleMapStepLegend(subject: any): subject is InfraWaffleMapStepLegend {
|
||||
return subject.type && subject.type === InfraWaffleMapLegendMode.step;
|
||||
return subject.type && subject.type === 'step';
|
||||
}
|
||||
|
||||
export function isInfraWaffleMapGradientLegend(
|
||||
subject: any
|
||||
): subject is InfraWaffleMapGradientLegend {
|
||||
return subject.type && subject.type === InfraWaffleMapLegendMode.gradient;
|
||||
return subject.type && subject.type === 'gradient';
|
||||
}
|
||||
|
|
|
@ -16,36 +16,23 @@ import React, { useCallback, useState, useMemo } from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { findInventoryModel } from '../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotGroupBy,
|
||||
SnapshotCustomMetricInput,
|
||||
} from '../../../common/http_api/snapshot_api';
|
||||
|
||||
interface WaffleInventorySwitcherProps {
|
||||
nodeType: InventoryItemType;
|
||||
changeNodeType: (nodeType: InventoryItemType) => void;
|
||||
changeGroupBy: (groupBy: SnapshotGroupBy) => void;
|
||||
changeMetric: (metric: SnapshotMetricInput) => void;
|
||||
changeCustomMetrics: (metrics: SnapshotCustomMetricInput[]) => void;
|
||||
changeAccount: (id: string) => void;
|
||||
changeRegion: (name: string) => void;
|
||||
}
|
||||
import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options';
|
||||
|
||||
const getDisplayNameForType = (type: InventoryItemType) => {
|
||||
const inventoryModel = findInventoryModel(type);
|
||||
return inventoryModel.displayName;
|
||||
};
|
||||
|
||||
export const WaffleInventorySwitcher: React.FC<WaffleInventorySwitcherProps> = ({
|
||||
changeNodeType,
|
||||
changeGroupBy,
|
||||
changeMetric,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomMetrics,
|
||||
nodeType,
|
||||
}) => {
|
||||
export const WaffleInventorySwitcher: React.FC = () => {
|
||||
const {
|
||||
changeNodeType,
|
||||
changeGroupBy,
|
||||
changeMetric,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomMetrics,
|
||||
nodeType,
|
||||
} = useWaffleOptionsContext();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const closePopover = useCallback(() => setIsOpen(false), []);
|
||||
const openPopover = useCallback(() => setIsOpen(true), []);
|
||||
|
|
|
@ -7,84 +7,60 @@
|
|||
import { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import React from 'react';
|
||||
import { Action } from 'typescript-fsa';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time';
|
||||
|
||||
interface WaffleTimeControlsProps {
|
||||
currentTime: number;
|
||||
isLiveStreaming?: boolean;
|
||||
onChangeTime?: (time: number) => void;
|
||||
startLiveStreaming?: (payload: void) => Action<void>;
|
||||
stopLiveStreaming?: (payload: void) => Action<void>;
|
||||
}
|
||||
export const WaffleTimeControls = () => {
|
||||
const {
|
||||
currentTime,
|
||||
isAutoReloading,
|
||||
startAutoReload,
|
||||
stopAutoReload,
|
||||
jumpToTime,
|
||||
} = useWaffleTimeContext();
|
||||
|
||||
export class WaffleTimeControls extends React.Component<WaffleTimeControlsProps> {
|
||||
public render() {
|
||||
const { currentTime, isLiveStreaming } = this.props;
|
||||
const currentMoment = moment(currentTime);
|
||||
|
||||
const currentMoment = moment(currentTime);
|
||||
const liveStreamingButton = isAutoReloading ? (
|
||||
<EuiButtonEmpty color="primary" iconSide="left" iconType="pause" onClick={stopAutoReload}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffleTime.stopRefreshingButtonLabel"
|
||||
defaultMessage="Stop refreshing"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButtonEmpty iconSide="left" iconType="play" onClick={startAutoReload}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffleTime.autoRefreshButtonLabel"
|
||||
defaultMessage="Auto-refresh"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
const liveStreamingButton = isLiveStreaming ? (
|
||||
<EuiButtonEmpty
|
||||
color="primary"
|
||||
iconSide="left"
|
||||
iconType="pause"
|
||||
onClick={this.stopLiveStreaming}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffleTime.stopRefreshingButtonLabel"
|
||||
defaultMessage="Stop refreshing"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButtonEmpty iconSide="left" iconType="play" onClick={this.startLiveStreaming}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffleTime.autoRefreshButtonLabel"
|
||||
defaultMessage="Auto-refresh"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
const handleChangeDate = useCallback(
|
||||
(time: Moment | null) => {
|
||||
if (time) {
|
||||
jumpToTime(time.valueOf());
|
||||
}
|
||||
},
|
||||
[jumpToTime]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFormControlLayout append={liveStreamingButton} data-test-subj="waffleDatePicker">
|
||||
<EuiDatePicker
|
||||
className="euiFieldText--inGroup"
|
||||
dateFormat="L LTS"
|
||||
disabled={isLiveStreaming}
|
||||
injectTimes={currentMoment ? [currentMoment] : []}
|
||||
isLoading={isLiveStreaming}
|
||||
onChange={this.handleChangeDate}
|
||||
popperPlacement="top-end"
|
||||
selected={currentMoment}
|
||||
shouldCloseOnSelect
|
||||
showTimeSelect
|
||||
timeFormat="LT"
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
);
|
||||
}
|
||||
|
||||
private handleChangeDate = (time: Moment | null) => {
|
||||
const { onChangeTime } = this.props;
|
||||
|
||||
if (onChangeTime && time) {
|
||||
onChangeTime(time.valueOf());
|
||||
}
|
||||
};
|
||||
|
||||
private startLiveStreaming = () => {
|
||||
const { startLiveStreaming } = this.props;
|
||||
|
||||
if (startLiveStreaming) {
|
||||
startLiveStreaming();
|
||||
}
|
||||
};
|
||||
|
||||
private stopLiveStreaming = () => {
|
||||
const { stopLiveStreaming } = this.props;
|
||||
|
||||
if (stopLiveStreaming) {
|
||||
stopLiveStreaming();
|
||||
}
|
||||
};
|
||||
}
|
||||
return (
|
||||
<EuiFormControlLayout append={liveStreamingButton} data-test-subj="waffleDatePicker">
|
||||
<EuiDatePicker
|
||||
className="euiFieldText--inGroup"
|
||||
dateFormat="L LTS"
|
||||
disabled={isAutoReloading}
|
||||
injectTimes={currentMoment ? [currentMoment] : []}
|
||||
isLoading={isAutoReloading}
|
||||
onChange={handleChangeDate}
|
||||
popperPlacement="top-end"
|
||||
selected={currentMoment}
|
||||
shouldCloseOnSelect
|
||||
showTimeSelect
|
||||
timeFormat="LT"
|
||||
/>
|
||||
</EuiFormControlLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,17 +13,18 @@ import { useHTTPRequest } from '../../hooks/use_http_request';
|
|||
import { throwErrors, createPlainError } from '../../../common/runtime_types';
|
||||
import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { getFilteredMetrics } from './lib/get_filtered_metrics';
|
||||
import { MetricsTimeInput } from '../../pages/metrics/hooks/use_metrics_time';
|
||||
|
||||
export function useMetadata(
|
||||
nodeId: string,
|
||||
nodeType: InventoryItemType,
|
||||
requiredMetrics: InventoryMetric[],
|
||||
sourceId: string
|
||||
sourceId: string,
|
||||
timeRange: MetricsTimeInput
|
||||
) {
|
||||
const decodeResponse = (response: any) => {
|
||||
return pipe(InfraMetadataRT.decode(response), fold(throwErrors(createPlainError), identity));
|
||||
};
|
||||
|
||||
const { error, loading, response, makeRequest } = useHTTPRequest<InfraMetadata>(
|
||||
'/api/infra/metadata',
|
||||
'POST',
|
||||
|
@ -31,6 +32,7 @@ export function useMetadata(
|
|||
nodeId,
|
||||
nodeType,
|
||||
sourceId,
|
||||
timeRange: { from: timeRange.from, to: timeRange.to },
|
||||
}),
|
||||
decodeResponse
|
||||
);
|
||||
|
|
|
@ -1,7 +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.
|
||||
*/
|
||||
|
||||
export * from './with_waffle_filters';
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const waffleNodesQuery = gql`
|
||||
query WaffleNodesQuery(
|
||||
$sourceId: ID!
|
||||
$timerange: InfraTimerangeInput!
|
||||
$filterQuery: String
|
||||
$metric: InfraSnapshotMetricInput!
|
||||
$groupBy: [InfraSnapshotGroupbyInput!]!
|
||||
$type: InfraNodeType!
|
||||
) {
|
||||
source(id: $sourceId) {
|
||||
id
|
||||
snapshot(timerange: $timerange, filterQuery: $filterQuery) {
|
||||
nodes(groupBy: $groupBy, metric: $metric, type: $type) {
|
||||
path {
|
||||
value
|
||||
label
|
||||
ip
|
||||
}
|
||||
metric {
|
||||
name
|
||||
value
|
||||
avg
|
||||
max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IIndexPattern } from 'src/plugins/data/public';
|
||||
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: IIndexPattern;
|
||||
}
|
||||
|
||||
export const withWaffleFilter = connect(
|
||||
(state: State) => ({
|
||||
filterQuery: waffleFilterSelectors.selectWaffleFilterQuery(state),
|
||||
filterQueryDraft: waffleFilterSelectors.selectWaffleFilterQueryDraft(state),
|
||||
filterQueryAsJson: waffleFilterSelectors.selectWaffleFilterQueryAsJson(state),
|
||||
isFilterQueryDraftValid: waffleFilterSelectors.selectIsWaffleFilterQueryDraftValid(state),
|
||||
}),
|
||||
(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);
|
||||
|
||||
/**
|
||||
* Url State
|
||||
*/
|
||||
|
||||
type WaffleFilterUrlState = ReturnType<typeof waffleFilterSelectors.selectWaffleFilterQuery>;
|
||||
|
||||
type WithWaffleFilterUrlStateProps = WithWaffleFilterProps;
|
||||
|
||||
export const WithWaffleFilterUrlState: React.FC<WithWaffleFilterUrlStateProps> = ({
|
||||
indexPattern,
|
||||
}) => (
|
||||
<WithWaffleFilter indexPattern={indexPattern}>
|
||||
{({ applyFilterQuery, filterQuery }) => (
|
||||
<UrlStateContainer
|
||||
urlState={filterQuery}
|
||||
urlStateKey="waffleFilter"
|
||||
mapToUrlState={mapToUrlState}
|
||||
onChange={urlState => {
|
||||
if (urlState) {
|
||||
applyFilterQuery(urlState);
|
||||
}
|
||||
}}
|
||||
onInitialize={urlState => {
|
||||
if (urlState) {
|
||||
applyFilterQuery(urlState);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleFilter>
|
||||
);
|
||||
|
||||
const mapToUrlState = (value: any): WaffleFilterUrlState | undefined =>
|
||||
value && value.kind === 'kuery' && typeof value.expression === 'string'
|
||||
? {
|
||||
kind: value.kind,
|
||||
expression: value.expression,
|
||||
}
|
||||
: undefined;
|
|
@ -1,265 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { isBoolean, isNumber } from 'lodash';
|
||||
import { InfraGroupByOptions } from '../../lib/lib';
|
||||
import { State, waffleOptionsActions, waffleOptionsSelectors } from '../../store';
|
||||
import { asChildFunctionRenderer } from '../../utils/typed_react';
|
||||
import { bindPlainActionCreators } from '../../utils/typed_redux';
|
||||
import { UrlStateContainer } from '../../utils/url_state';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotGroupBy,
|
||||
SnapshotCustomMetricInputRT,
|
||||
} from '../../../common/http_api/snapshot_api';
|
||||
import {
|
||||
SnapshotMetricTypeRT,
|
||||
InventoryItemType,
|
||||
ItemTypeRT,
|
||||
} from '../../../common/inventory_models/types';
|
||||
|
||||
const selectOptionsUrlState = createSelector(
|
||||
waffleOptionsSelectors.selectMetric,
|
||||
waffleOptionsSelectors.selectView,
|
||||
waffleOptionsSelectors.selectGroupBy,
|
||||
waffleOptionsSelectors.selectNodeType,
|
||||
waffleOptionsSelectors.selectCustomOptions,
|
||||
waffleOptionsSelectors.selectBoundsOverride,
|
||||
waffleOptionsSelectors.selectAutoBounds,
|
||||
waffleOptionsSelectors.selectAccountId,
|
||||
waffleOptionsSelectors.selectRegion,
|
||||
waffleOptionsSelectors.selectCustomMetrics,
|
||||
(
|
||||
metric,
|
||||
view,
|
||||
groupBy,
|
||||
nodeType,
|
||||
customOptions,
|
||||
boundsOverride,
|
||||
autoBounds,
|
||||
accountId,
|
||||
region,
|
||||
customMetrics
|
||||
) => ({
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
view,
|
||||
customOptions,
|
||||
boundsOverride,
|
||||
autoBounds,
|
||||
accountId,
|
||||
region,
|
||||
customMetrics,
|
||||
})
|
||||
);
|
||||
|
||||
export const withWaffleOptions = connect(
|
||||
(state: State) => ({
|
||||
metric: waffleOptionsSelectors.selectMetric(state),
|
||||
groupBy: waffleOptionsSelectors.selectGroupBy(state),
|
||||
nodeType: waffleOptionsSelectors.selectNodeType(state),
|
||||
view: waffleOptionsSelectors.selectView(state),
|
||||
customOptions: waffleOptionsSelectors.selectCustomOptions(state),
|
||||
boundsOverride: waffleOptionsSelectors.selectBoundsOverride(state),
|
||||
autoBounds: waffleOptionsSelectors.selectAutoBounds(state),
|
||||
accountId: waffleOptionsSelectors.selectAccountId(state),
|
||||
region: waffleOptionsSelectors.selectRegion(state),
|
||||
urlState: selectOptionsUrlState(state),
|
||||
customMetrics: waffleOptionsSelectors.selectCustomMetrics(state),
|
||||
}),
|
||||
bindPlainActionCreators({
|
||||
changeMetric: waffleOptionsActions.changeMetric,
|
||||
changeGroupBy: waffleOptionsActions.changeGroupBy,
|
||||
changeNodeType: waffleOptionsActions.changeNodeType,
|
||||
changeView: waffleOptionsActions.changeView,
|
||||
changeCustomOptions: waffleOptionsActions.changeCustomOptions,
|
||||
changeBoundsOverride: waffleOptionsActions.changeBoundsOverride,
|
||||
changeAutoBounds: waffleOptionsActions.changeAutoBounds,
|
||||
changeAccount: waffleOptionsActions.changeAccount,
|
||||
changeRegion: waffleOptionsActions.changeRegion,
|
||||
changeCustomMetrics: waffleOptionsActions.changeCustomMetrics,
|
||||
})
|
||||
);
|
||||
|
||||
export const WithWaffleOptions = asChildFunctionRenderer(withWaffleOptions);
|
||||
|
||||
/**
|
||||
* Url State
|
||||
*/
|
||||
|
||||
interface WaffleOptionsUrlState {
|
||||
metric?: ReturnType<typeof waffleOptionsSelectors.selectMetric>;
|
||||
groupBy?: ReturnType<typeof waffleOptionsSelectors.selectGroupBy>;
|
||||
nodeType?: ReturnType<typeof waffleOptionsSelectors.selectNodeType>;
|
||||
view?: ReturnType<typeof waffleOptionsSelectors.selectView>;
|
||||
customOptions?: ReturnType<typeof waffleOptionsSelectors.selectCustomOptions>;
|
||||
bounds?: ReturnType<typeof waffleOptionsSelectors.selectBoundsOverride>;
|
||||
auto?: ReturnType<typeof waffleOptionsSelectors.selectAutoBounds>;
|
||||
accountId?: ReturnType<typeof waffleOptionsSelectors.selectAccountId>;
|
||||
region?: ReturnType<typeof waffleOptionsSelectors.selectRegion>;
|
||||
customMetrics?: ReturnType<typeof waffleOptionsSelectors.selectCustomMetrics>;
|
||||
}
|
||||
|
||||
export const WithWaffleOptionsUrlState = () => (
|
||||
<WithWaffleOptions>
|
||||
{({
|
||||
changeMetric,
|
||||
urlState,
|
||||
changeGroupBy,
|
||||
changeNodeType,
|
||||
changeView,
|
||||
changeCustomOptions,
|
||||
changeAutoBounds,
|
||||
changeBoundsOverride,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomMetrics,
|
||||
}) => (
|
||||
<UrlStateContainer<WaffleOptionsUrlState>
|
||||
urlState={urlState}
|
||||
urlStateKey="waffleOptions"
|
||||
mapToUrlState={mapToUrlState}
|
||||
onChange={newUrlState => {
|
||||
if (newUrlState && newUrlState.metric) {
|
||||
changeMetric(newUrlState.metric);
|
||||
}
|
||||
if (newUrlState && newUrlState.groupBy) {
|
||||
changeGroupBy(newUrlState.groupBy);
|
||||
}
|
||||
if (newUrlState && newUrlState.nodeType) {
|
||||
changeNodeType(newUrlState.nodeType);
|
||||
}
|
||||
if (newUrlState && newUrlState.view) {
|
||||
changeView(newUrlState.view);
|
||||
}
|
||||
if (newUrlState && newUrlState.customOptions) {
|
||||
changeCustomOptions(newUrlState.customOptions);
|
||||
}
|
||||
if (newUrlState && newUrlState.bounds) {
|
||||
changeBoundsOverride(newUrlState.bounds);
|
||||
}
|
||||
if (newUrlState && newUrlState.auto) {
|
||||
changeAutoBounds(newUrlState.auto);
|
||||
}
|
||||
if (newUrlState && newUrlState.accountId) {
|
||||
changeAccount(newUrlState.accountId);
|
||||
}
|
||||
if (newUrlState && newUrlState.region) {
|
||||
changeRegion(newUrlState.region);
|
||||
}
|
||||
if (newUrlState && newUrlState.customMetrics) {
|
||||
changeCustomMetrics(newUrlState.customMetrics);
|
||||
}
|
||||
}}
|
||||
onInitialize={initialUrlState => {
|
||||
if (initialUrlState && initialUrlState.metric) {
|
||||
changeMetric(initialUrlState.metric);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.groupBy) {
|
||||
changeGroupBy(initialUrlState.groupBy);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.nodeType) {
|
||||
changeNodeType(initialUrlState.nodeType);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.view) {
|
||||
changeView(initialUrlState.view);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.customOptions) {
|
||||
changeCustomOptions(initialUrlState.customOptions);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.bounds) {
|
||||
changeBoundsOverride(initialUrlState.bounds);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.auto) {
|
||||
changeAutoBounds(initialUrlState.auto);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.accountId) {
|
||||
changeAccount(initialUrlState.accountId);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.region) {
|
||||
changeRegion(initialUrlState.region);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.customMetrics) {
|
||||
changeCustomMetrics(initialUrlState.customMetrics);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleOptions>
|
||||
);
|
||||
|
||||
const mapToUrlState = (value: any): WaffleOptionsUrlState | undefined =>
|
||||
value
|
||||
? {
|
||||
metric: mapToMetricUrlState(value.metric),
|
||||
groupBy: mapToGroupByUrlState(value.groupBy),
|
||||
nodeType: mapToNodeTypeUrlState(value.nodeType),
|
||||
view: mapToViewUrlState(value.view),
|
||||
customOptions: mapToCustomOptionsUrlState(value.customOptions),
|
||||
bounds: mapToBoundsOverideUrlState(value.boundsOverride),
|
||||
auto: mapToAutoBoundsUrlState(value.autoBounds),
|
||||
accountId: value.accountId,
|
||||
region: value.region,
|
||||
customMetrics: mapToCustomMetricsUrlState(value.customMetrics),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const isInfraNodeType = (value: any): value is InventoryItemType => value in ItemTypeRT;
|
||||
|
||||
const isInfraSnapshotMetricInput = (subject: any): subject is SnapshotMetricInput => {
|
||||
return subject != null && subject.type in SnapshotMetricTypeRT;
|
||||
};
|
||||
|
||||
const isInfraSnapshotGroupbyInput = (subject: any): subject is SnapshotGroupBy => {
|
||||
return subject != null && subject.type != null;
|
||||
};
|
||||
|
||||
const isInfraGroupByOption = (subject: any): subject is InfraGroupByOptions => {
|
||||
return subject != null && subject.text != null && subject.field != null;
|
||||
};
|
||||
|
||||
const mapToMetricUrlState = (subject: any) => {
|
||||
return subject && isInfraSnapshotMetricInput(subject) ? subject : undefined;
|
||||
};
|
||||
|
||||
const mapToGroupByUrlState = (subject: any) => {
|
||||
return subject && Array.isArray(subject) && subject.every(isInfraSnapshotGroupbyInput)
|
||||
? subject
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const mapToNodeTypeUrlState = (subject: any) => {
|
||||
return isInfraNodeType(subject) ? subject : undefined;
|
||||
};
|
||||
|
||||
const mapToViewUrlState = (subject: any) => {
|
||||
return subject && ['map', 'table'].includes(subject) ? subject : undefined;
|
||||
};
|
||||
|
||||
const mapToCustomOptionsUrlState = (subject: any) => {
|
||||
return subject && Array.isArray(subject) && subject.every(isInfraGroupByOption)
|
||||
? subject
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const mapToCustomMetricsUrlState = (subject: any) => {
|
||||
return subject && Array.isArray(subject) && subject.every(s => SnapshotCustomMetricInputRT.is(s))
|
||||
? subject
|
||||
: [];
|
||||
};
|
||||
|
||||
const mapToBoundsOverideUrlState = (subject: any) => {
|
||||
return subject != null && isNumber(subject.max) && isNumber(subject.min) ? subject : undefined;
|
||||
};
|
||||
|
||||
const mapToAutoBoundsUrlState = (subject: any) => {
|
||||
return subject != null && isBoolean(subject) ? subject : undefined;
|
||||
};
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { State, waffleTimeActions, waffleTimeSelectors } from '../../store';
|
||||
import { asChildFunctionRenderer } from '../../utils/typed_react';
|
||||
import { bindPlainActionCreators } from '../../utils/typed_redux';
|
||||
import { UrlStateContainer } from '../../utils/url_state';
|
||||
|
||||
export const withWaffleTime = connect(
|
||||
(state: State) => ({
|
||||
currentTime: waffleTimeSelectors.selectCurrentTime(state),
|
||||
currentTimeRange: waffleTimeSelectors.selectCurrentTimeRange(state),
|
||||
isAutoReloading: waffleTimeSelectors.selectIsAutoReloading(state),
|
||||
urlState: selectTimeUrlState(state),
|
||||
}),
|
||||
bindPlainActionCreators({
|
||||
jumpToTime: waffleTimeActions.jumpToTime,
|
||||
startAutoReload: waffleTimeActions.startAutoReload,
|
||||
stopAutoReload: waffleTimeActions.stopAutoReload,
|
||||
})
|
||||
);
|
||||
|
||||
export const WithWaffleTime = asChildFunctionRenderer(withWaffleTime, {
|
||||
onCleanup: ({ stopAutoReload }) => stopAutoReload(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Url State
|
||||
*/
|
||||
|
||||
interface WaffleTimeUrlState {
|
||||
time?: ReturnType<typeof waffleTimeSelectors.selectCurrentTime>;
|
||||
autoReload?: ReturnType<typeof waffleTimeSelectors.selectIsAutoReloading>;
|
||||
}
|
||||
|
||||
export const WithWaffleTimeUrlState = () => (
|
||||
<WithWaffleTime>
|
||||
{({ jumpToTime, startAutoReload, stopAutoReload, urlState }) => (
|
||||
<UrlStateContainer
|
||||
urlState={urlState}
|
||||
urlStateKey="waffleTime"
|
||||
mapToUrlState={mapToUrlState}
|
||||
onChange={newUrlState => {
|
||||
if (newUrlState && newUrlState.time) {
|
||||
jumpToTime(newUrlState.time);
|
||||
}
|
||||
if (newUrlState && newUrlState.autoReload) {
|
||||
startAutoReload();
|
||||
} else if (
|
||||
newUrlState &&
|
||||
typeof newUrlState.autoReload !== 'undefined' &&
|
||||
!newUrlState.autoReload
|
||||
) {
|
||||
stopAutoReload();
|
||||
}
|
||||
}}
|
||||
onInitialize={initialUrlState => {
|
||||
if (initialUrlState) {
|
||||
jumpToTime(initialUrlState.time ? initialUrlState.time : Date.now());
|
||||
}
|
||||
if (initialUrlState && initialUrlState.autoReload) {
|
||||
startAutoReload();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleTime>
|
||||
);
|
||||
|
||||
const selectTimeUrlState = createSelector(
|
||||
waffleTimeSelectors.selectCurrentTime,
|
||||
waffleTimeSelectors.selectIsAutoReloading,
|
||||
(time, autoReload) => ({
|
||||
time,
|
||||
autoReload,
|
||||
})
|
||||
);
|
||||
|
||||
const mapToUrlState = (value: any): WaffleTimeUrlState | undefined =>
|
||||
value
|
||||
? {
|
||||
time: mapToTimeUrlState(value.time),
|
||||
autoReload: mapToAutoReloadUrlState(value.autoReload),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const mapToTimeUrlState = (value: any) => (value && typeof value === 'number' ? value : undefined);
|
||||
|
||||
const mapToAutoReloadUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);
|
|
@ -1,145 +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 { createSelector } from 'reselect';
|
||||
import { IIndexPattern } from 'src/plugins/data/public';
|
||||
import {
|
||||
State,
|
||||
waffleOptionsActions,
|
||||
waffleOptionsSelectors,
|
||||
waffleTimeSelectors,
|
||||
waffleTimeActions,
|
||||
waffleFilterActions,
|
||||
waffleFilterSelectors,
|
||||
initialState,
|
||||
} from '../../store';
|
||||
import { asChildFunctionRenderer } from '../../utils/typed_react';
|
||||
import { convertKueryToElasticSearchQuery } from '../../utils/kuery';
|
||||
|
||||
const selectViewState = createSelector(
|
||||
waffleOptionsSelectors.selectMetric,
|
||||
waffleOptionsSelectors.selectView,
|
||||
waffleOptionsSelectors.selectGroupBy,
|
||||
waffleOptionsSelectors.selectNodeType,
|
||||
waffleOptionsSelectors.selectCustomOptions,
|
||||
waffleOptionsSelectors.selectBoundsOverride,
|
||||
waffleOptionsSelectors.selectAutoBounds,
|
||||
waffleTimeSelectors.selectCurrentTime,
|
||||
waffleTimeSelectors.selectIsAutoReloading,
|
||||
waffleFilterSelectors.selectWaffleFilterQuery,
|
||||
waffleOptionsSelectors.selectCustomMetrics,
|
||||
(
|
||||
metric,
|
||||
view,
|
||||
groupBy,
|
||||
nodeType,
|
||||
customOptions,
|
||||
boundsOverride,
|
||||
autoBounds,
|
||||
time,
|
||||
autoReload,
|
||||
filterQuery,
|
||||
customMetrics
|
||||
) => ({
|
||||
time,
|
||||
autoReload,
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
view,
|
||||
customOptions,
|
||||
boundsOverride,
|
||||
autoBounds,
|
||||
filterQuery,
|
||||
customMetrics,
|
||||
})
|
||||
);
|
||||
|
||||
interface Props {
|
||||
indexPattern: IIndexPattern;
|
||||
}
|
||||
|
||||
export const withWaffleViewState = connect(
|
||||
(state: State) => ({
|
||||
viewState: selectViewState(state),
|
||||
defaultViewState: selectViewState(initialState),
|
||||
}),
|
||||
(dispatch, ownProps: Props) => {
|
||||
return {
|
||||
onViewChange: (viewState: WaffleViewState) => {
|
||||
if (viewState.time) {
|
||||
dispatch(waffleTimeActions.jumpToTime(viewState.time));
|
||||
}
|
||||
if (viewState.autoReload) {
|
||||
dispatch(waffleTimeActions.startAutoReload());
|
||||
} else if (typeof viewState.autoReload !== 'undefined' && !viewState.autoReload) {
|
||||
dispatch(waffleTimeActions.stopAutoReload());
|
||||
}
|
||||
if (viewState.metric) {
|
||||
dispatch(waffleOptionsActions.changeMetric(viewState.metric));
|
||||
}
|
||||
if (viewState.groupBy) {
|
||||
dispatch(waffleOptionsActions.changeGroupBy(viewState.groupBy));
|
||||
}
|
||||
if (viewState.nodeType) {
|
||||
dispatch(waffleOptionsActions.changeNodeType(viewState.nodeType));
|
||||
}
|
||||
if (viewState.view) {
|
||||
dispatch(waffleOptionsActions.changeView(viewState.view));
|
||||
}
|
||||
if (viewState.customOptions) {
|
||||
dispatch(waffleOptionsActions.changeCustomOptions(viewState.customOptions));
|
||||
}
|
||||
if (viewState.customMetrics) {
|
||||
dispatch(waffleOptionsActions.changeCustomMetrics(viewState.customMetrics));
|
||||
}
|
||||
if (viewState.boundsOverride) {
|
||||
dispatch(waffleOptionsActions.changeBoundsOverride(viewState.boundsOverride));
|
||||
}
|
||||
if (viewState.autoBounds) {
|
||||
dispatch(waffleOptionsActions.changeAutoBounds(viewState.autoBounds));
|
||||
}
|
||||
if (viewState.filterQuery) {
|
||||
dispatch(
|
||||
waffleFilterActions.applyWaffleFilterQuery({
|
||||
query: viewState.filterQuery,
|
||||
serializedQuery: convertKueryToElasticSearchQuery(
|
||||
viewState.filterQuery.expression,
|
||||
ownProps.indexPattern
|
||||
),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
waffleFilterActions.applyWaffleFilterQuery({
|
||||
query: null,
|
||||
serializedQuery: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const WithWaffleViewState = asChildFunctionRenderer(withWaffleViewState);
|
||||
|
||||
/**
|
||||
* View State
|
||||
*/
|
||||
export interface WaffleViewState {
|
||||
metric?: ReturnType<typeof waffleOptionsSelectors.selectMetric>;
|
||||
groupBy?: ReturnType<typeof waffleOptionsSelectors.selectGroupBy>;
|
||||
nodeType?: ReturnType<typeof waffleOptionsSelectors.selectNodeType>;
|
||||
view?: ReturnType<typeof waffleOptionsSelectors.selectView>;
|
||||
customOptions?: ReturnType<typeof waffleOptionsSelectors.selectCustomOptions>;
|
||||
customMetrics?: ReturnType<typeof waffleOptionsSelectors.selectCustomMetrics>;
|
||||
boundsOverride?: ReturnType<typeof waffleOptionsSelectors.selectBoundsOverride>;
|
||||
autoBounds?: ReturnType<typeof waffleOptionsSelectors.selectAutoBounds>;
|
||||
time?: ReturnType<typeof waffleTimeSelectors.selectCurrentTime>;
|
||||
autoReload?: ReturnType<typeof waffleTimeSelectors.selectIsAutoReloading>;
|
||||
filterQuery?: ReturnType<typeof waffleFilterSelectors.selectWaffleFilterQuery>;
|
||||
}
|
|
@ -8,7 +8,7 @@ import moment from 'moment';
|
|||
import React from 'react';
|
||||
|
||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||
import { InfraFormatterType, InfraOptions, InfraWaffleMapLegendMode } from '../lib/lib';
|
||||
import { InfraFormatterType, InfraOptions } from '../lib/lib';
|
||||
import { RendererFunction } from '../utils/typed_react';
|
||||
|
||||
const euiVisColorPalette = euiPaletteColorBlind();
|
||||
|
@ -29,7 +29,7 @@ const initialState = {
|
|||
metric: { type: 'cpu' },
|
||||
groupBy: [],
|
||||
legend: {
|
||||
type: InfraWaffleMapLegendMode.gradient,
|
||||
type: 'gradient',
|
||||
rules: [
|
||||
{
|
||||
value: 0,
|
||||
|
|
|
@ -136,18 +136,13 @@ export interface InfraWaffleMapGradientRule {
|
|||
color: string;
|
||||
}
|
||||
|
||||
export enum InfraWaffleMapLegendMode {
|
||||
step = 'step',
|
||||
gradient = 'gradient',
|
||||
}
|
||||
|
||||
export interface InfraWaffleMapStepLegend {
|
||||
type: InfraWaffleMapLegendMode.step;
|
||||
type: 'step';
|
||||
rules: InfraWaffleMapStepRule[];
|
||||
}
|
||||
|
||||
export interface InfraWaffleMapGradientLegend {
|
||||
type: InfraWaffleMapLegendMode.gradient;
|
||||
type: 'gradient';
|
||||
rules: InfraWaffleMapGradientRule[];
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,9 @@ import { MetricsSettingsPage } from './settings';
|
|||
import { AppNavigation } from '../../components/navigation/app_navigation';
|
||||
import { SourceLoadingPage } from '../../components/source_loading_page';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { WaffleOptionsProvider } from '../inventory_view/hooks/use_waffle_options';
|
||||
import { WaffleTimeProvider } from '../inventory_view/hooks/use_waffle_time';
|
||||
import { WaffleFiltersProvider } from '../inventory_view/hooks/use_waffle_filters';
|
||||
import { AlertDropdown } from '../../components/alerting/metrics/alert_dropdown';
|
||||
|
||||
export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
||||
|
@ -32,96 +35,101 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
|||
|
||||
return (
|
||||
<Source.Provider sourceId="default">
|
||||
<ColumnarPage>
|
||||
<DocumentTitle
|
||||
title={i18n.translate('xpack.infra.homePage.documentTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
/>
|
||||
<WaffleOptionsProvider>
|
||||
<WaffleTimeProvider>
|
||||
<WaffleFiltersProvider>
|
||||
<ColumnarPage>
|
||||
<DocumentTitle
|
||||
title={i18n.translate('xpack.infra.homePage.documentTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
/>
|
||||
|
||||
<HelpCenterContent
|
||||
feedbackLink="https://discuss.elastic.co/c/metrics"
|
||||
appName={i18n.translate('xpack.infra.header.infrastructureHelpAppName', {
|
||||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
/>
|
||||
<HelpCenterContent
|
||||
feedbackLink="https://discuss.elastic.co/c/metrics"
|
||||
appName={i18n.translate('xpack.infra.header.infrastructureHelpAppName', {
|
||||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
/>
|
||||
|
||||
<Header
|
||||
breadcrumbs={[
|
||||
{
|
||||
text: i18n.translate('xpack.infra.header.infrastructureTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
|
||||
/>
|
||||
|
||||
<AppNavigation
|
||||
aria-label={i18n.translate('xpack.infra.header.infrastructureNavigationTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup gutterSize={'none'} alignItems={'center'}>
|
||||
<EuiFlexItem>
|
||||
<RoutedTabs
|
||||
tabs={[
|
||||
<Header
|
||||
breadcrumbs={[
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', {
|
||||
defaultMessage: 'Inventory',
|
||||
text: i18n.translate('xpack.infra.header.infrastructureTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
pathname: '/inventory',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
|
||||
defaultMessage: 'Metrics Explorer',
|
||||
}),
|
||||
pathname: '/explorer',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.settingsTabTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
pathname: '/settings',
|
||||
},
|
||||
]}
|
||||
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Route path={'/explorer'} component={AlertDropdown} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</AppNavigation>
|
||||
<AppNavigation
|
||||
aria-label={i18n.translate('xpack.infra.header.infrastructureNavigationTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
})}
|
||||
>
|
||||
<EuiFlexGroup gutterSize={'none'} alignItems={'center'}>
|
||||
<EuiFlexItem>
|
||||
<RoutedTabs
|
||||
tabs={[
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', {
|
||||
defaultMessage: 'Inventory',
|
||||
}),
|
||||
pathname: '/inventory',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
|
||||
defaultMessage: 'Metrics Explorer',
|
||||
}),
|
||||
pathname: '/explorer',
|
||||
},
|
||||
{
|
||||
app: 'metrics',
|
||||
title: i18n.translate('xpack.infra.homePage.settingsTabTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
pathname: '/settings',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Route path={'/explorer'} component={AlertDropdown} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</AppNavigation>
|
||||
|
||||
<Switch>
|
||||
<Route path={'/inventory'} component={SnapshotPage} />
|
||||
<Route
|
||||
path={'/explorer'}
|
||||
render={props => (
|
||||
<WithSource>
|
||||
{({ configuration, createDerivedIndexPattern }) => (
|
||||
<MetricsExplorerOptionsContainer.Provider>
|
||||
<WithMetricsExplorerOptionsUrlState />
|
||||
{configuration ? (
|
||||
<MetricsExplorerPage
|
||||
derivedIndexPattern={createDerivedIndexPattern('metrics')}
|
||||
source={configuration}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<SourceLoadingPage />
|
||||
)}
|
||||
</MetricsExplorerOptionsContainer.Provider>
|
||||
)}
|
||||
</WithSource>
|
||||
)}
|
||||
/>
|
||||
<Route path={'/settings'} component={MetricsSettingsPage} />
|
||||
</Switch>
|
||||
</ColumnarPage>
|
||||
<Switch>
|
||||
<Route path={'/inventory'} component={SnapshotPage} />
|
||||
<Route
|
||||
path={'/explorer'}
|
||||
render={props => (
|
||||
<WithSource>
|
||||
{({ configuration, createDerivedIndexPattern }) => (
|
||||
<MetricsExplorerOptionsContainer.Provider>
|
||||
<WithMetricsExplorerOptionsUrlState />
|
||||
{configuration ? (
|
||||
<MetricsExplorerPage
|
||||
derivedIndexPattern={createDerivedIndexPattern('metrics')}
|
||||
source={configuration}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<SourceLoadingPage />
|
||||
)}
|
||||
</MetricsExplorerOptionsContainer.Provider>
|
||||
)}
|
||||
</WithSource>
|
||||
)}
|
||||
/>
|
||||
<Route path={'/settings'} component={MetricsSettingsPage} />
|
||||
</Switch>
|
||||
</ColumnarPage>
|
||||
</WaffleFiltersProvider>
|
||||
</WaffleTimeProvider>
|
||||
</WaffleOptionsProvider>
|
||||
</Source.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { SnapshotPageContent } from './page_content';
|
||||
import { SnapshotToolbar } from './toolbar';
|
||||
|
||||
import { DocumentTitle } from '../../../components/document_title';
|
||||
|
@ -19,17 +18,14 @@ import { SourceErrorPage } from '../../../components/source_error_page';
|
|||
import { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||
import { ViewSourceConfigurationButton } from '../../../components/source_configuration';
|
||||
import { Source } from '../../../containers/source';
|
||||
import { WithWaffleFilterUrlState } from '../../../containers/waffle/with_waffle_filters';
|
||||
import { WithWaffleOptionsUrlState } from '../../../containers/waffle/with_waffle_options';
|
||||
import { WithWaffleTimeUrlState } from '../../../containers/waffle/with_waffle_time';
|
||||
import { useTrackPageview } from '../../../../../observability/public';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { Layout } from '../../../components/inventory/layout';
|
||||
import { useLinkProps } from '../../../hooks/use_link_props';
|
||||
|
||||
export const SnapshotPage = () => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
const {
|
||||
createDerivedIndexPattern,
|
||||
hasFailedLoadingSource,
|
||||
isLoading,
|
||||
loadSourceFailureMessage,
|
||||
|
@ -60,11 +56,8 @@ export const SnapshotPage = () => {
|
|||
<SourceLoadingPage />
|
||||
) : metricIndicesExist ? (
|
||||
<>
|
||||
<WithWaffleTimeUrlState />
|
||||
<WithWaffleFilterUrlState indexPattern={createDerivedIndexPattern('metrics')} />
|
||||
<WithWaffleOptionsUrlState />
|
||||
<SnapshotToolbar />
|
||||
<SnapshotPageContent />
|
||||
<Layout />
|
||||
</>
|
||||
) : hasFailedLoadingSource ? (
|
||||
<SourceErrorPage errorMessage={loadSourceFailureMessage || ''} retry={loadSource} />
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { WithWaffleFilter } from '../../../containers/waffle/with_waffle_filters';
|
||||
import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options';
|
||||
import { WithWaffleTime } from '../../../containers/waffle/with_waffle_time';
|
||||
import { WithOptions } from '../../../containers/with_options';
|
||||
import { WithSource } from '../../../containers/with_source';
|
||||
import { Layout } from '../../../components/inventory/layout';
|
||||
|
||||
export const SnapshotPageContent: React.FC = () => (
|
||||
<WithSource>
|
||||
{({ configuration, createDerivedIndexPattern, sourceId }) => (
|
||||
<WithOptions>
|
||||
{({ wafflemap }) => (
|
||||
<WithWaffleFilter indexPattern={createDerivedIndexPattern('metrics')}>
|
||||
{({ filterQueryAsJson, applyFilterQuery }) => (
|
||||
<WithWaffleTime>
|
||||
{({ currentTime }) => (
|
||||
<WithWaffleOptions>
|
||||
{({
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
view,
|
||||
changeView,
|
||||
autoBounds,
|
||||
boundsOverride,
|
||||
accountId,
|
||||
region,
|
||||
}) => (
|
||||
<Layout
|
||||
currentTime={currentTime}
|
||||
filterQuery={filterQueryAsJson}
|
||||
metric={metric}
|
||||
groupBy={groupBy}
|
||||
nodeType={nodeType}
|
||||
sourceId={sourceId}
|
||||
options={{
|
||||
...wafflemap,
|
||||
metric,
|
||||
fields: configuration && configuration.fields,
|
||||
groupBy,
|
||||
}}
|
||||
onDrilldown={applyFilterQuery}
|
||||
view={view}
|
||||
onViewChange={changeView}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
accountId={accountId}
|
||||
region={region}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleOptions>
|
||||
)}
|
||||
</WithWaffleTime>
|
||||
)}
|
||||
</WithWaffleFilter>
|
||||
)}
|
||||
</WithOptions>
|
||||
)}
|
||||
</WithSource>
|
||||
);
|
|
@ -5,92 +5,24 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
import { AutocompleteField } from '../../../components/autocomplete_field';
|
||||
import { Toolbar } from '../../../components/eui/toolbar';
|
||||
import { WaffleTimeControls } from '../../../components/waffle/waffle_time_controls';
|
||||
import { WithWaffleFilter } from '../../../containers/waffle/with_waffle_filters';
|
||||
import { WithWaffleTime } from '../../../containers/waffle/with_waffle_time';
|
||||
import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion';
|
||||
import { WithSource } from '../../../containers/with_source';
|
||||
import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options';
|
||||
import { WaffleInventorySwitcher } from '../../../components/waffle/waffle_inventory_switcher';
|
||||
import { SearchBar } from '../../inventory_view/compontents/search_bar';
|
||||
|
||||
export const SnapshotToolbar = () => (
|
||||
<Toolbar>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<WithWaffleOptions>
|
||||
{({
|
||||
changeMetric,
|
||||
changeNodeType,
|
||||
changeGroupBy,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomMetrics,
|
||||
nodeType,
|
||||
}) => (
|
||||
<WaffleInventorySwitcher
|
||||
nodeType={nodeType}
|
||||
changeNodeType={changeNodeType}
|
||||
changeMetric={changeMetric}
|
||||
changeGroupBy={changeGroupBy}
|
||||
changeAccount={changeAccount}
|
||||
changeRegion={changeRegion}
|
||||
changeCustomMetrics={changeCustomMetrics}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleOptions>
|
||||
<WaffleInventorySwitcher />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<WithSource>
|
||||
{({ createDerivedIndexPattern }) => (
|
||||
<WithKueryAutocompletion indexPattern={createDerivedIndexPattern('metrics')}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<WithWaffleFilter indexPattern={createDerivedIndexPattern('metrics')}>
|
||||
{({
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
}) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)',
|
||||
}
|
||||
)}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft.expression : ''}
|
||||
autoFocus={true}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleFilter>
|
||||
)}
|
||||
</WithKueryAutocompletion>
|
||||
)}
|
||||
</WithSource>
|
||||
<SearchBar />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<WithWaffleTime resetOnUnmount>
|
||||
{({ currentTime, isAutoReloading, jumpToTime, startAutoReload, stopAutoReload }) => (
|
||||
<WaffleTimeControls
|
||||
currentTime={currentTime}
|
||||
isLiveStreaming={isAutoReloading}
|
||||
onChangeTime={jumpToTime}
|
||||
startLiveStreaming={startAutoReload}
|
||||
stopLiveStreaming={stopAutoReload}
|
||||
/>
|
||||
)}
|
||||
</WithWaffleTime>
|
||||
<WaffleTimeControls />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useContext } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Source } from '../../../containers/source';
|
||||
import { AutocompleteField } from '../../../components/autocomplete_field';
|
||||
import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion';
|
||||
import { useWaffleFiltersContext } from '../hooks/use_waffle_filters';
|
||||
|
||||
export const SearchBar = () => {
|
||||
const { createDerivedIndexPattern } = useContext(Source.Context);
|
||||
const {
|
||||
applyFilterQueryFromKueryExpression,
|
||||
filterQueryDraft,
|
||||
isFilterQueryDraftValid,
|
||||
setFilterQueryDraftFromKueryExpression,
|
||||
} = useWaffleFiltersContext();
|
||||
return (
|
||||
<WithKueryAutocompletion indexPattern={createDerivedIndexPattern('metrics')}>
|
||||
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
|
||||
<AutocompleteField
|
||||
isLoadingSuggestions={isLoadingSuggestions}
|
||||
isValid={isFilterQueryDraftValid}
|
||||
loadSuggestions={loadSuggestions}
|
||||
onChange={setFilterQueryDraftFromKueryExpression}
|
||||
onSubmit={applyFilterQueryFromKueryExpression}
|
||||
placeholder={i18n.translate('xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder', {
|
||||
defaultMessage: 'Search for infrastructure data… (e.g. host.name:host-1)',
|
||||
})}
|
||||
suggestions={suggestions}
|
||||
value={filterQueryDraft ? filterQueryDraft : ''}
|
||||
autoFocus={true}
|
||||
/>
|
||||
)}
|
||||
</WithKueryAutocompletion>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import * as rt from 'io-ts';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
import createContainter from 'constate';
|
||||
import { useUrlState } from '../../../utils/use_url_state';
|
||||
import { useSourceContext } from '../../../containers/source';
|
||||
import { convertKueryToElasticSearchQuery } from '../../../utils/kuery';
|
||||
import { esKuery } from '../../../../../../../src/plugins/data/public';
|
||||
|
||||
const validateKuery = (expression: string) => {
|
||||
try {
|
||||
esKuery.fromKueryExpression(expression);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const DEFAULT_WAFFLE_FILTERS_STATE: WaffleFiltersState = { kind: 'kuery', expression: '' };
|
||||
|
||||
export const useWaffleFilters = () => {
|
||||
const { createDerivedIndexPattern } = useSourceContext();
|
||||
const indexPattern = createDerivedIndexPattern('metrics');
|
||||
|
||||
const [urlState, setUrlState] = useUrlState<WaffleFiltersState>({
|
||||
defaultState: DEFAULT_WAFFLE_FILTERS_STATE,
|
||||
decodeUrlState,
|
||||
encodeUrlState,
|
||||
urlStateKey: 'waffleFilter',
|
||||
});
|
||||
|
||||
const [state, setState] = useState<WaffleFiltersState>(urlState);
|
||||
|
||||
useEffect(() => setUrlState(state), [setUrlState, state]);
|
||||
|
||||
const [filterQueryDraft, setFilterQueryDraft] = useState<string>(urlState.expression);
|
||||
|
||||
const filterQueryAsJson = useMemo(
|
||||
() => convertKueryToElasticSearchQuery(urlState.expression, indexPattern),
|
||||
[indexPattern, urlState.expression]
|
||||
);
|
||||
|
||||
const applyFilterQueryFromKueryExpression = useCallback(
|
||||
(expression: string) => {
|
||||
setState(previous => ({
|
||||
...previous,
|
||||
kind: 'kuery',
|
||||
expression,
|
||||
}));
|
||||
},
|
||||
[setState]
|
||||
);
|
||||
|
||||
const applyFilterQuery = useCallback((filterQuery: WaffleFiltersState) => {
|
||||
setState(filterQuery);
|
||||
setFilterQueryDraft(filterQuery.expression);
|
||||
}, []);
|
||||
|
||||
const isFilterQueryDraftValid = useMemo(() => validateKuery(filterQueryDraft), [
|
||||
filterQueryDraft,
|
||||
]);
|
||||
|
||||
return {
|
||||
filterQuery: urlState,
|
||||
filterQueryDraft,
|
||||
filterQueryAsJson,
|
||||
applyFilterQuery,
|
||||
setFilterQueryDraftFromKueryExpression: setFilterQueryDraft,
|
||||
applyFilterQueryFromKueryExpression,
|
||||
isFilterQueryDraftValid,
|
||||
setWaffleFiltersState: applyFilterQuery,
|
||||
};
|
||||
};
|
||||
|
||||
export const WaffleFiltersStateRT = rt.type({
|
||||
kind: rt.literal('kuery'),
|
||||
expression: rt.string,
|
||||
});
|
||||
|
||||
export type WaffleFiltersState = rt.TypeOf<typeof WaffleFiltersStateRT>;
|
||||
const encodeUrlState = WaffleFiltersStateRT.encode;
|
||||
const decodeUrlState = (value: unknown) =>
|
||||
pipe(WaffleFiltersStateRT.decode(value), fold(constant(undefined), identity));
|
||||
export const WaffleFilters = createContainter(useWaffleFilters);
|
||||
export const [WaffleFiltersProvider, useWaffleFiltersContext] = WaffleFilters;
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 { useCallback, useState, useEffect } from 'react';
|
||||
import * as rt from 'io-ts';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
import createContainer from 'constate';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotGroupBy,
|
||||
SnapshotCustomMetricInput,
|
||||
SnapshotMetricInputRT,
|
||||
SnapshotGroupByRT,
|
||||
SnapshotCustomMetricInputRT,
|
||||
} from '../../../../common/http_api/snapshot_api';
|
||||
import { useUrlState } from '../../../utils/use_url_state';
|
||||
import { InventoryItemType, ItemTypeRT } from '../../../../common/inventory_models/types';
|
||||
|
||||
export const DEFAULT_WAFFLE_OPTIONS_STATE: WaffleOptionsState = {
|
||||
metric: { type: 'cpu' },
|
||||
groupBy: [],
|
||||
nodeType: 'host',
|
||||
view: 'map',
|
||||
customOptions: [],
|
||||
boundsOverride: { max: 1, min: 0 },
|
||||
autoBounds: true,
|
||||
accountId: '',
|
||||
region: '',
|
||||
customMetrics: [],
|
||||
};
|
||||
|
||||
export const useWaffleOptions = () => {
|
||||
const [urlState, setUrlState] = useUrlState<WaffleOptionsState>({
|
||||
defaultState: DEFAULT_WAFFLE_OPTIONS_STATE,
|
||||
decodeUrlState,
|
||||
encodeUrlState,
|
||||
urlStateKey: 'waffleOptions',
|
||||
});
|
||||
|
||||
const [state, setState] = useState<WaffleOptionsState>(urlState);
|
||||
|
||||
useEffect(() => setUrlState(state), [setUrlState, state]);
|
||||
|
||||
const changeMetric = useCallback(
|
||||
(metric: SnapshotMetricInput) => setState(previous => ({ ...previous, metric })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeGroupBy = useCallback(
|
||||
(groupBy: SnapshotGroupBy) => setState(previous => ({ ...previous, groupBy })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeNodeType = useCallback(
|
||||
(nodeType: InventoryItemType) => setState(previous => ({ ...previous, nodeType })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeView = useCallback((view: string) => setState(previous => ({ ...previous, view })), [
|
||||
setState,
|
||||
]);
|
||||
|
||||
const changeCustomOptions = useCallback(
|
||||
(customOptions: Array<{ text: string; field: string }>) =>
|
||||
setState(previous => ({ ...previous, customOptions })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeAutoBounds = useCallback(
|
||||
(autoBounds: boolean) => setState(previous => ({ ...previous, autoBounds })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeBoundsOverride = useCallback(
|
||||
(boundsOverride: { min: number; max: number }) =>
|
||||
setState(previous => ({ ...previous, boundsOverride })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeAccount = useCallback(
|
||||
(accountId: string) => setState(previous => ({ ...previous, accountId })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeRegion = useCallback(
|
||||
(region: string) => setState(previous => ({ ...previous, region })),
|
||||
[setState]
|
||||
);
|
||||
|
||||
const changeCustomMetrics = useCallback(
|
||||
(customMetrics: SnapshotCustomMetricInput[]) => {
|
||||
setState(previous => ({ ...previous, customMetrics }));
|
||||
},
|
||||
[setState]
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeNodeType,
|
||||
changeView,
|
||||
changeCustomOptions,
|
||||
changeAutoBounds,
|
||||
changeBoundsOverride,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomMetrics,
|
||||
setWaffleOptionsState: setState,
|
||||
};
|
||||
};
|
||||
|
||||
export const WaffleOptionsStateRT = rt.type({
|
||||
metric: SnapshotMetricInputRT,
|
||||
groupBy: SnapshotGroupByRT,
|
||||
nodeType: ItemTypeRT,
|
||||
view: rt.string,
|
||||
customOptions: rt.array(
|
||||
rt.type({
|
||||
text: rt.string,
|
||||
field: rt.string,
|
||||
})
|
||||
),
|
||||
boundsOverride: rt.type({
|
||||
min: rt.number,
|
||||
max: rt.number,
|
||||
}),
|
||||
autoBounds: rt.boolean,
|
||||
accountId: rt.string,
|
||||
region: rt.string,
|
||||
customMetrics: rt.array(SnapshotCustomMetricInputRT),
|
||||
});
|
||||
|
||||
export type WaffleOptionsState = rt.TypeOf<typeof WaffleOptionsStateRT>;
|
||||
const encodeUrlState = (state: WaffleOptionsState) => {
|
||||
return WaffleOptionsStateRT.encode(state);
|
||||
};
|
||||
const decodeUrlState = (value: unknown) =>
|
||||
pipe(WaffleOptionsStateRT.decode(value), fold(constant(undefined), identity));
|
||||
|
||||
export const WaffleOptions = createContainer(useWaffleOptions);
|
||||
export const [WaffleOptionsProvider, useWaffleOptionsContext] = WaffleOptions;
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { useCallback, useState, useEffect } from 'react';
|
||||
import * as rt from 'io-ts';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
import createContainer from 'constate';
|
||||
import { useUrlState } from '../../../utils/use_url_state';
|
||||
|
||||
export const DEFAULT_WAFFLE_TIME_STATE: WaffleTimeState = {
|
||||
currentTime: Date.now(),
|
||||
isAutoReloading: false,
|
||||
};
|
||||
|
||||
export const useWaffleTime = () => {
|
||||
const [urlState, setUrlState] = useUrlState<WaffleTimeState>({
|
||||
defaultState: DEFAULT_WAFFLE_TIME_STATE,
|
||||
decodeUrlState,
|
||||
encodeUrlState,
|
||||
urlStateKey: 'waffleTime',
|
||||
});
|
||||
|
||||
const [state, setState] = useState<WaffleTimeState>(urlState);
|
||||
|
||||
useEffect(() => setUrlState(state), [setUrlState, state]);
|
||||
|
||||
const { currentTime, isAutoReloading } = urlState;
|
||||
|
||||
const startAutoReload = useCallback(() => {
|
||||
setState(previous => ({ ...previous, isAutoReloading: true }));
|
||||
}, [setState]);
|
||||
|
||||
const stopAutoReload = useCallback(() => {
|
||||
setState(previous => ({ ...previous, isAutoReloading: false }));
|
||||
}, [setState]);
|
||||
|
||||
const jumpToTime = useCallback(
|
||||
(time: number) => {
|
||||
setState(previous => ({ ...previous, currentTime: time }));
|
||||
},
|
||||
[setState]
|
||||
);
|
||||
|
||||
const currentTimeRange = {
|
||||
from: currentTime - 1000 * 60 * 5,
|
||||
interval: '1m',
|
||||
to: currentTime,
|
||||
};
|
||||
|
||||
return {
|
||||
currentTime,
|
||||
currentTimeRange,
|
||||
isAutoReloading,
|
||||
startAutoReload,
|
||||
stopAutoReload,
|
||||
jumpToTime,
|
||||
setWaffleTimeState: setState,
|
||||
};
|
||||
};
|
||||
|
||||
export const WaffleTimeStateRT = rt.type({
|
||||
currentTime: rt.number,
|
||||
isAutoReloading: rt.boolean,
|
||||
});
|
||||
|
||||
export type WaffleTimeState = rt.TypeOf<typeof WaffleTimeStateRT>;
|
||||
const encodeUrlState = WaffleTimeStateRT.encode;
|
||||
const decodeUrlState = (value: unknown) =>
|
||||
pipe(WaffleTimeStateRT.decode(value), fold(constant(undefined), identity));
|
||||
|
||||
export const WaffleTime = createContainer(useWaffleTime);
|
||||
export const [WaffleTimeProvider, useWaffleTimeContext] = WaffleTime;
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import {
|
||||
useWaffleOptionsContext,
|
||||
DEFAULT_WAFFLE_OPTIONS_STATE,
|
||||
WaffleOptionsState,
|
||||
} from './use_waffle_options';
|
||||
import { useWaffleTimeContext, DEFAULT_WAFFLE_TIME_STATE } from './use_waffle_time';
|
||||
import {
|
||||
useWaffleFiltersContext,
|
||||
DEFAULT_WAFFLE_FILTERS_STATE,
|
||||
WaffleFiltersState,
|
||||
} from './use_waffle_filters';
|
||||
|
||||
export const useWaffleViewState = () => {
|
||||
const {
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
view,
|
||||
customOptions,
|
||||
customMetrics,
|
||||
boundsOverride,
|
||||
autoBounds,
|
||||
accountId,
|
||||
region,
|
||||
setWaffleOptionsState,
|
||||
} = useWaffleOptionsContext();
|
||||
const { currentTime, isAutoReloading, setWaffleTimeState } = useWaffleTimeContext();
|
||||
const { filterQuery, setWaffleFiltersState } = useWaffleFiltersContext();
|
||||
|
||||
const viewState: WaffleViewState = {
|
||||
metric,
|
||||
groupBy,
|
||||
nodeType,
|
||||
view,
|
||||
customOptions,
|
||||
customMetrics,
|
||||
boundsOverride,
|
||||
autoBounds,
|
||||
accountId,
|
||||
region,
|
||||
time: currentTime,
|
||||
autoReload: isAutoReloading,
|
||||
filterQuery,
|
||||
};
|
||||
|
||||
const defaultViewState: WaffleViewState = {
|
||||
...DEFAULT_WAFFLE_OPTIONS_STATE,
|
||||
filterQuery: DEFAULT_WAFFLE_FILTERS_STATE,
|
||||
time: DEFAULT_WAFFLE_TIME_STATE.currentTime,
|
||||
autoReload: DEFAULT_WAFFLE_TIME_STATE.isAutoReloading,
|
||||
};
|
||||
|
||||
const onViewChange = useCallback(
|
||||
(newState: WaffleViewState) => {
|
||||
setWaffleOptionsState({
|
||||
metric: newState.metric,
|
||||
groupBy: newState.groupBy,
|
||||
nodeType: newState.nodeType,
|
||||
view: newState.view,
|
||||
customOptions: newState.customOptions,
|
||||
customMetrics: newState.customMetrics,
|
||||
boundsOverride: newState.boundsOverride,
|
||||
autoBounds: newState.autoBounds,
|
||||
accountId: newState.accountId,
|
||||
region: newState.region,
|
||||
});
|
||||
if (newState.time) {
|
||||
setWaffleTimeState({
|
||||
currentTime: newState.time,
|
||||
isAutoReloading: newState.autoReload,
|
||||
});
|
||||
}
|
||||
setWaffleFiltersState(newState.filterQuery);
|
||||
},
|
||||
[setWaffleOptionsState, setWaffleTimeState, setWaffleFiltersState]
|
||||
);
|
||||
|
||||
return {
|
||||
viewState,
|
||||
defaultViewState,
|
||||
onViewChange,
|
||||
};
|
||||
};
|
||||
|
||||
export type WaffleViewState = WaffleOptionsState & {
|
||||
time: number;
|
||||
autoReload: boolean;
|
||||
filterQuery: WaffleFiltersState;
|
||||
};
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
|||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time';
|
||||
import { replaceMetricTimeInQueryString } from '../metrics/hooks/use_metrics_time';
|
||||
import { useHostIpToName } from './use_host_ip_to_name';
|
||||
import { getFromFromLocation, getToFromLocation } from './query_params';
|
||||
import { LoadingPage } from '../../components/loading_page';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time';
|
||||
import { replaceMetricTimeInQueryString } from '../metrics/hooks/use_metrics_time';
|
||||
import { getFromFromLocation, getToFromLocation } from './query_params';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { LinkDescriptor } from '../../hooks/use_link_props';
|
||||
|
|
|
@ -22,7 +22,7 @@ import { MetricsTimeControls } from './time_controls';
|
|||
import { SideNavContext, NavItem } from '../lib/side_nav_context';
|
||||
import { PageBody } from './page_body';
|
||||
import { euiStyled } from '../../../../../observability/public';
|
||||
import { MetricsTimeInput } from '../containers/with_metrics_time';
|
||||
import { MetricsTimeInput } from '../hooks/use_metrics_time';
|
||||
import { InfraMetadata } from '../../../../common/http_api/metadata_api';
|
||||
import { PageError } from './page_error';
|
||||
import { MetadataContext } from '../../../pages/metrics/containers/metadata_context';
|
||||
|
@ -94,7 +94,7 @@ export const NodeDetailsPage = (props: Props) => {
|
|||
setRefreshInterval={props.setRefreshInterval}
|
||||
onChangeTimeRange={props.setTimeRange}
|
||||
setAutoReload={props.setAutoReload}
|
||||
onRefresh={props.triggerRefresh}
|
||||
onRefresh={refetch}
|
||||
/>
|
||||
</MetricsTitleTimeRangeContainer>
|
||||
</EuiPageHeaderSection>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { findLayout } from '../../../../common/inventory_models/layouts';
|
||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
||||
|
||||
import { MetricsTimeInput } from '../containers/with_metrics_time';
|
||||
import { MetricsTimeInput } from '../hooks/use_metrics_time';
|
||||
import { InfraLoadingPanel } from '../../../components/loading';
|
||||
import { NoData } from '../../../components/empty_states';
|
||||
import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api';
|
||||
|
@ -19,9 +19,9 @@ interface Props {
|
|||
refetch: () => void;
|
||||
type: InventoryItemType;
|
||||
metrics: NodeDetailsMetricData[];
|
||||
onChangeRangeTime?: (time: MetricsTimeInput) => void;
|
||||
isLiveStreaming?: boolean;
|
||||
stopLiveStreaming?: () => void;
|
||||
onChangeRangeTime: (time: MetricsTimeInput) => void;
|
||||
isLiveStreaming: boolean;
|
||||
stopLiveStreaming: () => void;
|
||||
}
|
||||
|
||||
export const PageBody = ({
|
||||
|
|
|
@ -41,6 +41,9 @@ export const Section: FunctionComponent<SectionProps> = ({
|
|||
if (metric === null) {
|
||||
return accumulatedChildren;
|
||||
}
|
||||
if (!child.props.label) {
|
||||
return accumulatedChildren;
|
||||
}
|
||||
return [
|
||||
...accumulatedChildren,
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ jest.mock('../../../utils/use_kibana_ui_setting', () => ({
|
|||
import React from 'react';
|
||||
import { MetricsTimeControls } from './time_controls';
|
||||
import { mount } from 'enzyme';
|
||||
import { MetricsTimeInput } from '../containers/with_metrics_time';
|
||||
import { MetricsTimeInput } from '../hooks/use_metrics_time';
|
||||
|
||||
describe('MetricsTimeControls', () => {
|
||||
it('should set a valid from and to value for Today', () => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { euiStyled } from '../../../../../observability/public';
|
||||
import { MetricsTimeInput } from '../containers/with_metrics_time';
|
||||
import { MetricsTimeInput } from '../hooks/use_metrics_time';
|
||||
import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting';
|
||||
import { mapKibanaQuickRangesToDatePickerRanges } from '../../../utils/map_timepicker_quickranges_to_datepicker_ranges';
|
||||
|
||||
|
@ -61,8 +61,8 @@ export const MetricsTimeControls = (props: MetricsTimeControlsProps) => {
|
|||
return (
|
||||
<MetricsTimeControlsContainer>
|
||||
<EuiSuperDatePicker
|
||||
start={currentTimeRange.from}
|
||||
end={currentTimeRange.to}
|
||||
start={currentTimeRange.from.toString()}
|
||||
end={currentTimeRange.to.toString()}
|
||||
isPaused={!isLiveStreaming}
|
||||
refreshInterval={refreshInterval ? refreshInterval : 0}
|
||||
onTimeChange={handleTimeChange}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const metricsQuery = gql`
|
||||
query MetricsQuery(
|
||||
$sourceId: ID!
|
||||
$timerange: InfraTimerangeInput!
|
||||
$metrics: [InfraMetric!]!
|
||||
$nodeId: ID!
|
||||
$cloudId: ID
|
||||
$nodeType: InfraNodeType!
|
||||
) {
|
||||
source(id: $sourceId) {
|
||||
id
|
||||
metrics(
|
||||
nodeIds: { nodeId: $nodeId, cloudId: $cloudId }
|
||||
timerange: $timerange
|
||||
metrics: $metrics
|
||||
nodeType: $nodeType
|
||||
) {
|
||||
id
|
||||
series {
|
||||
id
|
||||
label
|
||||
data {
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -1,201 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import createContainer from 'constate';
|
||||
import React, { useContext, useState, useCallback } from 'react';
|
||||
import { isNumber } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import * as rt from 'io-ts';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state';
|
||||
import { InfraTimerangeInput } from '../../../graphql/types';
|
||||
|
||||
export interface MetricsTimeInput {
|
||||
from: string;
|
||||
to: string;
|
||||
interval: string;
|
||||
}
|
||||
|
||||
interface MetricsTimeState {
|
||||
timeRange: MetricsTimeInput;
|
||||
parsedTimeRange: InfraTimerangeInput;
|
||||
setTimeRange: (timeRange: MetricsTimeInput) => void;
|
||||
refreshInterval: number;
|
||||
setRefreshInterval: (refreshInterval: number) => void;
|
||||
isAutoReloading: boolean;
|
||||
setAutoReload: (isAutoReloading: boolean) => void;
|
||||
lastRefresh: number;
|
||||
triggerRefresh: () => void;
|
||||
}
|
||||
|
||||
const parseRange = (range: MetricsTimeInput) => {
|
||||
const parsedFrom = dateMath.parse(range.from);
|
||||
const parsedTo = dateMath.parse(range.to, { roundUp: true });
|
||||
return {
|
||||
...range,
|
||||
from:
|
||||
(parsedFrom && parsedFrom.valueOf()) ||
|
||||
moment()
|
||||
.subtract(1, 'hour')
|
||||
.valueOf(),
|
||||
to: (parsedTo && parsedTo.valueOf()) || moment().valueOf(),
|
||||
};
|
||||
};
|
||||
|
||||
export const useMetricsTime = () => {
|
||||
const defaultRange = {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
interval: '>=1m',
|
||||
};
|
||||
const [isAutoReloading, setAutoReload] = useState(false);
|
||||
const [refreshInterval, setRefreshInterval] = useState(5000);
|
||||
const [lastRefresh, setLastRefresh] = useState<number>(moment().valueOf());
|
||||
const [timeRange, setTimeRange] = useState(defaultRange);
|
||||
|
||||
const [parsedTimeRange, setParsedTimeRange] = useState(parseRange(defaultRange));
|
||||
|
||||
const updateTimeRange = useCallback((range: MetricsTimeInput) => {
|
||||
setTimeRange(range);
|
||||
setParsedTimeRange(parseRange(range));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
timeRange,
|
||||
setTimeRange: updateTimeRange,
|
||||
parsedTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
lastRefresh,
|
||||
triggerRefresh: useCallback(() => setLastRefresh(moment().valueOf()), [setLastRefresh]),
|
||||
};
|
||||
};
|
||||
|
||||
export const MetricsTimeContainer = createContainer(useMetricsTime);
|
||||
|
||||
interface WithMetricsTimeProps {
|
||||
children: (args: MetricsTimeState) => React.ReactElement;
|
||||
}
|
||||
export const WithMetricsTime: React.FunctionComponent<WithMetricsTimeProps> = ({
|
||||
children,
|
||||
}: WithMetricsTimeProps) => {
|
||||
const metricsTimeState = useContext(MetricsTimeContainer.Context);
|
||||
return children({ ...metricsTimeState });
|
||||
};
|
||||
|
||||
/**
|
||||
* Url State
|
||||
*/
|
||||
|
||||
interface MetricsTimeUrlState {
|
||||
time?: MetricsTimeState['timeRange'];
|
||||
autoReload?: boolean;
|
||||
refreshInterval?: number;
|
||||
}
|
||||
|
||||
export const WithMetricsTimeUrlState = () => (
|
||||
<WithMetricsTime>
|
||||
{({
|
||||
timeRange,
|
||||
setTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
}) => (
|
||||
<UrlStateContainer
|
||||
urlState={{
|
||||
time: timeRange,
|
||||
autoReload: isAutoReloading,
|
||||
refreshInterval,
|
||||
}}
|
||||
urlStateKey="metricTime"
|
||||
mapToUrlState={mapToUrlState}
|
||||
onChange={newUrlState => {
|
||||
if (newUrlState && newUrlState.time) {
|
||||
setTimeRange(newUrlState.time);
|
||||
}
|
||||
if (newUrlState && newUrlState.autoReload) {
|
||||
setAutoReload(true);
|
||||
} else if (
|
||||
newUrlState &&
|
||||
typeof newUrlState.autoReload !== 'undefined' &&
|
||||
!newUrlState.autoReload
|
||||
) {
|
||||
setAutoReload(false);
|
||||
}
|
||||
if (newUrlState && newUrlState.refreshInterval) {
|
||||
setRefreshInterval(newUrlState.refreshInterval);
|
||||
}
|
||||
}}
|
||||
onInitialize={initialUrlState => {
|
||||
if (initialUrlState && initialUrlState.time) {
|
||||
if (
|
||||
timeRange.from !== initialUrlState.time.from ||
|
||||
timeRange.to !== initialUrlState.time.to ||
|
||||
timeRange.interval !== initialUrlState.time.interval
|
||||
) {
|
||||
setTimeRange(initialUrlState.time);
|
||||
}
|
||||
}
|
||||
if (initialUrlState && initialUrlState.autoReload) {
|
||||
setAutoReload(true);
|
||||
}
|
||||
if (initialUrlState && initialUrlState.refreshInterval) {
|
||||
setRefreshInterval(initialUrlState.refreshInterval);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</WithMetricsTime>
|
||||
);
|
||||
|
||||
const mapToUrlState = (value: any): MetricsTimeUrlState | undefined =>
|
||||
value
|
||||
? {
|
||||
time: mapToTimeUrlState(value.time),
|
||||
autoReload: mapToAutoReloadUrlState(value.autoReload),
|
||||
refreshInterval: mapToRefreshInterval(value.refreshInterval),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const MetricsTimeRT = rt.type({
|
||||
from: rt.union([rt.string, rt.number]),
|
||||
to: rt.union([rt.string, rt.number]),
|
||||
interval: rt.string,
|
||||
});
|
||||
|
||||
const mapToTimeUrlState = (value: any) => {
|
||||
const result = MetricsTimeRT.decode(value);
|
||||
if (isRight(result)) {
|
||||
const resultValue = result.right;
|
||||
const to = isNumber(resultValue.to) ? moment(resultValue.to).toISOString() : resultValue.to;
|
||||
const from = isNumber(resultValue.from)
|
||||
? moment(resultValue.from).toISOString()
|
||||
: resultValue.from;
|
||||
return { ...resultValue, from, to };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const mapToAutoReloadUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);
|
||||
|
||||
const mapToRefreshInterval = (value: any) => (typeof value === 'number' ? value : undefined);
|
||||
|
||||
export const replaceMetricTimeInQueryString = (from: number, to: number) =>
|
||||
Number.isNaN(from) || Number.isNaN(to)
|
||||
? (value: string) => value
|
||||
: replaceStateKeyInQueryString<MetricsTimeUrlState>('metricTime', {
|
||||
autoReload: false,
|
||||
time: {
|
||||
interval: '>=1m',
|
||||
from: moment(from).toISOString(),
|
||||
to: moment(to).toISOString(),
|
||||
},
|
||||
});
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { mountHook } from 'test_utils/enzyme_helpers';
|
||||
|
||||
import { useMetricsTime } from './with_metrics_time';
|
||||
import { useMetricsTime } from './use_metrics_time';
|
||||
|
||||
describe('useMetricsTime hook', () => {
|
||||
describe('timeRange state', () => {
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import createContainer from 'constate';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import moment from 'moment';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import * as rt from 'io-ts';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { constant, identity } from 'fp-ts/lib/function';
|
||||
import { useUrlState } from '../../../utils/use_url_state';
|
||||
import { replaceStateKeyInQueryString } from '../../../utils/url_state';
|
||||
|
||||
const parseRange = (range: MetricsTimeInput) => {
|
||||
const parsedFrom = dateMath.parse(range.from.toString());
|
||||
const parsedTo = dateMath.parse(range.to.toString(), { roundUp: true });
|
||||
return {
|
||||
...range,
|
||||
from:
|
||||
(parsedFrom && parsedFrom.valueOf()) ||
|
||||
moment()
|
||||
.subtract(1, 'hour')
|
||||
.valueOf(),
|
||||
to: (parsedTo && parsedTo.valueOf()) || moment().valueOf(),
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_TIMERANGE: MetricsTimeInput = {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
interval: '>=1m',
|
||||
};
|
||||
|
||||
const DEFAULT_URL_STATE: MetricsTimeUrlState = {
|
||||
time: DEFAULT_TIMERANGE,
|
||||
autoReload: false,
|
||||
refreshInterval: 5000,
|
||||
};
|
||||
|
||||
export const useMetricsTime = () => {
|
||||
const [urlState, setUrlState] = useUrlState<MetricsTimeUrlState>({
|
||||
defaultState: DEFAULT_URL_STATE,
|
||||
decodeUrlState,
|
||||
encodeUrlState,
|
||||
urlStateKey: 'metricTime',
|
||||
});
|
||||
|
||||
const [isAutoReloading, setAutoReload] = useState(urlState.autoReload || false);
|
||||
const [refreshInterval, setRefreshInterval] = useState(urlState.refreshInterval || 5000);
|
||||
const [lastRefresh, setLastRefresh] = useState<number>(moment().valueOf());
|
||||
const [timeRange, setTimeRange] = useState(urlState.time || DEFAULT_TIMERANGE);
|
||||
|
||||
useEffect(() => {
|
||||
const newState = {
|
||||
time: timeRange,
|
||||
autoReload: isAutoReloading,
|
||||
refreshInterval,
|
||||
};
|
||||
return setUrlState(newState);
|
||||
}, [isAutoReloading, refreshInterval, setUrlState, timeRange]);
|
||||
|
||||
const [parsedTimeRange, setParsedTimeRange] = useState(
|
||||
parseRange(urlState.time || DEFAULT_TIMERANGE)
|
||||
);
|
||||
|
||||
const updateTimeRange = useCallback((range: MetricsTimeInput) => {
|
||||
setTimeRange(range);
|
||||
setParsedTimeRange(parseRange(range));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
timeRange,
|
||||
setTimeRange: updateTimeRange,
|
||||
parsedTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
lastRefresh,
|
||||
triggerRefresh: useCallback(() => {
|
||||
return setLastRefresh(moment().valueOf());
|
||||
}, [setLastRefresh]),
|
||||
};
|
||||
};
|
||||
|
||||
export const MetricsTimeInputRT = rt.type({
|
||||
from: rt.union([rt.string, rt.number]),
|
||||
to: rt.union([rt.string, rt.number]),
|
||||
interval: rt.string,
|
||||
});
|
||||
export type MetricsTimeInput = rt.TypeOf<typeof MetricsTimeInputRT>;
|
||||
|
||||
export const MetricsTimeUrlStateRT = rt.partial({
|
||||
time: MetricsTimeInputRT,
|
||||
autoReload: rt.boolean,
|
||||
refreshInterval: rt.number,
|
||||
});
|
||||
export type MetricsTimeUrlState = rt.TypeOf<typeof MetricsTimeUrlStateRT>;
|
||||
|
||||
const encodeUrlState = MetricsTimeUrlStateRT.encode;
|
||||
const decodeUrlState = (value: unknown) =>
|
||||
pipe(MetricsTimeUrlStateRT.decode(value), fold(constant(undefined), identity));
|
||||
|
||||
export const replaceMetricTimeInQueryString = (from: number, to: number) =>
|
||||
Number.isNaN(from) || Number.isNaN(to)
|
||||
? (value: string) => value
|
||||
: replaceStateKeyInQueryString<MetricsTimeUrlState>('metricTime', {
|
||||
autoReload: false,
|
||||
time: {
|
||||
interval: '>=1m',
|
||||
from: moment(from).toISOString(),
|
||||
to: moment(to).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
export const MetricsTimeContainer = createContainer(useMetricsTime);
|
||||
export const [MetricsTimeProvider, useMetricsTimeContext] = MetricsTimeContainer;
|
|
@ -9,7 +9,6 @@ import { euiStyled, EuiTheme, withTheme } from '../../../../observability/public
|
|||
import { DocumentTitle } from '../../components/document_title';
|
||||
import { Header } from '../../components/header';
|
||||
import { ColumnarPage, PageContent } from '../../components/page';
|
||||
import { WithMetricsTime, WithMetricsTimeUrlState } from './containers/with_metrics_time';
|
||||
import { withMetricPageProviders } from './page_providers';
|
||||
import { useMetadata } from '../../containers/metadata/use_metadata';
|
||||
import { Source } from '../../containers/source';
|
||||
|
@ -19,6 +18,7 @@ import { NavItem } from './lib/side_nav_context';
|
|||
import { NodeDetailsPage } from './components/node_details_page';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { useMetricsTimeContext } from './hooks/use_metrics_time';
|
||||
import { useLinkProps } from '../../hooks/use_link_props';
|
||||
|
||||
const DetailPageContent = euiStyled(PageContent)`
|
||||
|
@ -37,19 +37,29 @@ interface Props {
|
|||
}
|
||||
|
||||
export const MetricDetail = withMetricPageProviders(
|
||||
withTheme(({ match, theme }: Props) => {
|
||||
withTheme(({ match }: Props) => {
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
const nodeId = match.params.node;
|
||||
const nodeType = match.params.type as InventoryItemType;
|
||||
const inventoryModel = findInventoryModel(nodeType);
|
||||
const { sourceId } = useContext(Source.Context);
|
||||
const {
|
||||
timeRange,
|
||||
parsedTimeRange,
|
||||
setTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
triggerRefresh,
|
||||
} = useMetricsTimeContext();
|
||||
const {
|
||||
name,
|
||||
filteredRequiredMetrics,
|
||||
loading: metadataLoading,
|
||||
cloudId,
|
||||
metadata,
|
||||
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId);
|
||||
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, parsedTimeRange);
|
||||
|
||||
const [sideNav, setSideNav] = useState<NavItem[]>([]);
|
||||
|
||||
|
@ -90,58 +100,41 @@ export const MetricDetail = withMetricPageProviders(
|
|||
}
|
||||
|
||||
return (
|
||||
<WithMetricsTime>
|
||||
{({
|
||||
timeRange,
|
||||
parsedTimeRange,
|
||||
setTimeRange,
|
||||
refreshInterval,
|
||||
setRefreshInterval,
|
||||
isAutoReloading,
|
||||
setAutoReload,
|
||||
triggerRefresh,
|
||||
}) => (
|
||||
<ColumnarPage>
|
||||
<Header
|
||||
breadcrumbs={breadcrumbs}
|
||||
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
|
||||
<ColumnarPage>
|
||||
<Header breadcrumbs={breadcrumbs} readOnlyBadge={!uiCapabilities?.infrastructure?.save} />
|
||||
<DocumentTitle
|
||||
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
|
||||
defaultMessage: 'Infrastructure | Metrics | {name}',
|
||||
values: {
|
||||
name,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<DetailPageContent data-test-subj="infraMetricsPage">
|
||||
{metadata ? (
|
||||
<NodeDetailsPage
|
||||
name={name}
|
||||
requiredMetrics={filteredRequiredMetrics}
|
||||
sourceId={sourceId}
|
||||
timeRange={timeRange}
|
||||
parsedTimeRange={parsedTimeRange}
|
||||
nodeType={nodeType}
|
||||
nodeId={nodeId}
|
||||
cloudId={cloudId}
|
||||
metadataLoading={metadataLoading}
|
||||
isAutoReloading={isAutoReloading}
|
||||
refreshInterval={refreshInterval}
|
||||
sideNav={sideNav}
|
||||
metadata={metadata}
|
||||
addNavItem={addNavItem}
|
||||
setRefreshInterval={setRefreshInterval}
|
||||
setAutoReload={setAutoReload}
|
||||
triggerRefresh={triggerRefresh}
|
||||
setTimeRange={setTimeRange}
|
||||
/>
|
||||
<WithMetricsTimeUrlState />
|
||||
<DocumentTitle
|
||||
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
|
||||
defaultMessage: 'Infrastructure | Metrics | {name}',
|
||||
values: {
|
||||
name,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<DetailPageContent data-test-subj="infraMetricsPage">
|
||||
{metadata ? (
|
||||
<NodeDetailsPage
|
||||
name={name}
|
||||
requiredMetrics={filteredRequiredMetrics}
|
||||
sourceId={sourceId}
|
||||
timeRange={timeRange}
|
||||
parsedTimeRange={parsedTimeRange}
|
||||
nodeType={nodeType}
|
||||
nodeId={nodeId}
|
||||
cloudId={cloudId}
|
||||
metadataLoading={metadataLoading}
|
||||
isAutoReloading={isAutoReloading}
|
||||
refreshInterval={refreshInterval}
|
||||
sideNav={sideNav}
|
||||
metadata={metadata}
|
||||
addNavItem={addNavItem}
|
||||
setRefreshInterval={setRefreshInterval}
|
||||
setAutoReload={setAutoReload}
|
||||
triggerRefresh={triggerRefresh}
|
||||
setTimeRange={setTimeRange}
|
||||
/>
|
||||
) : null}
|
||||
</DetailPageContent>
|
||||
</ColumnarPage>
|
||||
)}
|
||||
</WithMetricsTime>
|
||||
) : null}
|
||||
</DetailPageContent>
|
||||
</ColumnarPage>
|
||||
);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { MetricsTimeContainer } from './containers/with_metrics_time';
|
||||
import { Source } from '../../containers/source';
|
||||
import { MetricsTimeProvider } from './hooks/use_metrics_time';
|
||||
|
||||
export const withMetricPageProviders = <T extends object>(Component: React.ComponentType<T>) => (
|
||||
props: T
|
||||
) => (
|
||||
<Source.Provider sourceId="default">
|
||||
<MetricsTimeContainer.Provider>
|
||||
<MetricsTimeProvider>
|
||||
<Component {...props} />
|
||||
</MetricsTimeContainer.Provider>
|
||||
</MetricsTimeProvider>
|
||||
</Source.Provider>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import rt from 'io-ts';
|
||||
import { EuiTheme } from '../../../../observability/public';
|
||||
import { InventoryFormatterTypeRT } from '../../../common/inventory_models/types';
|
||||
import { MetricsTimeInput } from './containers/with_metrics_time';
|
||||
import { MetricsTimeInput } from './hooks/use_metrics_time';
|
||||
import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api';
|
||||
|
||||
export interface LayoutProps {
|
||||
|
|
|
@ -1,7 +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.
|
||||
*/
|
||||
|
||||
export { waffleFilterActions, waffleTimeActions, waffleOptionsActions } from './local';
|
|
@ -1,11 +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 { combineEpics } from 'redux-observable';
|
||||
|
||||
import { createLocalEpic } from './local';
|
||||
|
||||
export const createRootEpic = <State>() => combineEpics(createLocalEpic<State>());
|
|
@ -1,11 +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.
|
||||
*/
|
||||
|
||||
export * from './actions';
|
||||
export * from './epics';
|
||||
export * from './reducer';
|
||||
export * from './selectors';
|
||||
export { createStore } from './store';
|
|
@ -1,9 +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.
|
||||
*/
|
||||
|
||||
export { waffleFilterActions } from './waffle_filter';
|
||||
export { waffleTimeActions } from './waffle_time';
|
||||
export { waffleOptionsActions } from './waffle_options';
|
|
@ -1,11 +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 { combineEpics } from 'redux-observable';
|
||||
|
||||
import { createWaffleTimeEpic } from './waffle_time';
|
||||
|
||||
export const createLocalEpic = <State>() => combineEpics(createWaffleTimeEpic<State>());
|
|
@ -1,10 +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.
|
||||
*/
|
||||
|
||||
export * from './actions';
|
||||
export * from './epic';
|
||||
export * from './reducer';
|
||||
export * from './selectors';
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter';
|
||||
import {
|
||||
initialWaffleOptionsState,
|
||||
waffleOptionsReducer,
|
||||
WaffleOptionsState,
|
||||
} from './waffle_options';
|
||||
import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time';
|
||||
|
||||
export interface LocalState {
|
||||
waffleFilter: WaffleFilterState;
|
||||
waffleTime: WaffleTimeState;
|
||||
waffleMetrics: WaffleOptionsState;
|
||||
}
|
||||
|
||||
export const initialLocalState: LocalState = {
|
||||
waffleFilter: initialWaffleFilterState,
|
||||
waffleTime: initialWaffleTimeState,
|
||||
waffleMetrics: initialWaffleOptionsState,
|
||||
};
|
||||
|
||||
export const localReducer = combineReducers<LocalState>({
|
||||
waffleFilter: waffleFilterReducer,
|
||||
waffleTime: waffleTimeReducer,
|
||||
waffleMetrics: waffleOptionsReducer,
|
||||
});
|
|
@ -1,26 +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 { globalizeSelectors } from '../../utils/typed_redux';
|
||||
import { LocalState } from './reducer';
|
||||
import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter';
|
||||
import { waffleOptionsSelectors as innerWaffleOptionsSelectors } from './waffle_options';
|
||||
import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time';
|
||||
|
||||
export const waffleFilterSelectors = globalizeSelectors(
|
||||
(state: LocalState) => state.waffleFilter,
|
||||
innerWaffleFilterSelectors
|
||||
);
|
||||
|
||||
export const waffleTimeSelectors = globalizeSelectors(
|
||||
(state: LocalState) => state.waffleTime,
|
||||
innerWaffleTimeSelectors
|
||||
);
|
||||
|
||||
export const waffleOptionsSelectors = globalizeSelectors(
|
||||
(state: LocalState) => state.waffleMetrics,
|
||||
innerWaffleOptionsSelectors
|
||||
);
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import actionCreatorFactory from 'typescript-fsa';
|
||||
|
||||
import { FilterQuery, SerializedFilterQuery } from './reducer';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_filter');
|
||||
|
||||
export const setWaffleFilterQueryDraft = actionCreator<FilterQuery>(
|
||||
'SET_WAFFLE_FILTER_QUERY_DRAFT'
|
||||
);
|
||||
|
||||
export const applyWaffleFilterQuery = actionCreator<SerializedFilterQuery>(
|
||||
'APPLY_WAFFLE_FILTER_QUERY'
|
||||
);
|
|
@ -1,11 +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 waffleFilterActions from './actions';
|
||||
import * as waffleFilterSelectors from './selectors';
|
||||
|
||||
export { waffleFilterActions, waffleFilterSelectors };
|
||||
export * from './reducer';
|
|
@ -1,43 +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 { reducerWithInitialState } from 'typescript-fsa-reducers/dist';
|
||||
|
||||
import { applyWaffleFilterQuery, setWaffleFilterQueryDraft } from './actions';
|
||||
|
||||
export interface KueryFilterQuery {
|
||||
kind: 'kuery';
|
||||
expression: string;
|
||||
}
|
||||
|
||||
export type FilterQuery = KueryFilterQuery;
|
||||
|
||||
export interface SerializedFilterQuery {
|
||||
query: FilterQuery | null;
|
||||
serializedQuery: string | null;
|
||||
}
|
||||
|
||||
export interface WaffleFilterState {
|
||||
filterQuery: SerializedFilterQuery | null;
|
||||
filterQueryDraft: KueryFilterQuery | null;
|
||||
}
|
||||
|
||||
export const initialWaffleFilterState: WaffleFilterState = {
|
||||
filterQuery: null,
|
||||
filterQueryDraft: null,
|
||||
};
|
||||
|
||||
export const waffleFilterReducer = reducerWithInitialState(initialWaffleFilterState)
|
||||
.case(setWaffleFilterQueryDraft, (state, filterQueryDraft) => ({
|
||||
...state,
|
||||
filterQueryDraft,
|
||||
}))
|
||||
.case(applyWaffleFilterQuery, (state, filterQuery) => ({
|
||||
...state,
|
||||
filterQuery,
|
||||
filterQueryDraft: filterQuery.query,
|
||||
}))
|
||||
.build();
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { esKuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { WaffleFilterState } from './reducer';
|
||||
|
||||
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 selectIsWaffleFilterQueryDraftValid = createSelector(
|
||||
selectWaffleFilterQueryDraft,
|
||||
filterQueryDraft => {
|
||||
if (filterQueryDraft && filterQueryDraft.kind === 'kuery') {
|
||||
try {
|
||||
esKuery.fromKueryExpression(filterQueryDraft.expression);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
|
@ -1,29 +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 actionCreatorFactory from 'typescript-fsa';
|
||||
import {
|
||||
SnapshotGroupBy,
|
||||
SnapshotMetricInput,
|
||||
SnapshotCustomMetricInput,
|
||||
} from '../../../../common/http_api/snapshot_api';
|
||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
||||
import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_options');
|
||||
|
||||
export const changeMetric = actionCreator<SnapshotMetricInput>('CHANGE_METRIC');
|
||||
export const changeGroupBy = actionCreator<SnapshotGroupBy>('CHANGE_GROUP_BY');
|
||||
export const changeCustomOptions = actionCreator<InfraGroupByOptions[]>('CHANGE_CUSTOM_OPTIONS');
|
||||
export const changeNodeType = actionCreator<InventoryItemType>('CHANGE_NODE_TYPE');
|
||||
export const changeView = actionCreator<string>('CHANGE_VIEW');
|
||||
export const changeBoundsOverride = actionCreator<InfraWaffleMapBounds>('CHANGE_BOUNDS_OVERRIDE');
|
||||
export const changeAutoBounds = actionCreator<boolean>('CHANGE_AUTO_BOUNDS');
|
||||
export const changeAccount = actionCreator<string>('CHANGE_ACCOUNT');
|
||||
export const changeRegion = actionCreator<string>('CHANGE_REGION');
|
||||
export const changeCustomMetrics = actionCreator<SnapshotCustomMetricInput[]>(
|
||||
'CHANGE_CUSTOM_METRICS'
|
||||
);
|
|
@ -1,11 +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 waffleOptionsActions from './actions';
|
||||
import * as waffleOptionsSelectors from './selector';
|
||||
|
||||
export { waffleOptionsActions, waffleOptionsSelectors };
|
||||
export * from './reducer';
|
|
@ -1,113 +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 { combineReducers } from 'redux';
|
||||
import { reducerWithInitialState } from 'typescript-fsa-reducers';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotGroupBy,
|
||||
SnapshotCustomMetricInput,
|
||||
} from '../../../../common/http_api/snapshot_api';
|
||||
import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib';
|
||||
import {
|
||||
changeAutoBounds,
|
||||
changeBoundsOverride,
|
||||
changeCustomOptions,
|
||||
changeGroupBy,
|
||||
changeMetric,
|
||||
changeNodeType,
|
||||
changeView,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomMetrics,
|
||||
} from './actions';
|
||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
||||
|
||||
export interface WaffleOptionsState {
|
||||
metric: SnapshotMetricInput;
|
||||
groupBy: SnapshotGroupBy;
|
||||
nodeType: InventoryItemType;
|
||||
view: string;
|
||||
customOptions: InfraGroupByOptions[];
|
||||
boundsOverride: InfraWaffleMapBounds;
|
||||
autoBounds: boolean;
|
||||
accountId: string;
|
||||
region: string;
|
||||
customMetrics: SnapshotCustomMetricInput[];
|
||||
}
|
||||
|
||||
export const initialWaffleOptionsState: WaffleOptionsState = {
|
||||
metric: { type: 'cpu' },
|
||||
groupBy: [],
|
||||
nodeType: 'host',
|
||||
view: 'map',
|
||||
customOptions: [],
|
||||
boundsOverride: { max: 1, min: 0 },
|
||||
autoBounds: true,
|
||||
accountId: '',
|
||||
region: '',
|
||||
customMetrics: [],
|
||||
};
|
||||
|
||||
const currentMetricReducer = reducerWithInitialState(initialWaffleOptionsState.metric).case(
|
||||
changeMetric,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentCustomOptionsReducer = reducerWithInitialState(
|
||||
initialWaffleOptionsState.customOptions
|
||||
).case(changeCustomOptions, (current, target) => target);
|
||||
|
||||
const currentGroupByReducer = reducerWithInitialState(initialWaffleOptionsState.groupBy).case(
|
||||
changeGroupBy,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentNodeTypeReducer = reducerWithInitialState(initialWaffleOptionsState.nodeType).case(
|
||||
changeNodeType,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentViewReducer = reducerWithInitialState(initialWaffleOptionsState.view).case(
|
||||
changeView,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentBoundsOverrideReducer = reducerWithInitialState(
|
||||
initialWaffleOptionsState.boundsOverride
|
||||
).case(changeBoundsOverride, (current, target) => target);
|
||||
|
||||
const currentAutoBoundsReducer = reducerWithInitialState(initialWaffleOptionsState.autoBounds).case(
|
||||
changeAutoBounds,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentAccountIdReducer = reducerWithInitialState(initialWaffleOptionsState.accountId).case(
|
||||
changeAccount,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentRegionReducer = reducerWithInitialState(initialWaffleOptionsState.region).case(
|
||||
changeRegion,
|
||||
(current, target) => target
|
||||
);
|
||||
|
||||
const currentCustomMetricsReducer = reducerWithInitialState(
|
||||
initialWaffleOptionsState.customMetrics
|
||||
).case(changeCustomMetrics, (current, target) => target);
|
||||
|
||||
export const waffleOptionsReducer = combineReducers<WaffleOptionsState>({
|
||||
metric: currentMetricReducer,
|
||||
groupBy: currentGroupByReducer,
|
||||
nodeType: currentNodeTypeReducer,
|
||||
view: currentViewReducer,
|
||||
customOptions: currentCustomOptionsReducer,
|
||||
boundsOverride: currentBoundsOverrideReducer,
|
||||
autoBounds: currentAutoBoundsReducer,
|
||||
accountId: currentAccountIdReducer,
|
||||
region: currentRegionReducer,
|
||||
customMetrics: currentCustomMetricsReducer,
|
||||
});
|
|
@ -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 { WaffleOptionsState } from './reducer';
|
||||
|
||||
export const selectMetric = (state: WaffleOptionsState) => state.metric;
|
||||
export const selectGroupBy = (state: WaffleOptionsState) => state.groupBy;
|
||||
export const selectCustomOptions = (state: WaffleOptionsState) => state.customOptions;
|
||||
export const selectNodeType = (state: WaffleOptionsState) => state.nodeType;
|
||||
export const selectView = (state: WaffleOptionsState) => state.view;
|
||||
export const selectBoundsOverride = (state: WaffleOptionsState) => state.boundsOverride;
|
||||
export const selectAutoBounds = (state: WaffleOptionsState) => state.autoBounds;
|
||||
export const selectAccountId = (state: WaffleOptionsState) => state.accountId;
|
||||
export const selectRegion = (state: WaffleOptionsState) => state.region;
|
||||
export const selectCustomMetrics = (state: WaffleOptionsState) => state.customMetrics;
|
|
@ -1,15 +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 actionCreatorFactory from 'typescript-fsa';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_time');
|
||||
|
||||
export const jumpToTime = actionCreator<number>('JUMP_TO_TIME');
|
||||
|
||||
export const startAutoReload = actionCreator('START_AUTO_RELOAD');
|
||||
|
||||
export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD');
|
|
@ -1,38 +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 { Epic } from 'redux-observable';
|
||||
import { timer } from 'rxjs';
|
||||
import { exhaustMap, filter, map, takeUntil, withLatestFrom } from 'rxjs/operators';
|
||||
|
||||
import { jumpToTime, startAutoReload, stopAutoReload } from './actions';
|
||||
|
||||
interface WaffleTimeEpicDependencies<State> {
|
||||
selectWaffleTimeUpdatePolicyInterval: (state: State) => number | null;
|
||||
}
|
||||
|
||||
export const createWaffleTimeEpic = <State>(): Epic<
|
||||
Action,
|
||||
Action,
|
||||
State,
|
||||
WaffleTimeEpicDependencies<State>
|
||||
> => (action$, state$, { selectWaffleTimeUpdatePolicyInterval }) => {
|
||||
const updateInterval$ = state$.pipe(map(selectWaffleTimeUpdatePolicyInterval), filter(isNotNull));
|
||||
|
||||
return action$.pipe(
|
||||
filter(startAutoReload.match),
|
||||
withLatestFrom(updateInterval$),
|
||||
exhaustMap(([action, updateInterval]) =>
|
||||
timer(0, updateInterval).pipe(
|
||||
map(() => jumpToTime(Date.now())),
|
||||
takeUntil(action$.pipe(filter(stopAutoReload.match)))
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const isNotNull = <T>(value: T | null): value is T => value !== null;
|
|
@ -1,12 +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 waffleTimeActions from './actions';
|
||||
import * as waffleTimeSelectors from './selectors';
|
||||
|
||||
export { waffleTimeActions, waffleTimeSelectors };
|
||||
export * from './epic';
|
||||
export * from './reducer';
|
|
@ -1,52 +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 { combineReducers } from 'redux';
|
||||
import { reducerWithInitialState } from 'typescript-fsa-reducers/dist';
|
||||
|
||||
import { jumpToTime, startAutoReload, stopAutoReload } from './actions';
|
||||
|
||||
interface ManualTimeUpdatePolicy {
|
||||
policy: 'manual';
|
||||
}
|
||||
|
||||
interface IntervalTimeUpdatePolicy {
|
||||
policy: 'interval';
|
||||
interval: number;
|
||||
}
|
||||
|
||||
type TimeUpdatePolicy = ManualTimeUpdatePolicy | IntervalTimeUpdatePolicy;
|
||||
|
||||
export interface WaffleTimeState {
|
||||
currentTime: number;
|
||||
updatePolicy: TimeUpdatePolicy;
|
||||
}
|
||||
|
||||
export const initialWaffleTimeState: WaffleTimeState = {
|
||||
currentTime: Date.now(),
|
||||
updatePolicy: {
|
||||
policy: 'manual',
|
||||
},
|
||||
};
|
||||
|
||||
const currentTimeReducer = reducerWithInitialState(initialWaffleTimeState.currentTime).case(
|
||||
jumpToTime,
|
||||
(currentTime, targetTime) => targetTime
|
||||
);
|
||||
|
||||
const updatePolicyReducer = reducerWithInitialState(initialWaffleTimeState.updatePolicy)
|
||||
.case(startAutoReload, () => ({
|
||||
policy: 'interval',
|
||||
interval: 5000,
|
||||
}))
|
||||
.case(stopAutoReload, () => ({
|
||||
policy: 'manual',
|
||||
}));
|
||||
|
||||
export const waffleTimeReducer = combineReducers<WaffleTimeState>({
|
||||
currentTime: currentTimeReducer,
|
||||
updatePolicy: updatePolicyReducer,
|
||||
});
|
|
@ -1,23 +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 { WaffleTimeState } from './reducer';
|
||||
|
||||
export const selectCurrentTime = (state: WaffleTimeState) => state.currentTime;
|
||||
|
||||
export const selectIsAutoReloading = (state: WaffleTimeState) =>
|
||||
state.updatePolicy.policy === 'interval';
|
||||
|
||||
export const selectTimeUpdatePolicyInterval = (state: WaffleTimeState) =>
|
||||
state.updatePolicy.policy === 'interval' ? state.updatePolicy.interval : null;
|
||||
|
||||
export const selectCurrentTimeRange = createSelector(selectCurrentTime, currentTime => ({
|
||||
from: currentTime - 1000 * 60 * 5,
|
||||
interval: '1m',
|
||||
to: currentTime,
|
||||
}));
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import { initialLocalState, localReducer, LocalState } from './local';
|
||||
|
||||
export interface State {
|
||||
local: LocalState;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
local: initialLocalState,
|
||||
};
|
||||
|
||||
export const reducer = combineReducers<State>({
|
||||
local: localReducer,
|
||||
});
|
|
@ -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 { globalizeSelectors } from '../utils/typed_redux';
|
||||
import {
|
||||
waffleFilterSelectors as localWaffleFilterSelectors,
|
||||
waffleOptionsSelectors as localWaffleOptionsSelectors,
|
||||
waffleTimeSelectors as localWaffleTimeSelectors,
|
||||
} from './local';
|
||||
import { State } from './reducer';
|
||||
/**
|
||||
* local selectors
|
||||
*/
|
||||
|
||||
const selectLocal = (state: State) => state.local;
|
||||
|
||||
export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors);
|
||||
export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors);
|
||||
export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors);
|
|
@ -1,50 +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, applyMiddleware, compose, createStore as createBasicStore } from 'redux';
|
||||
import { createEpicMiddleware } from 'redux-observable';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { createRootEpic, initialState, reducer, State, waffleTimeSelectors } from '.';
|
||||
import { InfraApolloClient, InfraObservableApi } from '../lib/lib';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose;
|
||||
}
|
||||
}
|
||||
|
||||
export interface StoreDependencies {
|
||||
apolloClient: Observable<InfraApolloClient>;
|
||||
observableApi: Observable<InfraObservableApi>;
|
||||
}
|
||||
|
||||
export function createStore({ apolloClient, observableApi }: StoreDependencies) {
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
const middlewareDependencies = {
|
||||
postToApi$: observableApi.pipe(map(({ post }) => post)),
|
||||
apolloClient$: apolloClient,
|
||||
selectWaffleTimeUpdatePolicyInterval: waffleTimeSelectors.selectTimeUpdatePolicyInterval,
|
||||
};
|
||||
|
||||
const epicMiddleware = createEpicMiddleware<Action, Action, State, typeof middlewareDependencies>(
|
||||
{
|
||||
dependencies: middlewareDependencies,
|
||||
}
|
||||
);
|
||||
|
||||
const store = createBasicStore(
|
||||
reducer,
|
||||
initialState,
|
||||
composeEnhancers(applyMiddleware(epicMiddleware))
|
||||
);
|
||||
|
||||
epicMiddleware.run(createRootEpic<State>());
|
||||
|
||||
return store;
|
||||
}
|
|
@ -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 { useSelector } from 'react-redux';
|
||||
import React, { createContext } from 'react';
|
||||
import { State, initialState } from '../store';
|
||||
|
||||
export const ReduxStateContext = createContext(initialState);
|
||||
|
||||
export const ReduxStateContextProvider = ({ children }: { children: JSX.Element }) => {
|
||||
const state = useSelector((store: State) => store);
|
||||
return <ReduxStateContext.Provider value={state}>{children}</ReduxStateContext.Provider>;
|
||||
};
|
|
@ -38,7 +38,7 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
|||
},
|
||||
async (requestContext, request, response) => {
|
||||
try {
|
||||
const { nodeId, nodeType, sourceId } = pipe(
|
||||
const { nodeId, nodeType, sourceId, timeRange } = pipe(
|
||||
InfraMetadataRequestRT.decode(request.body),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
@ -52,7 +52,8 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
|||
requestContext,
|
||||
configuration,
|
||||
nodeId,
|
||||
nodeType
|
||||
nodeType,
|
||||
timeRange
|
||||
);
|
||||
const metricFeatures = pickFeatureName(metricsMetadata.buckets).map(
|
||||
nameToFeature('metrics')
|
||||
|
@ -62,7 +63,13 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
|||
const cloudInstanceId = get<string>(info, 'cloud.instance.id');
|
||||
|
||||
const cloudMetricsMetadata = cloudInstanceId
|
||||
? await getCloudMetricsMetadata(framework, requestContext, configuration, cloudInstanceId)
|
||||
? await getCloudMetricsMetadata(
|
||||
framework,
|
||||
requestContext,
|
||||
configuration,
|
||||
cloudInstanceId,
|
||||
timeRange
|
||||
)
|
||||
: { buckets: [] };
|
||||
const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map(
|
||||
nameToFeature('metrics')
|
||||
|
|
|
@ -21,7 +21,8 @@ export const getCloudMetricsMetadata = async (
|
|||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
instanceId: string
|
||||
instanceId: string,
|
||||
timeRange: { from: number; to: number }
|
||||
): Promise<InfraCloudMetricsAdapterResponse> => {
|
||||
const metricQuery = {
|
||||
allowNoIndices: true,
|
||||
|
@ -30,7 +31,18 @@ export const getCloudMetricsMetadata = async (
|
|||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [{ match: { 'cloud.instance.id': instanceId } }],
|
||||
filter: [
|
||||
{ match: { 'cloud.instance.id': instanceId } },
|
||||
{
|
||||
range: {
|
||||
[sourceConfiguration.fields.timestamp]: {
|
||||
gte: timeRange.from,
|
||||
lte: timeRange.to,
|
||||
format: 'epoch_millis',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
should: CLOUD_METRICS_MODULES.map(module => ({ match: { 'event.module': module } })),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,7 +26,8 @@ export const getMetricMetadata = async (
|
|||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
nodeId: string,
|
||||
nodeType: InventoryItemType
|
||||
nodeType: InventoryItemType,
|
||||
timeRange: { from: number; to: number }
|
||||
): Promise<InfraMetricsAdapterResponse> => {
|
||||
const fields = findInventoryFields(nodeType, sourceConfiguration.fields);
|
||||
const metricQuery = {
|
||||
|
@ -41,6 +42,15 @@ export const getMetricMetadata = async (
|
|||
{
|
||||
match: { [fields.id]: nodeId },
|
||||
},
|
||||
{
|
||||
range: {
|
||||
[sourceConfiguration.fields.timestamp]: {
|
||||
gte: timeRange.from,
|
||||
lte: timeRange.to,
|
||||
format: 'epoch_millis',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,6 +12,28 @@ import {
|
|||
} from '../../../../plugins/infra/common/http_api/metadata_api';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
import { DATES } from './constants';
|
||||
|
||||
const timeRange700 = {
|
||||
from: DATES['7.0.0'].hosts.min,
|
||||
to: DATES[`7.0.0`].hosts.max,
|
||||
};
|
||||
|
||||
const timeRange660 = {
|
||||
from: DATES['6.6.0'].docker.min,
|
||||
to: DATES[`6.6.0`].docker.max,
|
||||
};
|
||||
|
||||
const timeRange800withAws = {
|
||||
from: DATES['8.0.0'].logs_and_metrics_with_aws.min,
|
||||
to: DATES[`8.0.0`].logs_and_metrics_with_aws.max,
|
||||
};
|
||||
|
||||
const timeRange800 = {
|
||||
from: DATES['8.0.0'].logs_and_metrics.min,
|
||||
to: DATES[`8.0.0`].logs_and_metrics.max,
|
||||
};
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const supertest = getService('supertest');
|
||||
|
@ -34,6 +56,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: 'demo-stack-mysql-01',
|
||||
nodeType: InfraNodeType.host,
|
||||
timeRange: timeRange700,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(metadata.features.length).to.be(12);
|
||||
|
@ -53,6 +76,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: '631f36a845514442b93c3fdd2dc91bcd8feb680b8ac5832c7fb8fdc167bb938e',
|
||||
nodeType: InfraNodeType.container,
|
||||
timeRange: timeRange660,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(metadata.features.length).to.be(10);
|
||||
|
@ -74,6 +98,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc',
|
||||
nodeType: InfraNodeType.host,
|
||||
timeRange: timeRange800withAws,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(metadata.features.length).to.be(58);
|
||||
|
@ -114,6 +139,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: 'ip-172-31-47-9.us-east-2.compute.internal',
|
||||
nodeType: InfraNodeType.host,
|
||||
timeRange: timeRange800withAws,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(metadata.features.length).to.be(19);
|
||||
|
@ -155,6 +181,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: '14887487-99f8-11e9-9a96-42010a84004d',
|
||||
nodeType: InfraNodeType.pod,
|
||||
timeRange: timeRange800withAws,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(metadata.features.length).to.be(29);
|
||||
|
@ -200,6 +227,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: 'c74b04834c6d7cc1800c3afbe31d0c8c0c267f06e9eb45c2b0c2df3e6cee40c5',
|
||||
nodeType: InfraNodeType.container,
|
||||
timeRange: timeRange800withAws,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(metadata.features.length).to.be(26);
|
||||
|
@ -251,6 +279,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc',
|
||||
nodeType: 'host',
|
||||
timeRange: timeRange800,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(
|
||||
|
@ -265,6 +294,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
sourceId: 'default',
|
||||
nodeId: 'c1031331-9ae0-11e9-9a96-42010a84004d',
|
||||
nodeType: 'pod',
|
||||
timeRange: timeRange800,
|
||||
});
|
||||
if (metadata) {
|
||||
expect(
|
||||
|
|
2
x-pack/typings/rison_node.d.ts
vendored
2
x-pack/typings/rison_node.d.ts
vendored
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
declare module 'rison-node' {
|
||||
export type RisonValue = null | boolean | number | string | RisonObject | RisonArray;
|
||||
export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface RisonArray extends Array<RisonValue> {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue