Skip to content

Commit

Permalink
upcoming: [DI-20709] - CSS updates for widget level filters for ACLP (l…
Browse files Browse the repository at this point in the history
…inode#10903)

* upcoming: [DI-20709] - CSS updates for widget level filters

* upcoming: [DI-20709] - ES lint fixes

* upcoming: [DI-20709] - Added comments

* upcoming: [DI-20709] - Added changeset

* upcoming: [DI-20709] - Color updates for zoomer component with use them hook

* upcoming: [DI-20585] - CSS changes

* upcoming: [DI-20585] - Removed unused values and imports

* upcoming: [DI-20585] - Removed unused values and imports

* upcoming: [DI-20585] - Use common utils

* upcoming: [DI-20585] - Comment updates

* upcoming: [DI-20585] - Remove any

* upcoming: [DI-20585] - Removing height and adjusting width. Removed divider in widget

* upcoming: [DI-20585] - Removed placeholder on selection and UT updates

* upcoming: [DI-20585] - UT updates

* upcoming: [DI-20585] - Using form control for width of filters

* upcoming: [DI-20585] - Alignment fix

* upcoming: [DI-20585] - PR comments

* upcoming: [DI-20585] - PR comments

* upcoming: [DI-20585] - Updated code syntax for handling small size screens

* upcoming: [DI-20585] - CamelCase for properties

* upcoming: [DI-20585] - Style to sx

---------

Co-authored-by: vmangalr <vmangalr@akamai.com>
  • Loading branch information
venkymano-akamai and vmangalr authored Sep 17, 2024
1 parent 3758ec1 commit 44763ea
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Update CSS for widget level filters and widget heading title for ACLP ([#10903](https://github.com/linode/manager/pull/10903))
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { getUserPreferenceObject } from '../Utils/UserPreference';
import { createObjectCopy } from '../Utils/utils';
import { CloudPulseWidget } from '../Widget/CloudPulseWidget';
import {
all_interval_options,
allIntervalOptions,
getInSeconds,
getIntervalIndex,
} from '../Widget/components/CloudPulseIntervalSelect';
Expand Down Expand Up @@ -124,7 +124,7 @@ export const CloudPulseDashboard = (props: DashboardProperties) => {
const getTimeGranularity = (scrapeInterval: string) => {
const scrapeIntervalValue = getInSeconds(scrapeInterval);
const index = getIntervalIndex(scrapeIntervalValue);
return index < 0 ? all_interval_options[0] : all_interval_options[index];
return index < 0 ? allIntervalOptions[0] : allIntervalOptions[index];
};

const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const queryMocks = vi.hoisted(() => ({
const selectTimeDurationPlaceholder = 'Select Time Duration';
const circleProgress = 'circle-progress';
const mandatoryFiltersError = 'Mandatory Filters not Selected';
const customNodeTypePlaceholder = 'Select Node Type';

vi.mock('src/queries/cloudpulse/dashboards', async () => {
const actual = await vi.importActual('src/queries/cloudpulse/dashboards');
Expand Down Expand Up @@ -98,8 +97,7 @@ describe('CloudPulseDashboardWithFilters component tests', () => {

expect(screen.getByTestId('CloseIcon')).toBeDefined();

const inputBox = screen.getByPlaceholderText(customNodeTypePlaceholder);
fireEvent.change(inputBox, { target: { value: '' } }); // clear the value
fireEvent.click(screen.getByTitle('Clear')); // clear the value
expect(screen.getByText(mandatoryFiltersError)).toBeDefined();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { styled } from '@mui/material';

import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { isToday } from 'src/utilities/isToday';
import { getMetrics } from 'src/utilities/statMetrics';

Expand Down Expand Up @@ -331,3 +334,18 @@ export const isDataEmpty = (data: DataSet[]): boolean => {
thisSeries.data.every((thisPoint) => thisPoint[1] === null)
);
};

/**
* Returns an autocomplete with updated styles according to UX, this will be used at widget level
*/
export const StyledWidgetAutocomplete = styled(Autocomplete, {
label: 'StyledAutocomplete',
})(({ theme }) => ({
'&& .MuiFormControl-root': {
minWidth: '90px',
[theme.breakpoints.down('sm')]: {
width: '100%', // 100% width for xs and small screens
},
width: '90px',
},
}));
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Box, Grid, Paper, Stack, Typography } from '@mui/material';
import { DateTime } from 'luxon';
import React from 'react';

import { Divider } from 'src/components/Divider';
import { useFlags } from 'src/hooks/useFlags';
import { useCloudPulseMetricsQuery } from 'src/queries/cloudpulse/metrics';
import { useProfile } from 'src/queries/profile/profile';
Expand Down Expand Up @@ -298,26 +297,22 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
justifyContent={{ sm: 'space-between' }}
padding={1}
>
<Typography
fontSize={{ sm: '1.5rem', xs: '2rem' }}
marginLeft={1}
variant="h1"
>
<Typography marginLeft={1} variant="h2">
{convertStringToCamelCasesWithSpaces(widget.label)}{' '}
{!isLoading &&
`(${currentUnit}${unit.endsWith('ps') ? '/s' : ''})`}
</Typography>
<Stack
alignItems={'center'}
direction={{ sm: 'row' }}
gap={1}
gap={{ md: 2, xs: 1 }}
width={{ sm: 'inherit', xs: '100%' }}
>
{availableMetrics?.scrape_interval && (
<CloudPulseIntervalSelect
default_interval={widget?.time_granularity}
defaultInterval={widget?.time_granularity}
onIntervalChange={handleIntervalChange}
scrape_interval={availableMetrics.scrape_interval}
scrapeInterval={availableMetrics.scrape_interval}
/>
)}
{Boolean(
Expand All @@ -339,7 +334,6 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => {
</Box>
</Stack>
</Stack>
<Divider />

<CloudPulseLineGraph
error={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { StyledWidgetAutocomplete } from '../../Utils/CloudPulseWidgetUtils';

export interface AggregateFunctionProperties {
/**
Expand Down Expand Up @@ -37,7 +37,7 @@ export const CloudPulseAggregateFunction = React.memo(
) || props.availableAggregateFunctions[0];

return (
<Autocomplete
<StyledWidgetAutocomplete
isOptionEqualToValue={(option, value) => {
return option.label == value.label;
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import { CloudPulseIntervalSelect } from './CloudPulseIntervalSelect';
import type { TimeGranularity } from '@linode/api-v4';

describe('Interval select component', () => {
const intervalSelectionChange = (_selectedInterval: TimeGranularity) => {};
const intervalSelectionChange = (_selectedInterval: TimeGranularity) => { };

it('should check for the selected value in interval select dropdown', () => {
const scrape_interval = '30s';
const default_interval = { unit: 'min', value: 5 };

const { getByRole } = renderWithTheme(
<CloudPulseIntervalSelect
default_interval={default_interval}
defaultInterval={default_interval}
onIntervalChange={intervalSelectionChange}
scrape_interval={scrape_interval}
scrapeInterval={scrape_interval}
/>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import React from 'react';

import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { StyledWidgetAutocomplete } from '../../Utils/CloudPulseWidgetUtils';

import type { TimeGranularity } from '@linode/api-v4';

interface IntervalOptions {
label: string;
unit: string;
value: number;
}

export interface IntervalSelectProperties {
/**
* Default time granularity to be selected
*/
default_interval?: TimeGranularity | undefined;
defaultInterval?: TimeGranularity | undefined;

/**
* Function to be triggered on aggregate function changed from dropdown
Expand All @@ -18,7 +24,7 @@ export interface IntervalSelectProperties {
/**
* scrape intervalto filter out minimum time granularity
*/
scrape_interval: string;
scrapeInterval: string;
}

export const getInSeconds = (interval: string) => {
Expand All @@ -39,7 +45,7 @@ export const getInSeconds = (interval: string) => {
};

// Intervals must be in ascending order here
export const all_interval_options = [
export const allIntervalOptions: IntervalOptions[] = [
{
label: '1 min',
unit: 'min',
Expand All @@ -62,14 +68,14 @@ export const all_interval_options = [
},
];

const autoIntervalOption = {
const autoIntervalOption: IntervalOptions = {
label: 'Auto',
unit: 'Auto',
value: -1,
};

export const getIntervalIndex = (scrapeIntervalValue: number) => {
return all_interval_options.findIndex(
return allIntervalOptions.findIndex(
(interval) =>
scrapeIntervalValue <=
getInSeconds(String(interval.value) + interval.unit.slice(0, 1))
Expand All @@ -78,26 +84,26 @@ export const getIntervalIndex = (scrapeIntervalValue: number) => {

export const CloudPulseIntervalSelect = React.memo(
(props: IntervalSelectProperties) => {
const scrapeIntervalValue = getInSeconds(props.scrape_interval);
const scrapeIntervalValue = getInSeconds(props.scrapeInterval);

const firstIntervalIndex = getIntervalIndex(scrapeIntervalValue);

// all intervals displayed if srape interval > highest available interval. Error handling done by api
const available_interval_options =
const availableIntervalOptions =
firstIntervalIndex < 0
? all_interval_options.slice()
: all_interval_options.slice(
? allIntervalOptions.slice()
: allIntervalOptions.slice(
firstIntervalIndex,
all_interval_options.length
allIntervalOptions.length
);

let default_interval =
props.default_interval?.unit === 'Auto'
props.defaultInterval?.unit === 'Auto'
? autoIntervalOption
: available_interval_options.find(
: availableIntervalOptions.find(
(obj) =>
obj.value === props.default_interval?.value &&
obj.unit === props.default_interval?.unit
obj.value === props.defaultInterval?.value &&
obj.unit === props.defaultInterval?.unit
);

if (!default_interval) {
Expand All @@ -109,11 +115,17 @@ export const CloudPulseIntervalSelect = React.memo(
}

return (
<Autocomplete
isOptionEqualToValue={(option, value) => {
<StyledWidgetAutocomplete
isOptionEqualToValue={(
option: IntervalOptions,
value: IntervalOptions
) => {
return option?.value === value?.value && option?.unit === value?.unit;
}}
onChange={(_: any, selectedInterval: any) => {
onChange={(
_: React.SyntheticEvent,
selectedInterval: IntervalOptions
) => {
props.onIntervalChange({
unit: selectedInterval?.unit,
value: selectedInterval?.value,
Expand All @@ -127,7 +139,7 @@ export const CloudPulseIntervalSelect = React.memo(
fullWidth={false}
label="Select an Interval"
noMarginTop={true}
options={[autoIntervalOption, ...available_interval_options]}
options={[autoIntervalOption, ...availableIntervalOptions]}
sx={{ width: { xs: '100%' } }}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ZoomInMap from '@mui/icons-material/ZoomInMap';
import ZoomOutMap from '@mui/icons-material/ZoomOutMap';
import { useTheme } from '@mui/material/styles';
import * as React from 'react';

export interface ZoomIconProperties {
Expand All @@ -9,6 +10,8 @@ export interface ZoomIconProperties {
}

export const ZoomIcon = React.memo((props: ZoomIconProperties) => {
const theme = useTheme();

const handleClick = (needZoomIn: boolean) => {
props.handleZoomToggle(needZoomIn);
};
Expand All @@ -17,18 +20,26 @@ export const ZoomIcon = React.memo((props: ZoomIconProperties) => {
if (props.zoomIn) {
return (
<ZoomInMap
sx={{
color: theme.color.grey1,
fontSize: 'x-large',
height: '34px',
}}
data-testid="zoom-in"
onClick={() => handleClick(false)}
style={{ color: 'grey', fontSize: 'x-large' }}
/>
);
}

return (
<ZoomOutMap
sx={{
color: theme.color.grey1,
fontSize: 'x-large',
height: '34px',
}}
data-testid="zoom-out"
onClick={() => handleClick(true)}
style={{ color: 'grey', fontSize: 'x-large' }}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ describe('CloudPulseCustomSelect component tests', () => {
type={CloudPulseSelectTypes.static}
/>
);

expect(screen.getByPlaceholderText(testFilter)).toBeDefined();
expect(screen.queryByPlaceholderText(testFilter)).toBeNull();
const keyDown = screen.getByTestId(keyboardArrowDownIcon);
fireEvent.click(keyDown);
fireEvent.click(screen.getByText('Test1'));
Expand All @@ -82,8 +81,7 @@ describe('CloudPulseCustomSelect component tests', () => {
type={CloudPulseSelectTypes.static}
/>
);

expect(screen.getByPlaceholderText(testFilter)).toBeDefined();
expect(screen.queryByPlaceholderText(testFilter)).toBeNull();
const keyDown = screen.getByTestId(keyboardArrowDownIcon);
fireEvent.click(keyDown);
expect(screen.getAllByText('Test1').length).toEqual(2); // here it should be 2
Expand Down Expand Up @@ -111,13 +109,17 @@ describe('CloudPulseCustomSelect component tests', () => {
type={CloudPulseSelectTypes.dynamic}
/>
);
expect(screen.getByPlaceholderText(testFilter)).toBeDefined();
expect(screen.queryByPlaceholderText(testFilter)).toBeNull();
const keyDown = screen.getByTestId(keyboardArrowDownIcon);
fireEvent.click(keyDown);
fireEvent.click(screen.getByText('Test1'));
const textField = screen.getByTestId('textfield-input');
expect(textField.getAttribute('value')).toEqual('Test1');
expect(selectionChnage).toHaveBeenCalledTimes(1);

// if we click on clear icon , placeholder should appear for single select
fireEvent.click(screen.getByTitle('Clear'));
expect(screen.getByPlaceholderText(testFilter)).toBeDefined();
});

it('should render a component successfully with required props dynamic multi select', () => {
Expand All @@ -133,7 +135,7 @@ describe('CloudPulseCustomSelect component tests', () => {
type={CloudPulseSelectTypes.dynamic}
/>
);
expect(screen.getByPlaceholderText(testFilter)).toBeDefined();
expect(screen.queryByPlaceholderText(testFilter)).toBeNull();
const keyDown = screen.getByTestId(keyboardArrowDownIcon);
fireEvent.click(keyDown);
expect(screen.getAllByText('Test1').length).toEqual(2); // here it should be 2
Expand All @@ -148,5 +150,9 @@ describe('CloudPulseCustomSelect component tests', () => {
expect(screen.getAllByText('Test1').length).toEqual(1);
expect(screen.getAllByText('Test2').length).toEqual(1);
expect(selectionChnage).toHaveBeenCalledTimes(2); // check if selection change is called twice as we selected two options

// if we click on clear icon , placeholder should appear
fireEvent.click(screen.getByTitle('Clear'));
expect(screen.getByPlaceholderText(testFilter)).toBeDefined();
});
});
Loading

0 comments on commit 44763ea

Please sign in to comment.