From 59fad8ffe894f70095f1b29a1b2d14afd168304c Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 14 Apr 2020 09:01:41 -0700 Subject: [PATCH] [Metrics UI] Refactor With* containers to hooks (#59503) * [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 --- test/functional/page_objects/common_page.ts | 24 +- test/typings/rison_node.d.ts | 2 +- typings/rison_node.d.ts | 2 +- .../infra/common/http_api/metadata_api.ts | 4 + .../inventory_models/aws_ec2/layout.tsx | 3 +- .../inventory_models/aws_rds/layout.tsx | 3 +- .../common/inventory_models/aws_s3/layout.tsx | 3 +- .../inventory_models/aws_sqs/layout.tsx | 3 +- .../inventory_models/container/layout.tsx | 3 +- .../common/inventory_models/host/layout.tsx | 8 +- .../common/inventory_models/pod/layout.tsx | 5 +- .../inventory_models/shared/layouts/aws.tsx | 3 +- .../inventory_models/shared/layouts/nginx.tsx | 9 +- .../common/saved_objects/inventory_view.ts | 10 +- .../plugins/infra/public/apps/start_app.tsx | 33 +-- .../public/components/inventory/layout.tsx | 105 ++++--- .../inventory/toolbars/save_views.tsx | 21 ++ .../components/inventory/toolbars/toolbar.tsx | 42 +-- .../inventory/toolbars/toolbar_wrapper.tsx | 74 +++-- .../components/nodes_overview/index.tsx | 6 +- .../infra/public/components/waffle/legend.tsx | 32 ++- .../waffle/lib/create_uptime_link.test.ts | 8 +- .../components/waffle/lib/type_guards.ts | 12 +- .../waffle/waffle_inventory_switcher.tsx | 35 +-- .../waffle/waffle_time_controls.tsx | 128 ++++----- .../containers/metadata/use_metadata.ts | 6 +- .../infra/public/containers/waffle/index.ts | 7 - .../waffle/waffle_nodes.gql_query.ts | 37 --- .../containers/waffle/with_waffle_filters.tsx | 96 ------- .../containers/waffle/with_waffle_options.tsx | 265 ------------------ .../containers/waffle/with_waffle_time.tsx | 96 ------- .../waffle/with_waffle_view_state.tsx | 145 ---------- .../infra/public/containers/with_options.tsx | 4 +- x-pack/plugins/infra/public/lib/lib.ts | 9 +- .../public/pages/infrastructure/index.tsx | 172 ++++++------ .../pages/infrastructure/snapshot/index.tsx | 11 +- .../infrastructure/snapshot/page_content.tsx | 68 ----- .../pages/infrastructure/snapshot/toolbar.tsx | 76 +---- .../inventory_view/compontents/search_bar.tsx | 40 +++ .../hooks/use_waffle_filters.ts | 93 ++++++ .../hooks/use_waffle_options.ts | 147 ++++++++++ .../inventory_view/hooks/use_waffle_time.ts | 76 +++++ .../hooks/use_waffle_view_state.ts | 95 +++++++ .../redirect_to_host_detail_via_ip.tsx | 2 +- .../pages/link_to/redirect_to_node_detail.tsx | 2 +- .../metrics/components/node_details_page.tsx | 4 +- .../pages/metrics/components/page_body.tsx | 8 +- .../pages/metrics/components/section.tsx | 3 + .../metrics/components/time_controls.test.tsx | 2 +- .../metrics/components/time_controls.tsx | 6 +- .../metrics/containers/metrics.gql_query.ts | 38 --- .../metrics/containers/with_metrics_time.tsx | 201 ------------- .../metrics_time.test.tsx | 2 +- .../pages/metrics/hooks/use_metrics_time.ts | 121 ++++++++ .../infra/public/pages/metrics/index.tsx | 101 ++++--- .../public/pages/metrics/page_providers.tsx | 6 +- .../infra/public/pages/metrics/types.ts | 2 +- x-pack/plugins/infra/public/store/actions.ts | 7 - x-pack/plugins/infra/public/store/epics.ts | 11 - x-pack/plugins/infra/public/store/index.ts | 11 - .../infra/public/store/local/actions.ts | 9 - .../plugins/infra/public/store/local/epic.ts | 11 - .../plugins/infra/public/store/local/index.ts | 10 - .../infra/public/store/local/reducer.ts | 33 --- .../infra/public/store/local/selectors.ts | 26 -- .../store/local/waffle_filter/actions.ts | 19 -- .../public/store/local/waffle_filter/index.ts | 11 - .../store/local/waffle_filter/reducer.ts | 43 --- .../store/local/waffle_filter/selectors.ts | 33 --- .../store/local/waffle_options/actions.ts | 29 -- .../store/local/waffle_options/index.ts | 11 - .../store/local/waffle_options/reducer.ts | 113 -------- .../store/local/waffle_options/selector.ts | 18 -- .../public/store/local/waffle_time/actions.ts | 15 - .../public/store/local/waffle_time/epic.ts | 38 --- .../public/store/local/waffle_time/index.ts | 12 - .../public/store/local/waffle_time/reducer.ts | 52 ---- .../store/local/waffle_time/selectors.ts | 23 -- x-pack/plugins/infra/public/store/reducer.ts | 21 -- .../plugins/infra/public/store/selectors.ts | 22 -- x-pack/plugins/infra/public/store/store.ts | 50 ---- .../infra/public/utils/redux_context.tsx | 16 -- .../infra/server/routes/metadata/index.ts | 13 +- .../metadata/lib/get_cloud_metric_metadata.ts | 16 +- .../metadata/lib/get_metric_metadata.ts | 12 +- .../api_integration/apis/infra/metadata.ts | 30 ++ x-pack/typings/rison_node.d.ts | 2 +- 87 files changed, 1087 insertions(+), 2149 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx delete mode 100644 x-pack/plugins/infra/public/containers/waffle/index.ts delete mode 100644 x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts delete mode 100644 x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx delete mode 100644 x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx delete mode 100644 x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx delete mode 100644 x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx delete mode 100644 x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx create mode 100644 x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx create mode 100644 x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts create mode 100644 x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts create mode 100644 x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts create mode 100644 x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.ts delete mode 100644 x-pack/plugins/infra/public/pages/metrics/containers/metrics.gql_query.ts delete mode 100644 x-pack/plugins/infra/public/pages/metrics/containers/with_metrics_time.tsx rename x-pack/plugins/infra/public/pages/metrics/{containers => hooks}/metrics_time.test.tsx (96%) create mode 100644 x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts delete mode 100644 x-pack/plugins/infra/public/store/actions.ts delete mode 100644 x-pack/plugins/infra/public/store/epics.ts delete mode 100644 x-pack/plugins/infra/public/store/index.ts delete mode 100644 x-pack/plugins/infra/public/store/local/actions.ts delete mode 100644 x-pack/plugins/infra/public/store/local/epic.ts delete mode 100644 x-pack/plugins/infra/public/store/local/index.ts delete mode 100644 x-pack/plugins/infra/public/store/local/reducer.ts delete mode 100644 x-pack/plugins/infra/public/store/local/selectors.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_filter/index.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_options/actions.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_options/index.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_options/selector.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_time/actions.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_time/epic.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_time/index.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts delete mode 100644 x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts delete mode 100644 x-pack/plugins/infra/public/store/reducer.ts delete mode 100644 x-pack/plugins/infra/public/store/selectors.ts delete mode 100644 x-pack/plugins/infra/public/store/store.ts delete mode 100644 x-pack/plugins/infra/public/utils/redux_context.tsx diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index f06baeb7a4167b..862e5127bb6704 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -247,25 +247,11 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo } currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//'); - const maxAdditionalLengthOnNavUrl = 230; - - // On several test failures at the end of the TileMap test we try to navigate back to - // Visualize so we can create the next Vertical Bar Chart, but we can see from the - // logging and the screenshot that it's still on the TileMap page. Why didn't the "get" - // with a new timestamped URL go? I thought that sleep(700) between the get and the - // refresh would solve the problem but didn't seem to always work. - // So this hack fails the navSuccessful check if the currentUrl doesn't match the - // appUrl plus up to 230 other chars. - // Navigating to Settings when there is a default index pattern has a URL length of 196 - // (from debug output). Some other tabs may also be long. But a rather simple configured - // visualization is about 1000 chars long. So at least we catch that case. - - // Browsers don't show the ':port' if it's 80 or 443 so we have to - // remove that part so we can get a match in the tests. - const navSuccessful = new RegExp( - appUrl.replace(':80/', '/').replace(':443/', '/') + - `.{0,${maxAdditionalLengthOnNavUrl}}$` - ).test(currentUrl); + + const navSuccessful = currentUrl + .replace(':80/', '/') + .replace(':443/', '/') + .startsWith(appUrl); if (!navSuccessful) { const msg = `App failed to load: ${appName} in ${defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`; diff --git a/test/typings/rison_node.d.ts b/test/typings/rison_node.d.ts index 2592c36e8ae9a5..a0497f421c3fe2 100644 --- a/test/typings/rison_node.d.ts +++ b/test/typings/rison_node.d.ts @@ -18,7 +18,7 @@ */ declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RisonArray extends Array {} diff --git a/typings/rison_node.d.ts b/typings/rison_node.d.ts index 2592c36e8ae9a5..a0497f421c3fe2 100644 --- a/typings/rison_node.d.ts +++ b/typings/rison_node.d.ts @@ -18,7 +18,7 @@ */ declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RisonArray extends Array {} diff --git a/x-pack/plugins/infra/common/http_api/metadata_api.ts b/x-pack/plugins/infra/common/http_api/metadata_api.ts index 7fc3c3e876f086..5ee96b479be8ee 100644 --- a/x-pack/plugins/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/infra/common/http_api/metadata_api.ts @@ -11,6 +11,10 @@ export const InfraMetadataRequestRT = rt.type({ nodeId: rt.string, nodeType: ItemTypeRT, sourceId: rt.string, + timeRange: rt.type({ + from: rt.number, + to: rt.number, + }), }); export const InfraMetadataFeatureRT = rt.type({ diff --git a/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx b/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx index c8e0680287526f..68bfe41fd538e6 100644 --- a/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx +++ b/x-pack/plugins/infra/common/inventory_models/aws_ec2/layout.tsx @@ -20,7 +20,7 @@ import { withTheme } from '../../../../observability/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetadataDetails } from '../../../public/pages/metrics/components/metadata_details'; -export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( +export const Layout = withTheme(({ metrics, theme, onChangeRangeTime }: LayoutPropsWithTheme) => ( ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( @@ -40,6 +40,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( />
- - + +
)); diff --git a/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx b/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx index 43b95d73f6d955..8bc2f3ee8b4b31 100644 --- a/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx +++ b/x-pack/plugins/infra/common/inventory_models/pod/layout.tsx @@ -23,7 +23,7 @@ import { MetadataDetails } from '../../../public/pages/metrics/components/metada // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LayoutContent } from '../../../public/pages/metrics/components/layout_content'; -export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( @@ -38,6 +38,7 @@ export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( />
- +
)); diff --git a/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx b/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx index fba48c4224e6b2..7a0b898d406ce7 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/layouts/aws.tsx @@ -18,7 +18,7 @@ import { ChartSectionVis } from '../../../../public/pages/metrics/components/cha // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { withTheme } from '../../../../../observability/public'; -export const Layout = withTheme(({ metrics, theme }: LayoutPropsWithTheme) => ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => (
( } )} metrics={metrics} + onChangeRangeTime={onChangeRangeTime} > ( +export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPropsWithTheme) => ( -
+
{ const [darkMode] = useUiSetting$('theme:darkMode'); @@ -56,19 +45,15 @@ export async function startApp( - - - - - - - - - - - - - + + + + + + + + + diff --git a/x-pack/plugins/infra/public/components/inventory/layout.tsx b/x-pack/plugins/infra/public/components/inventory/layout.tsx index 4dd9803c7bfce0..3c91f9fa5946fc 100644 --- a/x-pack/plugins/infra/public/components/inventory/layout.tsx +++ b/x-pack/plugins/infra/public/components/inventory/layout.tsx @@ -5,64 +5,89 @@ */ import React from 'react'; -import { InfraWaffleMapOptions, InfraWaffleMapBounds } from '../../lib/lib'; -import { KueryFilterQuery } from '../../store/local/waffle_filter'; +import { useInterval } from 'react-use'; +import { euiPaletteColorBlind } from '@elastic/eui'; import { NodesOverview } from '../nodes_overview'; import { Toolbar } from './toolbars/toolbar'; import { PageContent } from '../page'; import { useSnapshot } from '../../containers/waffle/use_snaphot'; import { useInventoryMeta } from '../../containers/inventory_metadata/use_inventory_meta'; -import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; -import { InventoryItemType } from '../../../common/inventory_models/types'; +import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time'; +import { useWaffleFiltersContext } from '../../pages/inventory_view/hooks/use_waffle_filters'; +import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options'; +import { useSourceContext } from '../../containers/source'; +import { InfraFormatterType, InfraWaffleMapGradientLegend } from '../../lib/lib'; -export interface LayoutProps { - options: InfraWaffleMapOptions; - nodeType: InventoryItemType; - onDrilldown: (filter: KueryFilterQuery) => void; - currentTime: number; - onViewChange: (view: string) => void; - view: string; - boundsOverride: InfraWaffleMapBounds; - autoBounds: boolean; +const euiVisColorPalette = euiPaletteColorBlind(); - filterQuery: string | null | undefined; - metric: SnapshotMetricInput; - groupBy: SnapshotGroupBy; - sourceId: string; - accountId: string; - region: string; -} - -export const Layout = (props: LayoutProps) => { - const { accounts, regions } = useInventoryMeta(props.sourceId, props.nodeType); +export const Layout = () => { + const { sourceId, source } = useSourceContext(); + const { + metric, + groupBy, + nodeType, + accountId, + region, + changeView, + view, + autoBounds, + boundsOverride, + } = useWaffleOptionsContext(); + const { accounts, regions } = useInventoryMeta(sourceId, nodeType); + const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); + const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext(); const { loading, nodes, reload, interval } = useSnapshot( - props.filterQuery, - props.metric, - props.groupBy, - props.nodeType, - props.sourceId, - props.currentTime, - props.accountId, - props.region + filterQueryAsJson, + metric, + groupBy, + nodeType, + sourceId, + currentTime, + accountId, + region + ); + + const options = { + formatter: InfraFormatterType.percent, + formatTemplate: '{{value}}', + legend: { + type: 'gradient', + rules: [ + { value: 0, color: '#D3DAE6' }, + { value: 1, color: euiVisColorPalette[1] }, + ], + } as InfraWaffleMapGradientLegend, + metric, + fields: source?.configuration?.fields, + groupBy, + }; + + useInterval( + () => { + if (!loading) { + jumpToTime(Date.now()); + } + }, + isAutoReloading ? 5000 : null ); return ( <> - + diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx new file mode 100644 index 00000000000000..cb315d3e17b032 --- /dev/null +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/save_views.tsx @@ -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 ( + + ); +}; diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx index c59ab994a018ce..63ab6d2f4465a4 100644 --- a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx @@ -5,7 +5,6 @@ */ import React, { FunctionComponent } from 'react'; -import { Action } from 'typescript-fsa'; import { EuiFlexItem } from '@elastic/eui'; import { SnapshotMetricInput, @@ -16,33 +15,23 @@ import { InventoryCloudAccount } from '../../../../common/http_api/inventory_met import { findToolbar } from '../../../../common/inventory_models/toolbars'; import { ToolbarWrapper } from './toolbar_wrapper'; -import { waffleOptionsSelectors } from '../../../store'; import { InfraGroupByOptions } from '../../../lib/lib'; -import { WithWaffleViewState } from '../../../containers/waffle/with_waffle_view_state'; -import { SavedViewsToolbarControls } from '../../saved_views/toolbar_control'; -import { inventoryViewSavedObjectType } from '../../../../common/saved_objects/inventory_view'; import { IIndexPattern } from '../../../../../../../src/plugins/data/public'; import { InventoryItemType } from '../../../../common/inventory_models/types'; +import { WaffleOptionsState } from '../../../pages/inventory_view/hooks/use_waffle_options'; +import { SavedViews } from './save_views'; -export interface ToolbarProps { +export interface ToolbarProps + extends Omit { createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern; - changeMetric: (payload: SnapshotMetricInput) => Action; - changeGroupBy: (payload: SnapshotGroupBy) => Action; - changeCustomOptions: (payload: InfraGroupByOptions[]) => Action; - changeAccount: (id: string) => Action; - changeRegion: (name: string) => Action; - customOptions: ReturnType; - groupBy: ReturnType; - metric: ReturnType; - nodeType: ReturnType; - accountId: ReturnType; - region: ReturnType; + changeMetric: (payload: SnapshotMetricInput) => void; + changeGroupBy: (payload: SnapshotGroupBy) => void; + changeCustomOptions: (payload: InfraGroupByOptions[]) => void; + changeAccount: (id: string) => void; + changeRegion: (name: string) => void; accounts: InventoryCloudAccount[]; regions: string[]; - customMetrics: ReturnType; - changeCustomMetrics: ( - payload: SnapshotCustomMetricInput[] - ) => Action; + changeCustomMetrics: (payload: SnapshotCustomMetricInput[]) => void; } const wrapToolbarItems = ( @@ -57,16 +46,7 @@ const wrapToolbarItems = ( - - {({ defaultViewState, viewState, onViewChange }) => ( - - )} - + )} diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx index 735539d063b01c..fefda94372cfb4 100644 --- a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx @@ -8,58 +8,52 @@ import React from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SnapshotMetricType } from '../../../../common/inventory_models/types'; -import { WithSource } from '../../../containers/with_source'; -import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options'; import { Toolbar } from '../../eui/toolbar'; import { ToolbarProps } from './toolbar'; import { fieldToName } from '../../waffle/lib/field_to_display_name'; +import { useSourceContext } from '../../../containers/source'; +import { useWaffleOptionsContext } from '../../../pages/inventory_view/hooks/use_waffle_options'; interface Props { children: (props: Omit) => React.ReactElement; } export const ToolbarWrapper = (props: Props) => { + const { + changeMetric, + changeGroupBy, + changeCustomOptions, + changeAccount, + changeRegion, + customOptions, + groupBy, + metric, + nodeType, + accountId, + region, + customMetrics, + changeCustomMetrics, + } = useWaffleOptionsContext(); + const { createDerivedIndexPattern } = useSourceContext(); return ( - - {({ createDerivedIndexPattern }) => ( - - {({ - changeMetric, - changeGroupBy, - changeCustomOptions, - changeAccount, - changeRegion, - customOptions, - groupBy, - metric, - nodeType, - accountId, - region, - customMetrics, - changeCustomMetrics, - }) => - props.children({ - createDerivedIndexPattern, - changeMetric, - changeGroupBy, - changeAccount, - changeRegion, - changeCustomOptions, - customOptions, - groupBy, - metric, - nodeType, - region, - accountId, - customMetrics, - changeCustomMetrics, - }) - } - - )} - + {props.children({ + createDerivedIndexPattern, + changeMetric, + changeGroupBy, + changeAccount, + changeRegion, + changeCustomOptions, + customOptions, + groupBy, + metric, + nodeType, + region, + accountId, + customMetrics, + changeCustomMetrics, + })} ); diff --git a/x-pack/plugins/infra/public/components/nodes_overview/index.tsx b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx index 4d61568a63b9ff..ef22e0486f8921 100644 --- a/x-pack/plugins/infra/public/components/nodes_overview/index.tsx +++ b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx @@ -12,7 +12,6 @@ import React from 'react'; import { euiStyled } from '../../../../observability/public'; import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib'; -import { KueryFilterQuery } from '../../store/local/waffle_filter'; import { createFormatter } from '../../utils/formatters'; import { NoData } from '../empty_states'; import { InfraLoadingPanel } from '../loading'; @@ -24,6 +23,11 @@ import { convertIntervalToString } from '../../utils/convert_interval_to_string' import { InventoryItemType } from '../../../common/inventory_models/types'; import { createFormatterForMetric } from '../metrics_explorer/helpers/create_formatter_for_metric'; +export interface KueryFilterQuery { + kind: 'kuery'; + expression: string; +} + interface Props { options: InfraWaffleMapOptions; nodeType: InventoryItemType; diff --git a/x-pack/plugins/infra/public/components/waffle/legend.tsx b/x-pack/plugins/infra/public/components/waffle/legend.tsx index de070efb35a1f7..13e533b225d4df 100644 --- a/x-pack/plugins/infra/public/components/waffle/legend.tsx +++ b/x-pack/plugins/infra/public/components/waffle/legend.tsx @@ -6,12 +6,12 @@ import React from 'react'; import { euiStyled } from '../../../../observability/public'; -import { WithWaffleOptions } from '../../containers/waffle/with_waffle_options'; import { InfraFormatter, InfraWaffleMapBounds, InfraWaffleMapLegend } from '../../lib/lib'; import { GradientLegend } from './gradient_legend'; import { LegendControls } from './legend_controls'; import { isInfraWaffleMapGradientLegend, isInfraWaffleMapStepLegend } from './lib/type_guards'; import { StepLegend } from './steps_legend'; +import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options'; interface Props { legend: InfraWaffleMapLegend; bounds: InfraWaffleMapBounds; @@ -25,22 +25,24 @@ interface LegendControlOptions { } export const Legend: React.FC = ({ dataBounds, legend, bounds, formatter }) => { + const { + changeBoundsOverride, + changeAutoBounds, + autoBounds, + boundsOverride, + } = useWaffleOptionsContext(); return ( - - {({ changeBoundsOverride, changeAutoBounds, autoBounds, boundsOverride }) => ( - { - changeBoundsOverride(options.bounds); - changeAutoBounds(options.auto); - }} - /> - )} - + { + changeBoundsOverride(options.bounds); + changeAutoBounds(options.auto); + }} + /> {isInfraWaffleMapGradientLegend(legend) && ( )} diff --git a/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts b/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts index 18e5838a15b563..902969c83ba393 100644 --- a/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts +++ b/x-pack/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts @@ -5,11 +5,7 @@ */ import { createUptimeLink } from './create_uptime_link'; -import { - InfraWaffleMapOptions, - InfraWaffleMapLegendMode, - InfraFormatterType, -} from '../../../lib/lib'; +import { InfraWaffleMapOptions, InfraFormatterType } from '../../../lib/lib'; import { SnapshotMetricType } from '../../../../common/inventory_models/types'; const options: InfraWaffleMapOptions = { @@ -26,7 +22,7 @@ const options: InfraWaffleMapOptions = { metric: { type: 'cpu' }, groupBy: [], legend: { - type: InfraWaffleMapLegendMode.gradient, + type: 'gradient', rules: [], }, }; diff --git a/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts b/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts index aff16374ae2626..f793afee1b948b 100644 --- a/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts +++ b/x-pack/plugins/infra/public/components/waffle/lib/type_guards.ts @@ -4,16 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - InfraWaffleMapGradientLegend, - InfraWaffleMapLegendMode, - InfraWaffleMapStepLegend, -} from '../../../lib/lib'; +import { InfraWaffleMapGradientLegend, InfraWaffleMapStepLegend } from '../../../lib/lib'; + export function isInfraWaffleMapStepLegend(subject: any): subject is InfraWaffleMapStepLegend { - return subject.type && subject.type === InfraWaffleMapLegendMode.step; + return subject.type && subject.type === 'step'; } + export function isInfraWaffleMapGradientLegend( subject: any ): subject is InfraWaffleMapGradientLegend { - return subject.type && subject.type === InfraWaffleMapLegendMode.gradient; + return subject.type && subject.type === 'gradient'; } diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index d265b418f010df..21da10a0a76502 100644 --- a/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -16,36 +16,23 @@ import React, { useCallback, useState, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { findInventoryModel } from '../../../common/inventory_models'; import { InventoryItemType } from '../../../common/inventory_models/types'; -import { - SnapshotMetricInput, - SnapshotGroupBy, - SnapshotCustomMetricInput, -} from '../../../common/http_api/snapshot_api'; - -interface WaffleInventorySwitcherProps { - nodeType: InventoryItemType; - changeNodeType: (nodeType: InventoryItemType) => void; - changeGroupBy: (groupBy: SnapshotGroupBy) => void; - changeMetric: (metric: SnapshotMetricInput) => void; - changeCustomMetrics: (metrics: SnapshotCustomMetricInput[]) => void; - changeAccount: (id: string) => void; - changeRegion: (name: string) => void; -} +import { useWaffleOptionsContext } from '../../pages/inventory_view/hooks/use_waffle_options'; const getDisplayNameForType = (type: InventoryItemType) => { const inventoryModel = findInventoryModel(type); return inventoryModel.displayName; }; -export const WaffleInventorySwitcher: React.FC = ({ - changeNodeType, - changeGroupBy, - changeMetric, - changeAccount, - changeRegion, - changeCustomMetrics, - nodeType, -}) => { +export const WaffleInventorySwitcher: React.FC = () => { + const { + changeNodeType, + changeGroupBy, + changeMetric, + changeAccount, + changeRegion, + changeCustomMetrics, + nodeType, + } = useWaffleOptionsContext(); const [isOpen, setIsOpen] = useState(false); const closePopover = useCallback(() => setIsOpen(false), []); const openPopover = useCallback(() => setIsOpen(true), []); diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx index 4f840336de8c34..458bb674afadef 100644 --- a/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx +++ b/x-pack/plugins/infra/public/components/waffle/waffle_time_controls.tsx @@ -7,84 +7,60 @@ import { EuiButtonEmpty, EuiDatePicker, EuiFormControlLayout } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import moment, { Moment } from 'moment'; -import React from 'react'; -import { Action } from 'typescript-fsa'; +import React, { useCallback } from 'react'; +import { useWaffleTimeContext } from '../../pages/inventory_view/hooks/use_waffle_time'; -interface WaffleTimeControlsProps { - currentTime: number; - isLiveStreaming?: boolean; - onChangeTime?: (time: number) => void; - startLiveStreaming?: (payload: void) => Action; - stopLiveStreaming?: (payload: void) => Action; -} +export const WaffleTimeControls = () => { + const { + currentTime, + isAutoReloading, + startAutoReload, + stopAutoReload, + jumpToTime, + } = useWaffleTimeContext(); -export class WaffleTimeControls extends React.Component { - public render() { - const { currentTime, isLiveStreaming } = this.props; + const currentMoment = moment(currentTime); - const currentMoment = moment(currentTime); + const liveStreamingButton = isAutoReloading ? ( + + + + ) : ( + + + + ); - const liveStreamingButton = isLiveStreaming ? ( - - - - ) : ( - - - - ); + const handleChangeDate = useCallback( + (time: Moment | null) => { + if (time) { + jumpToTime(time.valueOf()); + } + }, + [jumpToTime] + ); - return ( - - - - ); - } - - private handleChangeDate = (time: Moment | null) => { - const { onChangeTime } = this.props; - - if (onChangeTime && time) { - onChangeTime(time.valueOf()); - } - }; - - private startLiveStreaming = () => { - const { startLiveStreaming } = this.props; - - if (startLiveStreaming) { - startLiveStreaming(); - } - }; - - private stopLiveStreaming = () => { - const { stopLiveStreaming } = this.props; - - if (stopLiveStreaming) { - stopLiveStreaming(); - } - }; -} + return ( + + + + ); +}; diff --git a/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts b/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts index 52c522ce8efd42..1ba016195bef40 100644 --- a/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts +++ b/x-pack/plugins/infra/public/containers/metadata/use_metadata.ts @@ -13,17 +13,18 @@ import { useHTTPRequest } from '../../hooks/use_http_request'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types'; import { getFilteredMetrics } from './lib/get_filtered_metrics'; +import { MetricsTimeInput } from '../../pages/metrics/hooks/use_metrics_time'; export function useMetadata( nodeId: string, nodeType: InventoryItemType, requiredMetrics: InventoryMetric[], - sourceId: string + sourceId: string, + timeRange: MetricsTimeInput ) { const decodeResponse = (response: any) => { return pipe(InfraMetadataRT.decode(response), fold(throwErrors(createPlainError), identity)); }; - const { error, loading, response, makeRequest } = useHTTPRequest( '/api/infra/metadata', 'POST', @@ -31,6 +32,7 @@ export function useMetadata( nodeId, nodeType, sourceId, + timeRange: { from: timeRange.from, to: timeRange.to }, }), decodeResponse ); diff --git a/x-pack/plugins/infra/public/containers/waffle/index.ts b/x-pack/plugins/infra/public/containers/waffle/index.ts deleted file mode 100644 index 40c4bfc8cf6789..00000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/index.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts b/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts deleted file mode 100644 index 1ca6bc8c397e5a..00000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/waffle_nodes.gql_query.ts +++ /dev/null @@ -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 - } - } - } - } - } -`; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx deleted file mode 100644 index 0214237ef52d8f..00000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_filters.tsx +++ /dev/null @@ -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; - -type WithWaffleFilterUrlStateProps = WithWaffleFilterProps; - -export const WithWaffleFilterUrlState: React.FC = ({ - indexPattern, -}) => ( - - {({ applyFilterQuery, filterQuery }) => ( - { - if (urlState) { - applyFilterQuery(urlState); - } - }} - onInitialize={urlState => { - if (urlState) { - applyFilterQuery(urlState); - } - }} - /> - )} - -); - -const mapToUrlState = (value: any): WaffleFilterUrlState | undefined => - value && value.kind === 'kuery' && typeof value.expression === 'string' - ? { - kind: value.kind, - expression: value.expression, - } - : undefined; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx deleted file mode 100644 index 47dd6a5a73a739..00000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx +++ /dev/null @@ -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; - groupBy?: ReturnType; - nodeType?: ReturnType; - view?: ReturnType; - customOptions?: ReturnType; - bounds?: ReturnType; - auto?: ReturnType; - accountId?: ReturnType; - region?: ReturnType; - customMetrics?: ReturnType; -} - -export const WithWaffleOptionsUrlState = () => ( - - {({ - changeMetric, - urlState, - changeGroupBy, - changeNodeType, - changeView, - changeCustomOptions, - changeAutoBounds, - changeBoundsOverride, - changeAccount, - changeRegion, - changeCustomMetrics, - }) => ( - - 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); - } - }} - /> - )} - -); - -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; -}; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx deleted file mode 100644 index 293f6184af21bb..00000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_time.tsx +++ /dev/null @@ -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; - autoReload?: ReturnType; -} - -export const WithWaffleTimeUrlState = () => ( - - {({ jumpToTime, startAutoReload, stopAutoReload, urlState }) => ( - { - 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(); - } - }} - /> - )} - -); - -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); diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx deleted file mode 100644 index 421c506166d047..00000000000000 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx +++ /dev/null @@ -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; - groupBy?: ReturnType; - nodeType?: ReturnType; - view?: ReturnType; - customOptions?: ReturnType; - customMetrics?: ReturnType; - boundsOverride?: ReturnType; - autoBounds?: ReturnType; - time?: ReturnType; - autoReload?: ReturnType; - filterQuery?: ReturnType; -} diff --git a/x-pack/plugins/infra/public/containers/with_options.tsx b/x-pack/plugins/infra/public/containers/with_options.tsx index 972722890ffefc..e18fc85a68d60b 100644 --- a/x-pack/plugins/infra/public/containers/with_options.tsx +++ b/x-pack/plugins/infra/public/containers/with_options.tsx @@ -8,7 +8,7 @@ import moment from 'moment'; import React from 'react'; import { euiPaletteColorBlind } from '@elastic/eui'; -import { InfraFormatterType, InfraOptions, InfraWaffleMapLegendMode } from '../lib/lib'; +import { InfraFormatterType, InfraOptions } from '../lib/lib'; import { RendererFunction } from '../utils/typed_react'; const euiVisColorPalette = euiPaletteColorBlind(); @@ -29,7 +29,7 @@ const initialState = { metric: { type: 'cpu' }, groupBy: [], legend: { - type: InfraWaffleMapLegendMode.gradient, + type: 'gradient', rules: [ { value: 0, diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 9f851e185018b6..e4de0caf9bb8ba 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -136,18 +136,13 @@ export interface InfraWaffleMapGradientRule { color: string; } -export enum InfraWaffleMapLegendMode { - step = 'step', - gradient = 'gradient', -} - export interface InfraWaffleMapStepLegend { - type: InfraWaffleMapLegendMode.step; + type: 'step'; rules: InfraWaffleMapStepRule[]; } export interface InfraWaffleMapGradientLegend { - type: InfraWaffleMapLegendMode.gradient; + type: 'gradient'; rules: InfraWaffleMapGradientRule[]; } diff --git a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx index 422eb53148fe6d..d592ae3480fc95 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/index.tsx @@ -25,6 +25,9 @@ import { MetricsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { SourceLoadingPage } from '../../components/source_loading_page'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { WaffleOptionsProvider } from '../inventory_view/hooks/use_waffle_options'; +import { WaffleTimeProvider } from '../inventory_view/hooks/use_waffle_time'; +import { WaffleFiltersProvider } from '../inventory_view/hooks/use_waffle_filters'; import { AlertDropdown } from '../../components/alerting/metrics/alert_dropdown'; export const InfrastructurePage = ({ match }: RouteComponentProps) => { @@ -32,96 +35,101 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { return ( - - - - + + + + + -
+ - - - - - - - - - - + + + + + + + + + + - - - ( - - {({ configuration, createDerivedIndexPattern }) => ( - - - {configuration ? ( - - ) : ( - - )} - - )} - - )} - /> - - - + + + ( + + {({ configuration, createDerivedIndexPattern }) => ( + + + {configuration ? ( + + ) : ( + + )} + + )} + + )} + /> + + + + + + ); }; diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx index dbb8b2d8e29521..48cc56388c0f24 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/index.tsx @@ -8,7 +8,6 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; -import { SnapshotPageContent } from './page_content'; import { SnapshotToolbar } from './toolbar'; import { DocumentTitle } from '../../../components/document_title'; @@ -19,17 +18,14 @@ import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { ViewSourceConfigurationButton } from '../../../components/source_configuration'; import { Source } from '../../../containers/source'; -import { WithWaffleFilterUrlState } from '../../../containers/waffle/with_waffle_filters'; -import { WithWaffleOptionsUrlState } from '../../../containers/waffle/with_waffle_options'; -import { WithWaffleTimeUrlState } from '../../../containers/waffle/with_waffle_time'; import { useTrackPageview } from '../../../../../observability/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { Layout } from '../../../components/inventory/layout'; import { useLinkProps } from '../../../hooks/use_link_props'; export const SnapshotPage = () => { const uiCapabilities = useKibana().services.application?.capabilities; const { - createDerivedIndexPattern, hasFailedLoadingSource, isLoading, loadSourceFailureMessage, @@ -60,11 +56,8 @@ export const SnapshotPage = () => { ) : metricIndicesExist ? ( <> - - - - + ) : hasFailedLoadingSource ? ( diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx deleted file mode 100644 index 83a4c8d3a497f6..00000000000000 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/page_content.tsx +++ /dev/null @@ -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 = () => ( - - {({ configuration, createDerivedIndexPattern, sourceId }) => ( - - {({ wafflemap }) => ( - - {({ filterQueryAsJson, applyFilterQuery }) => ( - - {({ currentTime }) => ( - - {({ - metric, - groupBy, - nodeType, - view, - changeView, - autoBounds, - boundsOverride, - accountId, - region, - }) => ( - - )} - - )} - - )} - - )} - - )} - -); diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx index 3606580e865045..ccdaa5e8dc7857 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx @@ -5,92 +5,24 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; -import { AutocompleteField } from '../../../components/autocomplete_field'; import { Toolbar } from '../../../components/eui/toolbar'; import { WaffleTimeControls } from '../../../components/waffle/waffle_time_controls'; -import { WithWaffleFilter } from '../../../containers/waffle/with_waffle_filters'; -import { WithWaffleTime } from '../../../containers/waffle/with_waffle_time'; -import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion'; -import { WithSource } from '../../../containers/with_source'; -import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options'; import { WaffleInventorySwitcher } from '../../../components/waffle/waffle_inventory_switcher'; +import { SearchBar } from '../../inventory_view/compontents/search_bar'; export const SnapshotToolbar = () => ( - - {({ - changeMetric, - changeNodeType, - changeGroupBy, - changeAccount, - changeRegion, - changeCustomMetrics, - nodeType, - }) => ( - - )} - + - - {({ createDerivedIndexPattern }) => ( - - {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( - - {({ - applyFilterQueryFromKueryExpression, - filterQueryDraft, - isFilterQueryDraftValid, - setFilterQueryDraftFromKueryExpression, - }) => ( - - )} - - )} - - )} - + - - {({ currentTime, isAutoReloading, jumpToTime, startAutoReload, stopAutoReload }) => ( - - )} - + diff --git a/x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx b/x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx new file mode 100644 index 00000000000000..f4fde46d434f83 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/compontents/search_bar.tsx @@ -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 ( + + {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts new file mode 100644 index 00000000000000..02c079dcaddc40 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_filters.ts @@ -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({ + defaultState: DEFAULT_WAFFLE_FILTERS_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'waffleFilter', + }); + + const [state, setState] = useState(urlState); + + useEffect(() => setUrlState(state), [setUrlState, state]); + + const [filterQueryDraft, setFilterQueryDraft] = useState(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; +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; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts new file mode 100644 index 00000000000000..2853917d5f6838 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_options.ts @@ -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({ + defaultState: DEFAULT_WAFFLE_OPTIONS_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'waffleOptions', + }); + + const [state, setState] = useState(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; +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; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts new file mode 100644 index 00000000000000..051b5e598cb757 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_time.ts @@ -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({ + defaultState: DEFAULT_WAFFLE_TIME_STATE, + decodeUrlState, + encodeUrlState, + urlStateKey: 'waffleTime', + }); + + const [state, setState] = useState(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; +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; diff --git a/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.ts new file mode 100644 index 00000000000000..869560b2b87095 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/inventory_view/hooks/use_waffle_view_state.ts @@ -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; +}; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx index 01b02f1acbbf25..b1dab3bd3f6737 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_host_detail_via_ip.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; +import { replaceMetricTimeInQueryString } from '../metrics/hooks/use_metrics_time'; import { useHostIpToName } from './use_host_ip_to_name'; import { getFromFromLocation, getToFromLocation } from './query_params'; import { LoadingPage } from '../../components/loading_page'; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx index 9eae632756a3ff..72a41f5264244f 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; +import { replaceMetricTimeInQueryString } from '../metrics/hooks/use_metrics_time'; import { getFromFromLocation, getToFromLocation } from './query_params'; import { InventoryItemType } from '../../../common/inventory_models/types'; import { LinkDescriptor } from '../../hooks/use_link_props'; diff --git a/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx b/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx index ea91c53faf6757..dd2a5f2bdb39e6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/node_details_page.tsx @@ -22,7 +22,7 @@ import { MetricsTimeControls } from './time_controls'; import { SideNavContext, NavItem } from '../lib/side_nav_context'; import { PageBody } from './page_body'; import { euiStyled } from '../../../../../observability/public'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { InfraMetadata } from '../../../../common/http_api/metadata_api'; import { PageError } from './page_error'; import { MetadataContext } from '../../../pages/metrics/containers/metadata_context'; @@ -94,7 +94,7 @@ export const NodeDetailsPage = (props: Props) => { setRefreshInterval={props.setRefreshInterval} onChangeTimeRange={props.setTimeRange} setAutoReload={props.setAutoReload} - onRefresh={props.triggerRefresh} + onRefresh={refetch} /> diff --git a/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx b/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx index 414b9c60adee39..e651d6b92d9811 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/page_body.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { findLayout } from '../../../../common/inventory_models/layouts'; import { InventoryItemType } from '../../../../common/inventory_models/types'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { InfraLoadingPanel } from '../../../components/loading'; import { NoData } from '../../../components/empty_states'; import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api'; @@ -19,9 +19,9 @@ interface Props { refetch: () => void; type: InventoryItemType; metrics: NodeDetailsMetricData[]; - onChangeRangeTime?: (time: MetricsTimeInput) => void; - isLiveStreaming?: boolean; - stopLiveStreaming?: () => void; + onChangeRangeTime: (time: MetricsTimeInput) => void; + isLiveStreaming: boolean; + stopLiveStreaming: () => void; } export const PageBody = ({ diff --git a/x-pack/plugins/infra/public/pages/metrics/components/section.tsx b/x-pack/plugins/infra/public/pages/metrics/components/section.tsx index 2f9ed9f54df826..68003737a1f145 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/section.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/section.tsx @@ -41,6 +41,9 @@ export const Section: FunctionComponent = ({ if (metric === null) { return accumulatedChildren; } + if (!child.props.label) { + return accumulatedChildren; + } return [ ...accumulatedChildren, { diff --git a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx index 91e25fd8ef5854..02ba506e8abe14 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.test.tsx @@ -19,7 +19,7 @@ jest.mock('../../../utils/use_kibana_ui_setting', () => ({ import React from 'react'; import { MetricsTimeControls } from './time_controls'; import { mount } from 'enzyme'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; describe('MetricsTimeControls', () => { it('should set a valid from and to value for Today', () => { diff --git a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx index b1daaa0320fabc..cdbdc9bb7ecdb5 100644 --- a/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/components/time_controls.tsx @@ -7,7 +7,7 @@ import { EuiSuperDatePicker, OnRefreshChangeProps, OnTimeChangeProps } from '@elastic/eui'; import React, { useCallback } from 'react'; import { euiStyled } from '../../../../../observability/public'; -import { MetricsTimeInput } from '../containers/with_metrics_time'; +import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; import { mapKibanaQuickRangesToDatePickerRanges } from '../../../utils/map_timepicker_quickranges_to_datepicker_ranges'; @@ -61,8 +61,8 @@ export const MetricsTimeControls = (props: MetricsTimeControlsProps) => { return ( 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(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 = ({ - children, -}: WithMetricsTimeProps) => { - const metricsTimeState = useContext(MetricsTimeContainer.Context); - return children({ ...metricsTimeState }); -}; - -/** - * Url State - */ - -interface MetricsTimeUrlState { - time?: MetricsTimeState['timeRange']; - autoReload?: boolean; - refreshInterval?: number; -} - -export const WithMetricsTimeUrlState = () => ( - - {({ - timeRange, - setTimeRange, - refreshInterval, - setRefreshInterval, - isAutoReloading, - setAutoReload, - }) => ( - { - 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); - } - }} - /> - )} - -); - -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('metricTime', { - autoReload: false, - time: { - interval: '>=1m', - from: moment(from).toISOString(), - to: moment(to).toISOString(), - }, - }); diff --git a/x-pack/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx b/x-pack/plugins/infra/public/pages/metrics/hooks/metrics_time.test.tsx similarity index 96% rename from x-pack/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx rename to x-pack/plugins/infra/public/pages/metrics/hooks/metrics_time.test.tsx index 350fa908109353..17fcc05406470a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/containers/metrics_time.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hooks/metrics_time.test.tsx @@ -6,7 +6,7 @@ import { mountHook } from 'test_utils/enzyme_helpers'; -import { useMetricsTime } from './with_metrics_time'; +import { useMetricsTime } from './use_metrics_time'; describe('useMetricsTime hook', () => { describe('timeRange state', () => { diff --git a/x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts b/x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts new file mode 100644 index 00000000000000..2ed86863535ff7 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hooks/use_metrics_time.ts @@ -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({ + 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(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; + +export const MetricsTimeUrlStateRT = rt.partial({ + time: MetricsTimeInputRT, + autoReload: rt.boolean, + refreshInterval: rt.number, +}); +export type MetricsTimeUrlState = rt.TypeOf; + +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('metricTime', { + autoReload: false, + time: { + interval: '>=1m', + from: moment(from).toISOString(), + to: moment(to).toISOString(), + }, + }); + +export const MetricsTimeContainer = createContainer(useMetricsTime); +export const [MetricsTimeProvider, useMetricsTimeContext] = MetricsTimeContainer; diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 52c9825a4d614f..531be40d2dc43b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -9,7 +9,6 @@ import { euiStyled, EuiTheme, withTheme } from '../../../../observability/public import { DocumentTitle } from '../../components/document_title'; import { Header } from '../../components/header'; import { ColumnarPage, PageContent } from '../../components/page'; -import { WithMetricsTime, WithMetricsTimeUrlState } from './containers/with_metrics_time'; import { withMetricPageProviders } from './page_providers'; import { useMetadata } from '../../containers/metadata/use_metadata'; import { Source } from '../../containers/source'; @@ -19,6 +18,7 @@ import { NavItem } from './lib/side_nav_context'; import { NodeDetailsPage } from './components/node_details_page'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { InventoryItemType } from '../../../common/inventory_models/types'; +import { useMetricsTimeContext } from './hooks/use_metrics_time'; import { useLinkProps } from '../../hooks/use_link_props'; const DetailPageContent = euiStyled(PageContent)` @@ -37,19 +37,29 @@ interface Props { } export const MetricDetail = withMetricPageProviders( - withTheme(({ match, theme }: Props) => { + withTheme(({ match }: Props) => { const uiCapabilities = useKibana().services.application?.capabilities; const nodeId = match.params.node; const nodeType = match.params.type as InventoryItemType; const inventoryModel = findInventoryModel(nodeType); const { sourceId } = useContext(Source.Context); + const { + timeRange, + parsedTimeRange, + setTimeRange, + refreshInterval, + setRefreshInterval, + isAutoReloading, + setAutoReload, + triggerRefresh, + } = useMetricsTimeContext(); const { name, filteredRequiredMetrics, loading: metadataLoading, cloudId, metadata, - } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId); + } = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, parsedTimeRange); const [sideNav, setSideNav] = useState([]); @@ -90,58 +100,41 @@ export const MetricDetail = withMetricPageProviders( } return ( - - {({ - timeRange, - parsedTimeRange, - setTimeRange, - refreshInterval, - setRefreshInterval, - isAutoReloading, - setAutoReload, - triggerRefresh, - }) => ( - -
- - +
+ + + {metadata ? ( + - - {metadata ? ( - - ) : null} - - - )} - + ) : null} + + ); }) ); diff --git a/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx b/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx index 0abbd597dd65cd..d3f10adec06ed7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/page_providers.tsx @@ -6,15 +6,15 @@ import React from 'react'; -import { MetricsTimeContainer } from './containers/with_metrics_time'; import { Source } from '../../containers/source'; +import { MetricsTimeProvider } from './hooks/use_metrics_time'; export const withMetricPageProviders = (Component: React.ComponentType) => ( props: T ) => ( - + - + ); diff --git a/x-pack/plugins/infra/public/pages/metrics/types.ts b/x-pack/plugins/infra/public/pages/metrics/types.ts index fd6243292ec07d..2cc261df289772 100644 --- a/x-pack/plugins/infra/public/pages/metrics/types.ts +++ b/x-pack/plugins/infra/public/pages/metrics/types.ts @@ -7,7 +7,7 @@ import rt from 'io-ts'; import { EuiTheme } from '../../../../observability/public'; import { InventoryFormatterTypeRT } from '../../../common/inventory_models/types'; -import { MetricsTimeInput } from './containers/with_metrics_time'; +import { MetricsTimeInput } from './hooks/use_metrics_time'; import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api'; export interface LayoutProps { diff --git a/x-pack/plugins/infra/public/store/actions.ts b/x-pack/plugins/infra/public/store/actions.ts deleted file mode 100644 index 8a5d1f6c668d0b..00000000000000 --- a/x-pack/plugins/infra/public/store/actions.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/store/epics.ts b/x-pack/plugins/infra/public/store/epics.ts deleted file mode 100644 index b5e48a4ec6214e..00000000000000 --- a/x-pack/plugins/infra/public/store/epics.ts +++ /dev/null @@ -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 = () => combineEpics(createLocalEpic()); diff --git a/x-pack/plugins/infra/public/store/index.ts b/x-pack/plugins/infra/public/store/index.ts deleted file mode 100644 index 025da41ec40d51..00000000000000 --- a/x-pack/plugins/infra/public/store/index.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/store/local/actions.ts b/x-pack/plugins/infra/public/store/local/actions.ts deleted file mode 100644 index 1c79d5a515cd4e..00000000000000 --- a/x-pack/plugins/infra/public/store/local/actions.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/store/local/epic.ts b/x-pack/plugins/infra/public/store/local/epic.ts deleted file mode 100644 index e1a051355576f7..00000000000000 --- a/x-pack/plugins/infra/public/store/local/epic.ts +++ /dev/null @@ -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 = () => combineEpics(createWaffleTimeEpic()); diff --git a/x-pack/plugins/infra/public/store/local/index.ts b/x-pack/plugins/infra/public/store/local/index.ts deleted file mode 100644 index c2843320bfd0cc..00000000000000 --- a/x-pack/plugins/infra/public/store/local/index.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/store/local/reducer.ts b/x-pack/plugins/infra/public/store/local/reducer.ts deleted file mode 100644 index 9e194a5d37f493..00000000000000 --- a/x-pack/plugins/infra/public/store/local/reducer.ts +++ /dev/null @@ -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({ - waffleFilter: waffleFilterReducer, - waffleTime: waffleTimeReducer, - waffleMetrics: waffleOptionsReducer, -}); diff --git a/x-pack/plugins/infra/public/store/local/selectors.ts b/x-pack/plugins/infra/public/store/local/selectors.ts deleted file mode 100644 index 56ffc53c2bc72a..00000000000000 --- a/x-pack/plugins/infra/public/store/local/selectors.ts +++ /dev/null @@ -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 -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts deleted file mode 100644 index a23f9b3108b5b1..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/actions.ts +++ /dev/null @@ -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( - 'SET_WAFFLE_FILTER_QUERY_DRAFT' -); - -export const applyWaffleFilterQuery = actionCreator( - 'APPLY_WAFFLE_FILTER_QUERY' -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/index.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/index.ts deleted file mode 100644 index 558314f2aeda8e..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/index.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts deleted file mode 100644 index 912ad96357334d..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/reducer.ts +++ /dev/null @@ -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(); diff --git a/x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts b/x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts deleted file mode 100644 index 047dabd3f0dd3e..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_filter/selectors.ts +++ /dev/null @@ -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; - } -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts deleted file mode 100644 index 88229c31b2056f..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts +++ /dev/null @@ -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('CHANGE_METRIC'); -export const changeGroupBy = actionCreator('CHANGE_GROUP_BY'); -export const changeCustomOptions = actionCreator('CHANGE_CUSTOM_OPTIONS'); -export const changeNodeType = actionCreator('CHANGE_NODE_TYPE'); -export const changeView = actionCreator('CHANGE_VIEW'); -export const changeBoundsOverride = actionCreator('CHANGE_BOUNDS_OVERRIDE'); -export const changeAutoBounds = actionCreator('CHANGE_AUTO_BOUNDS'); -export const changeAccount = actionCreator('CHANGE_ACCOUNT'); -export const changeRegion = actionCreator('CHANGE_REGION'); -export const changeCustomMetrics = actionCreator( - 'CHANGE_CUSTOM_METRICS' -); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/index.ts b/x-pack/plugins/infra/public/store/local/waffle_options/index.ts deleted file mode 100644 index 3ecf108eb49d4f..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/index.ts +++ /dev/null @@ -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'; diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts deleted file mode 100644 index 3789228a7c16b0..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts +++ /dev/null @@ -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({ - metric: currentMetricReducer, - groupBy: currentGroupByReducer, - nodeType: currentNodeTypeReducer, - view: currentViewReducer, - customOptions: currentCustomOptionsReducer, - boundsOverride: currentBoundsOverrideReducer, - autoBounds: currentAutoBoundsReducer, - accountId: currentAccountIdReducer, - region: currentRegionReducer, - customMetrics: currentCustomMetricsReducer, -}); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts b/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts deleted file mode 100644 index 4487af156df971..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts +++ /dev/null @@ -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; diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_time/actions.ts deleted file mode 100644 index fe79f2f536a936..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/actions.ts +++ /dev/null @@ -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('JUMP_TO_TIME'); - -export const startAutoReload = actionCreator('START_AUTO_RELOAD'); - -export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD'); diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/epic.ts b/x-pack/plugins/infra/public/store/local/waffle_time/epic.ts deleted file mode 100644 index 986d6b17a24242..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/epic.ts +++ /dev/null @@ -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 { - selectWaffleTimeUpdatePolicyInterval: (state: State) => number | null; -} - -export const createWaffleTimeEpic = (): Epic< - Action, - Action, - State, - WaffleTimeEpicDependencies -> => (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 = (value: T | null): value is T => value !== null; diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/index.ts b/x-pack/plugins/infra/public/store/local/waffle_time/index.ts deleted file mode 100644 index 2b99a6d6d57600..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/index.ts +++ /dev/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'; diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts deleted file mode 100644 index 026e5decf5d376..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/reducer.ts +++ /dev/null @@ -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({ - currentTime: currentTimeReducer, - updatePolicy: updatePolicyReducer, -}); diff --git a/x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts b/x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts deleted file mode 100644 index 0b6d01bdf52883..00000000000000 --- a/x-pack/plugins/infra/public/store/local/waffle_time/selectors.ts +++ /dev/null @@ -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, -})); diff --git a/x-pack/plugins/infra/public/store/reducer.ts b/x-pack/plugins/infra/public/store/reducer.ts deleted file mode 100644 index 2536ddbee401bf..00000000000000 --- a/x-pack/plugins/infra/public/store/reducer.ts +++ /dev/null @@ -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({ - local: localReducer, -}); diff --git a/x-pack/plugins/infra/public/store/selectors.ts b/x-pack/plugins/infra/public/store/selectors.ts deleted file mode 100644 index f4011c232cba45..00000000000000 --- a/x-pack/plugins/infra/public/store/selectors.ts +++ /dev/null @@ -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); diff --git a/x-pack/plugins/infra/public/store/store.ts b/x-pack/plugins/infra/public/store/store.ts deleted file mode 100644 index cae0622c5e4a15..00000000000000 --- a/x-pack/plugins/infra/public/store/store.ts +++ /dev/null @@ -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; - observableApi: Observable; -} - -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( - { - dependencies: middlewareDependencies, - } - ); - - const store = createBasicStore( - reducer, - initialState, - composeEnhancers(applyMiddleware(epicMiddleware)) - ); - - epicMiddleware.run(createRootEpic()); - - return store; -} diff --git a/x-pack/plugins/infra/public/utils/redux_context.tsx b/x-pack/plugins/infra/public/utils/redux_context.tsx deleted file mode 100644 index f249d72a6b56fc..00000000000000 --- a/x-pack/plugins/infra/public/utils/redux_context.tsx +++ /dev/null @@ -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 {children}; -}; diff --git a/x-pack/plugins/infra/server/routes/metadata/index.ts b/x-pack/plugins/infra/server/routes/metadata/index.ts index 03d28110d612a1..c45f191b1130d8 100644 --- a/x-pack/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/metadata/index.ts @@ -38,7 +38,7 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { }, async (requestContext, request, response) => { try { - const { nodeId, nodeType, sourceId } = pipe( + const { nodeId, nodeType, sourceId, timeRange } = pipe( InfraMetadataRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); @@ -52,7 +52,8 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { requestContext, configuration, nodeId, - nodeType + nodeType, + timeRange ); const metricFeatures = pickFeatureName(metricsMetadata.buckets).map( nameToFeature('metrics') @@ -62,7 +63,13 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { const cloudInstanceId = get(info, 'cloud.instance.id'); const cloudMetricsMetadata = cloudInstanceId - ? await getCloudMetricsMetadata(framework, requestContext, configuration, cloudInstanceId) + ? await getCloudMetricsMetadata( + framework, + requestContext, + configuration, + cloudInstanceId, + timeRange + ) : { buckets: [] }; const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map( nameToFeature('metrics') diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts index 75ca3ae3caee21..54a1ca0aaa7e04 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_cloud_metric_metadata.ts @@ -21,7 +21,8 @@ export const getCloudMetricsMetadata = async ( framework: KibanaFramework, requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, - instanceId: string + instanceId: string, + timeRange: { from: number; to: number } ): Promise => { const metricQuery = { allowNoIndices: true, @@ -30,7 +31,18 @@ export const getCloudMetricsMetadata = async ( body: { query: { bool: { - filter: [{ match: { 'cloud.instance.id': instanceId } }], + filter: [ + { match: { 'cloud.instance.id': instanceId } }, + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: timeRange.from, + lte: timeRange.to, + format: 'epoch_millis', + }, + }, + }, + ], should: CLOUD_METRICS_MODULES.map(module => ({ match: { 'event.module': module } })), }, }, diff --git a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts index 191339565b813f..7753d3161039b4 100644 --- a/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts +++ b/x-pack/plugins/infra/server/routes/metadata/lib/get_metric_metadata.ts @@ -26,7 +26,8 @@ export const getMetricMetadata = async ( requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, nodeId: string, - nodeType: InventoryItemType + nodeType: InventoryItemType, + timeRange: { from: number; to: number } ): Promise => { const fields = findInventoryFields(nodeType, sourceConfiguration.fields); const metricQuery = { @@ -41,6 +42,15 @@ export const getMetricMetadata = async ( { match: { [fields.id]: nodeId }, }, + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: timeRange.from, + lte: timeRange.to, + format: 'epoch_millis', + }, + }, + }, ], }, }, diff --git a/x-pack/test/api_integration/apis/infra/metadata.ts b/x-pack/test/api_integration/apis/infra/metadata.ts index b693881abcdf72..5187cc5e3ec26a 100644 --- a/x-pack/test/api_integration/apis/infra/metadata.ts +++ b/x-pack/test/api_integration/apis/infra/metadata.ts @@ -12,6 +12,28 @@ import { } from '../../../../plugins/infra/common/http_api/metadata_api'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { DATES } from './constants'; + +const timeRange700 = { + from: DATES['7.0.0'].hosts.min, + to: DATES[`7.0.0`].hosts.max, +}; + +const timeRange660 = { + from: DATES['6.6.0'].docker.min, + to: DATES[`6.6.0`].docker.max, +}; + +const timeRange800withAws = { + from: DATES['8.0.0'].logs_and_metrics_with_aws.min, + to: DATES[`8.0.0`].logs_and_metrics_with_aws.max, +}; + +const timeRange800 = { + from: DATES['8.0.0'].logs_and_metrics.min, + to: DATES[`8.0.0`].logs_and_metrics.max, +}; + export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); @@ -34,6 +56,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'demo-stack-mysql-01', nodeType: InfraNodeType.host, + timeRange: timeRange700, }); if (metadata) { expect(metadata.features.length).to.be(12); @@ -53,6 +76,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: '631f36a845514442b93c3fdd2dc91bcd8feb680b8ac5832c7fb8fdc167bb938e', nodeType: InfraNodeType.container, + timeRange: timeRange660, }); if (metadata) { expect(metadata.features.length).to.be(10); @@ -74,6 +98,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc', nodeType: InfraNodeType.host, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(58); @@ -114,6 +139,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'ip-172-31-47-9.us-east-2.compute.internal', nodeType: InfraNodeType.host, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(19); @@ -155,6 +181,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: '14887487-99f8-11e9-9a96-42010a84004d', nodeType: InfraNodeType.pod, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(29); @@ -200,6 +227,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'c74b04834c6d7cc1800c3afbe31d0c8c0c267f06e9eb45c2b0c2df3e6cee40c5', nodeType: InfraNodeType.container, + timeRange: timeRange800withAws, }); if (metadata) { expect(metadata.features.length).to.be(26); @@ -251,6 +279,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc', nodeType: 'host', + timeRange: timeRange800, }); if (metadata) { expect( @@ -265,6 +294,7 @@ export default function({ getService }: FtrProviderContext) { sourceId: 'default', nodeId: 'c1031331-9ae0-11e9-9a96-42010a84004d', nodeType: 'pod', + timeRange: timeRange800, }); if (metadata) { expect( diff --git a/x-pack/typings/rison_node.d.ts b/x-pack/typings/rison_node.d.ts index ec8e5c1f407adc..f830adc8974456 100644 --- a/x-pack/typings/rison_node.d.ts +++ b/x-pack/typings/rison_node.d.ts @@ -5,7 +5,7 @@ */ declare module 'rison-node' { - export type RisonValue = null | boolean | number | string | RisonObject | RisonArray; + export type RisonValue = undefined | null | boolean | number | string | RisonObject | RisonArray; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RisonArray extends Array {}