mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 10:23:14 -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+@/, '//');
|
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
|
const navSuccessful = currentUrl
|
||||||
// Visualize so we can create the next Vertical Bar Chart, but we can see from the
|
.replace(':80/', '/')
|
||||||
// logging and the screenshot that it's still on the TileMap page. Why didn't the "get"
|
.replace(':443/', '/')
|
||||||
// with a new timestamped URL go? I thought that sleep(700) between the get and the
|
.startsWith(appUrl);
|
||||||
// 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);
|
|
||||||
|
|
||||||
if (!navSuccessful) {
|
if (!navSuccessful) {
|
||||||
const msg = `App failed to load: ${appName} in ${defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`;
|
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' {
|
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
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface RisonArray extends Array<RisonValue> {}
|
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' {
|
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
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface RisonArray extends Array<RisonValue> {}
|
export interface RisonArray extends Array<RisonValue> {}
|
||||||
|
|
|
@ -11,6 +11,10 @@ export const InfraMetadataRequestRT = rt.type({
|
||||||
nodeId: rt.string,
|
nodeId: rt.string,
|
||||||
nodeType: ItemTypeRT,
|
nodeType: ItemTypeRT,
|
||||||
sourceId: rt.string,
|
sourceId: rt.string,
|
||||||
|
timeRange: rt.type({
|
||||||
|
from: rt.number,
|
||||||
|
to: rt.number,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const InfraMetadataFeatureRT = rt.type({
|
export const InfraMetadataFeatureRT = rt.type({
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { withTheme } from '../../../../observability/public';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { MetadataDetails } from '../../../public/pages/metrics/components/metadata_details';
|
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>
|
<React.Fragment>
|
||||||
<MetadataDetails
|
<MetadataDetails
|
||||||
fields={[
|
fields={[
|
||||||
|
@ -42,6 +42,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection
|
<SubSection
|
||||||
id="awsEC2CpuUtilization"
|
id="awsEC2CpuUtilization"
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { withTheme } from '../../../../observability/public';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
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>
|
<React.Fragment>
|
||||||
<LayoutContent>
|
<LayoutContent>
|
||||||
<Section
|
<Section
|
||||||
|
@ -30,6 +30,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection
|
<SubSection
|
||||||
id="awsRDSCpuTotal"
|
id="awsRDSCpuTotal"
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { withTheme } from '../../../../observability/public';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
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>
|
<React.Fragment>
|
||||||
<LayoutContent>
|
<LayoutContent>
|
||||||
<Section
|
<Section
|
||||||
|
@ -30,6 +30,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection
|
<SubSection
|
||||||
id="awsS3BucketSize"
|
id="awsS3BucketSize"
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { withTheme } from '../../../../observability/public';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
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>
|
<React.Fragment>
|
||||||
<LayoutContent>
|
<LayoutContent>
|
||||||
<Section
|
<Section
|
||||||
|
@ -30,6 +30,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection
|
<SubSection
|
||||||
id="awsSQSMessagesVisible"
|
id="awsSQSMessagesVisible"
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { LayoutContent } from '../../../public/pages/metrics/components/layout_c
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { MetadataDetails } from '../../../public/pages/metrics/components/metadata_details';
|
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>
|
<React.Fragment>
|
||||||
<MetadataDetails />
|
<MetadataDetails />
|
||||||
<LayoutContent>
|
<LayoutContent>
|
||||||
|
@ -40,6 +40,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection id="containerOverview">
|
<SubSection id="containerOverview">
|
||||||
<GaugesSectionVis
|
<GaugesSectionVis
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { MetadataDetails } from '../../../public/pages/metrics/components/metada
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
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>
|
<React.Fragment>
|
||||||
<MetadataDetails
|
<MetadataDetails
|
||||||
fields={[
|
fields={[
|
||||||
|
@ -52,6 +52,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection id="hostSystemOverview">
|
<SubSection id="hostSystemOverview">
|
||||||
<GaugesSectionVis
|
<GaugesSectionVis
|
||||||
|
@ -242,6 +243,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection id="hostK8sOverview">
|
<SubSection id="hostK8sOverview">
|
||||||
<GaugesSectionVis
|
<GaugesSectionVis
|
||||||
|
@ -371,8 +373,8 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
/>
|
/>
|
||||||
</SubSection>
|
</SubSection>
|
||||||
</Section>
|
</Section>
|
||||||
<Aws.Layout metrics={metrics} />
|
<Aws.Layout metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
|
||||||
<Ngnix.Layout metrics={metrics} />
|
<Ngnix.Layout metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
));
|
));
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { MetadataDetails } from '../../../public/pages/metrics/components/metada
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { LayoutContent } from '../../../public/pages/metrics/components/layout_content';
|
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>
|
<React.Fragment>
|
||||||
<MetadataDetails />
|
<MetadataDetails />
|
||||||
<LayoutContent>
|
<LayoutContent>
|
||||||
|
@ -38,6 +38,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection id="podOverview">
|
<SubSection id="podOverview">
|
||||||
<GaugesSectionVis
|
<GaugesSectionVis
|
||||||
|
@ -161,7 +162,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
/>
|
/>
|
||||||
</SubSection>
|
</SubSection>
|
||||||
</Section>
|
</Section>
|
||||||
<Nginx.Layout metrics={metrics} />
|
<Nginx.Layout metrics={metrics} onChangeRangeTime={onChangeRangeTime} />
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
));
|
));
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { ChartSectionVis } from '../../../../public/pages/metrics/components/cha
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { withTheme } from '../../../../../observability/public';
|
import { withTheme } from '../../../../../observability/public';
|
||||||
|
|
||||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Section
|
<Section
|
||||||
navLabel="AWS"
|
navLabel="AWS"
|
||||||
|
@ -29,6 +29,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
metrics={metrics}
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
>
|
>
|
||||||
<SubSection id="awsOverview">
|
<SubSection id="awsOverview">
|
||||||
<GaugesSectionVis
|
<GaugesSectionVis
|
||||||
|
|
|
@ -16,9 +16,14 @@ import { ChartSectionVis } from '../../../../public/pages/metrics/components/cha
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { withTheme } from '../../../../../observability/public';
|
import { withTheme } from '../../../../../observability/public';
|
||||||
|
|
||||||
export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => (
|
export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Section navLabel="Nginx" sectionLabel="Nginx" metrics={metrics}>
|
<Section
|
||||||
|
navLabel="Nginx"
|
||||||
|
sectionLabel="Nginx"
|
||||||
|
metrics={metrics}
|
||||||
|
onChangeRangeTime={onChangeRangeTime}
|
||||||
|
>
|
||||||
<SubSection
|
<SubSection
|
||||||
id="nginxHits"
|
id="nginxHits"
|
||||||
label={i18n.translate(
|
label={i18n.translate(
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { ElasticsearchMappingOf } from '../../server/utils/typed_elasticsearch_mappings';
|
import { ElasticsearchMappingOf } from '../../server/utils/typed_elasticsearch_mappings';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// 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';
|
export const inventoryViewSavedObjectType = 'inventory-view';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
@ -102,7 +102,7 @@ export const inventoryViewSavedObjectMappings: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
type: 'integer',
|
type: 'long',
|
||||||
},
|
},
|
||||||
autoReload: {
|
autoReload: {
|
||||||
type: 'boolean',
|
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 React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { ApolloProvider } from 'react-apollo';
|
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';
|
import { CoreStart, AppMountParameters } from 'kibana/public';
|
||||||
|
|
||||||
// TODO use theme provided from parentApp when kibana supports it
|
// 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
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { EuiThemeProvider } from '../../../observability/public/typings/eui_styled_components';
|
import { EuiThemeProvider } from '../../../observability/public/typings/eui_styled_components';
|
||||||
import { InfraFrontendLibs } from '../lib/lib';
|
import { InfraFrontendLibs } from '../lib/lib';
|
||||||
import { createStore } from '../store';
|
|
||||||
import { ApolloClientContext } from '../utils/apollo_context';
|
import { ApolloClientContext } from '../utils/apollo_context';
|
||||||
import { ReduxStateContextProvider } from '../utils/redux_context';
|
|
||||||
import { HistoryContext } from '../utils/history_context';
|
import { HistoryContext } from '../utils/history_context';
|
||||||
import {
|
import {
|
||||||
useUiSetting$,
|
useUiSetting$,
|
||||||
|
@ -43,12 +38,6 @@ export async function startApp(
|
||||||
) {
|
) {
|
||||||
const { element, appBasePath } = params;
|
const { element, appBasePath } = params;
|
||||||
const history = createBrowserHistory({ basename: appBasePath });
|
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 InfraPluginRoot: React.FunctionComponent = () => {
|
||||||
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
|
||||||
|
|
||||||
|
@ -56,8 +45,6 @@ export async function startApp(
|
||||||
<core.i18n.Context>
|
<core.i18n.Context>
|
||||||
<EuiErrorBoundary>
|
<EuiErrorBoundary>
|
||||||
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
|
<TriggersActionsProvider triggersActionsUI={triggersActionsUI}>
|
||||||
<ReduxStoreProvider store={store}>
|
|
||||||
<ReduxStateContextProvider>
|
|
||||||
<ApolloProvider client={libs.apolloClient}>
|
<ApolloProvider client={libs.apolloClient}>
|
||||||
<ApolloClientContext.Provider value={libs.apolloClient}>
|
<ApolloClientContext.Provider value={libs.apolloClient}>
|
||||||
<EuiThemeProvider darkMode={darkMode}>
|
<EuiThemeProvider darkMode={darkMode}>
|
||||||
|
@ -67,8 +54,6 @@ export async function startApp(
|
||||||
</EuiThemeProvider>
|
</EuiThemeProvider>
|
||||||
</ApolloClientContext.Provider>
|
</ApolloClientContext.Provider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</ReduxStateContextProvider>
|
|
||||||
</ReduxStoreProvider>
|
|
||||||
</TriggersActionsProvider>
|
</TriggersActionsProvider>
|
||||||
</EuiErrorBoundary>
|
</EuiErrorBoundary>
|
||||||
</core.i18n.Context>
|
</core.i18n.Context>
|
||||||
|
|
|
@ -5,64 +5,89 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { InfraWaffleMapOptions, InfraWaffleMapBounds } from '../../lib/lib';
|
import { useInterval } from 'react-use';
|
||||||
import { KueryFilterQuery } from '../../store/local/waffle_filter';
|
|
||||||
|
|
||||||
|
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||||
import { NodesOverview } from '../nodes_overview';
|
import { NodesOverview } from '../nodes_overview';
|
||||||
import { Toolbar } from './toolbars/toolbar';
|
import { Toolbar } from './toolbars/toolbar';
|
||||||
import { PageContent } from '../page';
|
import { PageContent } from '../page';
|
||||||
import { useSnapshot } from '../../containers/waffle/use_snaphot';
|
import { useSnapshot } from '../../containers/waffle/use_snaphot';
|
||||||
import { useInventoryMeta } from '../../containers/inventory_metadata/use_inventory_meta';
|
import { useInventoryMeta } from '../../containers/inventory_metadata/use_inventory_meta';
|
||||||
import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api';
|
import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time';
|
||||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
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 {
|
const euiVisColorPalette = euiPaletteColorBlind();
|
||||||
options: InfraWaffleMapOptions;
|
|
||||||
nodeType: InventoryItemType;
|
|
||||||
onDrilldown: (filter: KueryFilterQuery) => void;
|
|
||||||
currentTime: number;
|
|
||||||
onViewChange: (view: string) => void;
|
|
||||||
view: string;
|
|
||||||
boundsOverride: InfraWaffleMapBounds;
|
|
||||||
autoBounds: boolean;
|
|
||||||
|
|
||||||
filterQuery: string | null | undefined;
|
export const Layout = () => {
|
||||||
metric: SnapshotMetricInput;
|
const { sourceId, source } = useSourceContext();
|
||||||
groupBy: SnapshotGroupBy;
|
const {
|
||||||
sourceId: string;
|
metric,
|
||||||
accountId: string;
|
groupBy,
|
||||||
region: string;
|
nodeType,
|
||||||
}
|
accountId,
|
||||||
|
region,
|
||||||
export const Layout = (props: LayoutProps) => {
|
changeView,
|
||||||
const { accounts, regions } = useInventoryMeta(props.sourceId, props.nodeType);
|
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(
|
const { loading, nodes, reload, interval } = useSnapshot(
|
||||||
props.filterQuery,
|
filterQueryAsJson,
|
||||||
props.metric,
|
metric,
|
||||||
props.groupBy,
|
groupBy,
|
||||||
props.nodeType,
|
nodeType,
|
||||||
props.sourceId,
|
sourceId,
|
||||||
props.currentTime,
|
currentTime,
|
||||||
props.accountId,
|
accountId,
|
||||||
props.region
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Toolbar accounts={accounts} regions={regions} nodeType={props.nodeType} />
|
<Toolbar accounts={accounts} regions={regions} nodeType={nodeType} />
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<NodesOverview
|
<NodesOverview
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
options={props.options}
|
options={options}
|
||||||
nodeType={props.nodeType}
|
nodeType={nodeType}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
reload={reload}
|
reload={reload}
|
||||||
onDrilldown={props.onDrilldown}
|
onDrilldown={applyFilterQuery}
|
||||||
currentTime={props.currentTime}
|
currentTime={currentTime}
|
||||||
onViewChange={props.onViewChange}
|
onViewChange={changeView}
|
||||||
view={props.view}
|
view={view}
|
||||||
autoBounds={props.autoBounds}
|
autoBounds={autoBounds}
|
||||||
boundsOverride={props.boundsOverride}
|
boundsOverride={boundsOverride}
|
||||||
interval={interval}
|
interval={interval}
|
||||||
/>
|
/>
|
||||||
</PageContent>
|
</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 React, { FunctionComponent } from 'react';
|
||||||
import { Action } from 'typescript-fsa';
|
|
||||||
import { EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexItem } from '@elastic/eui';
|
||||||
import {
|
import {
|
||||||
SnapshotMetricInput,
|
SnapshotMetricInput,
|
||||||
|
@ -16,33 +15,23 @@ import { InventoryCloudAccount } from '../../../../common/http_api/inventory_met
|
||||||
import { findToolbar } from '../../../../common/inventory_models/toolbars';
|
import { findToolbar } from '../../../../common/inventory_models/toolbars';
|
||||||
import { ToolbarWrapper } from './toolbar_wrapper';
|
import { ToolbarWrapper } from './toolbar_wrapper';
|
||||||
|
|
||||||
import { waffleOptionsSelectors } from '../../../store';
|
|
||||||
import { InfraGroupByOptions } from '../../../lib/lib';
|
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 { IIndexPattern } from '../../../../../../../src/plugins/data/public';
|
||||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
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;
|
createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern;
|
||||||
changeMetric: (payload: SnapshotMetricInput) => Action<SnapshotMetricInput>;
|
changeMetric: (payload: SnapshotMetricInput) => void;
|
||||||
changeGroupBy: (payload: SnapshotGroupBy) => Action<SnapshotGroupBy>;
|
changeGroupBy: (payload: SnapshotGroupBy) => void;
|
||||||
changeCustomOptions: (payload: InfraGroupByOptions[]) => Action<InfraGroupByOptions[]>;
|
changeCustomOptions: (payload: InfraGroupByOptions[]) => void;
|
||||||
changeAccount: (id: string) => Action<string>;
|
changeAccount: (id: string) => void;
|
||||||
changeRegion: (name: string) => Action<string>;
|
changeRegion: (name: string) => void;
|
||||||
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>;
|
|
||||||
accounts: InventoryCloudAccount[];
|
accounts: InventoryCloudAccount[];
|
||||||
regions: string[];
|
regions: string[];
|
||||||
customMetrics: ReturnType<typeof waffleOptionsSelectors.selectCustomMetrics>;
|
changeCustomMetrics: (payload: SnapshotCustomMetricInput[]) => void;
|
||||||
changeCustomMetrics: (
|
|
||||||
payload: SnapshotCustomMetricInput[]
|
|
||||||
) => Action<SnapshotCustomMetricInput[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapToolbarItems = (
|
const wrapToolbarItems = (
|
||||||
|
@ -57,16 +46,7 @@ const wrapToolbarItems = (
|
||||||
<ToolbarItems {...props} accounts={accounts} regions={regions} />
|
<ToolbarItems {...props} accounts={accounts} regions={regions} />
|
||||||
<EuiFlexItem grow={true} />
|
<EuiFlexItem grow={true} />
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<WithWaffleViewState indexPattern={props.createDerivedIndexPattern('metrics')}>
|
<SavedViews />
|
||||||
{({ defaultViewState, viewState, onViewChange }) => (
|
|
||||||
<SavedViewsToolbarControls
|
|
||||||
defaultViewState={defaultViewState}
|
|
||||||
viewState={viewState}
|
|
||||||
onViewChange={onViewChange}
|
|
||||||
viewType={inventoryViewSavedObjectType}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</WithWaffleViewState>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -8,24 +8,18 @@ import React from 'react';
|
||||||
import { EuiFlexGroup } from '@elastic/eui';
|
import { EuiFlexGroup } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
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 { Toolbar } from '../../eui/toolbar';
|
||||||
import { ToolbarProps } from './toolbar';
|
import { ToolbarProps } from './toolbar';
|
||||||
import { fieldToName } from '../../waffle/lib/field_to_display_name';
|
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 {
|
interface Props {
|
||||||
children: (props: Omit<ToolbarProps, 'accounts' | 'regions'>) => React.ReactElement;
|
children: (props: Omit<ToolbarProps, 'accounts' | 'regions'>) => React.ReactElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolbarWrapper = (props: Props) => {
|
export const ToolbarWrapper = (props: Props) => {
|
||||||
return (
|
const {
|
||||||
<Toolbar>
|
|
||||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
|
||||||
<WithSource>
|
|
||||||
{({ createDerivedIndexPattern }) => (
|
|
||||||
<WithWaffleOptions>
|
|
||||||
{({
|
|
||||||
changeMetric,
|
changeMetric,
|
||||||
changeGroupBy,
|
changeGroupBy,
|
||||||
changeCustomOptions,
|
changeCustomOptions,
|
||||||
|
@ -39,8 +33,12 @@ export const ToolbarWrapper = (props: Props) => {
|
||||||
region,
|
region,
|
||||||
customMetrics,
|
customMetrics,
|
||||||
changeCustomMetrics,
|
changeCustomMetrics,
|
||||||
}) =>
|
} = useWaffleOptionsContext();
|
||||||
props.children({
|
const { createDerivedIndexPattern } = useSourceContext();
|
||||||
|
return (
|
||||||
|
<Toolbar>
|
||||||
|
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||||
|
{props.children({
|
||||||
createDerivedIndexPattern,
|
createDerivedIndexPattern,
|
||||||
changeMetric,
|
changeMetric,
|
||||||
changeGroupBy,
|
changeGroupBy,
|
||||||
|
@ -55,11 +53,7 @@ export const ToolbarWrapper = (props: Props) => {
|
||||||
accountId,
|
accountId,
|
||||||
customMetrics,
|
customMetrics,
|
||||||
changeCustomMetrics,
|
changeCustomMetrics,
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
</WithWaffleOptions>
|
|
||||||
)}
|
|
||||||
</WithSource>
|
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,7 +12,6 @@ import React from 'react';
|
||||||
|
|
||||||
import { euiStyled } from '../../../../observability/public';
|
import { euiStyled } from '../../../../observability/public';
|
||||||
import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib';
|
import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib';
|
||||||
import { KueryFilterQuery } from '../../store/local/waffle_filter';
|
|
||||||
import { createFormatter } from '../../utils/formatters';
|
import { createFormatter } from '../../utils/formatters';
|
||||||
import { NoData } from '../empty_states';
|
import { NoData } from '../empty_states';
|
||||||
import { InfraLoadingPanel } from '../loading';
|
import { InfraLoadingPanel } from '../loading';
|
||||||
|
@ -24,6 +23,11 @@ import { convertIntervalToString } from '../../utils/convert_interval_to_string'
|
||||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||||
import { createFormatterForMetric } from '../metrics_explorer/helpers/create_formatter_for_metric';
|
import { createFormatterForMetric } from '../metrics_explorer/helpers/create_formatter_for_metric';
|
||||||
|
|
||||||
|
export interface KueryFilterQuery {
|
||||||
|
kind: 'kuery';
|
||||||
|
expression: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
options: InfraWaffleMapOptions;
|
options: InfraWaffleMapOptions;
|
||||||
nodeType: InventoryItemType;
|
nodeType: InventoryItemType;
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { euiStyled } from '../../../../observability/public';
|
import { euiStyled } from '../../../../observability/public';
|
||||||
import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options';
|
|
||||||
import { InfraFormatter, InfraWaffleMapBounds, InfraWaffleMapLegend } from '../../lib/lib';
|
import { InfraFormatter, InfraWaffleMapBounds, InfraWaffleMapLegend } from '../../lib/lib';
|
||||||
import { GradientLegend } from './gradient_legend';
|
import { GradientLegend } from './gradient_legend';
|
||||||
import { LegendControls } from './legend_controls';
|
import { LegendControls } from './legend_controls';
|
||||||
import { isInfraWaffleMapGradientLegend, isInfraWaffleMapStepLegend } from './lib/type_guards';
|
import { isInfraWaffleMapGradientLegend, isInfraWaffleMapStepLegend } from './lib/type_guards';
|
||||||
import { StepLegend } from './steps_legend';
|
import { StepLegend } from './steps_legend';
|
||||||
|
import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options';
|
||||||
interface Props {
|
interface Props {
|
||||||
legend: InfraWaffleMapLegend;
|
legend: InfraWaffleMapLegend;
|
||||||
bounds: InfraWaffleMapBounds;
|
bounds: InfraWaffleMapBounds;
|
||||||
|
@ -25,10 +25,14 @@ interface LegendControlOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter }) => {
|
export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter }) => {
|
||||||
|
const {
|
||||||
|
changeBoundsOverride,
|
||||||
|
changeAutoBounds,
|
||||||
|
autoBounds,
|
||||||
|
boundsOverride,
|
||||||
|
} = useWaffleOptionsContext();
|
||||||
return (
|
return (
|
||||||
<LegendContainer>
|
<LegendContainer>
|
||||||
<WithWaffleOptions>
|
|
||||||
{({ changeBoundsOverride, changeAutoBounds, autoBounds, boundsOverride }) => (
|
|
||||||
<LegendControls
|
<LegendControls
|
||||||
dataBounds={dataBounds}
|
dataBounds={dataBounds}
|
||||||
bounds={bounds}
|
bounds={bounds}
|
||||||
|
@ -39,8 +43,6 @@ export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter
|
||||||
changeAutoBounds(options.auto);
|
changeAutoBounds(options.auto);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</WithWaffleOptions>
|
|
||||||
{isInfraWaffleMapGradientLegend(legend) && (
|
{isInfraWaffleMapGradientLegend(legend) && (
|
||||||
<GradientLegend formatter={formatter} legend={legend} bounds={bounds} />
|
<GradientLegend formatter={formatter} legend={legend} bounds={bounds} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,11 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createUptimeLink } from './create_uptime_link';
|
import { createUptimeLink } from './create_uptime_link';
|
||||||
import {
|
import { InfraWaffleMapOptions, InfraFormatterType } from '../../../lib/lib';
|
||||||
InfraWaffleMapOptions,
|
|
||||||
InfraWaffleMapLegendMode,
|
|
||||||
InfraFormatterType,
|
|
||||||
} from '../../../lib/lib';
|
|
||||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||||
|
|
||||||
const options: InfraWaffleMapOptions = {
|
const options: InfraWaffleMapOptions = {
|
||||||
|
@ -26,7 +22,7 @@ const options: InfraWaffleMapOptions = {
|
||||||
metric: { type: 'cpu' },
|
metric: { type: 'cpu' },
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
legend: {
|
legend: {
|
||||||
type: InfraWaffleMapLegendMode.gradient,
|
type: 'gradient',
|
||||||
rules: [],
|
rules: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,16 +4,14 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { InfraWaffleMapGradientLegend, InfraWaffleMapStepLegend } from '../../../lib/lib';
|
||||||
InfraWaffleMapGradientLegend,
|
|
||||||
InfraWaffleMapLegendMode,
|
|
||||||
InfraWaffleMapStepLegend,
|
|
||||||
} from '../../../lib/lib';
|
|
||||||
export function isInfraWaffleMapStepLegend(subject: any): subject is InfraWaffleMapStepLegend {
|
export function isInfraWaffleMapStepLegend(subject: any): subject is InfraWaffleMapStepLegend {
|
||||||
return subject.type && subject.type === InfraWaffleMapLegendMode.step;
|
return subject.type && subject.type === 'step';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInfraWaffleMapGradientLegend(
|
export function isInfraWaffleMapGradientLegend(
|
||||||
subject: any
|
subject: any
|
||||||
): subject is InfraWaffleMapGradientLegend {
|
): subject is InfraWaffleMapGradientLegend {
|
||||||
return subject.type && subject.type === InfraWaffleMapLegendMode.gradient;
|
return subject.type && subject.type === 'gradient';
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,28 +16,15 @@ import React, { useCallback, useState, useMemo } from 'react';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { findInventoryModel } from '../../../common/inventory_models';
|
import { findInventoryModel } from '../../../common/inventory_models';
|
||||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||||
import {
|
import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDisplayNameForType = (type: InventoryItemType) => {
|
const getDisplayNameForType = (type: InventoryItemType) => {
|
||||||
const inventoryModel = findInventoryModel(type);
|
const inventoryModel = findInventoryModel(type);
|
||||||
return inventoryModel.displayName;
|
return inventoryModel.displayName;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WaffleInventorySwitcher: React.FC<WaffleInventorySwitcherProps> = ({
|
export const WaffleInventorySwitcher: React.FC = () => {
|
||||||
|
const {
|
||||||
changeNodeType,
|
changeNodeType,
|
||||||
changeGroupBy,
|
changeGroupBy,
|
||||||
changeMetric,
|
changeMetric,
|
||||||
|
@ -45,7 +32,7 @@ export const WaffleInventorySwitcher: React.FC<WaffleInventorySwitcherProps> = (
|
||||||
changeRegion,
|
changeRegion,
|
||||||
changeCustomMetrics,
|
changeCustomMetrics,
|
||||||
nodeType,
|
nodeType,
|
||||||
}) => {
|
} = useWaffleOptionsContext();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const closePopover = useCallback(() => setIsOpen(false), []);
|
const closePopover = useCallback(() => setIsOpen(false), []);
|
||||||
const openPopover = useCallback(() => setIsOpen(true), []);
|
const openPopover = useCallback(() => setIsOpen(true), []);
|
||||||
|
|
|
@ -7,37 +7,29 @@
|
||||||
import { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui';
|
import { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import moment, { Moment } from 'moment';
|
import moment, { Moment } from 'moment';
|
||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Action } from 'typescript-fsa';
|
import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time';
|
||||||
|
|
||||||
interface WaffleTimeControlsProps {
|
export const WaffleTimeControls = () => {
|
||||||
currentTime: number;
|
const {
|
||||||
isLiveStreaming?: boolean;
|
currentTime,
|
||||||
onChangeTime?: (time: number) => void;
|
isAutoReloading,
|
||||||
startLiveStreaming?: (payload: void) => Action<void>;
|
startAutoReload,
|
||||||
stopLiveStreaming?: (payload: void) => Action<void>;
|
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 = isLiveStreaming ? (
|
const liveStreamingButton = isAutoReloading ? (
|
||||||
<EuiButtonEmpty
|
<EuiButtonEmpty color="primary" iconSide="left" iconType="pause" onClick={stopAutoReload}>
|
||||||
color="primary"
|
|
||||||
iconSide="left"
|
|
||||||
iconType="pause"
|
|
||||||
onClick={this.stopLiveStreaming}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.infra.waffleTime.stopRefreshingButtonLabel"
|
id="xpack.infra.waffleTime.stopRefreshingButtonLabel"
|
||||||
defaultMessage="Stop refreshing"
|
defaultMessage="Stop refreshing"
|
||||||
/>
|
/>
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
) : (
|
) : (
|
||||||
<EuiButtonEmpty iconSide="left" iconType="play" onClick={this.startLiveStreaming}>
|
<EuiButtonEmpty iconSide="left" iconType="play" onClick={startAutoReload}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.infra.waffleTime.autoRefreshButtonLabel"
|
id="xpack.infra.waffleTime.autoRefreshButtonLabel"
|
||||||
defaultMessage="Auto-refresh"
|
defaultMessage="Auto-refresh"
|
||||||
|
@ -45,15 +37,24 @@ export class WaffleTimeControls extends React.Component<WaffleTimeControlsProps>
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleChangeDate = useCallback(
|
||||||
|
(time: Moment | null) => {
|
||||||
|
if (time) {
|
||||||
|
jumpToTime(time.valueOf());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[jumpToTime]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFormControlLayout append={liveStreamingButton} data-test-subj="waffleDatePicker">
|
<EuiFormControlLayout append={liveStreamingButton} data-test-subj="waffleDatePicker">
|
||||||
<EuiDatePicker
|
<EuiDatePicker
|
||||||
className="euiFieldText--inGroup"
|
className="euiFieldText--inGroup"
|
||||||
dateFormat="L LTS"
|
dateFormat="L LTS"
|
||||||
disabled={isLiveStreaming}
|
disabled={isAutoReloading}
|
||||||
injectTimes={currentMoment ? [currentMoment] : []}
|
injectTimes={currentMoment ? [currentMoment] : []}
|
||||||
isLoading={isLiveStreaming}
|
isLoading={isAutoReloading}
|
||||||
onChange={this.handleChangeDate}
|
onChange={handleChangeDate}
|
||||||
popperPlacement="top-end"
|
popperPlacement="top-end"
|
||||||
selected={currentMoment}
|
selected={currentMoment}
|
||||||
shouldCloseOnSelect
|
shouldCloseOnSelect
|
||||||
|
@ -62,29 +63,4 @@ export class WaffleTimeControls extends React.Component<WaffleTimeControlsProps>
|
||||||
/>
|
/>
|
||||||
</EuiFormControlLayout>
|
</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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,17 +13,18 @@ import { useHTTPRequest } from '../../hooks/use_http_request';
|
||||||
import { throwErrors, createPlainError } from '../../../common/runtime_types';
|
import { throwErrors, createPlainError } from '../../../common/runtime_types';
|
||||||
import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types';
|
import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types';
|
||||||
import { getFilteredMetrics } from './lib/get_filtered_metrics';
|
import { getFilteredMetrics } from './lib/get_filtered_metrics';
|
||||||
|
import { MetricsTimeInput } from '../../pages/metrics/hooks/use_metrics_time';
|
||||||
|
|
||||||
export function useMetadata(
|
export function useMetadata(
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
nodeType: InventoryItemType,
|
nodeType: InventoryItemType,
|
||||||
requiredMetrics: InventoryMetric[],
|
requiredMetrics: InventoryMetric[],
|
||||||
sourceId: string
|
sourceId: string,
|
||||||
|
timeRange: MetricsTimeInput
|
||||||
) {
|
) {
|
||||||
const decodeResponse = (response: any) => {
|
const decodeResponse = (response: any) => {
|
||||||
return pipe(InfraMetadataRT.decode(response), fold(throwErrors(createPlainError), identity));
|
return pipe(InfraMetadataRT.decode(response), fold(throwErrors(createPlainError), identity));
|
||||||
};
|
};
|
||||||
|
|
||||||
const { error, loading, response, makeRequest } = useHTTPRequest<InfraMetadata>(
|
const { error, loading, response, makeRequest } = useHTTPRequest<InfraMetadata>(
|
||||||
'/api/infra/metadata',
|
'/api/infra/metadata',
|
||||||
'POST',
|
'POST',
|
||||||
|
@ -31,6 +32,7 @@ export function useMetadata(
|
||||||
nodeId,
|
nodeId,
|
||||||
nodeType,
|
nodeType,
|
||||||
sourceId,
|
sourceId,
|
||||||
|
timeRange: { from: timeRange.from, to: timeRange.to },
|
||||||
}),
|
}),
|
||||||
decodeResponse
|
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 React from 'react';
|
||||||
|
|
||||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||||
import { InfraFormatterType, InfraOptions, InfraWaffleMapLegendMode } from '../lib/lib';
|
import { InfraFormatterType, InfraOptions } from '../lib/lib';
|
||||||
import { RendererFunction } from '../utils/typed_react';
|
import { RendererFunction } from '../utils/typed_react';
|
||||||
|
|
||||||
const euiVisColorPalette = euiPaletteColorBlind();
|
const euiVisColorPalette = euiPaletteColorBlind();
|
||||||
|
@ -29,7 +29,7 @@ const initialState = {
|
||||||
metric: { type: 'cpu' },
|
metric: { type: 'cpu' },
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
legend: {
|
legend: {
|
||||||
type: InfraWaffleMapLegendMode.gradient,
|
type: 'gradient',
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
value: 0,
|
value: 0,
|
||||||
|
|
|
@ -136,18 +136,13 @@ export interface InfraWaffleMapGradientRule {
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InfraWaffleMapLegendMode {
|
|
||||||
step = 'step',
|
|
||||||
gradient = 'gradient',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InfraWaffleMapStepLegend {
|
export interface InfraWaffleMapStepLegend {
|
||||||
type: InfraWaffleMapLegendMode.step;
|
type: 'step';
|
||||||
rules: InfraWaffleMapStepRule[];
|
rules: InfraWaffleMapStepRule[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfraWaffleMapGradientLegend {
|
export interface InfraWaffleMapGradientLegend {
|
||||||
type: InfraWaffleMapLegendMode.gradient;
|
type: 'gradient';
|
||||||
rules: InfraWaffleMapGradientRule[];
|
rules: InfraWaffleMapGradientRule[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ import { MetricsSettingsPage } from './settings';
|
||||||
import { AppNavigation } from '../../components/navigation/app_navigation';
|
import { AppNavigation } from '../../components/navigation/app_navigation';
|
||||||
import { SourceLoadingPage } from '../../components/source_loading_page';
|
import { SourceLoadingPage } from '../../components/source_loading_page';
|
||||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
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';
|
import { AlertDropdown } from '../../components/alerting/metrics/alert_dropdown';
|
||||||
|
|
||||||
export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
||||||
|
@ -32,6 +35,9 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Source.Provider sourceId="default">
|
<Source.Provider sourceId="default">
|
||||||
|
<WaffleOptionsProvider>
|
||||||
|
<WaffleTimeProvider>
|
||||||
|
<WaffleFiltersProvider>
|
||||||
<ColumnarPage>
|
<ColumnarPage>
|
||||||
<DocumentTitle
|
<DocumentTitle
|
||||||
title={i18n.translate('xpack.infra.homePage.documentTitle', {
|
title={i18n.translate('xpack.infra.homePage.documentTitle', {
|
||||||
|
@ -56,7 +62,6 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
||||||
]}
|
]}
|
||||||
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
|
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppNavigation
|
<AppNavigation
|
||||||
aria-label={i18n.translate('xpack.infra.header.infrastructureNavigationTitle', {
|
aria-label={i18n.translate('xpack.infra.header.infrastructureNavigationTitle', {
|
||||||
defaultMessage: 'Metrics',
|
defaultMessage: 'Metrics',
|
||||||
|
@ -122,6 +127,9 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
||||||
<Route path={'/settings'} component={MetricsSettingsPage} />
|
<Route path={'/settings'} component={MetricsSettingsPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</ColumnarPage>
|
</ColumnarPage>
|
||||||
|
</WaffleFiltersProvider>
|
||||||
|
</WaffleTimeProvider>
|
||||||
|
</WaffleOptionsProvider>
|
||||||
</Source.Provider>
|
</Source.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
import { SnapshotPageContent } from './page_content';
|
|
||||||
import { SnapshotToolbar } from './toolbar';
|
import { SnapshotToolbar } from './toolbar';
|
||||||
|
|
||||||
import { DocumentTitle } from '../../../components/document_title';
|
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 { SourceLoadingPage } from '../../../components/source_loading_page';
|
||||||
import { ViewSourceConfigurationButton } from '../../../components/source_configuration';
|
import { ViewSourceConfigurationButton } from '../../../components/source_configuration';
|
||||||
import { Source } from '../../../containers/source';
|
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 { useTrackPageview } from '../../../../../observability/public';
|
||||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
import { Layout } from '../../../components/inventory/layout';
|
||||||
import { useLinkProps } from '../../../hooks/use_link_props';
|
import { useLinkProps } from '../../../hooks/use_link_props';
|
||||||
|
|
||||||
export const SnapshotPage = () => {
|
export const SnapshotPage = () => {
|
||||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||||
const {
|
const {
|
||||||
createDerivedIndexPattern,
|
|
||||||
hasFailedLoadingSource,
|
hasFailedLoadingSource,
|
||||||
isLoading,
|
isLoading,
|
||||||
loadSourceFailureMessage,
|
loadSourceFailureMessage,
|
||||||
|
@ -60,11 +56,8 @@ export const SnapshotPage = () => {
|
||||||
<SourceLoadingPage />
|
<SourceLoadingPage />
|
||||||
) : metricIndicesExist ? (
|
) : metricIndicesExist ? (
|
||||||
<>
|
<>
|
||||||
<WithWaffleTimeUrlState />
|
|
||||||
<WithWaffleFilterUrlState indexPattern={createDerivedIndexPattern('metrics')} />
|
|
||||||
<WithWaffleOptionsUrlState />
|
|
||||||
<SnapshotToolbar />
|
<SnapshotToolbar />
|
||||||
<SnapshotPageContent />
|
<Layout />
|
||||||
</>
|
</>
|
||||||
) : hasFailedLoadingSource ? (
|
) : hasFailedLoadingSource ? (
|
||||||
<SourceErrorPage errorMessage={loadSourceFailureMessage || ''} retry={loadSource} />
|
<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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AutocompleteField } from '../../../components/autocomplete_field';
|
|
||||||
import { Toolbar } from '../../../components/eui/toolbar';
|
import { Toolbar } from '../../../components/eui/toolbar';
|
||||||
import { WaffleTimeControls } from '../../../components/waffle/waffle_time_controls';
|
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 { WaffleInventorySwitcher } from '../../../components/waffle/waffle_inventory_switcher';
|
||||||
|
import { SearchBar } from '../../inventory_view/compontents/search_bar';
|
||||||
|
|
||||||
export const SnapshotToolbar = () => (
|
export const SnapshotToolbar = () => (
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
|
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<WithWaffleOptions>
|
<WaffleInventorySwitcher />
|
||||||
{({
|
|
||||||
changeMetric,
|
|
||||||
changeNodeType,
|
|
||||||
changeGroupBy,
|
|
||||||
changeAccount,
|
|
||||||
changeRegion,
|
|
||||||
changeCustomMetrics,
|
|
||||||
nodeType,
|
|
||||||
}) => (
|
|
||||||
<WaffleInventorySwitcher
|
|
||||||
nodeType={nodeType}
|
|
||||||
changeNodeType={changeNodeType}
|
|
||||||
changeMetric={changeMetric}
|
|
||||||
changeGroupBy={changeGroupBy}
|
|
||||||
changeAccount={changeAccount}
|
|
||||||
changeRegion={changeRegion}
|
|
||||||
changeCustomMetrics={changeCustomMetrics}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</WithWaffleOptions>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<WithSource>
|
<SearchBar />
|
||||||
{({ 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>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<WithWaffleTime resetOnUnmount>
|
<WaffleTimeControls />
|
||||||
{({ currentTime, isAutoReloading, jumpToTime, startAutoReload, stopAutoReload }) => (
|
|
||||||
<WaffleTimeControls
|
|
||||||
currentTime={currentTime}
|
|
||||||
isLiveStreaming={isAutoReloading}
|
|
||||||
onChangeTime={jumpToTime}
|
|
||||||
startLiveStreaming={startAutoReload}
|
|
||||||
stopLiveStreaming={stopAutoReload}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</WithWaffleTime>
|
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</Toolbar>
|
</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 { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||||
import { i18n } from '@kbn/i18n';
|
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 { useHostIpToName } from './use_host_ip_to_name';
|
||||||
import { getFromFromLocation, getToFromLocation } from './query_params';
|
import { getFromFromLocation, getToFromLocation } from './query_params';
|
||||||
import { LoadingPage } from '../../components/loading_page';
|
import { LoadingPage } from '../../components/loading_page';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
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 { getFromFromLocation, getToFromLocation } from './query_params';
|
||||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||||
import { LinkDescriptor } from '../../hooks/use_link_props';
|
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 { SideNavContext, NavItem } from '../lib/side_nav_context';
|
||||||
import { PageBody } from './page_body';
|
import { PageBody } from './page_body';
|
||||||
import { euiStyled } from '../../../../../observability/public';
|
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 { InfraMetadata } from '../../../../common/http_api/metadata_api';
|
||||||
import { PageError } from './page_error';
|
import { PageError } from './page_error';
|
||||||
import { MetadataContext } from '../../../pages/metrics/containers/metadata_context';
|
import { MetadataContext } from '../../../pages/metrics/containers/metadata_context';
|
||||||
|
@ -94,7 +94,7 @@ export const NodeDetailsPage = (props: Props) => {
|
||||||
setRefreshInterval={props.setRefreshInterval}
|
setRefreshInterval={props.setRefreshInterval}
|
||||||
onChangeTimeRange={props.setTimeRange}
|
onChangeTimeRange={props.setTimeRange}
|
||||||
setAutoReload={props.setAutoReload}
|
setAutoReload={props.setAutoReload}
|
||||||
onRefresh={props.triggerRefresh}
|
onRefresh={refetch}
|
||||||
/>
|
/>
|
||||||
</MetricsTitleTimeRangeContainer>
|
</MetricsTitleTimeRangeContainer>
|
||||||
</EuiPageHeaderSection>
|
</EuiPageHeaderSection>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import { findLayout } from '../../../../common/inventory_models/layouts';
|
import { findLayout } from '../../../../common/inventory_models/layouts';
|
||||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
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 { InfraLoadingPanel } from '../../../components/loading';
|
||||||
import { NoData } from '../../../components/empty_states';
|
import { NoData } from '../../../components/empty_states';
|
||||||
import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api';
|
import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api';
|
||||||
|
@ -19,9 +19,9 @@ interface Props {
|
||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
type: InventoryItemType;
|
type: InventoryItemType;
|
||||||
metrics: NodeDetailsMetricData[];
|
metrics: NodeDetailsMetricData[];
|
||||||
onChangeRangeTime?: (time: MetricsTimeInput) => void;
|
onChangeRangeTime: (time: MetricsTimeInput) => void;
|
||||||
isLiveStreaming?: boolean;
|
isLiveStreaming: boolean;
|
||||||
stopLiveStreaming?: () => void;
|
stopLiveStreaming: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PageBody = ({
|
export const PageBody = ({
|
||||||
|
|
|
@ -41,6 +41,9 @@ export const Section: FunctionComponent<SectionProps> = ({
|
||||||
if (metric === null) {
|
if (metric === null) {
|
||||||
return accumulatedChildren;
|
return accumulatedChildren;
|
||||||
}
|
}
|
||||||
|
if (!child.props.label) {
|
||||||
|
return accumulatedChildren;
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
...accumulatedChildren,
|
...accumulatedChildren,
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ jest.mock('../../../utils/use_kibana_ui_setting', () => ({
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MetricsTimeControls } from './time_controls';
|
import { MetricsTimeControls } from './time_controls';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { MetricsTimeInput } from '../containers/with_metrics_time';
|
import { MetricsTimeInput } from '../hooks/use_metrics_time';
|
||||||
|
|
||||||
describe('MetricsTimeControls', () => {
|
describe('MetricsTimeControls', () => {
|
||||||
it('should set a valid from and to value for Today', () => {
|
it('should set a valid from and to value for Today', () => {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui';
|
import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { euiStyled } from '../../../../../observability/public';
|
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 { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting';
|
||||||
import { mapKibanaQuickRangesToDatePickerRanges } from '../../../utils/map_timepicker_quickranges_to_datepicker_ranges';
|
import { mapKibanaQuickRangesToDatePickerRanges } from '../../../utils/map_timepicker_quickranges_to_datepicker_ranges';
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ export const MetricsTimeControls = (props: MetricsTimeControlsProps) => {
|
||||||
return (
|
return (
|
||||||
<MetricsTimeControlsContainer>
|
<MetricsTimeControlsContainer>
|
||||||
<EuiSuperDatePicker
|
<EuiSuperDatePicker
|
||||||
start={currentTimeRange.from}
|
start={currentTimeRange.from.toString()}
|
||||||
end={currentTimeRange.to}
|
end={currentTimeRange.to.toString()}
|
||||||
isPaused={!isLiveStreaming}
|
isPaused={!isLiveStreaming}
|
||||||
refreshInterval={refreshInterval ? refreshInterval : 0}
|
refreshInterval={refreshInterval ? refreshInterval : 0}
|
||||||
onTimeChange={handleTimeChange}
|
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 { mountHook } from 'test_utils/enzyme_helpers';
|
||||||
|
|
||||||
import { useMetricsTime } from './with_metrics_time';
|
import { useMetricsTime } from './use_metrics_time';
|
||||||
|
|
||||||
describe('useMetricsTime hook', () => {
|
describe('useMetricsTime hook', () => {
|
||||||
describe('timeRange state', () => {
|
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 { DocumentTitle } from '../../components/document_title';
|
||||||
import { Header } from '../../components/header';
|
import { Header } from '../../components/header';
|
||||||
import { ColumnarPage, PageContent } from '../../components/page';
|
import { ColumnarPage, PageContent } from '../../components/page';
|
||||||
import { WithMetricsTime, WithMetricsTimeUrlState } from './containers/with_metrics_time';
|
|
||||||
import { withMetricPageProviders } from './page_providers';
|
import { withMetricPageProviders } from './page_providers';
|
||||||
import { useMetadata } from '../../containers/metadata/use_metadata';
|
import { useMetadata } from '../../containers/metadata/use_metadata';
|
||||||
import { Source } from '../../containers/source';
|
import { Source } from '../../containers/source';
|
||||||
|
@ -19,6 +18,7 @@ import { NavItem } from './lib/side_nav_context';
|
||||||
import { NodeDetailsPage } from './components/node_details_page';
|
import { NodeDetailsPage } from './components/node_details_page';
|
||||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||||
|
import { useMetricsTimeContext } from './hooks/use_metrics_time';
|
||||||
import { useLinkProps } from '../../hooks/use_link_props';
|
import { useLinkProps } from '../../hooks/use_link_props';
|
||||||
|
|
||||||
const DetailPageContent = euiStyled(PageContent)`
|
const DetailPageContent = euiStyled(PageContent)`
|
||||||
|
@ -37,19 +37,29 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MetricDetail = withMetricPageProviders(
|
export const MetricDetail = withMetricPageProviders(
|
||||||
withTheme(({ match, theme }: Props) => {
|
withTheme(({ match }: Props) => {
|
||||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||||
const nodeId = match.params.node;
|
const nodeId = match.params.node;
|
||||||
const nodeType = match.params.type as InventoryItemType;
|
const nodeType = match.params.type as InventoryItemType;
|
||||||
const inventoryModel = findInventoryModel(nodeType);
|
const inventoryModel = findInventoryModel(nodeType);
|
||||||
const { sourceId } = useContext(Source.Context);
|
const { sourceId } = useContext(Source.Context);
|
||||||
|
const {
|
||||||
|
timeRange,
|
||||||
|
parsedTimeRange,
|
||||||
|
setTimeRange,
|
||||||
|
refreshInterval,
|
||||||
|
setRefreshInterval,
|
||||||
|
isAutoReloading,
|
||||||
|
setAutoReload,
|
||||||
|
triggerRefresh,
|
||||||
|
} = useMetricsTimeContext();
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
filteredRequiredMetrics,
|
filteredRequiredMetrics,
|
||||||
loading: metadataLoading,
|
loading: metadataLoading,
|
||||||
cloudId,
|
cloudId,
|
||||||
metadata,
|
metadata,
|
||||||
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId);
|
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, parsedTimeRange);
|
||||||
|
|
||||||
const [sideNav, setSideNav] = useState<NavItem[]>([]);
|
const [sideNav, setSideNav] = useState<NavItem[]>([]);
|
||||||
|
|
||||||
|
@ -90,23 +100,8 @@ export const MetricDetail = withMetricPageProviders(
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithMetricsTime>
|
|
||||||
{({
|
|
||||||
timeRange,
|
|
||||||
parsedTimeRange,
|
|
||||||
setTimeRange,
|
|
||||||
refreshInterval,
|
|
||||||
setRefreshInterval,
|
|
||||||
isAutoReloading,
|
|
||||||
setAutoReload,
|
|
||||||
triggerRefresh,
|
|
||||||
}) => (
|
|
||||||
<ColumnarPage>
|
<ColumnarPage>
|
||||||
<Header
|
<Header breadcrumbs={breadcrumbs} readOnlyBadge={!uiCapabilities?.infrastructure?.save} />
|
||||||
breadcrumbs={breadcrumbs}
|
|
||||||
readOnlyBadge={!uiCapabilities?.infrastructure?.save}
|
|
||||||
/>
|
|
||||||
<WithMetricsTimeUrlState />
|
|
||||||
<DocumentTitle
|
<DocumentTitle
|
||||||
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
|
title={i18n.translate('xpack.infra.metricDetailPage.documentTitle', {
|
||||||
defaultMessage: 'Infrastructure | Metrics | {name}',
|
defaultMessage: 'Infrastructure | Metrics | {name}',
|
||||||
|
@ -140,8 +135,6 @@ export const MetricDetail = withMetricPageProviders(
|
||||||
) : null}
|
) : null}
|
||||||
</DetailPageContent>
|
</DetailPageContent>
|
||||||
</ColumnarPage>
|
</ColumnarPage>
|
||||||
)}
|
|
||||||
</WithMetricsTime>
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,15 +6,15 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { MetricsTimeContainer } from './containers/with_metrics_time';
|
|
||||||
import { Source } from '../../containers/source';
|
import { Source } from '../../containers/source';
|
||||||
|
import { MetricsTimeProvider } from './hooks/use_metrics_time';
|
||||||
|
|
||||||
export const withMetricPageProviders = <T extends object>(Component: React.ComponentType<T>) => (
|
export const withMetricPageProviders = <T extends object>(Component: React.ComponentType<T>) => (
|
||||||
props: T
|
props: T
|
||||||
) => (
|
) => (
|
||||||
<Source.Provider sourceId="default">
|
<Source.Provider sourceId="default">
|
||||||
<MetricsTimeContainer.Provider>
|
<MetricsTimeProvider>
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
</MetricsTimeContainer.Provider>
|
</MetricsTimeProvider>
|
||||||
</Source.Provider>
|
</Source.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import rt from 'io-ts';
|
import rt from 'io-ts';
|
||||||
import { EuiTheme } from '../../../../observability/public';
|
import { EuiTheme } from '../../../../observability/public';
|
||||||
import { InventoryFormatterTypeRT } from '../../../common/inventory_models/types';
|
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';
|
import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api';
|
||||||
|
|
||||||
export interface LayoutProps {
|
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) => {
|
async (requestContext, request, response) => {
|
||||||
try {
|
try {
|
||||||
const { nodeId, nodeType, sourceId } = pipe(
|
const { nodeId, nodeType, sourceId, timeRange } = pipe(
|
||||||
InfraMetadataRequestRT.decode(request.body),
|
InfraMetadataRequestRT.decode(request.body),
|
||||||
fold(throwErrors(Boom.badRequest), identity)
|
fold(throwErrors(Boom.badRequest), identity)
|
||||||
);
|
);
|
||||||
|
@ -52,7 +52,8 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
||||||
requestContext,
|
requestContext,
|
||||||
configuration,
|
configuration,
|
||||||
nodeId,
|
nodeId,
|
||||||
nodeType
|
nodeType,
|
||||||
|
timeRange
|
||||||
);
|
);
|
||||||
const metricFeatures = pickFeatureName(metricsMetadata.buckets).map(
|
const metricFeatures = pickFeatureName(metricsMetadata.buckets).map(
|
||||||
nameToFeature('metrics')
|
nameToFeature('metrics')
|
||||||
|
@ -62,7 +63,13 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
||||||
const cloudInstanceId = get<string>(info, 'cloud.instance.id');
|
const cloudInstanceId = get<string>(info, 'cloud.instance.id');
|
||||||
|
|
||||||
const cloudMetricsMetadata = cloudInstanceId
|
const cloudMetricsMetadata = cloudInstanceId
|
||||||
? await getCloudMetricsMetadata(framework, requestContext, configuration, cloudInstanceId)
|
? await getCloudMetricsMetadata(
|
||||||
|
framework,
|
||||||
|
requestContext,
|
||||||
|
configuration,
|
||||||
|
cloudInstanceId,
|
||||||
|
timeRange
|
||||||
|
)
|
||||||
: { buckets: [] };
|
: { buckets: [] };
|
||||||
const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map(
|
const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map(
|
||||||
nameToFeature('metrics')
|
nameToFeature('metrics')
|
||||||
|
|
|
@ -21,7 +21,8 @@ export const getCloudMetricsMetadata = async (
|
||||||
framework: KibanaFramework,
|
framework: KibanaFramework,
|
||||||
requestContext: RequestHandlerContext,
|
requestContext: RequestHandlerContext,
|
||||||
sourceConfiguration: InfraSourceConfiguration,
|
sourceConfiguration: InfraSourceConfiguration,
|
||||||
instanceId: string
|
instanceId: string,
|
||||||
|
timeRange: { from: number; to: number }
|
||||||
): Promise<InfraCloudMetricsAdapterResponse> => {
|
): Promise<InfraCloudMetricsAdapterResponse> => {
|
||||||
const metricQuery = {
|
const metricQuery = {
|
||||||
allowNoIndices: true,
|
allowNoIndices: true,
|
||||||
|
@ -30,7 +31,18 @@ export const getCloudMetricsMetadata = async (
|
||||||
body: {
|
body: {
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
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 } })),
|
should: CLOUD_METRICS_MODULES.map(module => ({ match: { 'event.module': module } })),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,7 +26,8 @@ export const getMetricMetadata = async (
|
||||||
requestContext: RequestHandlerContext,
|
requestContext: RequestHandlerContext,
|
||||||
sourceConfiguration: InfraSourceConfiguration,
|
sourceConfiguration: InfraSourceConfiguration,
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
nodeType: InventoryItemType
|
nodeType: InventoryItemType,
|
||||||
|
timeRange: { from: number; to: number }
|
||||||
): Promise<InfraMetricsAdapterResponse> => {
|
): Promise<InfraMetricsAdapterResponse> => {
|
||||||
const fields = findInventoryFields(nodeType, sourceConfiguration.fields);
|
const fields = findInventoryFields(nodeType, sourceConfiguration.fields);
|
||||||
const metricQuery = {
|
const metricQuery = {
|
||||||
|
@ -41,6 +42,15 @@ export const getMetricMetadata = async (
|
||||||
{
|
{
|
||||||
match: { [fields.id]: nodeId },
|
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';
|
} from '../../../../plugins/infra/common/http_api/metadata_api';
|
||||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
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) {
|
export default function({ getService }: FtrProviderContext) {
|
||||||
const esArchiver = getService('esArchiver');
|
const esArchiver = getService('esArchiver');
|
||||||
const supertest = getService('supertest');
|
const supertest = getService('supertest');
|
||||||
|
@ -34,6 +56,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: 'demo-stack-mysql-01',
|
nodeId: 'demo-stack-mysql-01',
|
||||||
nodeType: InfraNodeType.host,
|
nodeType: InfraNodeType.host,
|
||||||
|
timeRange: timeRange700,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(metadata.features.length).to.be(12);
|
expect(metadata.features.length).to.be(12);
|
||||||
|
@ -53,6 +76,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: '631f36a845514442b93c3fdd2dc91bcd8feb680b8ac5832c7fb8fdc167bb938e',
|
nodeId: '631f36a845514442b93c3fdd2dc91bcd8feb680b8ac5832c7fb8fdc167bb938e',
|
||||||
nodeType: InfraNodeType.container,
|
nodeType: InfraNodeType.container,
|
||||||
|
timeRange: timeRange660,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(metadata.features.length).to.be(10);
|
expect(metadata.features.length).to.be(10);
|
||||||
|
@ -74,6 +98,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc',
|
nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc',
|
||||||
nodeType: InfraNodeType.host,
|
nodeType: InfraNodeType.host,
|
||||||
|
timeRange: timeRange800withAws,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(metadata.features.length).to.be(58);
|
expect(metadata.features.length).to.be(58);
|
||||||
|
@ -114,6 +139,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: 'ip-172-31-47-9.us-east-2.compute.internal',
|
nodeId: 'ip-172-31-47-9.us-east-2.compute.internal',
|
||||||
nodeType: InfraNodeType.host,
|
nodeType: InfraNodeType.host,
|
||||||
|
timeRange: timeRange800withAws,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(metadata.features.length).to.be(19);
|
expect(metadata.features.length).to.be(19);
|
||||||
|
@ -155,6 +181,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: '14887487-99f8-11e9-9a96-42010a84004d',
|
nodeId: '14887487-99f8-11e9-9a96-42010a84004d',
|
||||||
nodeType: InfraNodeType.pod,
|
nodeType: InfraNodeType.pod,
|
||||||
|
timeRange: timeRange800withAws,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(metadata.features.length).to.be(29);
|
expect(metadata.features.length).to.be(29);
|
||||||
|
@ -200,6 +227,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: 'c74b04834c6d7cc1800c3afbe31d0c8c0c267f06e9eb45c2b0c2df3e6cee40c5',
|
nodeId: 'c74b04834c6d7cc1800c3afbe31d0c8c0c267f06e9eb45c2b0c2df3e6cee40c5',
|
||||||
nodeType: InfraNodeType.container,
|
nodeType: InfraNodeType.container,
|
||||||
|
timeRange: timeRange800withAws,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(metadata.features.length).to.be(26);
|
expect(metadata.features.length).to.be(26);
|
||||||
|
@ -251,6 +279,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc',
|
nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc',
|
||||||
nodeType: 'host',
|
nodeType: 'host',
|
||||||
|
timeRange: timeRange800,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(
|
expect(
|
||||||
|
@ -265,6 +294,7 @@ export default function({ getService }: FtrProviderContext) {
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
nodeId: 'c1031331-9ae0-11e9-9a96-42010a84004d',
|
nodeId: 'c1031331-9ae0-11e9-9a96-42010a84004d',
|
||||||
nodeType: 'pod',
|
nodeType: 'pod',
|
||||||
|
timeRange: timeRange800,
|
||||||
});
|
});
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
expect(
|
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' {
|
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
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
export interface RisonArray extends Array<RisonValue> {}
|
export interface RisonArray extends Array<RisonValue> {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue