Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Changes for providing ability to zoom in inside the `CloudPulse Metrics Graphs` ([#13317](https://github.com/linode/manager/pull/13317))
58 changes: 57 additions & 1 deletion packages/manager/src/components/AreaChart/AreaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Area,
CartesianGrid,
Legend,
ReferenceArea,
ResponsiveContainer,
Tooltip,
XAxis,
Expand All @@ -26,6 +27,7 @@ import {
} from './utils';

import type { TooltipProps } from 'recharts';
import type { CategoricalChartFunc } from 'recharts/types/chart/generateCategoricalChart';
import type { MetricsDisplayRow } from 'src/components/LineGraph/MetricsDisplay';

export interface DataSet {
Expand All @@ -47,6 +49,32 @@ export interface AreaProps {
dataKey: string;
}

interface ZoomCallbacks {
/**
* Callback fired on mouse down event on the chart
*/
onMouseDown?: CategoricalChartFunc;
/**
* Callback fired on mouse move event on the chart
*/
onMouseMove?: CategoricalChartFunc;
/**
* Callback fired on mouse up event on the chart
*/
onMouseUp?: CategoricalChartFunc;
}

interface ReferenceAreaProps {
/**
* Ending x-axis value of the reference area
*/
referenceEnd: number;
/**
* Starting x-axis value of the reference area
*/
referenceStart: number;
}

interface XAxisProps {
/**
* format for the x-axis timestamp
Expand Down Expand Up @@ -118,6 +146,11 @@ export interface AreaChartProps {
*/
margin?: { bottom: number; left: number; right: number; top: number };

/**
* reference area to be highlighted on the chart
*/
referenceArea?: null | ReferenceAreaProps;

/**
* control the visibility of dots for each data points
*/
Expand Down Expand Up @@ -171,6 +204,11 @@ export interface AreaChartProps {
* y-axis properties
*/
yAxisProps?: YAxisProps;

/**
* zoom callbacks (onMouseDown, onMouseMove, onMouseUp)
*/
zoomCallbacks?: ZoomCallbacks;
}

export const AreaChart = (props: AreaChartProps) => {
Expand All @@ -195,9 +233,13 @@ export const AreaChart = (props: AreaChartProps) => {
xAxisTickCount,
yAxisProps,
tooltipCustomValueFormatter,
zoomCallbacks,
referenceArea,
} = props;

const theme = useTheme();
const { onMouseDown, onMouseMove, onMouseUp } = zoomCallbacks ?? {};
const { referenceStart, referenceEnd } = referenceArea ?? {};

const [activeSeries, setActiveSeries] = React.useState<Array<string>>([]);
const handleLegendClick = (dataKey: string) => {
Expand Down Expand Up @@ -280,7 +322,14 @@ export const AreaChart = (props: AreaChartProps) => {
height={height}
width={width}
>
<_AreaChart aria-label={ariaLabel} data={data} margin={margin}>
<_AreaChart
aria-label={ariaLabel}
data={data}
margin={margin}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
>
<CartesianGrid
stroke={theme.color.grey7}
strokeDasharray="3 3"
Expand Down Expand Up @@ -341,6 +390,13 @@ export const AreaChart = (props: AreaChartProps) => {
wrapperStyle={legendStyles}
/>
)}
{referenceStart !== undefined && referenceEnd !== undefined && (
<ReferenceArea
strokeOpacity={0.3}
x1={referenceStart}
x2={referenceEnd}
/>
)}
{areas.map(({ color, dataKey }) => (
<Area
connectNulls={connectNulls}
Expand Down
5 changes: 5 additions & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ interface AclpFlag {
*/
enabled: boolean;

/**
* This property indicates whether to enable zoom in charts or not
*/
enableZoomInCharts?: boolean;

/**
* This property indicates for which unit, we need to humanize the values e.g., count, iops etc.,
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/features/CloudPulse/Utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,5 +694,5 @@ export const humanizeLargeData = (value: number) => {
if (value >= 1000) {
return +(value / 1000).toFixed(1) + 'K';
}
return `${roundTo(value, 1)}`;
return `${roundTo(value, 2)}`;
};
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
const [groupBy, setGroupBy] = React.useState<string[] | undefined>(
props.widget.group_by
);
const [isZoomed, setIsZoomed] = React.useState(false);
const theme = useTheme();

const {
Expand Down Expand Up @@ -401,6 +402,10 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
},
[savePref, updatePreferences, widget.label]
);

const handleZoomStateChange = React.useCallback((zoomed: boolean) => {
setIsZoomed(zoomed);
}, []);
const {
data: metricsList,
error,
Expand Down Expand Up @@ -428,6 +433,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
label: widget.label,
timeStamp,
url: flags.aclpReadEndpoint!,
shouldRefresh: !isZoomed,
}
);
let data: DataSet[] = [];
Expand Down Expand Up @@ -462,6 +468,19 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
const hours = end.diff(start, 'hours').hours;
const tickFormat = hours <= 24 ? 'hh:mm a' : 'LLL dd';

const zoomResetKey = React.useMemo(() => {
const { preset, start, end, timeZone } = props.duration;

if (preset) {
return `preset:${preset}`;
}
if (!start || !end || !timeZone) {
return 'custom:missing-params';
}

return `custom:${start},${end},${timeZone}`;
}, [props.duration]);

React.useEffect(() => {
if (
filteredSelections.length !== (dimensionFilters?.length ?? 0) &&
Expand Down Expand Up @@ -585,12 +604,16 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
metricsApiCallError === jweTokenExpiryError ||
isJweTokenFetching
} // keep loading until we are trying to fetch the refresh token
onZoomChange={handleZoomStateChange}
showDot
showLegend={data.length !== 0}
timezone={timezone}
unit={`${currentUnit}${unit.endsWith('ps') ? '/s' : ''}`}
variant={variant}
xAxis={{ tickFormat, tickGap: 60 }}
zoomResetKey={
zoomResetKey // key to reset zoom when duration changes
}
/>
</Paper>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ class ResizeObserver {
unobserve() {}
}

const zoomResetKey = 'test-zoom';

describe('CloudPulseLineGraph', () => {
window.ResizeObserver = ResizeObserver;

it('should render AreaChart when data is provided', () => {
const { container, getByRole } = renderWithTheme(
<CloudPulseLineGraph {...mockData} />
<CloudPulseLineGraph {...mockData} zoomResetKey={zoomResetKey} />
);
const table = getByRole('table');

Expand All @@ -53,7 +55,11 @@ describe('CloudPulseLineGraph', () => {

it('should show error state', () => {
const { getByText } = renderWithTheme(
<CloudPulseLineGraph {...mockData} error="Test error" />
<CloudPulseLineGraph
{...mockData}
error="Test error"
zoomResetKey={zoomResetKey}
/>
);

expect(getByText('Test error')).toBeInTheDocument();
Expand All @@ -66,7 +72,7 @@ describe('CloudPulseLineGraph', () => {
};

const { getByText } = renderWithTheme(
<CloudPulseLineGraph {...emptyData} />
<CloudPulseLineGraph {...emptyData} zoomResetKey={zoomResetKey} />
);

expect(getByText('No data to display')).toBeInTheDocument();
Expand Down
Loading