Skip to content

Commit 6a4e3ba

Browse files
poc: [M3-9996, M3-10048] - Assigning alert definitions (beta) to a Linode during creation (#12248)
* Add beta ACLP alerts to Alerts tab on Linode details page * Added changeset: Add beta ACLP contextual alerts to the Alerts tab on the Linode details page * Rename legacy alerts heading to `Default Alerts` * Save progress... * Fix aclp alerts validation schema * Support initial values for legacy alerts in createLinode flow * Some clean up... * Fix alerts validation schema * Keep AlertsReusableComponent decoupled * Merge edit & create logic in action table (#1) * Merge edit & create logic in action table * make components flexible * update handler func * type correction * optimize state updates * remove unused func * update type * refactor & cleanup * Add Additional Options section to Create Linode flow * Added changeset: Assigning alert definitions to a Linode during creation * Added changeset: Beta ACLP alerts property to the `CreateLinodeRequest` type * Clean up: types * Fix: Use relative type import in apiv4 * Update imports after merging the latest from develop * Update feature flag references * Destruture linode create payload options --------- Co-authored-by: Ankita <[email protected]>
1 parent dec7372 commit 6a4e3ba

File tree

12 files changed

+231
-29
lines changed

12 files changed

+231
-29
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/api-v4": Upcoming Features
3+
---
4+
5+
Beta ACLP alerts property to the `CreateLinodeRequest` type ([#12248](https://github.com/linode/manager/pull/12248))

packages/api-v4/src/cloudpulse/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,23 @@ export interface DeleteAlertPayload {
349349
alertId: number;
350350
serviceType: string;
351351
}
352+
353+
/**
354+
* Represents the payload for CloudPulse alerts, included only when the ACLP beta mode is enabled.
355+
*
356+
* In Beta mode, the `alerts` object contains enabled system and user alert IDs.
357+
* - Legacy mode: `alerts` is not included (read-only mode).
358+
* - Beta mode: `alerts` is passed and editable.
359+
*/
360+
export interface CloudPulseAlertsPayload {
361+
/**
362+
* Array of enabled system alert IDs in ACLP (Beta) mode.
363+
* Only included in Beta mode.
364+
*/
365+
system: number[];
366+
/**
367+
* Array of enabled user alert IDs in ACLP (Beta) mode.
368+
* Only included in Beta mode.
369+
*/
370+
user: number[];
371+
}

packages/api-v4/src/linodes/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CloudPulseAlertsPayload } from '../cloudpulse/types';
12
import type { IPAddress, IPRange } from '../networking/types';
23
import type { LinodePlacementGroupPayload } from '../placement-groups/types';
34
import type { Region, RegionSite } from '../regions';
@@ -538,6 +539,10 @@ export interface CreateLinodePlacementGroupPayload {
538539
}
539540

540541
export interface CreateLinodeRequest {
542+
/**
543+
* Beta Aclp alerts
544+
*/
545+
alerts?: CloudPulseAlertsPayload | null;
541546
/**
542547
* A list of public SSH keys that will be automatically appended to the root user’s
543548
* `~/.ssh/authorized_keys`file when deploying from an Image.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
Assigning alert definitions to a Linode during creation ([#12248](https://github.com/linode/manager/pull/12248))

packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.tsx

Lines changed: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
2121
import { AlertConfirmationDialog } from '../AlertsLanding/AlertConfirmationDialog';
2222
import { AlertInformationActionRow } from './AlertInformationActionRow';
2323

24-
import type { Alert, APIError, EntityAlertUpdatePayload } from '@linode/api-v4';
24+
import type {
25+
Alert,
26+
APIError,
27+
CloudPulseAlertsPayload,
28+
EntityAlertUpdatePayload,
29+
} from '@linode/api-v4';
2530

2631
export interface AlertInformationActionTableProps {
2732
/**
@@ -36,19 +41,28 @@ export interface AlertInformationActionTableProps {
3641

3742
/**
3843
* Id of the selected entity
44+
* Only use in edit flow
3945
*/
40-
entityId: string;
46+
entityId?: string;
4147

4248
/**
4349
* Name of the selected entity
50+
* Only use in edit flow
4451
*/
45-
entityName: string;
52+
entityName?: string;
4653

4754
/**
4855
* Error received from API
4956
*/
5057
error?: APIError[] | null;
5158

59+
/**
60+
* Called when an alert is toggled on or off.
61+
* Only use in create flow.
62+
* @param payload enabled alerts ids
63+
*/
64+
onToggleAlert?: (payload: CloudPulseAlertsPayload) => void;
65+
5266
/**
5367
* Column name by which columns will be ordered by default
5468
*/
@@ -67,10 +81,37 @@ export interface TableColumnHeader {
6781
label: string;
6882
}
6983

84+
export interface AlertRowPropsOptions {
85+
/**
86+
* Enabled alerts payload
87+
*/
88+
enabledAlerts: CloudPulseAlertsPayload;
89+
90+
/**
91+
* Id of the entity
92+
* Only use in edit flow.
93+
*/
94+
entityId?: string;
95+
96+
/**
97+
* Callback function to handle alert toggle
98+
* Only use in create flow.
99+
*/
100+
onToggleAlert?: (payload: CloudPulseAlertsPayload) => void;
101+
}
102+
70103
export const AlertInformationActionTable = (
71104
props: AlertInformationActionTableProps
72105
) => {
73-
const { alerts, columns, entityId, entityName, error, orderByColumn } = props;
106+
const {
107+
alerts,
108+
columns,
109+
entityId,
110+
entityName,
111+
error,
112+
orderByColumn,
113+
onToggleAlert,
114+
} = props;
74115

75116
const _error = error
76117
? getAPIErrorOrDefault(error, 'Error while fetching the alerts')
@@ -79,16 +120,43 @@ export const AlertInformationActionTable = (
79120
const [selectedAlert, setSelectedAlert] = React.useState<Alert>({} as Alert);
80121
const [isDialogOpen, setIsDialogOpen] = React.useState<boolean>(false);
81122
const [isLoading, setIsLoading] = React.useState<boolean>(false);
123+
const [enabledAlerts, setEnabledAlerts] =
124+
React.useState<CloudPulseAlertsPayload>({
125+
system: [],
126+
user: [],
127+
});
82128

83129
const { mutateAsync: addEntity } = useAddEntityToAlert();
84130

85131
const { mutateAsync: removeEntity } = useRemoveEntityFromAlert();
86132

133+
const getAlertRowProps = (alert: Alert, options: AlertRowPropsOptions) => {
134+
const { entityId, enabledAlerts, onToggleAlert } = options;
135+
136+
// Ensure that at least one of entityId or onToggleAlert is provided
137+
if (!(entityId || onToggleAlert)) {
138+
return null;
139+
}
140+
141+
const isEditMode = !!entityId;
142+
143+
const handleToggle = isEditMode
144+
? handleToggleEditFlow
145+
: handleToggleCreateFlow;
146+
const status = isEditMode
147+
? alert.entity_ids.includes(entityId)
148+
: enabledAlerts[alert.type].includes(alert.id);
149+
150+
return { handleToggle, status };
151+
};
152+
87153
const handleCancel = () => {
88154
setIsDialogOpen(false);
89155
};
90156
const handleConfirm = React.useCallback(
91157
(alert: Alert, currentStatus: boolean) => {
158+
if (entityId === undefined) return;
159+
92160
const payload: EntityAlertUpdatePayload = {
93161
alert,
94162
entityId,
@@ -117,12 +185,31 @@ export const AlertInformationActionTable = (
117185
},
118186
[addEntity, enqueueSnackbar, entityId, entityName, removeEntity]
119187
);
120-
const handleToggle = (alert: Alert) => {
188+
189+
const handleToggleEditFlow = (alert: Alert) => {
121190
setIsDialogOpen(true);
122191
setSelectedAlert(alert);
123192
};
124193

125-
const isEnabled = selectedAlert.entity_ids?.includes(entityId) ?? false;
194+
const handleToggleCreateFlow = (alert: Alert) => {
195+
if (!onToggleAlert) return;
196+
197+
setEnabledAlerts((prev: CloudPulseAlertsPayload) => {
198+
const newPayload = { ...prev };
199+
const index = newPayload[alert.type].indexOf(alert.id);
200+
// If the alert is already in the payload, remove it, otherwise add it
201+
if (index !== -1) {
202+
newPayload[alert.type].splice(index, 1);
203+
} else {
204+
newPayload[alert.type].push(alert.id);
205+
}
206+
207+
onToggleAlert(newPayload);
208+
return newPayload;
209+
});
210+
};
211+
212+
const isEnabled = selectedAlert.entity_ids?.includes(entityId ?? '') ?? false;
126213

127214
return (
128215
<>
@@ -170,14 +257,24 @@ export const AlertInformationActionTable = (
170257
length={paginatedAndOrderedAlerts.length}
171258
loading={false}
172259
/>
173-
{paginatedAndOrderedAlerts?.map((alert) => (
174-
<AlertInformationActionRow
175-
alert={alert}
176-
handleToggle={handleToggle}
177-
key={alert.id}
178-
status={alert.entity_ids.includes(entityId)}
179-
/>
180-
))}
260+
{paginatedAndOrderedAlerts?.map((alert) => {
261+
const rowProps = getAlertRowProps(alert, {
262+
enabledAlerts,
263+
entityId,
264+
onToggleAlert,
265+
});
266+
267+
if (!rowProps) return null;
268+
269+
return (
270+
<AlertInformationActionRow
271+
alert={alert}
272+
handleToggle={rowProps.handleToggle}
273+
key={alert.id}
274+
status={rowProps.status}
275+
/>
276+
);
277+
})}
181278
</TableBody>
182279
</Table>
183280
</Grid>

packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,28 @@ import {
2323
} from '../Utils/utils';
2424
import { AlertInformationActionTable } from './AlertInformationActionTable';
2525

26-
import type { AlertDefinitionType } from '@linode/api-v4';
26+
import type {
27+
AlertDefinitionType,
28+
CloudPulseAlertsPayload,
29+
} from '@linode/api-v4';
2730

2831
interface AlertReusableComponentProps {
2932
/**
3033
* Id for the selected entity
3134
*/
32-
entityId: string;
35+
entityId?: string;
3336

3437
/**
3538
* Name of the selected entity
3639
*/
37-
entityName: string;
40+
entityName?: string;
41+
42+
/**
43+
* Called when an alert is toggled on or off.
44+
* Only use in create flow.
45+
* @param payload enabled alerts ids
46+
*/
47+
onToggleAlert?: (payload: CloudPulseAlertsPayload) => void;
3848

3949
/**
4050
* Service type of selected entity
@@ -43,7 +53,7 @@ interface AlertReusableComponentProps {
4353
}
4454

4555
export const AlertReusableComponent = (props: AlertReusableComponentProps) => {
46-
const { entityId, entityName, serviceType } = props;
56+
const { entityId, entityName, onToggleAlert, serviceType } = props;
4757
const {
4858
data: alerts,
4959
error,
@@ -70,6 +80,7 @@ export const AlertReusableComponent = (props: AlertReusableComponentProps) => {
7080
if (isLoading) {
7181
return <CircleProgress />;
7282
}
83+
7384
return (
7485
<Paper>
7586
<Stack gap={3}>
@@ -125,6 +136,7 @@ export const AlertReusableComponent = (props: AlertReusableComponentProps) => {
125136
entityId={entityId}
126137
entityName={entityName}
127138
error={error}
139+
onToggleAlert={onToggleAlert}
128140
orderByColumn="Alert Name"
129141
/>
130142
</Stack>

packages/manager/src/features/Linodes/LinodeCreate/Actions.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,9 @@ export const Actions = () => {
7474
<ApiAwarenessModal
7575
isOpen={isAPIAwarenessModalOpen}
7676
onClose={() => setIsAPIAwarenessModalOpen(false)}
77-
payLoad={getLinodeCreatePayload(
78-
structuredClone(getValues()),
79-
isLinodeInterfacesEnabled
80-
)}
77+
payLoad={getLinodeCreatePayload(structuredClone(getValues()), {
78+
isShowingNewNetworkingUI: isLinodeInterfacesEnabled,
79+
})}
8180
/>
8281
</Box>
8382
);

packages/manager/src/features/Linodes/LinodeCreate/AdditionalOptions/Alerts/Alerts.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
import { usePreferences } from '@linode/queries';
2-
import { Accordion, BetaChip, Notice } from '@linode/ui';
2+
import { Accordion, BetaChip } from '@linode/ui';
33
import * as React from 'react';
4+
import { useController, useFormContext } from 'react-hook-form';
45

6+
import { AlertReusableComponent } from 'src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent';
57
import { AclpPreferenceToggle } from 'src/features/Linodes/AclpPreferenceToggle';
68
import { LinodeSettingsAlertsPanel } from 'src/features/Linodes/LinodesDetail/LinodeSettings/LinodeSettingsAlertsPanel';
79
import { useFlags } from 'src/hooks/useFlags';
810

11+
import type { LinodeCreateFormValues } from '../../utilities';
12+
import type { CloudPulseAlertsPayload } from '@linode/api-v4';
13+
914
export const Alerts = () => {
1015
const flags = useFlags();
1116
const { data: isAclpAlertsPreferenceBeta } = usePreferences(
1217
(preferences) => preferences?.isAclpAlertsBeta
1318
);
1419

20+
const { control } = useFormContext<LinodeCreateFormValues>();
21+
const { field } = useController({
22+
control,
23+
name: 'alerts',
24+
defaultValue: { system: [], user: [] },
25+
});
26+
27+
const handleToggleAlert = (updatedAlerts: CloudPulseAlertsPayload) => {
28+
field.onChange(updatedAlerts);
29+
};
30+
1531
return (
1632
<Accordion
1733
detailProps={{ sx: { p: 0 } }}
@@ -26,7 +42,10 @@ export const Alerts = () => {
2642
>
2743
{flags.aclpBetaServices?.alerts && <AclpPreferenceToggle type="alerts" />}
2844
{flags.aclpBetaServices?.alerts && isAclpAlertsPreferenceBeta ? (
29-
<Notice variant="info">ACLP Alerts coming soon...</Notice>
45+
<AlertReusableComponent
46+
onToggleAlert={handleToggleAlert}
47+
serviceType="linode"
48+
/>
3049
) : (
3150
<LinodeSettingsAlertsPanel />
3251
)}

0 commit comments

Comments
 (0)