Skip to content

Commit

Permalink
upcoming: [DI-22132] - Added criteria section in alert details page f…
Browse files Browse the repository at this point in the history
…or ACLP Alerting (linode#11477)

* upcoming: [DI-22596] - Criteria changes

* upcoming: [DI-22596] - Criteria changes

* upcoming: [DI-22596] - Criteria changes

* upcoming: [DI-22596] - Alert detail chips

* upcoming: [DI-22596] - CSS changes

* upcoming: [DI-22596] - Code refactoring

* upcoming: [DI-22596] - Add types

* upcoming: [DI-22132] - Code refactoring

* upcoming: [DI-22132] - Add changeset

* upcoming: [DI-22132] - Add factories and constants

* upcoming: [DI-22132] - Use factories in mock

* upcoming: [DI-22132] - Refactor alert criteria component

* upcoming: [DI-22132] - Code refactoring, util update and constants update

* upcoming: [DI-22132] - Code refactoring

* upcoming: [DI-22132] - UT updates and code clean up

* upcoming: [DI-22132] - Code updates

* upcoming: [DI-22132] - Comment update and label update

* upcoming: [DI-22132] - Code refactoring and updates

* upcoming: [DI-22132] - Reusable typography

* upcoming: [DI-22132] - Use common typography

* upcoming: [DI-22132] - Rename common typography

* upcoming: [DI-22132] - Add logical comments

* upcoming: [DI-22132] - Add spacing constant

* upcoming: [DI-22132] - Code refactoring

* upcoming: [DI-22132] - Code refactoring

* upcoming: [DI-22132] - CSS fixes

* upcoming: [DI-22132] - Remove pick random

* upcoming: [DI-22132] - Code merge error fixes

* upcoming: [DI-22132] - Merge imports into one

* upcoming: [DI-22132] - Color changes for PR

* upcoming: [DI-22132] - ES lint issue fix

* upcoming: [DI-22132] - Height changes to px value

* upcoming: [DI-22132] - Constants and text update

---------

Co-authored-by: vmangalr <vmangalr@akamai.com>
  • Loading branch information
venkymano-akamai and vmangalr authored Jan 14, 2025
1 parent bd63112 commit aefd870
Show file tree
Hide file tree
Showing 13 changed files with 504 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add Alert Details Criteria section in Cloud Pulse Alert Details page ([#11477](https://github.com/linode/manager/pull/11477))
31 changes: 28 additions & 3 deletions packages/manager/src/factories/cloudpulse/alerts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import Factory from 'src/factories/factoryProxy';

import type {
AlertDefinitionDimensionFilter,
AlertDefinitionMetricCriteria,
} from '@linode/api-v4';
import type { Alert } from '@linode/api-v4';

export const alertDimensionsFactory = Factory.Sync.makeFactory<AlertDefinitionDimensionFilter>(
{
dimension_label: 'operating_system',
label: 'Operating System',
operator: 'eq',
value: 'Linux',
}
);

export const alertRulesFactory = Factory.Sync.makeFactory<AlertDefinitionMetricCriteria>(
{
aggregation_type: 'avg',
dimension_filters: alertDimensionsFactory.buildList(1),
label: 'CPU Usage',
metric: 'cpu_usage',
operator: 'eq',
threshold: 60,
unit: 'Bytes',
}
);

export const alertFactory = Factory.Sync.makeFactory<Alert>({
channels: [],
created: new Date().toISOString(),
Expand All @@ -20,9 +45,9 @@ export const alertFactory = Factory.Sync.makeFactory<Alert>({
tags: ['tag1', 'tag2'],
trigger_conditions: {
criteria_condition: 'ALL',
evaluation_period_seconds: 0,
polling_interval_seconds: 0,
trigger_occurrences: 0,
evaluation_period_seconds: 240,
polling_interval_seconds: 120,
trigger_occurrences: 3,
},
type: 'user',
updated: new Date().toISOString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ describe('AlertDetail component tests', () => {
const { getByText } = renderWithTheme(<AlertDetail />);
// validate overview is present with its couple of properties (values will be validated in its own components test)
expect(getByText('Overview')).toBeInTheDocument();
expect(getByText('Criteria')).toBeInTheDocument(); // validate if criteria is present
expect(getByText('Name:')).toBeInTheDocument();
expect(getByText('Description:')).toBeInTheDocument();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, CircleProgress } from '@linode/ui';
import { Box, Chip, CircleProgress, Typography } from '@linode/ui';
import { styled, useTheme } from '@mui/material';
import React from 'react';
import { useParams } from 'react-router-dom';
Expand All @@ -10,6 +10,7 @@ import { Placeholder } from 'src/components/Placeholder/Placeholder';
import { useAlertDefinitionQuery } from 'src/queries/cloudpulse/alerts';

import { getAlertBoxStyles } from '../Utils/utils';
import { AlertDetailCriteria } from './AlertDetailCriteria';
import { AlertDetailOverview } from './AlertDetailOverview';

interface RouteParams {
Expand Down Expand Up @@ -48,12 +49,14 @@ export const AlertDetail = () => {
}, [alertId, serviceType]);

const theme = useTheme();
const nonSuccessBoxHeight = '600px';
const sectionMaxHeight = '785px';

if (isFetching) {
return (
<>
<Breadcrumb crumbOverrides={crumbOverrides} pathname={pathname} />
<Box alignContent="center" height={theme.spacing(75)}>
<Box alignContent="center" height={nonSuccessBoxHeight}>
<CircleProgress />
</Box>
</>
Expand All @@ -64,7 +67,7 @@ export const AlertDetail = () => {
return (
<>
<Breadcrumb crumbOverrides={crumbOverrides} pathname={pathname} />
<Box alignContent="center" height={theme.spacing(75)}>
<Box alignContent="center" height={nonSuccessBoxHeight}>
<ErrorState errorText="An error occurred while loading the definitions. Please try again later." />
</Box>
</>
Expand All @@ -75,7 +78,7 @@ export const AlertDetail = () => {
return (
<>
<Breadcrumb crumbOverrides={crumbOverrides} pathname={pathname} />
<Box alignContent="center" height={theme.spacing(75)}>
<Box alignContent="center" height={nonSuccessBoxHeight}>
<StyledPlaceholder
icon={AlertsIcon}
isEntity
Expand All @@ -93,11 +96,21 @@ export const AlertDetail = () => {
<Box display="flex" flexDirection={{ md: 'row', xs: 'column' }} gap={2}>
<Box
flexBasis="50%"
maxHeight={theme.spacing(98.125)}
maxHeight={sectionMaxHeight}
sx={{ ...getAlertBoxStyles(theme), overflow: 'auto' }}
>
<AlertDetailOverview alertDetails={alertDetails} />
</Box>
<Box
sx={{
...getAlertBoxStyles(theme),
overflow: 'auto',
}}
flexBasis="50%"
maxHeight={sectionMaxHeight}
>
<AlertDetailCriteria alertDetails={alertDetails} />
</Box>
</Box>
</Box>
</>
Expand All @@ -114,3 +127,25 @@ export const StyledPlaceholder = styled(Placeholder, {
maxHeight: theme.spacing(10),
},
}));

export const StyledAlertChip = styled(Chip, {
label: 'StyledAlertChip',
shouldForwardProp: (prop) => prop !== 'borderRadius',
})<{
borderRadius?: string;
}>(({ borderRadius, theme }) => ({
'& .MuiChip-label': {
color: theme.tokens.content.Text.Primary.Default,
marginRight: theme.spacing(1),
},
backgroundColor: theme.tokens.background.Normal,
borderRadius: borderRadius || 0,
height: theme.spacing(3),
}));

export const StyledAlertTypography = styled(Typography, {
label: 'StyledAlertTypography',
})(({ theme }) => ({
color: theme.tokens.content.Text.Primary.Default,
fontSize: theme.typography.body1.fontSize,
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';

import {
alertDimensionsFactory,
alertFactory,
alertRulesFactory,
} from 'src/factories';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { metricOperatorTypeMap } from '../constants';
import { convertSecondsToMinutes } from '../Utils/utils';
import { AlertDetailCriteria } from './AlertDetailCriteria';

describe('AlertDetailCriteria component tests', () => {
it('should render the alert detail criteria successfully on correct inputs', () => {
const alertDetails = alertFactory.build({
rule_criteria: {
rules: alertRulesFactory.buildList(2, {
aggregation_type: 'avg',
dimension_filters: alertDimensionsFactory.buildList(2),
label: 'CPU Usage',
metric: 'cpu_usage',
operator: 'gt',
unit: 'bytes',
}),
},
});
const { getAllByText, getByText } = renderWithTheme(
<AlertDetailCriteria alertDetails={alertDetails} />
);
const { rules } = alertDetails.rule_criteria;
expect(getAllByText('Metric Threshold:').length).toBe(rules.length);
expect(getAllByText('Dimension Filter:').length).toBe(rules.length);
expect(getByText('Criteria')).toBeInTheDocument();
expect(getAllByText('Average').length).toBe(2);
expect(getAllByText('CPU Usage').length).toBe(2);
expect(getAllByText('bytes').length).toBe(2);
expect(getAllByText(metricOperatorTypeMap['gt']).length).toBe(2);
const {
evaluation_period_seconds,
polling_interval_seconds,
} = alertDetails.trigger_conditions;
expect(
getByText(convertSecondsToMinutes(polling_interval_seconds))
).toBeInTheDocument();
expect(
getByText(convertSecondsToMinutes(evaluation_period_seconds))
).toBeInTheDocument();
});

it('should render the alert detail criteria even if rules are empty', () => {
const alert = alertFactory.build({
rule_criteria: {
rules: [],
},
});
const { getByText, queryByText } = renderWithTheme(
<AlertDetailCriteria alertDetails={alert} />
);
expect(getByText('Criteria')).toBeInTheDocument(); // empty criteria should be there
expect(queryByText('Metric Threshold:')).not.toBeInTheDocument();
expect(queryByText('Dimension Filter:')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Typography } from '@linode/ui';
import { Grid, useTheme } from '@mui/material';
import React from 'react';

import { convertSecondsToMinutes } from '../Utils/utils';
import { StyledAlertChip, StyledAlertTypography } from './AlertDetail';
import { DisplayAlertDetailChips } from './DisplayAlertDetailChips';
import { RenderAlertMetricsAndDimensions } from './RenderAlertsMetricsAndDimensions';

import type { Alert } from '@linode/api-v4';

interface CriteriaProps {
/**
* The alert detail object for which the criteria needs to be displayed
*/
alertDetails: Alert;
}

export const AlertDetailCriteria = React.memo((props: CriteriaProps) => {
const { alertDetails } = props;
const {
evaluation_period_seconds: evaluationPeriod,
polling_interval_seconds: pollingIntervalSeconds,
trigger_occurrences: triggerOccurrences,
} = alertDetails.trigger_conditions;
const { rule_criteria: ruleCriteria = { rules: [] } } = alertDetails;
const theme = useTheme();

// Memoized trigger criteria rendering
const renderTriggerCriteria = React.useMemo(
() => (
<>
<Grid item sm={4} xs={12}>
<StyledAlertTypography fontFamily={theme.font.bold}>
Trigger Alert When:
</StyledAlertTypography>
</Grid>
<Grid alignItems="center" container item md={8} xs={12}>
<StyledAlertChip
borderRadius={theme.spacing(0.3)}
label="All"
variant="outlined"
/>
<StyledAlertTypography marginRight={0.5}>
criteria are met for
</StyledAlertTypography>
<StyledAlertChip
borderRadius={theme.spacing(0.3)}
label={triggerOccurrences}
variant="outlined"
/>
<StyledAlertTypography>
consecutive occurrences.
</StyledAlertTypography>
</Grid>
</>
),
[theme, triggerOccurrences]
);
return (
<>
<Typography marginBottom={2} variant="h2">
Criteria
</Typography>
<Grid alignItems="center" container spacing={1}>
<RenderAlertMetricsAndDimensions ruleCriteria={ruleCriteria} />
<DisplayAlertDetailChips // label chip for polling interval
label="Polling Interval"
mergeChips
values={[convertSecondsToMinutes(pollingIntervalSeconds)]}
/>
<DisplayAlertDetailChips // label chip for evaluation period
label="Evaluation Period"
mergeChips
values={[convertSecondsToMinutes(evaluationPeriod)]}
/>
{renderTriggerCriteria} {/** Render the trigger criteria */}
</Grid>
</>
);
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Typography } from '@linode/ui';
import { Grid, useTheme } from '@mui/material';
import React from 'react';

import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';

import { StyledAlertTypography } from './AlertDetail';

import type { Status } from 'src/components/StatusIcon/StatusIcon';

interface AlertDetailRowProps {
Expand Down Expand Up @@ -46,13 +47,9 @@ export const AlertDetailRow = React.memo((props: AlertDetailRowProps) => {
return (
<Grid container item xs={12}>
<Grid item sm={labelGridColumns} xs={12}>
<Typography
color={theme.tokens.content.Text.Primary.Default}
fontFamily={theme.font.bold}
variant="body1"
>
<StyledAlertTypography fontFamily={theme.font.bold}>
{label}:
</Typography>
</StyledAlertTypography>
</Grid>
<Grid container item sm={valueGridColumns} xs={12}>
{status && (
Expand All @@ -63,12 +60,7 @@ export const AlertDetailRow = React.memo((props: AlertDetailRowProps) => {
status={status}
/>
)}
<Typography
color={theme.tokens.content.Text.Primary.Default}
variant="body1"
>
{value}
</Typography>
<StyledAlertTypography>{value}</StyledAlertTypography>
</Grid>
</Grid>
);
Expand Down
Loading

0 comments on commit aefd870

Please sign in to comment.