Skip to content

Commit 5ac76c1

Browse files
authored
Fixed an issue where the pool selection was not displayed correctly when eligible in a hierarchical relationship field (#5909)
* extracted pool-select component from relationship-one component * added pool on relationship hierarchical
1 parent 4b1598d commit 5ac76c1

File tree

7 files changed

+181
-136
lines changed

7 files changed

+181
-136
lines changed

changelog/5888.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed an issue where the pool selection was not displayed correctly when eligible in a hierarchical relationship field.

frontend/app/src/shared/components/form/fields/relationship-hierarchical.field.tsx

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
import { POOLS_PEER } from "@/entities/ipam/constants";
12
import { RelationshipNode } from "@/entities/nodes/relationships/domain/types";
23
import {
34
RelationshipHierarchicalInput,
45
RelationshipHierarchicalManyInput,
56
} from "@/entities/nodes/relationships/ui/relationship-hierarchical-input";
7+
import { useSchema } from "@/entities/schema/ui/hooks/useSchema";
68
import { DEFAULT_FORM_FIELD_VALUE } from "@/shared/components/form/constants";
79
import { LabelFormField } from "@/shared/components/form/fields/common";
10+
import { PoolValue } from "@/shared/components/form/pool-selector";
811
import {
912
DynamicRelationshipFieldProps,
1013
FormRelationshipValue,
1114
} from "@/shared/components/form/type";
1215
import { updateRelationshipFieldValue } from "@/shared/components/form/utils/updateFormFieldValue";
16+
import { PoolSelect } from "@/shared/components/inputs/pool-select";
1317
import { FormField, FormInput, FormMessage } from "@/shared/components/ui/form";
1418

1519
export interface RelationshipHierarchicalFieldProps
@@ -32,6 +36,16 @@ export default function RelationshipHierarchicalField({
3236
render={({ field }) => {
3337
const fieldData: FormRelationshipValue = field.value;
3438

39+
const peer = props.relationship.peer;
40+
const { schema, isNode } = useSchema(peer);
41+
const canSelectFromPool =
42+
isNode && !!schema.inherit_from?.some((from) => POOLS_PEER.includes(from));
43+
const selectedPoolId = fieldData?.source?.type === "pool" ? fieldData.source.id : null;
44+
45+
const onChange = (newValue: RelationshipNode | RelationshipNode[] | PoolValue | null) => {
46+
field.onChange(updateRelationshipFieldValue(newValue, defaultValue));
47+
};
48+
3549
return (
3650
<div className="flex flex-col gap-2">
3751
<LabelFormField
@@ -42,27 +56,28 @@ export default function RelationshipHierarchicalField({
4256
fieldData={fieldData}
4357
/>
4458

45-
<FormInput>
46-
{props.relationship.cardinality === "many" ? (
47-
<RelationshipHierarchicalManyInput
48-
{...field}
49-
peer={props.relationship.peer}
50-
value={fieldData.value as RelationshipNode[] | null}
51-
onChange={(newValue) => {
52-
field.onChange(updateRelationshipFieldValue(newValue, defaultValue));
53-
}}
54-
/>
55-
) : (
56-
<RelationshipHierarchicalInput
57-
{...field}
58-
peer={props.relationship.peer}
59-
value={fieldData.value as RelationshipNode | null}
60-
onChange={(newValue) => {
61-
field.onChange(updateRelationshipFieldValue(newValue, defaultValue));
62-
}}
63-
/>
59+
<div className="flex gap-2">
60+
<FormInput>
61+
{props.relationship.cardinality === "many" ? (
62+
<RelationshipHierarchicalManyInput
63+
{...field}
64+
peer={peer}
65+
value={fieldData.value as RelationshipNode[] | null}
66+
onChange={onChange}
67+
/>
68+
) : (
69+
<RelationshipHierarchicalInput
70+
{...field}
71+
peer={props.relationship.peer}
72+
value={fieldData.value as RelationshipNode | null}
73+
onChange={onChange}
74+
/>
75+
)}
76+
</FormInput>
77+
{canSelectFromPool && (
78+
<PoolSelect peer={peer} selectedPoolId={selectedPoolId} onChange={onChange} />
6479
)}
65-
</FormInput>
80+
</div>
6681

6782
<FormMessage />
6883
</div>

frontend/app/src/shared/components/form/fields/relationship.field.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { POOLS_PEER } from "@/entities/ipam/constants";
12
import { getRelationshipParent } from "@/entities/nodes/api/getRelationshipParent";
23
import { Node } from "@/entities/nodes/getObjectItemDisplayValue";
34
import {
@@ -6,13 +7,16 @@ import {
67
profileSchemasAtom,
78
templateSchemasAtom,
89
} from "@/entities/schema/stores/schema.atom";
10+
import { useSchema } from "@/entities/schema/ui/hooks/useSchema";
911
import useQuery from "@/shared/api/graphql/useQuery";
1012
import { LabelFormField } from "@/shared/components/form/fields/common";
13+
import { PoolValue } from "@/shared/components/form/pool-selector";
1114
import {
1215
DynamicRelationshipFieldProps,
1316
FormRelationshipValue,
1417
} from "@/shared/components/form/type";
1518
import { updateRelationshipFieldValue } from "@/shared/components/form/utils/updateFormFieldValue";
19+
import { PoolSelect } from "@/shared/components/inputs/pool-select";
1620
import { RelationshipInput } from "@/shared/components/inputs/relationship-one";
1721
import { Badge } from "@/shared/components/ui/badge";
1822
import {
@@ -340,29 +344,45 @@ const RelationshipField = ({
340344
render={({ field }) => {
341345
const fieldData: FormRelationshipValue = field.value;
342346

347+
const peer = relationship?.peer;
348+
const { schema, isNode } = useSchema(peer);
349+
const canSelectFromPool =
350+
isNode && !!schema.inherit_from?.some((from) => POOLS_PEER.includes(from));
351+
const selectedPoolId = fieldData?.source?.type === "pool" ? fieldData.source.id : null;
352+
353+
const onChange = (newValue: Node | PoolValue | null) => {
354+
field.onChange(updateRelationshipFieldValue(newValue, defaultValue));
355+
};
356+
357+
const value =
358+
fieldData?.value && !Array.isArray(fieldData.value) ? (fieldData.value as Node) : null;
359+
343360
return (
344361
<div className="relative flex flex-col space-y-2">
345362
<LabelFormField
346363
label={label}
347364
unique={unique}
348365
required={!!rules?.required}
349366
description={description}
350-
variant={parentRelationship && "small"}
367+
variant={parentRelationship ? "small" : undefined}
351368
fieldData={fieldData}
352369
/>
353370

354-
<FormInput>
355-
<RelationshipInput
356-
{...field}
357-
{...props}
358-
value={fieldData?.value}
359-
onChange={(newValue) => {
360-
field.onChange(updateRelationshipFieldValue(newValue, defaultValue));
361-
}}
362-
peer={relationship?.peer}
363-
parent={{ name: parentRelationship?.name, value: selectedParent?.id }}
364-
/>
365-
</FormInput>
371+
<div className="flex gap-2">
372+
<FormInput>
373+
<RelationshipInput
374+
{...field}
375+
{...props}
376+
value={value}
377+
onChange={onChange}
378+
peer={peer}
379+
parent={{ name: parentRelationship?.name, value: selectedParent?.id }}
380+
/>
381+
</FormInput>
382+
{canSelectFromPool && (
383+
<PoolSelect peer={peer} selectedPoolId={selectedPoolId} onChange={onChange} />
384+
)}
385+
</div>
366386
<FormMessage />
367387
</div>
368388
);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { POOLS_DICTIONNARY } from "@/entities/ipam/constants";
2+
import { getDropdownOptions } from "@/entities/nodes/api/dropdownOptions";
3+
import { Node, RelationshipManyType } from "@/entities/nodes/getObjectItemDisplayValue";
4+
import { useLazyQuery } from "@/shared/api/graphql/useQuery";
5+
import { Button } from "@/shared/components/buttons/button-primitive";
6+
import { PoolValue } from "@/shared/components/form/pool-selector";
7+
import {
8+
Combobox,
9+
ComboboxContent,
10+
ComboboxEmpty,
11+
ComboboxItem,
12+
ComboboxList,
13+
} from "@/shared/components/ui/combobox";
14+
import { PopoverTrigger } from "@/shared/components/ui/popover";
15+
import { Spinner } from "@/shared/components/ui/spinner";
16+
import { Tooltip } from "@/shared/components/ui/tooltip";
17+
import { gql } from "@apollo/client";
18+
import { Icon } from "@iconify-icon/react";
19+
import React from "react";
20+
21+
export interface PoolSelectProps {
22+
peer: string;
23+
selectedPoolId: string | null;
24+
onChange: (value: PoolValue | null) => void;
25+
}
26+
27+
export function PoolSelect({ peer, onChange, selectedPoolId }: PoolSelectProps) {
28+
const [isOpen, setIsOpen] = React.useState(false);
29+
30+
const poolPeer = POOLS_DICTIONNARY[peer];
31+
const poolsQueryString = poolPeer ? getDropdownOptions({ kind: poolPeer }) : "query { ok }";
32+
const poolsQuery = gql`
33+
${poolsQueryString}
34+
`;
35+
const [loadPoolList, { loading: isPoolListLoading, data: poolsData }] = useLazyQuery(poolsQuery);
36+
37+
return (
38+
<Combobox open={isOpen} onOpenChange={setIsOpen}>
39+
<Tooltip content="select a pool" enabled>
40+
<PopoverTrigger asChild>
41+
<Button
42+
variant="outline"
43+
className="h-10 w-10 border-gray-300"
44+
data-testid="select-open-pool-option-button"
45+
>
46+
<Icon icon="mdi:view-grid-outline" className="text-gray-500" />
47+
</Button>
48+
</PopoverTrigger>
49+
</Tooltip>
50+
51+
<ComboboxContent align="end" fitTriggerWidth={false} onOpenAutoFocus={() => loadPoolList()}>
52+
<ComboboxList>
53+
{!isPoolListLoading && <ComboboxEmpty>No pools found</ComboboxEmpty>}
54+
55+
{!isPoolListLoading &&
56+
poolsData &&
57+
(poolsData[poolPeer] as RelationshipManyType).edges
58+
.map((edge) => edge.node)
59+
.filter((node): node is Node => !!node)
60+
.map((pool) => {
61+
return (
62+
<ComboboxItem
63+
key={pool.id}
64+
value={pool.id}
65+
keywords={[pool.display_label]}
66+
selectedValue={selectedPoolId}
67+
onSelect={() => {
68+
if (selectedPoolId === pool.id) {
69+
onChange(null);
70+
} else {
71+
onChange({
72+
from_pool: {
73+
id: pool.id,
74+
name: pool.display_label,
75+
kind: pool.__typename,
76+
},
77+
});
78+
}
79+
setIsOpen(false);
80+
}}
81+
>
82+
<span className="truncate">{pool.display_label}</span>
83+
</ComboboxItem>
84+
);
85+
})}
86+
87+
{isPoolListLoading && <Spinner className="flex justify-center m-2" />}
88+
</ComboboxList>
89+
</ComboboxContent>
90+
</Combobox>
91+
);
92+
}

0 commit comments

Comments
 (0)