Skip to content

Commit

Permalink
histogram shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
jpinsonneau committed Jan 19, 2023
1 parent 82836bb commit f6715b0
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 15 deletions.
15 changes: 10 additions & 5 deletions web/src/components/metrics/histogram.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
.histogram-range {
.histogram-range-container {
position: absolute;
left: 0;
right: 0;
text-align: center;
margin: 5px;
left: 1.5rem;
right: 1.5rem;
justify-content: center;
margin: 0;
z-index: 999;
}

.histogram-zoom-container {
justify-content: end !important;
}
128 changes: 122 additions & 6 deletions web/src/components/metrics/histogram.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Chart, ChartAxis, ChartBar, ChartStack, ChartThemeColor, createContainer } from '@patternfly/react-charts';
import { Bullseye, EmptyStateBody, Spinner, Text } from '@patternfly/react-core';
import { AngleRightIcon, AngleLeftIcon, SearchMinusIcon, SearchPlusIcon } from '@patternfly/react-icons';
import { Bullseye, Button, EmptyStateBody, Flex, FlexItem, Spinner, Text, Tooltip } from '@patternfly/react-core';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { NamedMetric, TopologyMetrics } from '../../api/loki';
Expand Down Expand Up @@ -29,7 +30,10 @@ export const Histogram: React.FC<{
isDark: boolean;
range?: TimeRange;
setRange: (tr: TimeRange) => void;
}> = ({ id, totalMetric, limit, isDark, range, setRange }) => {
zoomRange: (zoom: boolean) => void;
}> = ({ id, totalMetric, limit, isDark, range, setRange, zoomRange }) => {
const { t } = useTranslation('plugin__netobserv-plugin');

const datapoints: ChartDataPoint[] = toHistogramDatapoints(totalMetric);
const defaultRange = getHistogramRangeFromLimit(totalMetric, limit);

Expand All @@ -39,9 +43,118 @@ export const Histogram: React.FC<{
observe(containerRef, dimensions, setDimensions);
}, [containerRef, dimensions]);

const moveRange = React.useCallback(
(next: boolean) => {
containerRef.current?.focus();

const r = range ? range : defaultRange;
const rSize = r.to - r.from;

let start = r.from - rSize;
if (next) {
start = r.to;
}
const end = start + rSize;

//try to get new range using limit
let updatedRange = getHistogramRangeFromLimit(totalMetric, limit, start);

//else fallback on previous / next range keeping actual width
if (!updatedRange.from || !updatedRange.to) {
const minTime = getDateMsInSeconds(Math.min(...datapoints.map(dp => dp.x.getTime())));
const maxTime = getDateMsInSeconds(Math.max(...datapoints.map(dp => dp.x.getTime())));

updatedRange = {
from: start > minTime ? start : minTime,
to: end < maxTime ? end : maxTime
};
}

if (updatedRange.from && updatedRange.to && updatedRange.from < updatedRange.to) {
setRange(updatedRange);
} else {
setRange(defaultRange);
}
},
[containerRef, datapoints, defaultRange, limit, range, setRange, totalMetric]
);

const onKeyDown = React.useCallback(
(key: string) => {
switch (key) {
case '+':
zoomRange(true);
break;
case '-':
zoomRange(false);
break;
case 'PageUp':
case 'ArrowRight':
moveRange(true);
break;
case 'PageDown':
case 'ArrowLeft':
moveRange(false);
break;
}
},
[moveRange, zoomRange]
);

const zoomButtonTips = () => {
return t('Zoom in / out histogram. You can also use plus or minus buttons while histogram is focused.');
};

const arrowButtonTips = () => {
return t('Move selected range. You can also use page or arrow buttons while histogram is focused.');
};

return (
<div id={`chart-${id}`} className="metrics-content-div" ref={containerRef}>
<Text className="histogram-range">{getDomainDisplayText(range ? range : defaultRange)}</Text>
<div
id={`chart-${id}`}
className="metrics-content-div"
ref={containerRef}
tabIndex={0}
onKeyDown={e => onKeyDown(e.key)}
>
<Flex className="histogram-range-container" direction={{ default: 'row' }}>
<FlexItem flex={{ default: 'flex_1' }} />
<FlexItem>
<Tooltip content={arrowButtonTips()}>
<Button variant="plain" onClick={() => moveRange(false)}>
<AngleLeftIcon />
</Button>
</Tooltip>
</FlexItem>
<FlexItem>
<Text>{getDomainDisplayText(range ? range : defaultRange)}</Text>
</FlexItem>
<FlexItem>
<Tooltip content={arrowButtonTips()}>
<Button variant="plain" onClick={() => moveRange(true)}>
<AngleRightIcon />
</Button>
</Tooltip>
</FlexItem>
<FlexItem flex={{ default: 'flex_1' }}>
<Flex className="histogram-zoom-container" direction={{ default: 'row' }}>
<FlexItem>
<Tooltip content={zoomButtonTips()}>
<Button variant="plain" onClick={() => zoomRange(false)}>
<SearchMinusIcon />
</Button>
</Tooltip>
</FlexItem>
<FlexItem>
<Tooltip content={zoomButtonTips()}>
<Button variant="plain" onClick={() => zoomRange(true)}>
<SearchPlusIcon />
</Button>
</Tooltip>
</FlexItem>
</Flex>
</FlexItem>
</Flex>
<Chart
themeColor={ChartThemeColor.multiUnordered}
containerComponent={
Expand All @@ -58,6 +171,7 @@ export const Histogram: React.FC<{
}
}}
onBrushDomainChangeEnd={(domain?: { x?: Array<Date | number> }) => {
containerRef.current?.focus();
if (
domain?.x &&
domain.x.length > 1 &&
Expand Down Expand Up @@ -88,7 +202,7 @@ export const Histogram: React.FC<{
height={dimensions.height}
padding={{
top: 30,
right: 10,
right: 25,
bottom: 35,
left: 60
}}
Expand Down Expand Up @@ -117,7 +231,8 @@ export const HistogramContainer: React.FC<{
isDark: boolean;
range?: TimeRange;
setRange: (tr: TimeRange) => void;
}> = ({ id, loading, totalMetric, limit, isDark, range, setRange }) => {
zoomRange: (zoom: boolean) => void;
}> = ({ id, loading, totalMetric, limit, isDark, range, setRange, zoomRange }) => {
const { t } = useTranslation('plugin__netobserv-plugin');

return totalMetric ? (
Expand All @@ -128,6 +243,7 @@ export const HistogramContainer: React.FC<{
isDark={isDark}
range={range}
setRange={setRange}
zoomRange={zoomRange}
/>
) : loading ? (
<Bullseye data-test="loading-histogram">
Expand Down
36 changes: 35 additions & 1 deletion web/src/components/netflow-traffic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import {
import { Column, ColumnSizeMap, getDefaultColumns } from '../utils/columns';
import { loadConfig } from '../utils/config';
import { ContextSingleton } from '../utils/context';
import { computeStepInterval, TimeRange } from '../utils/datetime';
import { computeStepInterval, getTimeRangeOptions, TimeRange } from '../utils/datetime';
import { getHTTPErrorDetails } from '../utils/errors';
import { useK8sModelsWithColors } from '../utils/k8s-models-hook';
import {
Expand Down Expand Up @@ -132,6 +132,7 @@ import { SearchComponent, SearchEvent, SearchHandle } from './search/search';
import './netflow-traffic.css';
import { TruncateLength } from './dropdowns/truncate-dropdown';
import HistogramContainer from './metrics/histogram';
import { formatDuration, getDateMsInSeconds, getDateSInMiliseconds, parseDuration } from '../utils/duration';

export type ViewId = 'overview' | 'table' | 'topology';

Expand Down Expand Up @@ -1005,6 +1006,38 @@ export const NetflowTraffic: React.FC<{
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [match, filters]);

const zoomRange = React.useCallback(
(zoom: boolean) => {
if (typeof range === 'number') {
const timeRangeOptions = getTimeRangeOptions(t, false);
const keys = Object.keys(timeRangeOptions);
const selectedKey = formatDuration(getDateSInMiliseconds(range as number));
let index = keys.indexOf(selectedKey);
if (zoom && index > 0) {
index--;
} else if (!zoom && index < keys.length) {
index++;
}

setRange(getDateMsInSeconds(parseDuration(keys[index])));
} else {
const updatedRange = { ...range };
const factor = ((zoom ? -1 : 1) * (range.to - range.from)) / 2;

updatedRange.from += factor;
updatedRange.to += factor;

const now = lastRefresh ? lastRefresh.getTime() : new Date().getTime();
if (getDateSInMiliseconds(updatedRange.to) > now) {
updatedRange.to = getDateMsInSeconds(now);
}

setRange(updatedRange);
}
},
[lastRefresh, range, t]
);

const isShowViewOptions = selectedViewId === 'table' ? showViewOptions && !showHistogram : showViewOptions;

return !_.isEmpty(extensions) ? (
Expand Down Expand Up @@ -1098,6 +1131,7 @@ export const NetflowTraffic: React.FC<{
isDark={isDarkTheme}
range={histogramRange}
setRange={setHistogramRange}
zoomRange={zoomRange}
/>
</ToolbarItem>
</Toolbar>
Expand Down
14 changes: 11 additions & 3 deletions web/src/utils/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ export interface TimeRange {
to: number;
}

export const getTimeRangeOptions = (t: TFunction) => {
return {
[CUSTOM_TIME_RANGE_KEY]: t('Custom time range'),
export const getTimeRangeOptions = (t: TFunction, includeCustom = true) => {
const timeOptions = {
'5m': t('Last 5 minutes'),
'15m': t('Last 15 minutes'),
'30m': t('Last 30 minutes'),
Expand All @@ -39,6 +38,15 @@ export const getTimeRangeOptions = (t: TFunction) => {
'1w': t('Last 1 week'),
'2w': t('Last 2 weeks')
};

if (includeCustom) {
return {
[CUSTOM_TIME_RANGE_KEY]: t('Custom time range'),
...timeOptions
};
} else {
return timeOptions;
}
};

export const dateFormatter = new Intl.DateTimeFormat(getLanguage(), {
Expand Down

0 comments on commit f6715b0

Please sign in to comment.