Skip to content

Commit 8006998

Browse files
authored
Fixed incorrect toast messages for IP address pool creation, updates, and errors (#5929)
* Corrected toast message on ip address pol create/update success/error * fix typo * Added changelog * fix changelog
1 parent 0e49a4e commit 8006998

File tree

4 files changed

+83
-220
lines changed

4 files changed

+83
-220
lines changed

changelog/5908.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed incorrect toast messages for IP address pool creation, updates, and errors.
Lines changed: 81 additions & 216 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,88 @@
1-
import { NUMBER_POOL_OBJECT } from "@/config/constants";
2-
import { useAuth } from "@/entities/authentication/ui/useAuth";
3-
import { currentBranchAtom } from "@/entities/branches/stores";
1+
import { useCurrentBranch } from "@/entities/branches/ui/branches-provider";
42
import { IP_ADDRESS_GENERIC } from "@/entities/ipam/constants";
53
import { createObject } from "@/entities/nodes/api/createObject";
64
import { updateObjectWithId } from "@/entities/nodes/api/updateObjectWithId";
7-
import { AttributeType, RelationshipType } from "@/entities/nodes/getObjectItemDisplayValue";
8-
import { nodeSchemasAtom } from "@/entities/schema/stores/schema.atom";
9-
import { schemaKindLabelState } from "@/entities/schema/stores/schemaKindLabel.atom";
5+
import { getSchema } from "@/entities/schema/domain/get-schema";
106
import { useSchema } from "@/entities/schema/ui/hooks/useSchema";
117
import graphqlClient from "@/shared/api/graphql/graphqlClientApollo";
12-
import { Button } from "@/shared/components/buttons/button-primitive";
13-
import { DEFAULT_FORM_FIELD_VALUE } from "@/shared/components/form/constants";
14-
import { LabelFormField } from "@/shared/components/form/fields/common";
15-
import InputField from "@/shared/components/form/fields/input.field";
16-
import NumberField from "@/shared/components/form/fields/number.field";
17-
import RelationshipManyField from "@/shared/components/form/fields/relationship-many.field";
18-
import RelationshipField from "@/shared/components/form/fields/relationship.field";
8+
import DynamicForm from "@/shared/components/form/dynamic-form";
199
import { NodeFormProps } from "@/shared/components/form/node-form";
20-
import { FormAttributeValue, FormFieldValue } from "@/shared/components/form/type";
21-
import { getCurrentFieldValue } from "@/shared/components/form/utils/getFieldDefaultValue";
10+
import { DynamicSelectFieldProps, FormFieldValue } from "@/shared/components/form/type";
2211
import { getFormFieldsFromSchema } from "@/shared/components/form/utils/getFormFieldsFromSchema";
23-
import { getRelationshipDefaultValue } from "@/shared/components/form/utils/getRelationshipDefaultValue";
2412
import { getCreateMutationFromFormData } from "@/shared/components/form/utils/mutations/getCreateMutationFromFormData";
25-
import { updateFormFieldValue } from "@/shared/components/form/utils/updateFormFieldValue";
26-
import { isRequired } from "@/shared/components/form/utils/validation";
2713
import { ALERT_TYPES, Alert } from "@/shared/components/ui/alert";
28-
import { Badge } from "@/shared/components/ui/badge";
29-
import {
30-
Combobox,
31-
ComboboxContent,
32-
ComboboxItem,
33-
ComboboxList,
34-
ComboboxTrigger,
35-
} from "@/shared/components/ui/combobox";
36-
import { Form, FormField, FormInput, FormSubmit } from "@/shared/components/ui/form";
37-
import { datetimeAtom } from "@/shared/stores/time.atom";
3814
import { stringifyWithoutQuotes } from "@/shared/utils/string";
3915
import { gql } from "@apollo/client";
40-
import { useAtomValue } from "jotai";
41-
import { useState } from "react";
42-
import { FieldValues, useForm } from "react-hook-form";
16+
import { useMemo } from "react";
4317
import { toast } from "react-toastify";
18+
import { capitalize } from "remeda";
4419
import { IP_ADDRESS_POOL } from "../constants";
4520

4621
const ADDRESS_DEFAULT_TYPE_FIELD_NAME = "default_address_type";
4722

48-
interface IpAddressPoolFormProps extends Pick<NodeFormProps, "onSuccess"> {
49-
currentObject?: Record<string, AttributeType | RelationshipType>;
50-
onCancel?: () => void;
51-
onUpdateComplete?: () => void;
52-
}
23+
interface IpAddressPoolFormProps extends NodeFormProps {}
5324

5425
export const IpAddressPoolForm = ({
5526
currentObject,
27+
isUpdate,
28+
onSubmit,
5629
onSuccess,
57-
onCancel,
58-
onUpdateComplete,
30+
...props
5931
}: IpAddressPoolFormProps) => {
60-
const branch = useAtomValue(currentBranchAtom);
61-
const date = useAtomValue(datetimeAtom);
62-
const { schema } = useSchema(IP_ADDRESS_POOL);
63-
const auth = useAuth();
32+
const { currentBranch } = useCurrentBranch();
33+
const { schema: genericAddressSchema, isGeneric } = useSchema(IP_ADDRESS_GENERIC);
34+
35+
const fields = useMemo(() => {
36+
const schemaFields = getFormFieldsFromSchema({
37+
...props,
38+
initialObject: currentObject,
39+
isUpdate,
40+
});
41+
42+
if (!genericAddressSchema || !isGeneric) return schemaFields;
43+
44+
// Replace default_address_type (text) field with a select
45+
return schemaFields.map((field) => {
46+
if (field.name === ADDRESS_DEFAULT_TYPE_FIELD_NAME) {
47+
const items =
48+
genericAddressSchema.used_by?.map((kind) => {
49+
const { schema } = getSchema(kind);
50+
51+
if (!schema) {
52+
return {
53+
key: kind,
54+
label: kind,
55+
};
56+
}
57+
58+
return {
59+
key: kind,
60+
label: (
61+
<div className="flex items-center justify-between w-full">
62+
<span>{schema.label}</span>
63+
<span className="text-xs text-gray-500">{schema.namespace}</span>
64+
</div>
65+
),
66+
};
67+
}) ?? [];
68+
69+
const defaultValue =
70+
isUpdate && currentObject
71+
? field.defaultValue
72+
: items.length === 1
73+
? { source: { type: "user" }, value: items[0]?.key }
74+
: field.defaultValue;
6475

65-
const defaultValues = {
66-
name: getCurrentFieldValue("name", currentObject),
67-
description: getCurrentFieldValue("description", currentObject),
68-
default_prefix_length: getCurrentFieldValue("default_prefix_length", currentObject),
69-
resources: getRelationshipDefaultValue({
70-
relationshipData: currentObject?.resources,
71-
}),
72-
ip_namespace: getRelationshipDefaultValue({
73-
relationshipData: currentObject?.ip_namespace,
74-
}),
75-
};
76-
77-
const fields = getFormFieldsFromSchema({
78-
schema,
79-
initialObject: currentObject,
80-
auth,
81-
});
82-
83-
const form = useForm<FieldValues>({
84-
defaultValues,
85-
});
86-
87-
const prefixLenghtAttribute = schema?.attributes?.find((attribute) => {
88-
return attribute.name === "default_prefix_length";
89-
});
90-
91-
const resourcesRelatiosnhip = schema?.relationships?.find((relationship) => {
92-
return relationship.name === "resources";
93-
});
94-
95-
const namespaceRelationship = schema?.relationships?.find((relationship) => {
96-
return relationship.name === "ip_namespace";
97-
});
76+
return {
77+
...field,
78+
type: "select",
79+
items,
80+
defaultValue,
81+
} as DynamicSelectFieldProps;
82+
}
83+
return field;
84+
});
85+
}, [props.schema.kind, genericAddressSchema?.kind, currentObject, isUpdate]);
9886

9987
async function handleSubmit(data: Record<string, FormFieldValue>) {
10088
try {
@@ -125,156 +113,33 @@ export const IpAddressPoolForm = ({
125113

126114
const result = await graphqlClient.mutate({
127115
mutation,
128-
context: {
129-
branch: branch?.name,
130-
date,
131-
},
116+
context: { branch: currentBranch.name },
132117
});
133118

134-
toast(<Alert type={ALERT_TYPES.SUCCESS} message={"Number pool created"} />, {
135-
toastId: "alert-success-number-pool-created",
119+
const operationType = isUpdate ? "update" : "create";
120+
toast(<Alert type={ALERT_TYPES.SUCCESS} message={`IP address pool ${operationType}d`} />, {
121+
toastId: `alert-success-ip-prefix-pool-${operationType}`,
136122
});
137123

138-
if (onSuccess) await onSuccess(result?.data?.[`${NUMBER_POOL_OBJECT}Create`]);
139-
if (onUpdateComplete) await onUpdateComplete();
124+
if (onSuccess) {
125+
const resultData = result?.data?.[`${IP_ADDRESS_POOL}${capitalize(operationType)}`];
126+
await onSuccess(resultData);
127+
}
140128
} catch (error: unknown) {
141-
console.error("An error occurred while creating the object: ", error);
129+
console.error(
130+
`An error occurred while ${isUpdate ? "updating" : "creating"} the IP address pool:`,
131+
error
132+
);
142133
}
143134
}
144135

145136
return (
146-
<div className={"bg-custom-white flex flex-col flex-1 overflow-auto p-4"}>
147-
<Form form={form} onSubmit={handleSubmit}>
148-
<InputField name="name" label="Name" rules={{ required: true }} />
149-
<InputField name="description" label="Description" />
150-
<AddressTypesCombobox currentObject={currentObject} />
151-
<NumberField
152-
name="default_prefix_length"
153-
label={prefixLenghtAttribute?.label}
154-
description={prefixLenghtAttribute?.description}
155-
/>
156-
<RelationshipManyField
157-
name="resources"
158-
type="relationship"
159-
label={resourcesRelatiosnhip?.label}
160-
description={resourcesRelatiosnhip?.description}
161-
relationship={{
162-
name: "resources",
163-
peer: "BuiltinIPPrefix",
164-
kind: "Attribute",
165-
label: "Resources",
166-
}}
167-
rules={{ required: true, validate: { required: isRequired } }}
168-
/>
169-
<RelationshipField
170-
name="ip_namespace"
171-
type="relationship"
172-
label={namespaceRelationship?.label}
173-
description={namespaceRelationship?.description}
174-
relationship={{ peer: "BuiltinIPNamespace", name: "ip_namespace" }}
175-
rules={{ required: true, validate: { required: isRequired } }}
176-
/>
177-
178-
<div className="text-right">
179-
{onCancel && (
180-
<Button variant="outline" className="mr-2" onClick={onCancel}>
181-
Cancel
182-
</Button>
183-
)}
184-
185-
<FormSubmit>Save</FormSubmit>
186-
</div>
187-
</Form>
188-
</div>
189-
);
190-
};
191-
192-
const AddressTypesCombobox = ({
193-
currentObject,
194-
}: { currentObject?: Record<string, AttributeType | RelationshipType> }) => {
195-
const { schema } = useSchema(IP_ADDRESS_POOL);
196-
const schemaList = useAtomValue(nodeSchemasAtom);
197-
const { schema: genericSchema, isGeneric } = useSchema(IP_ADDRESS_GENERIC);
198-
const schemaKindName = useAtomValue(schemaKindLabelState);
199-
const [open, setOpen] = useState(false);
200-
201-
const prefixTypeAttribute = schema?.attributes?.find((attribute) => {
202-
return attribute.name === ADDRESS_DEFAULT_TYPE_FIELD_NAME;
203-
});
204-
205-
const items =
206-
(isGeneric &&
207-
genericSchema?.used_by?.map((kind) => {
208-
const currentSchema = schemaList.find((schema) => schema.kind === kind);
209-
210-
return {
211-
value: kind,
212-
label: schemaKindName[kind],
213-
namespace: currentSchema?.namespace,
214-
};
215-
})) ??
216-
[];
217-
218-
const currentValue = getCurrentFieldValue(ADDRESS_DEFAULT_TYPE_FIELD_NAME, currentObject);
219-
220-
const defaultValue =
221-
(items[0] ?? currentValue)
222-
? { source: { type: "user" }, value: items[0].value }
223-
: DEFAULT_FORM_FIELD_VALUE;
224-
225-
return (
226-
<FormField
227-
name={ADDRESS_DEFAULT_TYPE_FIELD_NAME}
228-
rules={{ required: true, validate: { required: isRequired } }}
229-
defaultValue={defaultValue}
230-
render={({ field }) => {
231-
const fieldData: FormAttributeValue = field.value;
232-
233-
return (
234-
<div className="flex flex-col gap-2">
235-
<LabelFormField
236-
label={prefixTypeAttribute?.label}
237-
required
238-
description={prefixTypeAttribute?.description}
239-
fieldData={fieldData}
240-
/>
241-
242-
<FormInput>
243-
<Combobox open={open} onOpenChange={setOpen}>
244-
<ComboboxTrigger data-testid="namespace-select">
245-
{items.find((item) => item.value === fieldData?.value)?.label}
246-
</ComboboxTrigger>
247-
248-
<ComboboxContent align="start" fitTriggerWidth={false}>
249-
<ComboboxList className="max-w-md">
250-
{items.map((item) => {
251-
return (
252-
<ComboboxItem
253-
key={item.value}
254-
value={item.value}
255-
selectedValue={fieldData?.value}
256-
onSelect={() => {
257-
const newValue = fieldData?.value === item.value ? null : item.value;
258-
field.onChange(
259-
updateFormFieldValue(newValue ?? null, DEFAULT_FORM_FIELD_VALUE)
260-
);
261-
setOpen(false);
262-
}}
263-
>
264-
<div className="overflow-hidden w-full flex items-center justify-between">
265-
<div className="truncate font-semibold">{item.label}</div>
266-
<Badge className="font-medium">{item.namespace}</Badge>
267-
</div>
268-
</ComboboxItem>
269-
);
270-
})}
271-
</ComboboxList>
272-
</ComboboxContent>
273-
</Combobox>
274-
</FormInput>
275-
</div>
276-
);
277-
}}
137+
<DynamicForm
138+
fields={fields}
139+
onSubmit={(formData: Record<string, FormFieldValue>) =>
140+
onSubmit ? onSubmit({ formData, fields }) : handleSubmit(formData)
141+
}
142+
className="p-4 overflow-auto"
278143
/>
279144
);
280145
};

frontend/app/src/shared/components/form/node-form.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { DynamicFieldProps, FormFieldValue, NumberPoolData } from "@/shared/comp
1717
import { getFormFieldsFromSchema } from "@/shared/components/form/utils/getFormFieldsFromSchema";
1818
import { getCreateMutationFromFormData } from "@/shared/components/form/utils/mutations/getCreateMutationFromFormData";
1919
import { ALERT_TYPES, Alert } from "@/shared/components/ui/alert";
20-
import useFilters from "@/shared/hooks/useFilters";
2120
import { datetimeAtom } from "@/shared/stores/time.atom";
2221
import { classNames } from "@/shared/utils/common";
2322
import { stringifyWithoutQuotes } from "@/shared/utils/string";
@@ -58,7 +57,6 @@ export const NodeForm = ({
5857
}: NodeFormProps) => {
5958
const branch = useAtomValue(currentBranchAtom);
6059
const date = useAtomValue(datetimeAtom);
61-
const [filters] = useFilters();
6260
const auth = useAuth();
6361

6462
const { data, loading } = useQuery(GET_FORM_REQUIREMENTS, { variables: { kind: schema.kind } });
@@ -84,7 +82,6 @@ export const NodeForm = ({
8482
objectTemplate,
8583
auth,
8684
isFilterForm,
87-
filters,
8885
pools: numberPools,
8986
isUpdate,
9087
});

frontend/app/src/shared/components/form/object-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const ObjectForm = ({ kind, currentProfiles, ...props }: ObjectFormProps) => {
111111
}
112112

113113
if (kind === IP_ADDRESS_POOL) {
114-
return <IpAddressPoolForm {...props} />;
114+
return <IpAddressPoolForm schema={schema} {...props} />;
115115
}
116116

117117
if (kind === IP_PREFIX_POOL) {

0 commit comments

Comments
 (0)