Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4cd5b39
Add beta ACLP alerts to Alerts tab on Linode details page
pmakode-akamai May 16, 2025
99cfdbd
Added changeset: Add beta ACLP contextual alerts to the Alerts tab on…
pmakode-akamai May 16, 2025
e896c4e
Rename legacy alerts heading to `Default Alerts`
pmakode-akamai May 19, 2025
f09962b
Merge branch 'develop' into M3-9989-add-beta-aclp-alerts-on-linode-de…
pmakode-akamai May 19, 2025
4c6d60a
Save progress...
pmakode-akamai May 19, 2025
c61d3ce
Fix aclp alerts validation schema
pmakode-akamai May 19, 2025
4024fe6
Support initial values for legacy alerts in createLinode flow
pmakode-akamai May 19, 2025
238bccb
Merge branch 'develop' into poc/M3-9996
pmakode-akamai May 20, 2025
a2c6464
Some clean up...
pmakode-akamai May 20, 2025
1d398af
Fix alerts validation schema
pmakode-akamai May 20, 2025
0f9dde1
Merge branch 'develop' into poc/M3-9996
pmakode-akamai May 20, 2025
26724f0
Merge branch 'develop' into poc/M3-9996
pmakode-akamai May 20, 2025
6c9ff39
Merge branch 'develop' into poc/M3-9996
pmakode-akamai May 21, 2025
258fc1c
Keep AlertsReusableComponent decoupled
pmakode-akamai May 21, 2025
cda86a8
Merge branch 'develop' into poc/M3-9996
pmakode-akamai Jun 3, 2025
f316d4a
Merge edit & create logic in action table (#1)
ankita-akamai Jun 3, 2025
12d116c
Add Additional Options section to Create Linode flow
pmakode-akamai Jun 3, 2025
656131a
Added changeset: Assigning alert definitions to a Linode during creation
pmakode-akamai Jun 3, 2025
f517604
Added changeset: Beta ACLP alerts property to the `CreateLinodeReques…
pmakode-akamai Jun 3, 2025
b38b95f
Clean up: types
pmakode-akamai Jun 3, 2025
7fd1131
Fix: Use relative type import in apiv4
pmakode-akamai Jun 3, 2025
90123d7
Resolve merge conflict and get latest from develop
pmakode-akamai Jun 10, 2025
43f6381
Update imports after merging the latest from develop
pmakode-akamai Jun 10, 2025
c112f1d
Resolve merge conflicts and get latest from develop
pmakode-akamai Jun 11, 2025
0d80d6d
Resolve merge conficts and document aclp alerts payload type
pmakode-akamai Jun 13, 2025
8950229
Update feature flag references
pmakode-akamai Jun 13, 2025
2b95ba8
Merge branch 'develop' into poc/M3-9996
pmakode-akamai Jun 16, 2025
c5c4196
Destruture linode create payload options
pmakode-akamai Jun 16, 2025
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
10 changes: 10 additions & 0 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
UpgradeToLinodeInterfaceSchema,
} from '@linode/validation';
import type { MaintenancePolicyId } from 'src/account';
import type { Alert } from 'src/cloudpulse';
import type { VPCIP } from 'src/vpcs';
import type { InferType } from 'yup';

Expand Down Expand Up @@ -162,6 +163,11 @@ export interface LinodeIPsResponseIPV6 {
slaac: IPAddress;
}

export interface LinodeAclpAlertsPayload {
system: Alert['id'][];
user: Alert['id'][];
}

export type LinodeStatus =
| 'booting'
| 'cloning'
Expand Down Expand Up @@ -537,6 +543,10 @@ export interface CreateLinodePlacementGroupPayload {
}

export interface CreateLinodeRequest {
/**
* Beta Aclp alerts
*/
alerts?: LinodeAclpAlertsPayload | null;
/**
* A list of public SSH keys that will be automatically appended to the root user’s
* `~/.ssh/authorized_keys`file when deploying from an Image.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add beta ACLP contextual alerts to the Alerts tab on the Linode details page ([#12236](https://github.com/linode/manager/pull/12236))
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Box } from '@linode/ui';
import { Grid, TableBody, TableHead } from '@mui/material';
import React from 'react';
import { useController, useFormContext } from 'react-hook-form';

import OrderBy from 'src/components/OrderBy';
import Paginate from 'src/components/Paginate';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
import { Table } from 'src/components/Table';
import { TableCell } from 'src/components/TableCell';
import { TableContentWrapper } from 'src/components/TableContentWrapper/TableContentWrapper';
import { TableRow } from 'src/components/TableRow';
import { TableSortCell } from 'src/components/TableSortCell';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';

import { AlertInformationActionRow } from './AlertInformationActionRow';

import type { Alert, APIError, LinodeAclpAlertsPayload } from '@linode/api-v4';
import type { LinodeCreateFormValues } from 'src/features/Linodes/LinodeCreate/utilities';

export interface AlertInfoActionTableCreateFlowProps {
/**
* List of alerts to be displayed
*/
alerts: Alert[];

/**
* List of table headers for each column
*/
columns: TableColumnHeader[];

/**
* Error received from API
*/
error?: APIError[] | null;

/**
* Column name by which columns will be ordered by default
*/
orderByColumn: string;
}

export interface TableColumnHeader {
/**
* Name of the column to be displayed
*/
columnName: string;

/**
* Corresponding key name in the alert object for which this column is
*/
label: string;
}

export const AlertInfoActionTableCreateFlow = (
props: AlertInfoActionTableCreateFlowProps
) => {
const { alerts, columns, error, orderByColumn } = props;

const _error = error
? getAPIErrorOrDefault(error, 'Error while fetching the alerts')
: undefined;

const generateInitialAlertsPayload = (alerts: Alert[]) => {
const initialPayload: LinodeAclpAlertsPayload = { system: [], user: [] };
alerts.forEach((alert) => {
if (alert.type === 'system') {
initialPayload.system.push(alert.id);
} else if (alert.type === 'user') {
initialPayload.user.push(alert.id);
}
});
return initialPayload;
};

const initialPayload = generateInitialAlertsPayload(alerts);

const { control } = useFormContext<LinodeCreateFormValues>();
const { field } = useController({
control,
name: 'alerts',
defaultValue: initialPayload,
});

const handleToggleCreateFlow = (alert: Alert) => {
const alerts = field.value;
const currentAlertIds = alerts?.[alert.type] || [];
const updatedAlerts = { ...alerts };

if (currentAlertIds?.includes(alert.id)) {
// Disable the alert (remove from the list)
updatedAlerts[alert.type] = currentAlertIds.filter(
(id) => id !== alert.id
);
} else {
// Enable the alert (add to the list)
updatedAlerts[alert.type] = [...currentAlertIds, alert.id];
}

field.onChange(updatedAlerts);
};

return (
<OrderBy data={alerts} order="asc" orderBy={orderByColumn}>
{({ data: orderedData, handleOrderChange, order, orderBy }) => (
<Paginate data={orderedData}>
{({
count,
data: paginatedAndOrderedAlerts,
handlePageChange,
handlePageSizeChange,
page,
pageSize,
}) => (
<Box>
<Grid>
<Table
colCount={columns.length + 1}
data-testid="alert-table"
size="small"
>
<TableHead>
<TableRow>
<TableCell actionCell />
{columns.map(({ columnName, label }) => {
return (
<TableSortCell
active={orderBy === label}
data-qa-header={label}
data-qa-sorting={label}
direction={order}
handleClick={handleOrderChange}
key={label}
label={label}
>
{columnName}
</TableSortCell>
);
})}
</TableRow>
</TableHead>
<TableBody>
<TableContentWrapper
error={_error}
length={paginatedAndOrderedAlerts.length}
loading={false}
/>
{paginatedAndOrderedAlerts?.map((alert) => (
<AlertInformationActionRow
alert={alert}
handleToggle={handleToggleCreateFlow}
key={alert.id}
status={field.value?.[alert.type].includes(alert.id)}
/>
))}
</TableBody>
</Table>
</Grid>
<PaginationFooter
count={count}
eventCategory="Alert Definitions Table"
handlePageChange={handlePageChange}
handleSizeChange={handlePageSizeChange}
page={page}
pageSize={pageSize}
/>
</Box>
)}
</Paginate>
)}
</OrderBy>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
convertAlertsToTypeSet,
filterAlertsByStatusAndType,
} from '../Utils/utils';
import { AlertInfoActionTableCreateFlow } from './AlertInfoActionTableCreateFlow';
import { AlertInformationActionTable } from './AlertInformationActionTable';

import type { AlertDefinitionType } from '@linode/api-v4';
Expand Down Expand Up @@ -118,14 +119,23 @@ export const AlertReusableComponent = (props: AlertReusableComponentProps) => {
/>
</Box>

<AlertInformationActionTable
alerts={filteredAlerts}
columns={AlertContextualViewTableHeaderMap}
entityId={entityId}
entityName={entityName}
error={error}
orderByColumn="Alert Name"
/>
{entityId ? (
<AlertInformationActionTable
alerts={filteredAlerts}
columns={AlertContextualViewTableHeaderMap}
entityId={entityId}
entityName={entityName}
error={error}
orderByColumn="Alert Name"
/>
) : (
<AlertInfoActionTableCreateFlow
alerts={filteredAlerts}
columns={AlertContextualViewTableHeaderMap}
error={error}
orderByColumn="Alert Name"
/>
)}
</Stack>
</Stack>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,9 @@ export const Actions = () => {
<ApiAwarenessModal
isOpen={isAPIAwarenessModalOpen}
onClose={() => setIsAPIAwarenessModalOpen(false)}
payLoad={getLinodeCreatePayload(
structuredClone(getValues()),
isLinodeInterfacesEnabled
)}
payLoad={getLinodeCreatePayload(structuredClone(getValues()), {
isShowingNewNetworkingUI: isLinodeInterfacesEnabled,
})}
/>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { usePreferences } from '@linode/queries';
import { Box } from '@linode/ui';
import * as React from 'react';

import { AlertReusableComponent } from 'src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent';
import { useFlags } from 'src/hooks/useFlags';

import { AclpPreferenceToggle } from '../../LinodesDetail/AclpPreferenceToggle';
import { LinodeSettingsAlertsPanel } from '../../LinodesDetail/LinodeSettings/LinodeSettingsAlertsPanel';

export const Alerts = () => {
const flags = useFlags();
const { data: isAclpAlertsPreferenceBeta } = usePreferences(
(preferences) => preferences?.isAclpAlertsBeta
);

return (
<Box>
{flags.aclpIntegration && <AclpPreferenceToggle type="alerts" />}
{flags.aclpIntegration && isAclpAlertsPreferenceBeta ? (
<AlertReusableComponent
entityId={''}
entityName={''}
serviceType="linode"
/>
) : (
<LinodeSettingsAlertsPanel isCreateFlow />
)}
</Box>
);
};

export default Alerts;
16 changes: 15 additions & 1 deletion packages/manager/src/features/Linodes/LinodeCreate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
useCloneLinodeMutation,
useCreateLinodeMutation,
useMutateAccountAgreements,
usePreferences,
useProfile,
} from '@linode/queries';
import { CircleProgress, Notice, Stack } from '@linode/ui';
Expand All @@ -23,6 +24,7 @@ import { TabList } from 'src/components/Tabs/TabList';
import { TabPanels } from 'src/components/Tabs/TabPanels';
import { Tabs } from 'src/components/Tabs/Tabs';
import { getRestrictedResourceText } from 'src/features/Account/utils';
import { useFlags } from 'src/hooks/useFlags';
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
import { useSecureVMNoticesEnabled } from 'src/hooks/useSecureVMNoticesEnabled';
import {
Expand All @@ -36,6 +38,7 @@ import {

import { Actions } from './Actions';
import { Addons } from './Addons/Addons';
import Alerts from './Alerts/Alerts';
import { Details } from './Details/Details';
import { LinodeCreateError } from './Error';
import { EUAgreement } from './EUAgreement';
Expand Down Expand Up @@ -79,6 +82,12 @@ export const LinodeCreate = () => {
const { data: profile } = useProfile();
const { isLinodeCloneFirewallEnabled } = useIsLinodeCloneFirewallEnabled();

const { data: isAclpAlertsPreferenceBeta } = usePreferences(
(preferences) => preferences?.isAclpAlertsBeta
);

const flags = useFlags();

const queryClient = useQueryClient();

const form = useForm<LinodeCreateFormValues, LinodeCreateFormContext>({
Expand Down Expand Up @@ -123,7 +132,11 @@ export const LinodeCreate = () => {
};

const onSubmit: SubmitHandler<LinodeCreateFormValues> = async (values) => {
const payload = getLinodeCreatePayload(values, isLinodeInterfacesEnabled);
const payload = getLinodeCreatePayload(values, {
isShowingNewNetworkingUI: isLinodeInterfacesEnabled,
isAclpIntegration: flags.aclpIntegration,
isAclpAlertsPreferenceBeta,
});

try {
const linode =
Expand Down Expand Up @@ -262,6 +275,7 @@ export const LinodeCreate = () => {
)}
<UserData />
{isLinodeInterfacesEnabled && <Networking />}
<Alerts />
<Addons />
<EUAgreement />
<Summary />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,19 @@ describe('getLinodeCreatePayload', () => {
it('should return a basic payload', () => {
const values = createLinodeRequestFactory.build() as LinodeCreateFormValues;

expect(getLinodeCreatePayload(values, false)).toEqual(values);
expect(
getLinodeCreatePayload(values, { isShowingNewNetworkingUI: false })
).toEqual(values);
});

it('should base64 encode metadata', () => {
const values = createLinodeRequestFactory.build({
metadata: { user_data: userData },
}) as LinodeCreateFormValues;

expect(getLinodeCreatePayload(values, false)).toEqual({
expect(
getLinodeCreatePayload(values, { isShowingNewNetworkingUI: false })
).toEqual({
...values,
metadata: { user_data: base64UserData },
});
Expand All @@ -73,7 +77,9 @@ describe('getLinodeCreatePayload', () => {
placement_group: {},
}) as LinodeCreateFormValues;

expect(getLinodeCreatePayload(values, false)).toEqual({
expect(
getLinodeCreatePayload(values, { isShowingNewNetworkingUI: false })
).toEqual({
...values,
placement_group: undefined,
});
Expand Down
Loading