Skip to content

Commit

Permalink
upcoming: [DI-19062] - Dashboard Select component in cloudpulse globa…
Browse files Browse the repository at this point in the history
…l filters view (#10589)

* upcoming: [DI-19062] - Dashboard Select component in cloudpulse global filters view

* upcoming: [DI-19062] - Added change set

* upcoming: [DI-19062] - Replaced the hard-coded url with placeholder

* upcoming: [DI-19062] - Updated placeholder in dashboard select component

* upcoming: [DI-19062] - Added test cases
  • Loading branch information
nikhagra-akamai authored Jun 25, 2024
1 parent ba34d6f commit 4579280
Show file tree
Hide file tree
Showing 13 changed files with 347 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Added types needed for DashboardSelect component ([#10589](https://github.com/linode/manager/pull/10589))
13 changes: 13 additions & 0 deletions packages/api-v4/src/cloudpulse/dashboards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ResourcePage } from 'src/types';
import Request, { setMethod, setURL } from '../request';
import { Dashboard } from './types';
import { API_ROOT } from 'src/constants';

//Returns the list of all the dashboards available
export const getDashboards = () =>
Request<ResourcePage<Dashboard>>(
setURL(
`${API_ROOT}/monitor/services/linode/dashboards`
),
setMethod('GET'),
);
3 changes: 3 additions & 0 deletions packages/api-v4/src/cloudpulse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './types'

export * from './dashboards'
45 changes: 45 additions & 0 deletions packages/api-v4/src/cloudpulse/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export interface Dashboard {
id: number;
label: string;
widgets: Widgets[];
created: string;
updated: string;
time_duration: TimeDuration;
service_type: string;
}

export interface TimeGranularity {
unit: string;
value: number;
}

export interface TimeDuration {
unit: string;
value: number;
}

export interface Widgets {
label: string;
metric: string;
aggregate_function: string;
group_by: string;
region_id: number;
namespace_id: number;
color: string;
size: number;
chart_type: string;
y_label: string;
filters: Filters[];
serviceType: string;
service_type: string;
resource_id: string[];
time_granularity: TimeGranularity;
time_duration: TimeDuration;
unit: string;
}

export interface Filters {
key: string;
operator: string;
value: string;
}
2 changes: 2 additions & 0 deletions packages/api-v4/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export * from './account';

export * from './aclb';

export * from './cloudpulse'

export * from './databases';

export * from './domains';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Added Dashboard Selection component inside the Global Filters of CloudPulse view. ([#10589](https://github.com/linode/manager/pull/10589))
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { CloudPulseTimeRangeSelect } from '../shared/CloudPulseTimeRangeSelect';

import type { CloudPulseResources } from '../shared/CloudPulseResourcesSelect';
import type { WithStartAndEnd } from 'src/features/Longview/request.types';
import { Dashboard } from '@linode/api-v4';
import { CloudPulseDashboardSelect } from '../shared/CloudPulseDashboardSelect';

export interface GlobalFilterProperties {
handleAnyFilterChange(filters: FiltersObject): undefined | void;
Expand All @@ -27,8 +29,10 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => {
start: 0,
});

const [selectedRegion, setRegion] = React.useState<string>();
const [selectedDashboard, setSelectedDashboard] = React.useState<Dashboard | undefined>();
const [selectedRegion, setRegion] = React.useState<string | undefined>();
const [, setResources] = React.useState<CloudPulseResources[]>(); // removed the unused variable, this will be used later point of time

React.useEffect(() => {
const triggerGlobalFilterChange = () => {
const globalFilters: FiltersObject = {
Expand Down Expand Up @@ -64,20 +68,32 @@ export const GlobalFilters = React.memo((props: GlobalFilterProperties) => {
[]
);

const handleDashboardChange = React.useCallback((dashboard: Dashboard | undefined) => {
setSelectedDashboard(dashboard);
setRegion(undefined);
}, [])

return (
<Grid container sx={{ ...itemSpacing, padding: '8px' }}>
<StyledGrid xs={12}>
<Grid sx={{ width: 300 }}>
<CloudPulseDashboardSelect
handleDashboardChange={handleDashboardChange}
/>
</Grid>
<Grid sx={{ marginLeft: 2, width: 250 }}>
<StyledCloudPulseRegionSelect
handleRegionChange={handleRegionChange}
selectedDashboard={selectedDashboard}
selectedRegion={selectedRegion}
/>
</Grid>

<Grid sx={{ marginLeft: 2, width: 350 }}>
<StyledCloudPulseResourcesSelect
handleResourcesSelection={handleResourcesSelection}
region={selectedRegion}
resourceType={'linode'} // for now passing this static value, will be made dynamic once resource selection component is ready
resourceType={selectedDashboard?.service_type}
/>
</Grid>
<Grid sx={{ marginLeft: 2, width: 250 }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { renderWithTheme } from "src/utilities/testHelpers";
import { CloudPulseDashboardSelect, CloudPulseDashboardSelectProps } from "./CloudPulseDashboardSelect";
import React from "react";
import { fireEvent, screen } from '@testing-library/react';

const props: CloudPulseDashboardSelectProps = {
handleDashboardChange: vi.fn(),
}

const queryMocks = vi.hoisted(() => ({
useCloudViewDashboardsQuery: vi.fn().mockReturnValue({}),
}));

vi.mock('src/queries/cloudpulse/dashboards', async () => {
const actual = await vi.importActual('src/queries/cloudpulse/dashboards');
return {
...actual,
useCloudViewDashboardsQuery: queryMocks.useCloudViewDashboardsQuery,
};
});

queryMocks.useCloudViewDashboardsQuery.mockReturnValue({
data:
{
data: [
{
id: 1,
type: "standard",
service_type: "linode",
label: "Dashboard 1",
created: "2024-04-29T17:09:29",
updated: null,
widgets: {}
}
]
}
,
isLoading: false,
error: false
});

describe("CloudPulse Dashboard select", () => {
it("Should render dashboard select component", () => {
const { getByTestId, getByPlaceholderText } = renderWithTheme(
<CloudPulseDashboardSelect {...props} />
);

expect(getByTestId('cloudview-dashboard-select')).toBeInTheDocument();
expect(getByPlaceholderText('Select a Dashboard')).toBeInTheDocument();

}),

it("Should render dashboard select component with data", () => {


renderWithTheme(<CloudPulseDashboardSelect {...props} />)

fireEvent.click(screen.getByRole('button', { name: 'Open' }));

expect(screen.getByRole('option', { name: 'Dashboard 1' })).toBeInTheDocument();
}),

it("Should select the option on click", () => {

renderWithTheme(<CloudPulseDashboardSelect {...props} />);

fireEvent.click(screen.getByRole("button", { name: "Open" }));
fireEvent.click(screen.getByRole("option", { name: "Dashboard 1" }));

expect(screen.getByRole("combobox")).toHaveAttribute("value", "Dashboard 1");
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';

import { Dashboard } from '@linode/api-v4'
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Box } from 'src/components/Box';
import { Typography } from 'src/components/Typography';
import { useCloudViewDashboardsQuery } from 'src/queries/cloudpulse/dashboards';


export interface CloudPulseDashboardSelectProps {
handleDashboardChange: (
dashboard: Dashboard | undefined
) => void
}

export const CloudPulseDashboardSelect = React.memo((props: CloudPulseDashboardSelectProps) => {

const {
data: dashboardsList,
error,
isLoading,
} = useCloudViewDashboardsQuery(true); //Fetch the list of dashboards

const errorText: string = error ? 'Error loading dashboards' : '';

const placeHolder = "Select a Dashboard";

// sorts dashboards by service type. Required due to unexpected autocomplete grouping behaviour
const getSortedDashboardsList = (options: Dashboard[]) => {
return options.sort(
(a, b) => -b.service_type.localeCompare(a.service_type)
);
};

if (!dashboardsList) {
return (
<Autocomplete
options={[]}
label=''
disabled={true}
onChange={() => { }}
data-testid="cloudview-dashboard-select"
placeholder={placeHolder}
errorText={errorText}
/>
)
}

return (
<Autocomplete
onChange={(_: any, dashboard: Dashboard) => {
props.handleDashboardChange(dashboard);
}}
options={ getSortedDashboardsList(dashboardsList.data) }
renderGroup={(params) => (
<Box key={params.key}>
<Typography
sx={{ marginLeft: '3.5%', textTransform: 'capitalize' }}
variant="h3"
>
{params.group}
</Typography>
{params.children}
</Box>
)}
autoHighlight
clearOnBlur
data-testid="cloudview-dashboard-select"
errorText={errorText}
fullWidth
groupBy={(option: Dashboard) => option.service_type}
isOptionEqualToValue={(option, value) => option.label === value.label}
label=""
loading={isLoading}
noMarginTop
placeholder={placeHolder}
/>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { CloudPulseRegionSelect } from './CloudPulseRegionSelect';

const props: CloudPulseRegionSelectProps = {
handleRegionChange: vi.fn(),
selectedDashboard: undefined,
selectedRegion: undefined
};

describe('CloudViewRegionSelect', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
/* eslint-disable no-console */
import { Dashboard } from '@linode/api-v4';
import * as React from 'react';

import { RegionSelect } from 'src/components/RegionSelect/RegionSelect';
import { useRegionsQuery } from 'src/queries/regions/regions';

export interface CloudPulseRegionSelectProps {
handleRegionChange: (region: string | undefined) => void;
selectedDashboard: Dashboard | undefined;
selectedRegion: string | undefined;
}

export const CloudPulseRegionSelect = React.memo(
(props: CloudPulseRegionSelectProps) => {
const { data: regions } = useRegionsQuery();
const [selectedRegion, setRegion] = React.useState<string>();

React.useEffect(() => {
if (selectedRegion) {
props.handleRegionChange(selectedRegion);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedRegion]);

return (
<RegionSelect
currentCapability={undefined}
disableClearable
disableClearable={false}
fullWidth
label=""
noMarginTop
onChange={(e, region) => setRegion(region.id)}
onChange={(e, region) => props.handleRegionChange(region?.id)}
regions={regions ? regions : []}
value={undefined}
disabled={!props.selectedDashboard}
value={props.selectedRegion}
/>
);
}
Expand Down
Loading

0 comments on commit 4579280

Please sign in to comment.