Skip to content
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/manager": Upcoming Features
---

CloudPulse: Add dimension filter value label transformation config at `DimensionTransform.ts` and update labels in metrics and alerts ([#12676](https://github.com/linode/manager/pull/12676))
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const getWidgetLegendRowValuesFromResponse = (
],
status: 'success',
unit,
serviceType,
});

// Destructure metrics data from the first legend row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const getWidgetLegendRowValuesFromResponse = (
],
status: 'success',
unit,
serviceType,
});

// Destructure metrics data from the first legend row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ const getWidgetLegendRowValuesFromResponse = (
],
status: 'success',
unit,
serviceType,
});

// Destructure metrics data from the first legend row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ export const AlertDetail = () => {
overflow: 'auto',
}}
>
<AlertDetailCriteria alertDetails={alertDetails} />
<AlertDetailCriteria
alertDetails={alertDetails}
serviceType={alertServiceType}
/>
</Box>
</Box>
<Box
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('AlertDetailCriteria component tests', () => {
},
});
const { getAllByText, getByText } = renderWithTheme(
<AlertDetailCriteria alertDetails={alertDetails} />
<AlertDetailCriteria alertDetails={alertDetails} serviceType="linode" />
);
const { rules } = alertDetails.rule_criteria;
expect(getAllByText('Metric Threshold:').length).toBe(rules.length);
Expand All @@ -53,7 +53,7 @@ describe('AlertDetailCriteria component tests', () => {
},
});
const { getByText, queryByText } = renderWithTheme(
<AlertDetailCriteria alertDetails={alert} />
<AlertDetailCriteria alertDetails={alert} serviceType="linode" />
);
expect(getByText('Criteria')).toBeInTheDocument(); // empty criteria should be there
expect(queryByText('Metric Threshold:')).not.toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import { StyledAlertChip, StyledAlertTypography } from './AlertDetail';
import { DisplayAlertDetailChips } from './DisplayAlertDetailChips';
import { RenderAlertMetricsAndDimensions } from './RenderAlertsMetricsAndDimensions';

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

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

export const AlertDetailCriteria = React.memo((props: CriteriaProps) => {
const { alertDetails } = props;
const { alertDetails, serviceType } = props;
const {
evaluation_period_seconds: evaluationPeriod,
polling_interval_seconds: pollingIntervalSeconds,
Expand Down Expand Up @@ -85,7 +89,10 @@ export const AlertDetailCriteria = React.memo((props: CriteriaProps) => {
alignItems: 'center',
}}
>
<RenderAlertMetricsAndDimensions ruleCriteria={ruleCriteria} />
<RenderAlertMetricsAndDimensions
ruleCriteria={ruleCriteria}
serviceType={serviceType}
/>
<DisplayAlertDetailChips // label chip for polling interval
label="Polling Interval"
mergeChips
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Divider } from '@linode/ui';
import { capitalize } from '@linode/utilities';
import { GridLegacy } from '@mui/material';
import React from 'react';

import NullComponent from 'src/components/NullComponent';
import { transformDimensionValue } from 'src/features/CloudPulse/Alerts/Utils/utils';

import {
aggregationTypeMap,
Expand All @@ -12,7 +12,10 @@ import {
} from '../constants';
import { DisplayAlertDetailChips } from './DisplayAlertDetailChips';

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

interface AlertMetricAndDimensionsProp {
/*
Expand All @@ -21,11 +24,15 @@ interface AlertMetricAndDimensionsProp {
ruleCriteria: {
rules: AlertDefinitionMetricCriteria[];
};
/**
* The service type of the alert for which the criteria needs to be displayed
*/
serviceType: CloudPulseServiceType;
}

export const RenderAlertMetricsAndDimensions = React.memo(
(props: AlertMetricAndDimensionsProp) => {
const { ruleCriteria } = props;
const { ruleCriteria, serviceType } = props;

if (!ruleCriteria.rules?.length) {
return <NullComponent />;
Expand Down Expand Up @@ -66,12 +73,17 @@ export const RenderAlertMetricsAndDimensions = React.memo(
values={dimensionFilters.map(
({
label: dimensionLabel,
dimension_label: dimensionFilterKey,
operator: dimensionOperator,
value,
}) => [
dimensionLabel,
dimensionOperatorTypeMap[dimensionOperator],
capitalize(value),
transformDimensionValue(
serviceType,
dimensionFilterKey,
value
),
]
)}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Autocomplete, Box, TextField } from '@linode/ui';
import { capitalize } from '@linode/utilities';
import { GridLegacy } from '@mui/material';
import React from 'react';
import { Controller, useFormContext, useWatch } from 'react-hook-form';
import type { FieldPathByValue } from 'react-hook-form';

import { transformDimensionValue } from 'src/features/CloudPulse/Alerts/Utils/utils';

import {
dimensionOperatorOptions,
HELPER_TEXT_MAP,
Expand Down Expand Up @@ -79,6 +80,8 @@ export const DimensionFilterField = (props: DimensionFilterFieldProps) => {
});

const dimensionValueWatcher = useWatch({ control, name: `${name}.value` });
const serviceTypeWatcher = useWatch({ control, name: 'serviceType' });

const selectedDimension =
dimensionOptions && dimensionFieldWatcher
? (dimensionOptions.find(
Expand All @@ -89,7 +92,11 @@ export const DimensionFilterField = (props: DimensionFilterFieldProps) => {
const valueOptions = () => {
if (selectedDimension !== null && selectedDimension.values) {
return selectedDimension.values.map((val) => ({
label: capitalize(val),
label: transformDimensionValue(
serviceTypeWatcher,
selectedDimension.dimension_label,
val
),
value: val,
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getSchemaWithEntityIdValidation,
getServiceTypeLabel,
handleMultipleError,
transformDimensionValue,
} from './utils';

import type { AlertValidationSchemaProps } from './utils';
Expand Down Expand Up @@ -471,3 +472,25 @@ describe('alertsFromEnabledServices', () => {
expect(result).toHaveLength(0);
});
});

describe('transformDimensionValue', () => {
it('should apply service-specific transformations', () => {
expect(transformDimensionValue('linode', 'type', '')).toBe('');
expect(transformDimensionValue('linode', 'operation', 'read')).toBe('Read');
expect(transformDimensionValue('dbaas', 'node_type', 'primary')).toBe(
'Primary'
);
expect(
transformDimensionValue('firewall', 'interface_type', 'public')
).toBe('PUBLIC');
expect(transformDimensionValue('nodebalancer', 'protocol', 'http')).toBe(
'HTTP'
);
});

it('should fallback to capitalize for unknown dimensions', () => {
expect(
transformDimensionValue('linode', 'unknown_dimension', 'test_value')
).toBe('Test_value');
});
});
23 changes: 23 additions & 0 deletions packages/manager/src/features/CloudPulse/Alerts/Utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
import type { FieldPath, FieldValues, UseFormSetError } from 'react-hook-form';
import { array, object, string } from 'yup';

import {
DIMENSION_TRANSFORM_CONFIG,
TRANSFORMS,
} from '../../shared/DimensionTransform';
import { aggregationTypeMap, metricOperatorTypeMap } from '../constants';

import type { CloudPulseResources } from '../../shared/CloudPulseResourcesSelect';
Expand Down Expand Up @@ -595,3 +599,22 @@ export const alertsFromEnabledServices = (
(alert) => aclpServices?.[alert.service_type]?.alerts?.enabled ?? false
);
};

/**
* Transform a dimension value using the appropriate transform function
* @param serviceType - The cloud pulse service type
* @param dimensionLabel - The dimension label
* @param value - The value to transform
* @returns Transformed value
*/
export const transformDimensionValue = (
serviceType: CloudPulseServiceType | null,
dimensionLabel: string,
value: string
): string => {
return (
(
serviceType && DIMENSION_TRANSFORM_CONFIG[serviceType]?.[dimensionLabel]
)?.(value) ?? TRANSFORMS.capitalize(value)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
mapResourceIdToName,
} from './CloudPulseWidgetUtils';

import type {
DimensionNameProperties,
LabelNameOptionsProps,
} from './CloudPulseWidgetUtils';
import type { CloudPulseMetricsResponse } from '@linode/api-v4';
import type { MetricsDisplayRow } from 'src/components/LineGraph/MetricsDisplay';

Expand Down Expand Up @@ -60,7 +64,7 @@ describe('generateMaxUnit method', () => {
});

describe('getLabelName method', () => {
const baseProps = {
const baseProps: LabelNameOptionsProps = {
label: 'CPU Usage',
metric: { entity_id: '123' },
resources: [{ id: '123', label: 'linode-1' }],
Expand Down Expand Up @@ -116,6 +120,7 @@ it('test generateGraphData with metrics data', () => {
resources: [{ id: '1', label: 'linode-1' }],
status: 'success',
unit: '%',
serviceType: 'linode',
});

expect(result.areas[0].dataKey).toBe('linode-1');
Expand All @@ -139,7 +144,8 @@ it('test generateGraphData with metrics data', () => {
});

describe('getDimensionName method', () => {
const baseProps = {
const baseProps: DimensionNameProperties = {
serviceType: 'linode',
metric: { entity_id: '123' },
resources: [{ id: '123', label: 'linode-1' }],
};
Expand Down Expand Up @@ -203,6 +209,29 @@ describe('getDimensionName method', () => {
const result = getDimensionName(props);
expect(result).toBe('123');
});

it('returns the transformed dimension value according to the service type', () => {
const props = {
...baseProps,
metric: {
entity_id: '123',
metric_name: 'test',
node_id: 'primary-1',
operation: 'read',
},
};
const result = getDimensionName(props);
expect(result).toBe('linode-1 | test | primary-1 | Read');
});

it('returns the actual value if dimension name is not found in the transform config', () => {
const props = {
...baseProps,
metric: { entity_id: '123', metric_name: 'test', node_id: 'primary-1' },
};
const result = getDimensionName(props);
expect(result).toBe('linode-1 | test | primary-1');
});
});

it('test mapResourceIdToName method', () => {
Expand Down
Loading