Skip to content

Commit

Permalink
[Metrics UI] Update position of legend and it's controls (#115854)
Browse files Browse the repository at this point in the history
* [Metrics UI] Update position of legend and it's controls

* updating button colors and moving history button back to the left

* updating legend placement

* removing unused dependencies

* Adding data-test-subj for legendControls

* removing unused deps

* Fix linting errors

* Move high value to top of legend

* Reclaim top space left open by GroupNameContainer

* Revert "Reclaim top space left open by GroupNameContainer"

This reverts commit 411e89e.

This extra space is also serving as between-group margin. Also it doesn't solve the scrollbar overlap for multi-group cases.

* Move legend after waffle map in dom

This allows the waffle map to scroll without it overlapping the legend.

* Move show/hide to right

* Move timeline legend next to title

* Move "hide history" button into timeline area

* Revert "Move "hide history" button into timeline area"

This reverts commit e6725c1.

* Revert "Move timeline legend next to title"

This reverts commit 3d204d3.

* Revert "Move show/hide to right"

This reverts commit fd1b9bd.

* Inline LegendControls and ViewSwitcher on mobile

* Better legend alignment with action buttons

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Kate Farrar <katherine.farrar@elastic.co>
Co-authored-by: Kate Farrar <kate.farrar@gmail.com>
Co-authored-by: Mat Schaffer <mat@elastic.co>
  • Loading branch information
5 people committed Mar 7, 2022
1 parent 23f7cff commit 7ac8361
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 432 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
data-test-subj="savedViews-openPopover"
iconType="arrowDown"
iconSide="right"
color="text"
>
{currentView
? currentView.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -57,17 +57,6 @@ export const BottomDrawer: React.FC<{
{isOpen ? hideHistory : showHistory}
</ShowHideButton>
</EuiFlexItem>
<EuiFlexItem
grow={false}
style={{
position: 'relative',
minWidth: 400,
height: '16px',
}}
>
{children}
</EuiFlexItem>
<RightSideSpacer />
</BottomActionTopBar>
<EuiFlexGroup style={{ marginTop: 0 }}>
<Timeline isVisible={isOpen} interval={interval} yAxisFormatter={formatter} />
Expand Down Expand Up @@ -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;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ 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';
import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter';
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;
Expand All @@ -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 (
<>
<PageContent>
<AutoSizer bounds>
{({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => (
<MainContainer ref={pageMeasureRef}>
<AutoSizer bounds>
{({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => (
<>
<TopActionContainer ref={topActionMeasureRef}>
<EuiFlexGroup
justifyContent="spaceBetween"
alignItems="center"
gutterSize="m"
>
<Toolbar nodeType={nodeType} currentTime={currentTime} />
<EuiFlexItem grow={false}>
<ViewSwitcher view={view} onChange={changeView} />
</EuiFlexItem>
</EuiFlexGroup>
</TopActionContainer>
<AutoSizer bounds>
{({ measureRef, bounds: { height = 0 } }) => (
<>
<NodesOverview
nodes={nodes}
options={options}
nodeType={nodeType}
loading={loading}
showLoading={showLoading}
reload={reload}
onDrilldown={applyFilterQuery}
currentTime={currentTime}
view={view}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
formatter={formatter}
bottomMargin={height}
topMargin={topActionHeight}
/>
{view === 'map' && (
<BottomDrawer
measureRef={measureRef}
interval={interval}
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]
*/
/* 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 (
<>
<PageContent>
<AutoSizer bounds>
{({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => (
<MainContainer ref={pageMeasureRef}>
<AutoSizer bounds>
{({
measureRef: topActionMeasureRef,
bounds: { height: topActionHeight = 0 },
}) => (
<>
<TopActionContainer ref={topActionMeasureRef}>
<EuiFlexGroup
justifyContent="spaceBetween"
alignItems="center"
gutterSize="m"
>
<Toolbar nodeType={nodeType} currentTime={currentTime} />
<EuiFlexGroup
responsive={false}
style={{ margin: 0, justifyContent: 'end' }}
>
{view === 'map' && (
<EuiFlexItem grow={false}>
<LegendControls
options={legend != null ? legend : DEFAULT_LEGEND}
dataBounds={dataBounds}
bounds={bounds}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
onChange={handleLegendControlChange}
/>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<ViewSwitcher view={view} onChange={changeView} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</TopActionContainer>
<AutoSizer bounds>
{({ measureRef, bounds: { height = 0 } }) => (
<>
<NodesOverview
nodes={nodes}
options={options}
nodeType={nodeType}
loading={loading}
showLoading={showLoading}
reload={reload}
onDrilldown={applyFilterQuery}
currentTime={currentTime}
view={view}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
formatter={formatter}
width={width}
>
<Legend
bottomMargin={height}
topMargin={topActionHeight}
/>
{view === 'map' && (
<BottomDrawer
measureRef={measureRef}
interval={interval}
formatter={formatter}
bounds={bounds}
dataBounds={dataBounds}
legend={options.legend}
width={width}
/>
</BottomDrawer>
)}
</>
)}
</AutoSizer>
</>
)}
</AutoSizer>
</MainContainer>
)}
</AutoSizer>
</PageContent>
</>
);
};
)}
</>
)}
</AutoSizer>
</>
)}
</AutoSizer>
</MainContainer>
)}
</AutoSizer>
</PageContent>
</>
);
}
);

const MainContainer = euiStyled.div`
position: relative;
Expand Down
Loading

0 comments on commit 7ac8361

Please sign in to comment.