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
6 changes: 6 additions & 0 deletions packages/manager/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [2025-10-02] - v1.151.2

### Fixed:

- DBaaS - Database Create Subnet field should display backend error messages

## [2025-09-25] - v1.151.1

### Added:
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "linode-manager",
"author": "Linode",
"description": "The Linode Manager website",
"version": "1.151.1",
"version": "1.151.2",
"private": true,
"type": "module",
"bugs": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ export const DatabaseCreate = () => {
if (ipErrors) {
setIPErrorsFromAPI(ipErrors);
}
handleAPIErrors(errors, setFieldError, setCreateError);
const parentFields = ['private_network']; // List of parent fields that need the full key from the errors response
handleAPIErrors(errors, setFieldError, setCreateError, parentFields);
}

setSubmitting(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BetaChip,
Box,
Checkbox,
FormHelperText,
Notice,
TooltipIcon,
Typography,
Expand Down Expand Up @@ -209,6 +210,16 @@ export const DatabaseVPCSelector = (props: DatabaseVPCSelectorProps) => {
'Adds a public endpoint to the database in addition to the private VPC endpoint.'
}
/>
{errors?.private_network?.public_access && (
<FormHelperText
className="error-for-scroll"
error
role="alert"
sx={{ marginTop: 0 }}
>
{errors?.private_network?.public_access}
</FormHelperText>
)}
</Box>
</>
) : (
Expand Down
34 changes: 32 additions & 2 deletions packages/manager/src/utilities/formikErrorUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ const setFieldError = vi.fn();
const setError = vi.fn();

describe('handleAPIErrors', () => {
it('should handle api error with a field', () => {
it('should handle API error for a regular field', () => {
const errorWithFlatField = [{ field: 'label', reason: 'Invalid label' }];
handleAPIErrors(errorWithFlatField, setFieldError, setError);
expect(setFieldError).toHaveBeenCalledWith(
'label',
errorWithFlatField[0].reason
);
expect(setError).not.toHaveBeenCalled();
});

it('should handle API error for a parent field when parentFields is not provided', () => {
handleAPIErrors(errorWithField, setFieldError, setError);
expect(setFieldError).toHaveBeenCalledWith(
'card_number',
Expand All @@ -26,7 +36,27 @@ describe('handleAPIErrors', () => {
expect(setError).not.toHaveBeenCalled();
});

it('should handle a general api error', () => {
it('should handle API error for a parent field when parentFields is provided', () => {
const errorWithParentField = [
{ field: 'private_network.subnet_id', reason: 'Invalid subnet ID' },
];

const parentFields = ['private_network']; // Provide parentFields so full field key is used

handleAPIErrors(
errorWithParentField,
setFieldError,
setError,
parentFields
);
expect(setFieldError).toHaveBeenCalledWith(
'private_network.subnet_id',
errorWithParentField[0].reason
);
expect(setError).not.toHaveBeenCalled();
});

it('should handle a general API error', () => {
handleAPIErrors(errorWithoutField, setFieldError, setError);
expect(setFieldError).not.toHaveBeenCalledWith();
expect(setError).toHaveBeenCalledWith(errorWithoutField[0].reason);
Expand Down
25 changes: 23 additions & 2 deletions packages/manager/src/utilities/formikErrorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,40 @@ export const handleGeneralErrors = (
}
};

/**
* This function checks if the parent field from the APIError object is included
* in parentFields list and returns true if if it's found.
* This check will determine whether to provide the full key (parent.child) or just the translated key
* in the handleAPIErrors function.
*/
const keepParentChildFieldKey = (
error: APIError,
parentFields: string[]
): boolean => {
const key = error.field?.split('.')[0];
return parentFields.includes(key ?? '');
};

export const handleAPIErrors = (
errors: APIError[],
setFieldError: (field: string, message: string) => void,
setError?: (message: string) => void
setError?: (message: string) => void,
parentFields?: string[]
) => {
errors.forEach((error: APIError) => {
if (error.field) {
/**
* The line below gets the field name because the API returns something like this...
* {"errors": [{"reason": "Invalid credit card number", "field": "data.card_number"}]}
* It takes 'data.card_number' and translates it to 'card_number'
* If parentFields is provided, then it will provide the full field key for those fields without translation
* ie. In the example above, if parentFields was ['data'] then the field key would continue to be 'data.card_number'.
* This will be useful for when we want to set error messages for the nested fields of a parent.
*/
const key = error.field.split('.')[error.field.split('.').length - 1];
const key = keepParentChildFieldKey(error, parentFields ?? [])
? error.field
: error.field.split('.')[error.field.split('.').length - 1];

if (key) {
setFieldError(key, error.reason);
}
Expand Down
Loading