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-18311] - Adding CloudPulse section to Cloud Manager #10397

Merged
Show file tree
Hide file tree
Changes from 7 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
---

Adding Dashboard Global Filters to the CloudPulse component ([#10397](https://github.com/linode/manager/pull/10397))
13 changes: 13 additions & 0 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ const PlacementGroups = React.lazy(() =>
}))
);

const CloudPulse = React.lazy(() =>
import('src/features/CloudPulse/CloudPulseLanding').then((module) => ({
default: module.CloudPulseLanding,
}))
);

export const MainContent = () => {
const { classes, cx } = useStyles();
const flags = useFlags();
Expand Down Expand Up @@ -239,6 +245,7 @@ export const MainContent = () => {
flags?.mainContentBanner?.key
);

const showCloudPulse = Boolean(flags.cloudView);
/**
* this is the case where the user has successfully completed signup
* but needs a manual review from Customer Support. In this case,
Expand Down Expand Up @@ -383,6 +390,12 @@ export const MainContent = () => {
<Route component={BetaRoutes} path="/betas" />
)}
<Route component={VPC} path="/vpcs" />
{showCloudPulse && (
<Route
component={CloudPulse}
path="/monitor/cloudpulse"
/>
)}
<Redirect exact from="/" to={defaultRoot} />
{/** We don't want to break any bookmarks. This can probably be removed eventually. */}
<Redirect from="/dashboard" to={defaultRoot} />
Expand Down
8 changes: 8 additions & 0 deletions packages/manager/src/assets/icons/cloudpulse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import { Link, LinkProps, useLocation } from 'react-router-dom';

import Account from 'src/assets/icons/account.svg';
import CloudPulse from 'src/assets/icons/cloudpulse.svg';
import Beta from 'src/assets/icons/entityIcons/beta.svg';
import Storage from 'src/assets/icons/entityIcons/bucket.svg';
import Database from 'src/assets/icons/entityIcons/database.svg';
Expand Down Expand Up @@ -58,6 +59,7 @@ type NavEntity =
| 'Longview'
| 'Managed'
| 'Marketplace'
| 'Monitor'
| 'NodeBalancers'
| 'Object Storage'
| 'Placement Groups'
Expand Down Expand Up @@ -170,6 +172,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
) ||
(checkRestrictedUser && !enginesLoading && !enginesError);

const showCloudPulse = Boolean(flags.cloudView);
const { isACLBEnabled } = useIsACLBEnabled();
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();

Expand Down Expand Up @@ -288,6 +291,13 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
href: '/longview',
icon: <Longview />,
},
{
display: 'Monitor',
hide: !showCloudPulse,
href: '/monitor/cloudpulse',
icon: <CloudPulse />,
isBeta: false,
},
{
attr: { 'data-qa-one-click-nav-btn': true },
display: 'Marketplace',
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/dev-tools/FeatureFlagTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const MOCK_FEATURE_FLAGS_STORAGE_KEY = 'devTools/mock-feature-flags';
const options: { flag: keyof Flags; label: string }[] = [
{ flag: 'aclb', label: 'ACLB' },
{ flag: 'aclbFullCreateFlow', label: 'ACLB Full Create Flow' },
{ flag: 'cloudView', label: 'CloudPulse' },
{ flag: 'disableLargestGbPlans', label: 'Disable Largest GB Plans' },
{ flag: 'linodeCloneUiChanges', label: 'Linode Clone UI Changes' },
{ flag: 'gecko2', label: 'Gecko' },
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface Flags {
aclb: boolean;
aclbFullCreateFlow: boolean;
apiMaintenance: APIMaintenance;
cloudView: boolean;
databaseBeta: boolean;
databaseResize: boolean;
databases: boolean;
Expand Down
23 changes: 23 additions & 0 deletions packages/manager/src/features/CloudPulse/CloudPulseLanding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import { Route, Switch } from 'react-router-dom';

import { LandingHeader } from 'src/components/LandingHeader/LandingHeader';
import { SuspenseLoader } from 'src/components/SuspenseLoader';

import { CloudPulseTabs } from './CloudPulseTabs';
export const CloudPulseLanding = () => {
return (
<>
<LandingHeader
breadcrumbProps={{ pathname: '/Akamai Cloud Pulse' }}
docsLabel="Getting Started"
docsLink="https://www.linode.com/docs/"
/>
<React.Suspense fallback={<SuspenseLoader />}>
<Switch>
<Route component={CloudPulseTabs} />
</Switch>
</React.Suspense>
</>
);
};
55 changes: 55 additions & 0 deletions packages/manager/src/features/CloudPulse/CloudPulseTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { styled } from '@mui/material/styles';
import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';

import { SuspenseLoader } from 'src/components/SuspenseLoader';
import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel';
import { TabLinkList } from 'src/components/Tabs/TabLinkList';
import { TabPanels } from 'src/components/Tabs/TabPanels';
import { Tabs } from 'src/components/Tabs/Tabs';

import { DashBoardLanding } from './Dashboard/DashboardLanding';
type Props = RouteComponentProps<{}>;

export const CloudPulseTabs = React.memo((props: Props) => {
const tabs = [
{
routeName: `${props.match.url}/dashboards`,
title: 'Dashboards',
},
];

const matches = (p: string) => {
return !Boolean(props.location.pathname.indexOf(p));
};
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved

const navToURL = (index: number) => {
props.history.push(tabs[index].routeName);
};

return (
<StyledTabs
index={Math.max(
tabs.findIndex((tab) => matches(tab.routeName)),
0
)}
onChange={navToURL}
>
<TabLinkList tabs={tabs} />

<React.Suspense fallback={<SuspenseLoader />}>
<TabPanels>
<SafeTabPanel index={0}>
<DashBoardLanding />
</SafeTabPanel>
</TabPanels>
</React.Suspense>
</StyledTabs>
);
});

const StyledTabs = styled(Tabs, {
label: 'StyledTabs',
})(() => ({
marginTop: 0,
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Paper } from '@mui/material';
import * as React from 'react';

import { FiltersObject, GlobalFilters } from '../Overview/GlobalFilters';

export const DashBoardLanding = () => {
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line @typescript-eslint/no-empty-function
const onFilterChange = (_filters: FiltersObject) => {};
return (
<Paper>
<div style={{ display: 'flex' }}>
jaalah-akamai marked this conversation as resolved.
Show resolved Hide resolved
<div style={{ width: '100%' }}>
<GlobalFilters handleAnyFilterChange={onFilterChange}></GlobalFilters>
</div>
</div>
</Paper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable no-console */
// import { Dashboard, TimeDuration, TimeGranularity } from '@linode/api-v4';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';

import { WithStartAndEnd } from 'src/features/Longview/request.types';

import { CloudPulseRegionSelect } from '../shared/RegionSelect';
import { CloudPulseTimeRangeSelect } from '../shared/TimeRangeSelect';

export interface GlobalFilterProperties {
handleAnyFilterChange(filters: FiltersObject): undefined | void;
}

export interface FiltersObject {
interval: string;
region: string;
resource: string[];
serviceType?: string;
timeRange: WithStartAndEnd;
}

export const GlobalFilters = React.memo((props: GlobalFilterProperties) => {
const [time, setTimeBox] = React.useState<WithStartAndEnd>({
end: 0,
start: 0,
});

const [selectedRegion, setRegion] = React.useState<string>();

const triggerGlobalFilterChange = () => {
const globalFilters = {} as FiltersObject;
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved
globalFilters.region = selectedRegion!;
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved
globalFilters.timeRange = time;
props.handleAnyFilterChange(globalFilters);
};

React.useEffect(() => {
triggerGlobalFilterChange();
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line react-hooks/exhaustive-deps
jaalah-akamai marked this conversation as resolved.
Show resolved Hide resolved
}, [time, selectedRegion]); // if anything changes, emit an event to parent component

const handleTimeRangeChange = (start: number, end: number) => {
console.log('TimeRange: ', start, end);
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved
setTimeBox({ end, start });
};

const handleRegionChange = (region: string | undefined) => {
jaalah-akamai marked this conversation as resolved.
Show resolved Hide resolved
console.log('Region: ', region);
santoshp210-akamai marked this conversation as resolved.
Show resolved Hide resolved
setRegion(region);
};

return (
<Grid container sx={{ ...itemSpacing, padding: '8px' }}>
<StyledGrid xs={12}>
<Grid sx={{ marginLeft: 2, width: 250 }}>
<StyledCloudPulseRegionSelect
handleRegionChange={handleRegionChange}
/>
</Grid>
<Grid sx={{ marginLeft: 12, width: 250 }}>
<StyledCloudPulseTimeRangeSelect
defaultValue={'Past 30 Minutes'}
handleStatsChange={handleTimeRangeChange}
hideLabel
label="Select Time Range"
/>
</Grid>
</StyledGrid>
</Grid>
);
});

const StyledCloudPulseRegionSelect = styled(CloudPulseRegionSelect, {
label: 'StyledCloudPulseRegionSelect',
})({
width: 150,
});

const StyledCloudPulseTimeRangeSelect = styled(CloudPulseTimeRangeSelect, {
label: 'StyledCloudPulseTimeRangeSelect',
})({
width: 150,
});

const StyledGrid = styled(Grid, { label: 'StyledGrid' })(({ theme }) => ({
alignItems: 'end',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'row',
justifyContent: 'start',
marginBottom: theme.spacing(1.25),
}));

const itemSpacing = {
boxSizing: 'border-box',
margin: '0',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';

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

import { CloudPulseRegionSelectProps } from './RegionSelect';
import { CloudPulseRegionSelect } from './RegionSelect';

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

describe('CloudViewRegionSelect', () => {
it('should render a Region Select component', () => {
const { getByTestId } = renderWithTheme(
<CloudPulseRegionSelect {...props} />
);
expect(getByTestId('region-select')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* eslint-disable no-console */
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;
}

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

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

return (
<RegionSelect
handleSelection={(value) => {
setRegion(value);
}}
currentCapability={undefined}
fullWidth
isClearable={false}
label=""
noMarginTop
regions={regions ? regions : []}
selectedId={null}
/>
);
}
);
Loading
Loading