diff --git a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx index b44f3ffa20df71..7b7c256d5ad593 100644 --- a/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/toolbar_control.tsx @@ -150,6 +150,7 @@ export function SavedViewsToolbarControls(props: Props) { data-test-subj="savedViews-openPopover" iconType="arrowDown" iconSide="right" + color="text" > {currentView ? currentView.name diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx index 3681d740d93d07..ad548a632573fd 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { useUiTracker } from '../../../../../../observability/public'; import { useWaffleOptionsContext } from '../hooks/use_waffle_options'; @@ -57,17 +57,6 @@ export const BottomDrawer: React.FC<{ {isOpen ? hideHistory : showHistory} - - {children} - - @@ -97,7 +86,3 @@ const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({ const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })` width: 140px; `; - -const RightSideSpacer = euiStyled(EuiSpacer).attrs({ size: 'xs' })` - width: 140px; -`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 5a3dafaabbd170..7f3de57b610a4d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -17,8 +17,12 @@ import { calculateBoundsFromNodes } from '../lib/calculate_bounds_from_nodes'; import { PageContent } from '../../../../components/page'; import { useWaffleTimeContext } from '../hooks/use_waffle_time'; import { useWaffleFiltersContext } from '../hooks/use_waffle_filters'; -import { DEFAULT_LEGEND, useWaffleOptionsContext } from '../hooks/use_waffle_options'; -import { InfraFormatterType } from '../../../../lib/lib'; +import { + DEFAULT_LEGEND, + useWaffleOptionsContext, + WaffleLegendOptions, +} from '../hooks/use_waffle_options'; +import { InfraFormatterType, InfraWaffleMapBounds } from '../../../../lib/lib'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { Toolbar } from './toolbars/toolbar'; import { ViewSwitcher } from './waffle/view_switcher'; @@ -26,7 +30,7 @@ import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_f import { createLegend } from '../lib/create_legend'; import { useWaffleViewState } from '../hooks/use_waffle_view_state'; import { BottomDrawer } from './bottom_drawer'; -import { Legend } from './waffle/legend'; +import { LegendControls } from './waffle/legend_controls'; interface Props { shouldLoadDefault: boolean; @@ -37,149 +41,184 @@ interface Props { loading: boolean; } -export const Layout = ({ - shouldLoadDefault, - currentView, - reload, - interval, - nodes, - loading, -}: Props) => { - const [showLoading, setShowLoading] = useState(true); - const { metric, groupBy, sort, nodeType, changeView, view, autoBounds, boundsOverride, legend } = - useWaffleOptionsContext(); - const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); - const { applyFilterQuery } = useWaffleFiltersContext(); - const legendPalette = legend?.palette ?? DEFAULT_LEGEND.palette; - const legendSteps = legend?.steps ?? DEFAULT_LEGEND.steps; - const legendReverseColors = legend?.reverseColors ?? DEFAULT_LEGEND.reverseColors; - - const options = { - formatter: InfraFormatterType.percent, - formatTemplate: '{{value}}', - legend: createLegend(legendPalette, legendSteps, legendReverseColors), - metric, - sort, - groupBy, - }; - - useInterval( - () => { - if (!loading) { - jumpToTime(Date.now()); - } - }, - isAutoReloading ? 5000 : null - ); - - const dataBounds = calculateBoundsFromNodes(nodes); - const bounds = autoBounds ? dataBounds : boundsOverride; - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]); - const { onViewChange } = useWaffleViewState(); - - useEffect(() => { - if (currentView) { - onViewChange(currentView); - } - }, [currentView, onViewChange]); - - useEffect(() => { - // load snapshot data after default view loaded, unless we're not loading a view - if (currentView != null || !shouldLoadDefault) { - reload(); - } - - /** - * INFO: why disable exhaustive-deps - * We need to wait on the currentView not to be null because it is loaded async and could change the view state. - * We don't actually need to watch the value of currentView though, since the view state will be synched up by the - * changing params in the reload method so we should only "watch" the reload method. - * - * TODO: Should refactor this in the future to make it more clear where all the view state is coming - * from and it's precedence [query params, localStorage, defaultView, out of the box view] - */ +interface LegendControlOptions { + auto: boolean; + bounds: InfraWaffleMapBounds; + legend: WaffleLegendOptions; +} + +export const Layout = React.memo( + ({ shouldLoadDefault, currentView, reload, interval, nodes, loading }: Props) => { + const [showLoading, setShowLoading] = useState(true); + const { + metric, + groupBy, + sort, + nodeType, + changeView, + view, + autoBounds, + boundsOverride, + legend, + changeBoundsOverride, + changeAutoBounds, + changeLegend, + } = useWaffleOptionsContext(); + const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext(); + const { applyFilterQuery } = useWaffleFiltersContext(); + const legendPalette = legend?.palette ?? DEFAULT_LEGEND.palette; + const legendSteps = legend?.steps ?? DEFAULT_LEGEND.steps; + const legendReverseColors = legend?.reverseColors ?? DEFAULT_LEGEND.reverseColors; + + const options = { + formatter: InfraFormatterType.percent, + formatTemplate: '{{value}}', + legend: createLegend(legendPalette, legendSteps, legendReverseColors), + metric, + sort, + groupBy, + }; + + useInterval( + () => { + if (!loading) { + jumpToTime(Date.now()); + } + }, + isAutoReloading ? 5000 : null + ); + + const dataBounds = calculateBoundsFromNodes(nodes); + const bounds = autoBounds ? dataBounds : boundsOverride; /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [reload, shouldLoadDefault]); - - useEffect(() => { - setShowLoading(true); - }, [options.metric, nodeType]); - - useEffect(() => { - const hasNodes = nodes && nodes.length; - // Don't show loading screen when we're auto-reloading - setShowLoading(!hasNodes); - }, [nodes]); - - return ( - <> - - - {({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => ( - - - {({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => ( - <> - - - - - - - - - - {({ measureRef, bounds: { height = 0 } }) => ( - <> - - {view === 'map' && ( - { + if (currentView) { + onViewChange(currentView); + } + }, [currentView, onViewChange]); + + useEffect(() => { + // load snapshot data after default view loaded, unless we're not loading a view + if (currentView != null || !shouldLoadDefault) { + reload(); + } + + /** + * INFO: why disable exhaustive-deps + * We need to wait on the currentView not to be null because it is loaded async and could change the view state. + * We don't actually need to watch the value of currentView though, since the view state will be synched up by the + * changing params in the reload method so we should only "watch" the reload method. + * + * TODO: Should refactor this in the future to make it more clear where all the view state is coming + * from and it's precedence [query params, localStorage, defaultView, out of the box view] + */ + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [reload, shouldLoadDefault]); + + useEffect(() => { + setShowLoading(true); + }, [options.metric, nodeType]); + + useEffect(() => { + const hasNodes = nodes && nodes.length; + // Don't show loading screen when we're auto-reloading + setShowLoading(!hasNodes); + }, [nodes]); + + const handleLegendControlChange = useCallback( + (opts: LegendControlOptions) => { + changeBoundsOverride(opts.bounds); + changeAutoBounds(opts.auto); + changeLegend(opts.legend); + }, + [changeBoundsOverride, changeAutoBounds, changeLegend] + ); + + return ( + <> + + + {({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => ( + + + {({ + measureRef: topActionMeasureRef, + bounds: { height: topActionHeight = 0 }, + }) => ( + <> + + + + + {view === 'map' && ( + + + + )} + + + + + + + + {({ measureRef, bounds: { height = 0 } }) => ( + <> + - + {view === 'map' && ( + - - )} - - )} - - - )} - - - )} - - - - ); -}; + )} + + )} + + + )} + + + )} + + + + ); + } +); const MainContainer = euiStyled.div` position: relative; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx index 297f24e95bc4f1..cec595e4be3d66 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/nodes_overview.tsx @@ -18,6 +18,7 @@ import { Map } from './waffle/map'; import { TableView } from './table_view'; import { SnapshotNode } from '../../../../../common/http_api/snapshot_api'; import { calculateBoundsFromNodes } from '../lib/calculate_bounds_from_nodes'; +import { Legend } from './waffle/legend'; export interface KueryFilterQuery { kind: 'kuery'; @@ -131,6 +132,12 @@ export const NodesOverview = ({ bottomMargin={bottomMargin} staticHeight={isStatic} /> + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend.tsx index d305203b738c37..853aa98bf62447 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React from 'react'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { @@ -17,13 +17,7 @@ import { GradientLegendRT, } from '../../../../../lib/lib'; import { GradientLegend } from './gradient_legend'; -import { LegendControls } from './legend_controls'; import { StepLegend } from './steps_legend'; -import { - DEFAULT_LEGEND, - useWaffleOptionsContext, - WaffleLegendOptions, -} from '../../hooks/use_waffle_options'; import { SteppedGradientLegend } from './stepped_gradient_legend'; interface Props { legend: InfraWaffleMapLegend; @@ -32,39 +26,9 @@ interface Props { formatter: InfraFormatter; } -interface LegendControlOptions { - auto: boolean; - bounds: InfraWaffleMapBounds; - legend: WaffleLegendOptions; -} - -export const Legend: React.FC = ({ dataBounds, legend, bounds, formatter }) => { - const { - changeBoundsOverride, - changeAutoBounds, - autoBounds, - legend: legendOptions, - changeLegend, - boundsOverride, - } = useWaffleOptionsContext(); - const handleChange = useCallback( - (options: LegendControlOptions) => { - changeBoundsOverride(options.bounds); - changeAutoBounds(options.auto); - changeLegend(options.legend); - }, - [changeBoundsOverride, changeAutoBounds, changeLegend] - ); +export const Legend: React.FC = ({ legend, bounds, formatter }) => { return ( - {GradientLegendRT.is(legend) && ( )} @@ -77,8 +41,6 @@ export const Legend: React.FC = ({ dataBounds, legend, bounds, formatter }; const LegendContainer = euiStyled.div` - position: absolute; - bottom: 0px; - left: 10px; - right: 10px; + margin: 0 10px; + display: flex; `; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend_controls.tsx index c7479434424a63..61b293888b85dc 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/legend_controls.tsx @@ -26,7 +26,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { SyntheticEvent, useState, useCallback, useEffect } from 'react'; import { first, last } from 'lodash'; -import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { InfraWaffleMapBounds, InventoryColorPalette, PALETTES } from '../../../../../lib/lib'; import { WaffleLegendOptions } from '../../hooks/use_waffle_options'; import { getColorPalette } from '../../lib/get_color_palette'; @@ -78,8 +77,10 @@ export const LegendControls = ({ const buttonComponent = ( - - Legend Options - - - <> - - - - - - - + Legend Options + + + <> + - - + + + + + - + + + + + + + + + } + isInvalid={!boundsValidRange} + display="columnCompressed" + error={errors} + > +
+ - - - + + + } + isInvalid={!boundsValidRange} + error={errors} + > +
+ - - + + + + + + - } - isInvalid={!boundsValidRange} - display="columnCompressed" - error={errors} - > -
- + + + + -
- - - } - isInvalid={!boundsValidRange} - error={errors} - > -
- -
-
- - - - - - - - - - - - - - - - + +
+
+ + ); }; - -const ControlContainer = euiStyled.div` - position: absolute; - top: -20px; - right: 6px; - bottom: 0; -`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/stepped_gradient_legend.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/stepped_gradient_legend.tsx index a9bcfa7995c200..339426b126b9e6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/stepped_gradient_legend.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/stepped_gradient_legend.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { EuiText } from '@elastic/eui'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { InfraWaffleMapBounds, @@ -22,18 +23,19 @@ type TickValue = 0 | 1; export const SteppedGradientLegend: React.FC = ({ legend, bounds, formatter }) => { return ( - - - - + - {legend.rules.map((rule, index) => ( - - ))} + {legend.rules + .slice() + .reverse() + .map((rule, index) => ( + + ))} + ); }; @@ -46,62 +48,38 @@ interface TickProps { const TickLabel = ({ value, bounds, formatter }: TickProps) => { const normalizedValue = value === 0 ? bounds.min : bounds.max * value; - const style = { left: `${value * 100}%` }; const label = formatter(normalizedValue); - return {label}; + return ( +
+ {label} +
+ ); }; -const GradientStep = euiStyled.div` - height: ${(props) => props.theme.eui.paddingSizes.s}; - flex: 1 1 auto; - &:first-child { - border-radius: ${(props) => props.theme.eui.euiBorderRadius} 0 0 ${(props) => - props.theme.eui.euiBorderRadius}; - } - &:last-child { - border-radius: 0 ${(props) => props.theme.eui.euiBorderRadius} ${(props) => - props.theme.eui.euiBorderRadius} 0; - } +const LegendContainer = euiStyled.div` + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; `; -const Ticks = euiStyled.div` - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - top: -18px; +const GradientContainer = euiStyled.div` + height: 200px; + width: 10px; + display: flex; + flex-direction: column; + align-items: stretch; `; -const Tick = euiStyled.div` - position: absolute; - font-size: 11px; - text-align: center; - top: 0; - left: 0; - white-space: nowrap; - transform: translate(-50%, 0); +const GradientStep = euiStyled.div` + flex: 1 1 auto; &:first-child { - padding-left: 5px; - transform: translate(0, 0); + border-radius: ${(props) => props.theme.eui.euiBorderRadius} ${(props) => + props.theme.eui.euiBorderRadius} 0 0; } &:last-child { - padding-right: 5px; - transform: translate(-100%, 0); + border-radius: 0 0 ${(props) => props.theme.eui.euiBorderRadius} ${(props) => + props.theme.eui.euiBorderRadius}; } `; - -const GradientContainer = euiStyled.div` - display: flex; - flex-direction; row; - align-items: stretch; - flex-grow: 1; -`; - -const LegendContainer = euiStyled.div` - position: absolute; - height: 10px; - bottom: 0; - left: 0; - right: 40px; -`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/view_switcher.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/view_switcher.tsx index 4dc288caa98332..8e911f7f829177 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/view_switcher.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/view_switcher.tsx @@ -37,8 +37,8 @@ export const ViewSwitcher = ({ view, onChange }: Props) => { defaultMessage: 'Switch between table and map view', })} options={buttons} - color="primary" - buttonSize="m" + color="text" + buttonSize="s" idSelected={view} onChange={onChange} isIconOnly