Skip to content

Commit

Permalink
[Infra UI] Add Legend Settings for Waffle Map (#32228) (#32550)
Browse files Browse the repository at this point in the history
* Adding legend controls

* Adding url support and finishing feature

* Moving config to right side; setting min/max to data boundries when deactivating auto

* Removing legend from max and min labels

* removing uneseccary unshift(0)

* Change autobounds behavior
  • Loading branch information
simianhacker authored Mar 6, 2019
1 parent 96197f7 commit db5e282
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 23 deletions.
40 changes: 25 additions & 15 deletions x-pack/plugins/infra/public/components/nodes_overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ interface Props {
onViewChange: (view: string) => void;
view: string;
intl: InjectedIntl;
boundsOverride: InfraWaffleMapBounds;
autoBounds: boolean;
}

interface MetricFormatter {
Expand All @@ -51,12 +53,10 @@ const METRIC_FORMATTERS: MetricFormatters = {
[InfraMetricType.cpu]: {
formatter: InfraFormatterType.percent,
template: '{{value}}',
bounds: { min: 0, max: 1 },
},
[InfraMetricType.memory]: {
formatter: InfraFormatterType.percent,
template: '{{value}}',
bounds: { min: 0, max: 1 },
},
[InfraMetricType.rx]: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
[InfraMetricType.tx]: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
Expand All @@ -67,19 +67,33 @@ const METRIC_FORMATTERS: MetricFormatters = {
};

const calculateBoundsFromNodes = (nodes: InfraNode[]): InfraWaffleMapBounds => {
const values = nodes.map(node => node.metric.value);
// if there is only one value then we need to set the bottom range to zero
if (values.length === 1) {
values.unshift(0);
const maxValues = nodes.map(node => node.metric.max);
const minValues = nodes.map(node => node.metric.value);
// if there is only one value then we need to set the bottom range to zero for min
// otherwise the legend will look silly since both values are the same for top and
// bottom.
if (minValues.length === 1) {
minValues.unshift(0);
}
return { min: min(values) || 0, max: max(values) || 0 };
return { min: min(minValues) || 0, max: max(maxValues) || 0 };
};

export const NodesOverview = injectI18n(
class extends React.Component<Props, {}> {
public static displayName = 'Waffle';
public render() {
const { loading, nodes, nodeType, reload, intl, view, options, timeRange } = this.props;
const {
autoBounds,
boundsOverride,
loading,
nodes,
nodeType,
reload,
intl,
view,
options,
timeRange,
} = this.props;
if (loading) {
return (
<InfraLoadingPanel
Expand Down Expand Up @@ -113,13 +127,8 @@ export const NodesOverview = injectI18n(
/>
);
}
const { metric } = this.props.options;
const metricFormatter = get(
METRIC_FORMATTERS,
metric.type,
METRIC_FORMATTERS[InfraMetricType.count]
);
const bounds = (metricFormatter && metricFormatter.bounds) || calculateBoundsFromNodes(nodes);
const dataBounds = calculateBoundsFromNodes(nodes);
const bounds = autoBounds ? dataBounds : boundsOverride;
return (
<MainContainer>
<ViewSwitcherContainer>
Expand All @@ -146,6 +155,7 @@ export const NodesOverview = injectI18n(
timeRange={timeRange}
onFilter={this.handleDrilldown}
bounds={bounds}
dataBounds={dataBounds}
/>
</MapContainer>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const GradientLegendContainer = styled.div`
height: 10px;
bottom: 0;
left: 0;
right: 0;
right: 40px;
`;

const GradientLegendTick = styled.div`
Expand Down
24 changes: 23 additions & 1 deletion x-pack/plugins/infra/public/components/waffle/legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,41 @@
*/
import React from 'react';
import styled from 'styled-components';
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';
interface Props {
legend: InfraWaffleMapLegend;
bounds: InfraWaffleMapBounds;
dataBounds: InfraWaffleMapBounds;
formatter: InfraFormatter;
}

export const Legend: React.SFC<Props> = ({ legend, bounds, formatter }) => {
interface LegendControlOptions {
auto: boolean;
bounds: InfraWaffleMapBounds;
}

export const Legend: React.SFC<Props> = ({ dataBounds, legend, bounds, formatter }) => {
return (
<LegendContainer>
<WithWaffleOptions>
{({ changeBoundsOverride, changeAutoBounds, autoBounds, boundsOverride }) => (
<LegendControls
dataBounds={dataBounds}
bounds={bounds}
autoBounds={autoBounds}
boundsOverride={boundsOverride}
onChange={(options: LegendControlOptions) => {
changeBoundsOverride(options.bounds);
changeAutoBounds(options.auto);
}}
/>
)}
</WithWaffleOptions>
{isInfraWaffleMapGradientLegend(legend) && (
<GradientLegend formatter={formatter} legend={legend} bounds={bounds} />
)}
Expand Down
170 changes: 170 additions & 0 deletions x-pack/plugins/infra/public/components/waffle/legend_controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* 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 {
EuiButton,
EuiButtonIcon,
EuiFieldNumber,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiPopover,
EuiPopoverTitle,
EuiSwitch,
EuiText,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React, { SyntheticEvent, useState } from 'react';
import styled from 'styled-components';
import { InfraWaffleMapBounds } from '../../lib/lib';

interface Props {
onChange: (options: { auto: boolean; bounds: InfraWaffleMapBounds }) => void;
bounds: InfraWaffleMapBounds;
dataBounds: InfraWaffleMapBounds;
autoBounds: boolean;
boundsOverride: InfraWaffleMapBounds;
intl: InjectedIntl;
}

export const LegendControls = injectI18n(
({ intl, autoBounds, boundsOverride, onChange, dataBounds }: Props) => {
const [isPopoverOpen, setPopoverState] = useState(false);
const [draftAuto, setDraftAuto] = useState(autoBounds);
const [draftBounds, setDraftBounds] = useState(autoBounds ? dataBounds : boundsOverride); // should come from bounds prop
const buttonComponent = (
<EuiButtonIcon
iconType="gear"
color="text"
aria-label={intl.formatMessage({
id: 'xpack.infra.legendControls.buttonLabel',
defaultMessage: 'configure legend',
})}
onClick={() => setPopoverState(true)}
/>
);

const handleAutoChange = (e: SyntheticEvent<HTMLInputElement>) => {
setDraftAuto(e.currentTarget.checked);
};

const createBoundsHandler = (name: string) => (e: SyntheticEvent<HTMLInputElement>) => {
const value = parseFloat(e.currentTarget.value);
setDraftBounds({ ...draftBounds, [name]: value });
};

const handlePopoverClose = () => {
setPopoverState(false);
};

const handleApplyClick = () => {
onChange({ auto: draftAuto, bounds: draftBounds });
};

const commited =
draftAuto === autoBounds &&
boundsOverride.min === draftBounds.min &&
boundsOverride.max === draftBounds.max;

const boundsValidRange = draftBounds.min < draftBounds.max;

return (
<ControlContainer>
<EuiPopover
isOpen={isPopoverOpen}
closePopover={handlePopoverClose}
id="legendControls"
button={buttonComponent}
withTitle
>
<EuiPopoverTitle>Legend Options</EuiPopoverTitle>
<EuiForm>
<EuiFormRow>
<EuiSwitch
name="bounds"
label={intl.formatMessage({
id: 'xpack.infra.legendControls.switchLabel',
defaultMessage: 'Auto calculate range',
})}
checked={draftAuto}
onChange={handleAutoChange}
/>
</EuiFormRow>
{(!boundsValidRange && (
<EuiText color="danger" grow={false} size="s">
<p>
<FormattedMessage
id="xpack.infra.legendControls.errorMessage"
defaultMessage="Min should be less than max"
/>
</p>
</EuiText>
)) ||
null}
<EuiFlexGroup style={{ marginTop: 0 }}>
<EuiFlexItem>
<EuiFormRow
label={intl.formatMessage({
id: 'xpack.infra.legendControls.minLabel',
defaultMessage: 'Min',
})}
isInvalid={!boundsValidRange}
>
<EuiFieldNumber
disabled={draftAuto}
step={0.1}
value={isNaN(draftBounds.min) ? '' : draftBounds.min}
isInvalid={!boundsValidRange}
name="legendMin"
onChange={createBoundsHandler('min')}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={intl.formatMessage({
id: 'xpack.infra.legendControls.maxLabel',
defaultMessage: 'Max',
})}
isInvalid={!boundsValidRange}
>
<EuiFieldNumber
disabled={draftAuto}
step={0.1}
isInvalid={!boundsValidRange}
value={isNaN(draftBounds.max) ? '' : draftBounds.max}
name="legendMax"
onChange={createBoundsHandler('max')}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiButton
type="submit"
size="s"
fill
disabled={commited || !boundsValidRange}
onClick={handleApplyClick}
>
<FormattedMessage
id="xpack.infra.legendControls.applyButton"
defaultMessage="Apply"
/>
</EuiButton>
</EuiForm>
</EuiPopover>
</ControlContainer>
);
}
);

const ControlContainer = styled.div`
position: absolute;
top: -20px;
right: 6px;
bottom: 0;
`;
9 changes: 8 additions & 1 deletion x-pack/plugins/infra/public/components/waffle/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface Props {
timeRange: InfraTimerangeInput;
onFilter: (filter: string) => void;
bounds: InfraWaffleMapBounds;
dataBounds: InfraWaffleMapBounds;
}

export const Map: React.SFC<Props> = ({
Expand All @@ -36,6 +37,7 @@ export const Map: React.SFC<Props> = ({
formatter,
bounds,
nodeType,
dataBounds,
}) => {
const map = nodesToWaffleMap(nodes);
return (
Expand Down Expand Up @@ -80,7 +82,12 @@ export const Map: React.SFC<Props> = ({
}
})}
</WaffleMapInnerContainer>
<Legend formatter={formatter} bounds={bounds} legend={options.legend} />
<Legend
formatter={formatter}
bounds={bounds}
dataBounds={dataBounds}
legend={options.legend}
/>
</WaffleMapOuterContainer>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const StepLegend: React.SFC<Props> = ({ legend, formatter }) => {

const StepLegendContainer = styled.div`
display: flex;
padding: 10px;
padding: 10px 40px 10px 10px;
`;

const StepContainer = styled.div`
Expand Down
Loading

0 comments on commit db5e282

Please sign in to comment.