[Metrics UI] Inventory view cleanup (#79881)

* Add background to Show History

* Fix legend centering

* Fix sentence casing in timeline popovers

* Improve small-screen responsiveness of filter dropdowns

* Improve top action responsiveness

* Remove unneeded align center

* Improve waffle map small screen responsiveness

* Fix inventory timeline color legend

* Fix loading spinner wrap

* Fix color legend

* Improve filter responsiveness

* Fix z-index on view switcher

* Move manage views flyout into portal

* Set waffle map to di display: static at s breakpoint

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Zacqary Adam Xeper 2020-10-14 16:34:35 -05:00 committed by GitHub
parent cb934344d3
commit f3ee7a32ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 132 additions and 108 deletions

View file

@ -27,10 +27,8 @@ export const LoadingPage = ({
<FlexPage data-test-subj={dataTestSubj}> <FlexPage data-test-subj={dataTestSubj}>
<EuiPageBody> <EuiPageBody>
<EuiPageContent verticalPosition="center" horizontalPosition="center"> <EuiPageContent verticalPosition="center" horizontalPosition="center">
<EuiFlexGroup alignItems="center"> <EuiFlexGroup alignItems="center" style={{ flexWrap: 'nowrap' }}>
<EuiFlexItem grow={false}> <EuiLoadingSpinner size="xl" style={{ marginRight: '8px' }} />
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
<EuiFlexItem data-test-subj="loadingMessage">{message}</EuiFlexItem> <EuiFlexItem data-test-subj="loadingMessage">{message}</EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
</EuiPageContent> </EuiPageContent>

View file

@ -16,6 +16,7 @@ import {
EuiInMemoryTable, EuiInMemoryTable,
EuiFlexGroup, EuiFlexGroup,
EuiButton, EuiButton,
EuiPortal,
} from '@elastic/eui'; } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
@ -157,34 +158,36 @@ export function SavedViewManageViewsFlyout<ViewState>({
]; ];
return ( return (
<EuiFlyout onClose={close} data-test-subj="loadViewsFlyout"> <EuiPortal>
<EuiFlyoutHeader> <EuiFlyout onClose={close} data-test-subj="loadViewsFlyout">
<EuiTitle size="m"> <EuiFlyoutHeader>
<h2> <EuiTitle size="m">
<FormattedMessage <h2>
defaultMessage="Manage saved views" <FormattedMessage
id="xpack.infra.openView.flyoutHeader" defaultMessage="Manage saved views"
/> id="xpack.infra.openView.flyoutHeader"
</h2> />
</EuiTitle> </h2>
</EuiFlyoutHeader> </EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody> <EuiFlyoutBody>
<EuiInMemoryTable <EuiInMemoryTable
items={views} items={views}
columns={columns} columns={columns}
loading={loading} loading={loading}
search={true} search={true}
pagination={true} pagination={true}
sorting={true} sorting={true}
/> />
</EuiFlyoutBody> </EuiFlyoutBody>
<EuiModalFooter> <EuiModalFooter>
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={close}> <EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={close}>
<FormattedMessage defaultMessage="Cancel" id="xpack.infra.openView.cancelButton" /> <FormattedMessage defaultMessage="Cancel" id="xpack.infra.openView.cancelButton" />
</EuiButtonEmpty> </EuiButtonEmpty>
</EuiModalFooter> </EuiModalFooter>
</EuiFlyout> </EuiFlyout>
</EuiPortal>
); );
} }

View file

@ -121,24 +121,29 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
]} ]}
/> />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem
<Route path={'/inventory'} component={AnomalyDetectionFlyout} /> grow={false}
</EuiFlexItem> style={{ flexDirection: 'row', alignItems: 'center' }}
<EuiFlexItem grow={false}> >
<Route path={'/explorer'} component={MetricsAlertDropdown} /> <EuiFlexItem grow={false}>
<Route path={'/inventory'} component={InventoryAlertDropdown} /> <Route path={'/inventory'} component={AnomalyDetectionFlyout} />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiButtonEmpty <Route path={'/explorer'} component={MetricsAlertDropdown} />
href={kibana.services?.application?.getUrlForApp( <Route path={'/inventory'} component={InventoryAlertDropdown} />
'/home#/tutorial_directory/metrics' </EuiFlexItem>
)} <EuiFlexItem grow={false}>
size="s" <EuiButtonEmpty
color="primary" href={kibana.services?.application?.getUrlForApp(
iconType="plusInCircle" '/home#/tutorial_directory/metrics'
> )}
{ADD_DATA_LABEL} size="s"
</EuiButtonEmpty> color="primary"
iconType="plusInCircle"
>
{ADD_DATA_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
</AppNavigation> </AppNavigation>

View file

@ -47,15 +47,12 @@ export const BottomDrawer: React.FC<{
style={{ style={{
position: 'relative', position: 'relative',
minWidth: 400, minWidth: 400,
alignSelf: 'center',
height: '16px', height: '16px',
}} }}
> >
{children} {children}
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <RightSideSpacer />
<EuiSpacer size="xs" />
</EuiFlexItem>
</BottomActionTopBar> </BottomActionTopBar>
<EuiFlexGroup style={{ marginTop: 0 }}> <EuiFlexGroup style={{ marginTop: 0 }}>
<Timeline isVisible={isOpen} interval={interval} yAxisFormatter={formatter} /> <Timeline isVisible={isOpen} interval={interval} yAxisFormatter={formatter} />
@ -85,3 +82,7 @@ const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({
const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })` const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })`
width: 140px; width: 140px;
`; `;
const RightSideSpacer = euiStyled(EuiSpacer).attrs({ size: 'xs' })`
width: 140px;
`;

View file

@ -104,46 +104,57 @@ export const Layout = () => {
<> <>
<PageContent> <PageContent>
<MainContainer> <MainContainer>
<TopActionContainer>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="m">
<Toolbar nodeType={nodeType} />
<EuiFlexItem grow={false}>
<IntervalLabel intervalAsString={intervalAsString} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ViewSwitcher view={view} onChange={changeView} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<SavedViewContainer>
<SavedViewsToolbarControls viewState={viewState} />
</SavedViewContainer>
</TopActionContainer>
<AutoSizer bounds> <AutoSizer bounds>
{({ measureRef, bounds: { height = 0 } }) => ( {({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => (
<> <>
<NodesOverview <TopActionContainer ref={topActionMeasureRef}>
nodes={nodes} <EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="m">
options={options} <Toolbar nodeType={nodeType} />
nodeType={nodeType} <EuiFlexItem grow={false}>
loading={loading} <IntervalLabel intervalAsString={intervalAsString} />
reload={reload} </EuiFlexItem>
onDrilldown={applyFilterQuery} <EuiFlexItem grow={false}>
currentTime={currentTime} <ViewSwitcher view={view} onChange={changeView} />
view={view} </EuiFlexItem>
autoBounds={autoBounds} </EuiFlexGroup>
boundsOverride={boundsOverride} <EuiSpacer />
formatter={formatter} <SavedViewContainer>
bottomMargin={height} <SavedViewsToolbarControls viewState={viewState} />
/> </SavedViewContainer>
<BottomDrawer measureRef={measureRef} interval={interval} formatter={formatter}> </TopActionContainer>
<Legend <AutoSizer bounds>
formatter={formatter} {({ measureRef, bounds: { height = 0 } }) => (
bounds={bounds} <>
dataBounds={dataBounds} <NodesOverview
legend={options.legend} nodes={nodes}
/> options={options}
</BottomDrawer> nodeType={nodeType}
loading={loading}
reload={reload}
onDrilldown={applyFilterQuery}
currentTime={currentTime}
view={view}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
formatter={formatter}
bottomMargin={height}
topMargin={topActionHeight}
/>
<BottomDrawer
measureRef={measureRef}
interval={interval}
formatter={formatter}
>
<Legend
formatter={formatter}
bounds={bounds}
dataBounds={dataBounds}
legend={options.legend}
/>
</BottomDrawer>
</>
)}
</AutoSizer>
</> </>
)} )}
</AutoSizer> </AutoSizer>

View file

@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { getBreakpoint } from '@elastic/eui';
import { InventoryItemType } from '../../../../../common/inventory_models/types'; import { InventoryItemType } from '../../../../../common/inventory_models/types';
import { euiStyled } from '../../../../../../observability/public'; import { euiStyled } from '../../../../../../observability/public';
@ -35,6 +36,7 @@ interface Props {
autoBounds: boolean; autoBounds: boolean;
formatter: InfraFormatter; formatter: InfraFormatter;
bottomMargin: number; bottomMargin: number;
topMargin: number;
} }
export const NodesOverview = ({ export const NodesOverview = ({
@ -50,6 +52,7 @@ export const NodesOverview = ({
formatter, formatter,
onDrilldown, onDrilldown,
bottomMargin, bottomMargin,
topMargin,
}: Props) => { }: Props) => {
const handleDrilldown = useCallback( const handleDrilldown = useCallback(
(filter: string) => { (filter: string) => {
@ -94,6 +97,7 @@ export const NodesOverview = ({
} }
const dataBounds = calculateBoundsFromNodes(nodes); const dataBounds = calculateBoundsFromNodes(nodes);
const bounds = autoBounds ? dataBounds : boundsOverride; const bounds = autoBounds ? dataBounds : boundsOverride;
const isStatic = ['xs', 's'].includes(getBreakpoint(window.innerWidth)!);
if (view === 'table') { if (view === 'table') {
return ( return (
@ -110,7 +114,7 @@ export const NodesOverview = ({
); );
} }
return ( return (
<MapContainer> <MapContainer top={topMargin} positionStatic={isStatic}>
<Map <Map
nodeType={nodeType} nodeType={nodeType}
nodes={nodes} nodes={nodes}
@ -121,6 +125,7 @@ export const NodesOverview = ({
bounds={bounds} bounds={bounds}
dataBounds={dataBounds} dataBounds={dataBounds}
bottomMargin={bottomMargin} bottomMargin={bottomMargin}
staticHeight={isStatic}
/> />
</MapContainer> </MapContainer>
); );
@ -130,10 +135,10 @@ const TableContainer = euiStyled.div`
padding: ${(props) => props.theme.eui.paddingSizes.l}; padding: ${(props) => props.theme.eui.paddingSizes.l};
`; `;
const MapContainer = euiStyled.div` const MapContainer = euiStyled.div<{ top: number; positionStatic: boolean }>`
position: absolute; position: ${(props) => (props.positionStatic ? 'static' : 'absolute')};
display: flex; display: flex;
top: 70px; top: ${(props) => props.top}px;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;

View file

@ -27,7 +27,7 @@ import { EuiIcon } from '@elastic/eui';
import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public'; import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public';
import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n'; import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n';
import { MetricsExplorerAggregation } from '../../../../../../common/http_api'; import { MetricsExplorerAggregation } from '../../../../../../common/http_api';
import { Color } from '../../../../../../common/color_palette'; import { colorTransformer, Color } from '../../../../../../common/color_palette';
import { useSourceContext } from '../../../../../containers/source'; import { useSourceContext } from '../../../../../containers/source';
import { useTimeline } from '../../hooks/use_timeline'; import { useTimeline } from '../../hooks/use_timeline';
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
@ -102,11 +102,12 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
}, [nodeType, metricsHostsAnomalies, metricsK8sAnomalies]); }, [nodeType, metricsHostsAnomalies, metricsK8sAnomalies]);
const metricLabel = toMetricOpt(metric.type)?.textLC; const metricLabel = toMetricOpt(metric.type)?.textLC;
const metricPopoverLabel = toMetricOpt(metric.type)?.text;
const chartMetric = { const chartMetric = {
color: Color.color0, color: Color.color0,
aggregation: 'avg' as MetricsExplorerAggregation, aggregation: 'avg' as MetricsExplorerAggregation,
label: metricLabel, label: metricPopoverLabel,
}; };
const dateFormatter = useMemo(() => { const dateFormatter = useMemo(() => {
@ -225,10 +226,7 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize={'s'} alignItems={'center'}> <EuiFlexGroup gutterSize={'s'} alignItems={'center'}>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiIcon <EuiIcon color={colorTransformer(chartMetric.color)} type={'dot'} />
color={getTimelineChartTheme(isDarkMode).crosshair.band.fill}
type={'dot'}
/>
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiText size={'xs'}> <EuiText size={'xs'}>
@ -335,11 +333,11 @@ const TimelineLoadingContainer = euiStyled.div`
`; `;
const noHistoryDataTitle = i18n.translate('xpack.infra.inventoryTimeline.noHistoryDataTitle', { const noHistoryDataTitle = i18n.translate('xpack.infra.inventoryTimeline.noHistoryDataTitle', {
defaultMessage: 'There is no history data to display.', defaultMessage: 'There is no historical data to display.',
}); });
const errorTitle = i18n.translate('xpack.infra.inventoryTimeline.errorTitle', { const errorTitle = i18n.translate('xpack.infra.inventoryTimeline.errorTitle', {
defaultMessage: 'Unable to display history data.', defaultMessage: 'Unable to show historical data.',
}); });
const checkNewDataButtonLabel = i18n.translate( const checkNewDataButtonLabel = i18n.translate(

View file

@ -5,7 +5,7 @@
*/ */
import React from 'react'; import React from 'react';
import { EuiFlexItem } from '@elastic/eui'; import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { fieldToName } from '../../lib/field_to_display_name'; import { fieldToName } from '../../lib/field_to_display_name';
import { useSourceContext } from '../../../../../containers/source'; import { useSourceContext } from '../../../../../containers/source';
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
@ -38,7 +38,7 @@ export const ToolbarWrapper = (props: Props) => {
} = useWaffleOptionsContext(); } = useWaffleOptionsContext();
const { createDerivedIndexPattern } = useSourceContext(); const { createDerivedIndexPattern } = useSourceContext();
return ( return (
<> <EuiFlexGroup responsive={false} wrap gutterSize="m">
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<WaffleInventorySwitcher /> <WaffleInventorySwitcher />
</EuiFlexItem> </EuiFlexItem>
@ -62,7 +62,7 @@ export const ToolbarWrapper = (props: Props) => {
customMetrics, customMetrics,
changeCustomMetrics, changeCustomMetrics,
})} })}
</> </EuiFlexGroup>
); );
}; };

View file

@ -27,6 +27,7 @@ interface Props {
bounds: InfraWaffleMapBounds; bounds: InfraWaffleMapBounds;
dataBounds: InfraWaffleMapBounds; dataBounds: InfraWaffleMapBounds;
bottomMargin: number; bottomMargin: number;
staticHeight: boolean;
} }
export const Map: React.FC<Props> = ({ export const Map: React.FC<Props> = ({
@ -39,6 +40,7 @@ export const Map: React.FC<Props> = ({
nodeType, nodeType,
dataBounds, dataBounds,
bottomMargin, bottomMargin,
staticHeight,
}) => { }) => {
const sortedNodes = sortNodes(options.sort, nodes); const sortedNodes = sortNodes(options.sort, nodes);
const map = nodesToWaffleMap(sortedNodes); const map = nodesToWaffleMap(sortedNodes);
@ -51,6 +53,7 @@ export const Map: React.FC<Props> = ({
ref={(el: any) => measureRef(el)} ref={(el: any) => measureRef(el)}
bottomMargin={bottomMargin} bottomMargin={bottomMargin}
data-test-subj="waffleMap" data-test-subj="waffleMap"
staticHeight={staticHeight}
> >
<WaffleMapInnerContainer> <WaffleMapInnerContainer>
{groupsWithLayout.map((group) => { {groupsWithLayout.map((group) => {
@ -92,7 +95,7 @@ export const Map: React.FC<Props> = ({
); );
}; };
const WaffleMapOuterContainer = euiStyled.div<{ bottomMargin: number }>` const WaffleMapOuterContainer = euiStyled.div<{ bottomMargin: number; staticHeight: boolean }>`
flex: 1 0 0%; flex: 1 0 0%;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
@ -100,6 +103,7 @@ const WaffleMapOuterContainer = euiStyled.div<{ bottomMargin: number }>`
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
margin-bottom: ${(props) => props.bottomMargin}px; margin-bottom: ${(props) => props.bottomMargin}px;
${(props) => props.staticHeight && 'min-height: 300px;'}
`; `;
const WaffleMapInnerContainer = euiStyled.div` const WaffleMapInnerContainer = euiStyled.div`

View file

@ -6,7 +6,6 @@
import { EuiButtonGroup, EuiButtonGroupProps } from '@elastic/eui'; import { EuiButtonGroup, EuiButtonGroupProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import React from 'react'; import React from 'react';
interface Props { interface Props {