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-19062] - Dashboard Select component in cloudpulse global filters view #10589

Merged
merged 5 commits into from
Jun 25, 2024
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/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
Loading