Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upcoming: [DI-20929] - Added applied filters view in CloudPulse #11354

Merged
merged 16 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
---

Add `CloudPulseAppliedFilter` and `CloudPulseAppliedFilterRenderer` components, update filter change handler function to add another parameter `label` ([#11354](https://github.com/linode/manager/pull/11354))
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Grid, Paper } from '@mui/material';
import { Box, Paper } from '@linode/ui';
import { Grid } from '@mui/material';
import * as React from 'react';

import { GlobalFilters } from '../Overview/GlobalFilters';
import { CloudPulseAppliedFilterRenderer } from '../shared/CloudPulseAppliedFilterRenderer';
import { CloudPulseDashboardRenderer } from './CloudPulseDashboardRenderer';

import type { Dashboard, TimeDuration } from '@linode/api-v4';

export type FilterValueType = number | number[] | string | string[] | undefined;

export interface FilterData {
id: { [filterKey: string]: FilterValueType };
label: { [filterKey: string]: string[] };
}
export interface DashboardProp {
dashboard?: Dashboard;
filterValue: {
Expand All @@ -17,26 +23,47 @@ export interface DashboardProp {
}

export const CloudPulseDashboardLanding = () => {
const [filterValue, setFilterValue] = React.useState<{
[key: string]: FilterValueType;
}>({});
const [filterData, setFilterData] = React.useState<FilterData>({
id: {},
label: {},
});

const [timeDuration, setTimeDuration] = React.useState<TimeDuration>();

const [dashboard, setDashboard] = React.useState<Dashboard>();

const [showAppliedFilters, setShowAppliedFilters] = React.useState<boolean>(
false
);

const toggleAppliedFilter = (isVisible: boolean) => {
setShowAppliedFilters(isVisible);
};

const onFilterChange = React.useCallback(
(filterKey: string, filterValue: FilterValueType) => {
setFilterValue((prev: { [key: string]: FilterValueType }) => ({
...prev,
[filterKey]: filterValue,
}));
(filterKey: string, filterValue: FilterValueType, labels: string[]) => {
setFilterData((prev: FilterData) => {
return {
id: {
...prev.id,
[filterKey]: filterValue,
},
label: {
...prev.label,
[filterKey]: labels,
},
};
});
},
[]
);

const onDashboardChange = React.useCallback((dashboardObj: Dashboard) => {
setDashboard(dashboardObj);
setFilterValue({}); // clear the filter values on dashboard change
setFilterData({
id: {},
label: {},
}); // clear the filter values on dashboard change
}, []);
const onTimeDurationChange = React.useCallback(
(timeDurationObj: TimeDuration) => {
Expand All @@ -48,16 +75,25 @@ export const CloudPulseDashboardLanding = () => {
<Grid container spacing={2}>
<Grid item xs={12}>
<Paper>
<GlobalFilters
handleAnyFilterChange={onFilterChange}
handleDashboardChange={onDashboardChange}
handleTimeDurationChange={onTimeDurationChange}
/>
<Box display={'flex'} flexDirection={'column'}>
<GlobalFilters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heads up, since Paper is now from linode/ui, it has built in padding - you need to add sx={{ padding: 0 }} to the Paper component to get the same padding as develop

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for informing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Box display={'flex'} flexDirection={'column'}>
<Box display="flex" flexDirection="column">

nit: we don't need object notation here

handleAnyFilterChange={onFilterChange}
handleDashboardChange={onDashboardChange}
handleTimeDurationChange={onTimeDurationChange}
handleToggleAppliedFilter={toggleAppliedFilter}
/>
{dashboard?.service_type && showAppliedFilters && (
<CloudPulseAppliedFilterRenderer
filters={filterData.label}
serviceType={dashboard.service_type}
/>
)}
</Box>
</Paper>
</Grid>
<CloudPulseDashboardRenderer
dashboard={dashboard}
filterValue={filterValue}
filterValue={filterData.id}
timeDuration={timeDuration}
/>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { useCloudPulseDashboardByIdQuery } from 'src/queries/cloudpulse/dashboards';

import { CloudPulseAppliedFilterRenderer } from '../shared/CloudPulseAppliedFilterRenderer';
import { CloudPulseDashboardFilterBuilder } from '../shared/CloudPulseDashboardFilterBuilder';
import { CloudPulseErrorPlaceholder } from '../shared/CloudPulseErrorPlaceholder';
import { CloudPulseTimeRangeSelect } from '../shared/CloudPulseTimeRangeSelect';
Expand All @@ -16,7 +17,7 @@ import {
} from '../Utils/ReusableDashboardFilterUtils';
import { CloudPulseDashboard } from './CloudPulseDashboard';

import type { FilterValueType } from './CloudPulseDashboardLanding';
import type { FilterData, FilterValueType } from './CloudPulseDashboardLanding';
import type { TimeDuration } from '@linode/api-v4';

export interface CloudPulseDashboardWithFiltersProp {
Expand All @@ -37,18 +38,38 @@ export const CloudPulseDashboardWithFilters = React.memo(
dashboardId
);

const [filterValue, setFilterValue] = React.useState<{
[key: string]: FilterValueType;
}>({});
const [filterValue, setFilterValue] = React.useState<FilterData>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we changed the above const's name to filterData, can we do the same here?

id: {},
label: {},
});

const [timeDuration, setTimeDuration] = React.useState<TimeDuration>({
unit: 'min',
value: 30,
});

const [showAppliedFilters, setShowAppliedFilters] = React.useState<boolean>(
false
);

const toggleAppliedFilter = (isVisible: boolean) => {
setShowAppliedFilters(isVisible);
};

const onFilterChange = React.useCallback(
(filterKey: string, value: FilterValueType) => {
setFilterValue((prev) => ({ ...prev, [filterKey]: value }));
(filterKey: string, value: FilterValueType, labels: string[]) => {
setFilterValue((prev) => {
return {
id: {
...prev.id,
[filterKey]: value,
},
label: {
...prev.label,
[filterKey]: labels,
},
};
});
},
[]
);
Expand Down Expand Up @@ -91,7 +112,7 @@ export const CloudPulseDashboardWithFilters = React.memo(
const isFilterBuilderNeeded = checkIfFilterBuilderNeeded(dashboard);
const isMandatoryFiltersSelected = checkMandatoryFiltersSelected({
dashboardObj: dashboard,
filterValue,
filterValue: filterValue.id,
resource,
timeDuration,
});
Expand Down Expand Up @@ -132,15 +153,24 @@ export const CloudPulseDashboardWithFilters = React.memo(
<CloudPulseDashboardFilterBuilder
dashboard={dashboard}
emitFilterChange={onFilterChange}
handleToggleAppliedFilter={toggleAppliedFilter}
isServiceAnalyticsIntegration={true}
/>
)}
<Grid item mb={3} mt={-3} xs={12}>
{showAppliedFilters && (
<CloudPulseAppliedFilterRenderer
filters={filterValue.label}
serviceType={dashboard.service_type}
/>
)}
</Grid>
</Paper>
{isMandatoryFiltersSelected ? (
<CloudPulseDashboard
{...getDashboardProperties({
dashboardObj: dashboard,
filterValue,
filterValue: filterValue.id,
resource,
timeDuration,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { GlobalFilters } from './GlobalFilters';
const mockHandleAnyFilterChange = vi.fn();
const mockHandleDashboardChange = vi.fn();
const mockHandleTimeDurationChange = vi.fn();
const mockHandleToggleAppliedFilter = vi.fn();
const timeRangeSelectId = 'cloudpulse-time-duration';
const setup = () => {
return renderWithTheme(
<GlobalFilters
handleAnyFilterChange={mockHandleAnyFilterChange}
handleDashboardChange={mockHandleDashboardChange}
handleTimeDurationChange={mockHandleTimeDurationChange}
handleToggleAppliedFilter={mockHandleToggleAppliedFilter}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@ import type { FilterValueType } from '../Dashboard/CloudPulseDashboardLanding';
import type { AclpConfig, Dashboard, TimeDuration } from '@linode/api-v4';

export interface GlobalFilterProperties {
handleAnyFilterChange(filterKey: string, filterValue: FilterValueType): void;
handleAnyFilterChange(
filterKey: string,
filterValue: FilterValueType,
labels: string[]
): void;
handleDashboardChange(dashboard: Dashboard | undefined): void;
handleTimeDurationChange(timeDuration: TimeDuration): void;
handleToggleAppliedFilter(isVisible: boolean): void;
}

export const GlobalFilters = React.memo((props: GlobalFilterProperties) => {
const {
handleAnyFilterChange,
handleDashboardChange,
handleTimeDurationChange,
handleToggleAppliedFilter,
} = props;

const {
Expand Down Expand Up @@ -68,19 +74,20 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => {
(
filterKey: string,
value: FilterValueType,
labels: string[],
savePref: boolean = false,
updatedPreferenceData: AclpConfig = {}
) => {
if (savePref) {
updatePreferences(updatedPreferenceData);
}
handleAnyFilterChange(filterKey, value);
handleAnyFilterChange(filterKey, value, labels);
},
[]
);

const handleGlobalRefresh = React.useCallback(() => {
handleAnyFilterChange(REFRESH, Date.now());
handleAnyFilterChange(REFRESH, Date.now(), []);
}, []);

const theme = useTheme();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: I wonder if we could avoid importing this useTheme and instead access/use the theme directly in the styles, like this sx={(theme)=> ({})}

Expand Down Expand Up @@ -143,6 +150,7 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => {
<CloudPulseDashboardFilterBuilder
dashboard={selectedDashboard}
emitFilterChange={emitFilterChange}
handleToggleAppliedFilter={handleToggleAppliedFilter}
isServiceAnalyticsIntegration={false}
preferences={preferences}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ interface CloudPulseMandatoryFilterCheckProps {
*/
export const getRegionProperties = (
props: CloudPulseFilterProperties,
handleRegionChange: (region: string | undefined, savePref?: boolean) => void
handleRegionChange: (
region: string | undefined,
labels: [],
savePref?: boolean
) => void
): CloudPulseRegionSelectProps => {
const { name: label, placeholder } = props.config.configuration;
const { dashboard, isServiceAnalyticsIntegration, preferences } = props;
Expand Down Expand Up @@ -119,6 +123,7 @@ export const getCustomSelectProperties = (
handleCustomSelectChange: (
filterKey: string,
value: FilterValueType,
labels: string[],
savePref?: boolean,
updatedPreferenceData?: {}
) => void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';

import { CloudPulseAppliedFilter } from './CloudPulseAppliedFilter';
import { CloudPulseAppliedFilterRenderer } from './CloudPulseAppliedFilterRenderer';

const data = {
region: ['us-east'],
resource: ['res1', 'res2'],
};

const testId = 'applied-filter';

describe('CloudPulse Applied Filter', () => {
it('should render applied filter component', () => {
const { getByTestId } = renderWithTheme(
<CloudPulseAppliedFilter filters={data} />
);
expect(getByTestId(testId)).toBeInTheDocument();
});

it('should render the applied filter key & values', () => {
const { getByTestId } = renderWithTheme(
<CloudPulseAppliedFilter filters={data} />
);
expect(getByTestId(testId)).toHaveTextContent('region');
expect(getByTestId(testId)).toHaveTextContent('res1');
expect(getByTestId(testId)).not.toHaveTextContent('resources');
});

it('should not render the applied filter component', () => {
const { queryByTestId } = renderWithTheme(
<CloudPulseAppliedFilterRenderer filters={{}} serviceType="abc" />
);

expect(queryByTestId(testId)).toBe(null);
});
});
Loading
Loading