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-21694] - Added the Create Alert Button and few components for the Create Alert Definition Form #11255

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
60b2814
Upcoming : [DI:21546] - Added the create button and name,description,…
santoshp210-akamai Oct 21, 2024
73e74d8
Upcoming: [DI:21456] - Added unit test for AlertSeverity component, c…
santoshp210-akamai Oct 22, 2024
36a2c49
Upcoming: [DI:21546] - Added the unit test for CreateAlertDefinition …
santoshp210-akamai Oct 23, 2024
ebe8ae0
upcoming: [DI:21546] - Added the onChange for the tabs
santoshp210-akamai Oct 23, 2024
e8b6c1d
Upcoming: [DI-21546] - Removed unneccessary components and made chang…
santoshp210-akamai Oct 29, 2024
e92313b
Merge branch 'create-alert/integration-testing' into feature/create-a…
santoshp210-akamai Nov 5, 2024
36cb606
Merge branch 'linode:develop' into feature/create-alert-definition
santoshp210-akamai Nov 6, 2024
1882348
Upcoming: [DI-21694] - Modified the changes with Severity component, …
santoshp210-akamai Nov 12, 2024
15fd616
Upcoming: [DI-21694] - Added null as empty value for the Severity com…
santoshp210-akamai Nov 13, 2024
2887f18
Merging latest develop changes (#23)
santoshp210-akamai Nov 13, 2024
c371de5
Revert "Merging latest develop changes (#23)" (#24)
santoshp210-akamai Nov 13, 2024
50e5640
Merging latest changes develop (#25)
santoshp210-akamai Nov 13, 2024
98df4e9
Merge branch 'develop' of https://github.com/linode/manager into feat…
santoshp210-akamai Nov 13, 2024
4f27785
Merge branch 'feature/create-alert-definition-page' of https://github…
santoshp210-akamai Nov 13, 2024
63f10fe
upcoming: [DI-21694] - Added changesets
santoshp210-akamai Nov 14, 2024
d809bd8
upcoming : [DI-21694] - Addressed the review comments
santoshp210-akamai Nov 18, 2024
5be9016
upcoming: [DI-21694] - Fixed the type safety issue for the AlertSever…
santoshp210-akamai Nov 18, 2024
4b26321
Merge pull request #28 from linode/develop
santoshp210-akamai Nov 18, 2024
8ddfbdc
upcoming: [DI-21694] - Fixed the dependency if Button component that …
santoshp210-akamai Nov 18, 2024
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
---

Add POST request endpoint for create alert in `alerts.ts`, add Alert, CreateAlertPayload types that are relevant for the Create Alert workflow ([#11255](https://github.com/linode/manager/pull/11255))
11 changes: 11 additions & 0 deletions packages/api-v4/src/cloudpulse/alerts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createAlertDefinitionSchema } from '@linode/validation';
import Request, { setURL, setMethod, setData } from '../request';
import { Alert, CreateAlertDefinitionPayload } from './types';
import { BETA_API_ROOT as API_ROOT } from 'src/constants';

export const createAlertDefinition = (data: CreateAlertDefinitionPayload) =>
Request<Alert>(
setURL(`${API_ROOT}/monitor/alert-definitions`),
setMethod('POST'),
setData(data, createAlertDefinitionSchema)
);
2 changes: 2 additions & 0 deletions packages/api-v4/src/cloudpulse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './types';
export * from './dashboards';

export * from './services';

export * from './alerts';
67 changes: 67 additions & 0 deletions packages/api-v4/src/cloudpulse/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export type AlertSeverityType = 0 | 1 | 2 | 3 | null;
type MetricAggregationType = 'avg' | 'sum' | 'min' | 'max' | 'count';
type MetricOperatorType = 'eq' | 'gt' | 'lt' | 'gte' | 'lte';
type DimensionFilterOperatorType = 'eq' | 'neq' | 'startswith' | 'endswith';
type AlertDefinitionType = 'default' | 'custom';
type AlertStatusType = 'enabled' | 'disabled';
export interface Dashboard {
id: number;
label: string;
Expand Down Expand Up @@ -132,3 +138,64 @@ export interface ServiceTypes {
export interface ServiceTypesList {
data: ServiceTypes[];
}

export interface CreateAlertDefinitionPayload {
label: string;
description?: string;
resource_ids?: string[];
severity: AlertSeverityType;
rule_criteria: {
rules: MetricCriteria[];
};
triggerCondition: TriggerCondition;
channel_ids: number[];
}
export interface CreateAlertDefinitionForm
extends CreateAlertDefinitionPayload {
region: string;
service_type: string;
engine_type: string;
}
export interface MetricCriteria {
metric: string;
aggregation_type: MetricAggregationType | '';
operator: MetricOperatorType | '';
value: number;
dimension_filters: DimensionFilter[];
}

export interface DimensionFilter {
dimension_label: string;
operator: DimensionFilterOperatorType | '';
value: string;
}

export interface TriggerCondition {
polling_interval_seconds: number;
evaluation_period_seconds: number;
trigger_occurrences: number;
}
export interface Alert {
id: number;
label: string;
description: string;
status: AlertStatusType;
type: AlertDefinitionType;
severity: AlertSeverityType;
service_type: string;
resource_ids: string[];
rule_criteria: {
rules: MetricCriteria[];
};
triggerCondition: TriggerCondition;
channels: {
id: string;
label: string;
url: string;
type: 'channel';
}[];
created_by: string;
updated_by: string;
created: string;
updated: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add Create Alert Button, Add Name, Description, Severity components to the Create Alert Form ([#11255](https://github.com/linode/manager/pull/11255))
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Paper } from '@linode/ui';
import * as React from 'react';
import { Route, Switch } from 'react-router-dom';

import { Paper } from '@linode/ui';
import { Typography } from 'src/components/Typography';

import { CreateAlertDefinition } from '../CreateAlert/CreateAlertDefinition';

export const AlertDefinitionLanding = () => {
return (
<Switch>
Expand All @@ -12,6 +14,10 @@ export const AlertDefinitionLanding = () => {
exact
path="/monitor/cloudpulse/alerts/definitions"
/>
<Route
component={() => <CreateAlertDefinition />}
path="/monitor/cloudpulse/alerts/definitions/create"
/>
</Switch>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
Redirect,
Route,
Switch,
useHistory,
useLocation,
useRouteMatch,
} from 'react-router-dom';

import { Button } from 'src/components/Button/Button';
import { TabLinkList } from 'src/components/Tabs/TabLinkList';
import { Tabs } from 'src/components/Tabs/Tabs';
import { useFlags } from 'src/hooks/useFlags';
Expand All @@ -20,6 +22,7 @@ export const AlertsLanding = React.memo(() => {
const flags = useFlags();
const { url } = useRouteMatch();
const { pathname } = useLocation();
const history = useHistory();
const alertTabs = React.useMemo<EnabledAlertTab[]>(
() => [
{
Expand All @@ -44,9 +47,17 @@ export const AlertsLanding = React.memo(() => {
),
[accessibleTabs, pathname]
);
const handleChange = (index: number) => {
history.push(alertTabs[index].tab.routeName);
};

return (
<Paper sx={{ padding: 2 }}>
<Tabs index={activeTabIndex} style={{ width: '100%' }}>
<Tabs
index={activeTabIndex}
onChange={handleChange}
sx={{ width: '100%' }}
>
<Box
sx={{
aligneItems: 'center',
Expand All @@ -57,6 +68,19 @@ export const AlertsLanding = React.memo(() => {
}}
>
<TabLinkList tabs={accessibleTabs} />
{pathname === `${url}/definitions` && (
<Box>
<Button
onClick={(_) => {
history.push(`${url}/definitions/create`);
}}
buttonType="primary"
variant="contained"
>
Create
</Button>
</Box>
)}
</Box>
<Switch>
<Route
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { fireEvent, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';

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

import { CreateAlertDefinition } from './CreateAlertDefinition';
describe('AlertDefinition Create', () => {
it('should render input components', () => {
const { getByLabelText } = renderWithTheme(<CreateAlertDefinition />);

expect(getByLabelText('Name')).toBeVisible();
expect(getByLabelText('Description (optional)')).toBeVisible();
expect(getByLabelText('Severity')).toBeVisible();
});
it('should be able to enter a value in the textbox', () => {
const { getByLabelText } = renderWithTheme(<CreateAlertDefinition />);
const input = getByLabelText('Name');

fireEvent.change(input, { target: { value: 'text' } });
const specificInput = within(screen.getByTestId('alert-name')).getByTestId(
'textfield-input'
);
expect(specificInput).toHaveAttribute('value', 'text');
});
it('should render client side validation errors', async () => {
const { getByText } = renderWithTheme(<CreateAlertDefinition />);

const submitButton = getByText('Submit').closest('button');

await userEvent.click(submitButton!);

expect(getByText('Name is required')).toBeVisible();
expect(getByText('Severity is required')).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { Paper } from '@linode/ui';
import { createAlertDefinitionSchema } from '@linode/validation';
import { useSnackbar } from 'notistack';
import * as React from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Breadcrumb } from 'src/components/Breadcrumb/Breadcrumb';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
import { useCreateAlertDefinition } from 'src/queries/cloudpulse/alerts';

import { CloudPulseAlertSeveritySelect } from './GeneralInformation/AlertSeveritySelect';

import type {
CreateAlertDefinitionForm,
CreateAlertDefinitionPayload,
MetricCriteria,
TriggerCondition,
} from '@linode/api-v4/lib/cloudpulse/types';

const triggerConditionInitialValues: TriggerCondition = {
evaluation_period_seconds: 0,
polling_interval_seconds: 0,
trigger_occurrences: 0,
};
const criteriaInitialValues: MetricCriteria[] = [
{
aggregation_type: '',
dimension_filters: [],
metric: '',
operator: '',
value: 0,
},
];
const initialValues: CreateAlertDefinitionForm = {
channel_ids: [],
engine_type: '',
label: '',
region: '',
resource_ids: [],
rule_criteria: { rules: criteriaInitialValues },
service_type: '',
severity: null,
triggerCondition: triggerConditionInitialValues,
};

const overrides = [
{
label: 'Definitions',
linkTo: '/monitor/cloudpulse/alerts/definitions',
position: 1,
},
{
label: 'Details',
linkTo: `/monitor/cloudpulse/alerts/definitions/create`,
position: 2,
},
];
export const CreateAlertDefinition = () => {
const history = useHistory();
const alertCreateExit = () =>
history.push('/monitor/cloudpulse/alerts/definitions');

const formMethods = useForm<CreateAlertDefinitionPayload>({
defaultValues: initialValues,
mode: 'onBlur',
resolver: yupResolver(createAlertDefinitionSchema),
});

const { control, formState, handleSubmit, setError } = formMethods;
const { enqueueSnackbar } = useSnackbar();
const { mutateAsync: createAlert } = useCreateAlertDefinition();

const onSubmit = handleSubmit(async (values) => {
try {
await createAlert(values);
enqueueSnackbar('Alert successfully created', {
variant: 'success',
});
alertCreateExit();
} catch (errors) {
for (const error of errors) {
if (error.field) {
setError(error.field, { message: error.reason });
} else {
setError('root', { message: error.reason });
}
}
}
});

return (
<Paper sx={{ paddingLeft: 1, paddingRight: 1, paddingTop: 2 }}>
<Breadcrumb crumbOverrides={overrides} pathname="/Definitions/Create" />
<FormProvider {...formMethods}>
<form onSubmit={onSubmit}>
<Typography marginTop={2} variant="h2">
1. General Information
</Typography>
<Controller
render={({ field, fieldState }) => (
<TextField
data-testid="alert-name"
errorText={fieldState.error?.message}
label="Name"
name="label"
onBlur={field.onBlur}
onChange={(e) => field.onChange(e.target.value)}
placeholder="Enter Name"
value={field.value ?? ''}
/>
)}
control={control}
name="label"
/>
<Controller
render={({ field, fieldState }) => (
<TextField
errorText={fieldState.error?.message}
label="Description"
name="description"
onBlur={field.onBlur}
onChange={(e) => field.onChange(e.target.value)}
optional
placeholder="Enter Description"
value={field.value ?? ''}
/>
)}
control={control}
name="description"
/>
<CloudPulseAlertSeveritySelect name="severity" />
<ActionsPanel
primaryButtonProps={{
label: 'Submit',
loading: formState.isSubmitting,
type: 'submit',
}}
secondaryButtonProps={{
label: 'Cancel',
onClick: alertCreateExit,
}}
sx={{ display: 'flex', justifyContent: 'flex-end' }}
/>
</form>
</FormProvider>
</Paper>
);
};
Loading