mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
cb934344d3
commit
f3ee7a32ca
10 changed files with 132 additions and 108 deletions
|
@ -27,10 +27,8 @@ export const LoadingPage = ({
|
|||
<FlexPage data-test-subj={dataTestSubj}>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center">
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" style={{ flexWrap: 'nowrap' }}>
|
||||
<EuiLoadingSpinner size="xl" style={{ marginRight: '8px' }} />
|
||||
<EuiFlexItem data-test-subj="loadingMessage">{message}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContent>
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
EuiInMemoryTable,
|
||||
EuiFlexGroup,
|
||||
EuiButton,
|
||||
EuiPortal,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -157,34 +158,36 @@ export function SavedViewManageViewsFlyout<ViewState>({
|
|||
];
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={close} data-test-subj="loadViewsFlyout">
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Manage saved views"
|
||||
id="xpack.infra.openView.flyoutHeader"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiPortal>
|
||||
<EuiFlyout onClose={close} data-test-subj="loadViewsFlyout">
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Manage saved views"
|
||||
id="xpack.infra.openView.flyoutHeader"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<EuiInMemoryTable
|
||||
items={views}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
search={true}
|
||||
pagination={true}
|
||||
sorting={true}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutBody>
|
||||
<EuiInMemoryTable
|
||||
items={views}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
search={true}
|
||||
pagination={true}
|
||||
sorting={true}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={close}>
|
||||
<FormattedMessage defaultMessage="Cancel" id="xpack.infra.openView.cancelButton" />
|
||||
</EuiButtonEmpty>
|
||||
</EuiModalFooter>
|
||||
</EuiFlyout>
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={close}>
|
||||
<FormattedMessage defaultMessage="Cancel" id="xpack.infra.openView.cancelButton" />
|
||||
</EuiButtonEmpty>
|
||||
</EuiModalFooter>
|
||||
</EuiFlyout>
|
||||
</EuiPortal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -121,24 +121,29 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => {
|
|||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Route path={'/inventory'} component={AnomalyDetectionFlyout} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Route path={'/explorer'} component={MetricsAlertDropdown} />
|
||||
<Route path={'/inventory'} component={InventoryAlertDropdown} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
href={kibana.services?.application?.getUrlForApp(
|
||||
'/home#/tutorial_directory/metrics'
|
||||
)}
|
||||
size="s"
|
||||
color="primary"
|
||||
iconType="plusInCircle"
|
||||
>
|
||||
{ADD_DATA_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
style={{ flexDirection: 'row', alignItems: 'center' }}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Route path={'/inventory'} component={AnomalyDetectionFlyout} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Route path={'/explorer'} component={MetricsAlertDropdown} />
|
||||
<Route path={'/inventory'} component={InventoryAlertDropdown} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
href={kibana.services?.application?.getUrlForApp(
|
||||
'/home#/tutorial_directory/metrics'
|
||||
)}
|
||||
size="s"
|
||||
color="primary"
|
||||
iconType="plusInCircle"
|
||||
>
|
||||
{ADD_DATA_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</AppNavigation>
|
||||
|
|
|
@ -47,15 +47,12 @@ export const BottomDrawer: React.FC<{
|
|||
style={{
|
||||
position: 'relative',
|
||||
minWidth: 400,
|
||||
alignSelf: 'center',
|
||||
height: '16px',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="xs" />
|
||||
</EuiFlexItem>
|
||||
<RightSideSpacer />
|
||||
</BottomActionTopBar>
|
||||
<EuiFlexGroup style={{ marginTop: 0 }}>
|
||||
<Timeline isVisible={isOpen} interval={interval} yAxisFormatter={formatter} />
|
||||
|
@ -85,3 +82,7 @@ const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({
|
|||
const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })`
|
||||
width: 140px;
|
||||
`;
|
||||
|
||||
const RightSideSpacer = euiStyled(EuiSpacer).attrs({ size: 'xs' })`
|
||||
width: 140px;
|
||||
`;
|
||||
|
|
|
@ -104,46 +104,57 @@ export const Layout = () => {
|
|||
<>
|
||||
<PageContent>
|
||||
<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>
|
||||
{({ measureRef, bounds: { height = 0 } }) => (
|
||||
{({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => (
|
||||
<>
|
||||
<NodesOverview
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
nodeType={nodeType}
|
||||
loading={loading}
|
||||
reload={reload}
|
||||
onDrilldown={applyFilterQuery}
|
||||
currentTime={currentTime}
|
||||
view={view}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
formatter={formatter}
|
||||
bottomMargin={height}
|
||||
/>
|
||||
<BottomDrawer measureRef={measureRef} interval={interval} formatter={formatter}>
|
||||
<Legend
|
||||
formatter={formatter}
|
||||
bounds={bounds}
|
||||
dataBounds={dataBounds}
|
||||
legend={options.legend}
|
||||
/>
|
||||
</BottomDrawer>
|
||||
<TopActionContainer ref={topActionMeasureRef}>
|
||||
<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>
|
||||
{({ measureRef, bounds: { height = 0 } }) => (
|
||||
<>
|
||||
<NodesOverview
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
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>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback } from 'react';
|
||||
import { getBreakpoint } from '@elastic/eui';
|
||||
|
||||
import { InventoryItemType } from '../../../../../common/inventory_models/types';
|
||||
import { euiStyled } from '../../../../../../observability/public';
|
||||
|
@ -35,6 +36,7 @@ interface Props {
|
|||
autoBounds: boolean;
|
||||
formatter: InfraFormatter;
|
||||
bottomMargin: number;
|
||||
topMargin: number;
|
||||
}
|
||||
|
||||
export const NodesOverview = ({
|
||||
|
@ -50,6 +52,7 @@ export const NodesOverview = ({
|
|||
formatter,
|
||||
onDrilldown,
|
||||
bottomMargin,
|
||||
topMargin,
|
||||
}: Props) => {
|
||||
const handleDrilldown = useCallback(
|
||||
(filter: string) => {
|
||||
|
@ -94,6 +97,7 @@ export const NodesOverview = ({
|
|||
}
|
||||
const dataBounds = calculateBoundsFromNodes(nodes);
|
||||
const bounds = autoBounds ? dataBounds : boundsOverride;
|
||||
const isStatic = ['xs', 's'].includes(getBreakpoint(window.innerWidth)!);
|
||||
|
||||
if (view === 'table') {
|
||||
return (
|
||||
|
@ -110,7 +114,7 @@ export const NodesOverview = ({
|
|||
);
|
||||
}
|
||||
return (
|
||||
<MapContainer>
|
||||
<MapContainer top={topMargin} positionStatic={isStatic}>
|
||||
<Map
|
||||
nodeType={nodeType}
|
||||
nodes={nodes}
|
||||
|
@ -121,6 +125,7 @@ export const NodesOverview = ({
|
|||
bounds={bounds}
|
||||
dataBounds={dataBounds}
|
||||
bottomMargin={bottomMargin}
|
||||
staticHeight={isStatic}
|
||||
/>
|
||||
</MapContainer>
|
||||
);
|
||||
|
@ -130,10 +135,10 @@ const TableContainer = euiStyled.div`
|
|||
padding: ${(props) => props.theme.eui.paddingSizes.l};
|
||||
`;
|
||||
|
||||
const MapContainer = euiStyled.div`
|
||||
position: absolute;
|
||||
const MapContainer = euiStyled.div<{ top: number; positionStatic: boolean }>`
|
||||
position: ${(props) => (props.positionStatic ? 'static' : 'absolute')};
|
||||
display: flex;
|
||||
top: 70px;
|
||||
top: ${(props) => props.top}px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
|
|
@ -27,7 +27,7 @@ import { EuiIcon } from '@elastic/eui';
|
|||
import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n';
|
||||
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 { useTimeline } from '../../hooks/use_timeline';
|
||||
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
|
||||
|
@ -102,11 +102,12 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
|
|||
}, [nodeType, metricsHostsAnomalies, metricsK8sAnomalies]);
|
||||
|
||||
const metricLabel = toMetricOpt(metric.type)?.textLC;
|
||||
const metricPopoverLabel = toMetricOpt(metric.type)?.text;
|
||||
|
||||
const chartMetric = {
|
||||
color: Color.color0,
|
||||
aggregation: 'avg' as MetricsExplorerAggregation,
|
||||
label: metricLabel,
|
||||
label: metricPopoverLabel,
|
||||
};
|
||||
|
||||
const dateFormatter = useMemo(() => {
|
||||
|
@ -225,10 +226,7 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize={'s'} alignItems={'center'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon
|
||||
color={getTimelineChartTheme(isDarkMode).crosshair.band.fill}
|
||||
type={'dot'}
|
||||
/>
|
||||
<EuiIcon color={colorTransformer(chartMetric.color)} type={'dot'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size={'xs'}>
|
||||
|
@ -335,11 +333,11 @@ const TimelineLoadingContainer = euiStyled.div`
|
|||
`;
|
||||
|
||||
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', {
|
||||
defaultMessage: 'Unable to display history data.',
|
||||
defaultMessage: 'Unable to show historical data.',
|
||||
});
|
||||
|
||||
const checkNewDataButtonLabel = i18n.translate(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { fieldToName } from '../../lib/field_to_display_name';
|
||||
import { useSourceContext } from '../../../../../containers/source';
|
||||
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
|
||||
|
@ -38,7 +38,7 @@ export const ToolbarWrapper = (props: Props) => {
|
|||
} = useWaffleOptionsContext();
|
||||
const { createDerivedIndexPattern } = useSourceContext();
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup responsive={false} wrap gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<WaffleInventorySwitcher />
|
||||
</EuiFlexItem>
|
||||
|
@ -62,7 +62,7 @@ export const ToolbarWrapper = (props: Props) => {
|
|||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
})}
|
||||
</>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ interface Props {
|
|||
bounds: InfraWaffleMapBounds;
|
||||
dataBounds: InfraWaffleMapBounds;
|
||||
bottomMargin: number;
|
||||
staticHeight: boolean;
|
||||
}
|
||||
|
||||
export const Map: React.FC<Props> = ({
|
||||
|
@ -39,6 +40,7 @@ export const Map: React.FC<Props> = ({
|
|||
nodeType,
|
||||
dataBounds,
|
||||
bottomMargin,
|
||||
staticHeight,
|
||||
}) => {
|
||||
const sortedNodes = sortNodes(options.sort, nodes);
|
||||
const map = nodesToWaffleMap(sortedNodes);
|
||||
|
@ -51,6 +53,7 @@ export const Map: React.FC<Props> = ({
|
|||
ref={(el: any) => measureRef(el)}
|
||||
bottomMargin={bottomMargin}
|
||||
data-test-subj="waffleMap"
|
||||
staticHeight={staticHeight}
|
||||
>
|
||||
<WaffleMapInnerContainer>
|
||||
{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%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
@ -100,6 +103,7 @@ const WaffleMapOuterContainer = euiStyled.div<{ bottomMargin: number }>`
|
|||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
margin-bottom: ${(props) => props.bottomMargin}px;
|
||||
${(props) => props.staticHeight && 'min-height: 300px;'}
|
||||
`;
|
||||
|
||||
const WaffleMapInnerContainer = euiStyled.div`
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import { EuiButtonGroup, EuiButtonGroupProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue