Skip to content

Commit cacbea1

Browse files
authored
Display hfid on relationship cells (#5782)
1 parent f745579 commit cacbea1

File tree

10 files changed

+153
-38
lines changed

10 files changed

+153
-38
lines changed

frontend/app/src/entities/nodes/object/ui/object-table/cells/table-row-identifier.tsx renamed to frontend/app/src/entities/nodes/object/ui/object-table/cells/table-identifier-cell.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@ import { getObjectDetailsUrl2 } from "@/entities/nodes/utils";
22
import { LinkButton } from "@/shared/components/buttons/button-primitive";
33
import { TableCell } from "@/shared/components/table/table-cell";
44

5-
export interface TableRowIdentifierProps {
5+
export interface TableIdentifierCellProps {
66
objectKind: string;
77
objectId: string;
8-
identifier: string | string[];
8+
label: string;
99
}
1010

11-
export function TableRowIdentifier({ objectKind, objectId, identifier }: TableRowIdentifierProps) {
12-
const display = Array.isArray(identifier) ? identifier.join(", ") : identifier;
11+
export function TableIdentifierCell({ objectKind, objectId, label }: TableIdentifierCellProps) {
1312
return (
14-
<TableCell className="sticky left-0 bg-white px-1">
13+
<TableCell className="sticky left-0 bg-white px-1" data-testid="identifier-cell">
1514
<LinkButton
1615
variant="ghost"
1716
size="sm"
1817
to={getObjectDetailsUrl2(objectKind, objectId)}
1918
className="truncate px-2.5 rounded-full text-custom-blue-700 hover:underline hover:bg-custom-blue-700/10"
2019
>
21-
{display}
20+
{label}
2221
</LinkButton>
2322

2423
<div className="absolute -right-4 top-0 bottom-0 w-4 bg-gradient-to-r from-gray-500/10 pointer-events-none" />
Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { getNodeLabel } from "@/entities/nodes/object/utils/get-node-label";
12
import {
2-
Node,
3-
RelationshipManyType,
4-
RelationshipOneType,
5-
RelationshipType,
6-
} from "@/entities/nodes/getObjectItemDisplayValue";
3+
NodeRelationship,
4+
NodeRelationshipMany,
5+
NodeRelationshipOne,
6+
} from "@/entities/nodes/types";
77
import { getObjectDetailsUrl2 } from "@/entities/nodes/utils";
88
import { useSchema } from "@/entities/schema/hooks/useSchema";
99
import { RelationshipSchema } from "@/entities/schema/types";
@@ -12,22 +12,22 @@ import { Icon } from "@iconify-icon/react";
1212

1313
export interface TableRelationshipCellProps {
1414
relationshipSchema: RelationshipSchema;
15-
relationshipData: RelationshipType;
15+
relationshipData: NodeRelationship;
1616
}
1717

1818
export function TableRelationshipCell({
1919
relationshipSchema,
2020
relationshipData,
2121
}: TableRelationshipCellProps) {
2222
if (relationshipSchema.cardinality === "one") {
23-
const { node } = relationshipData as RelationshipOneType;
23+
const { node } = relationshipData as NodeRelationshipOne;
2424

2525
if (!node) return "-";
2626

2727
return <RelationshipNodeDisplay node={node} />;
2828
}
2929

30-
const nodes = (relationshipData as RelationshipManyType).edges
30+
const nodes = (relationshipData as NodeRelationshipMany).edges
3131
.map(({ node }) => node)
3232
.filter((node) => !!node);
3333

@@ -36,18 +36,20 @@ export function TableRelationshipCell({
3636
return nodes.map((node) => <RelationshipNodeDisplay key={node.id} node={node} />);
3737
}
3838

39-
export function RelationshipNodeDisplay({ node }: { node: Node }) {
39+
export function RelationshipNodeDisplay({ node }: NodeRelationshipOne) {
4040
const { schema } = useSchema(node.__typename);
4141

42+
if (!schema) return "Unknown schema";
43+
4244
return (
4345
<LinkButton
4446
variant="outline"
4547
size="sm"
4648
to={getObjectDetailsUrl2(node.__typename, node.id)}
4749
className="rounded-full truncate hover:underline hover:border-custom-blue-700 pr-2.5"
4850
>
49-
<Icon icon={schema?.icon ?? "mdi:cube-outline"} className="mr-1 text-custom-blue-800" />
50-
{node.display_label}
51+
<Icon icon={schema.icon ?? "mdi:cube-outline"} className="mr-1 text-custom-blue-800" />
52+
{getNodeLabel({ node, schema })}
5153
</LinkButton>
5254
);
5355
}

frontend/app/src/entities/nodes/object/ui/object-table/get-object-table-columns.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { KindBodyCell } from "@/entities/nodes/object/ui/object-table/cells/gene
33
import { KindHeaderCell } from "@/entities/nodes/object/ui/object-table/cells/generics/kind-header-cell";
44
import { TableAttributeCell } from "@/entities/nodes/object/ui/object-table/cells/table-attribute-cell";
55
import { TableColumnHeader } from "@/entities/nodes/object/ui/object-table/cells/table-column-header";
6+
import { TableIdentifierCell } from "@/entities/nodes/object/ui/object-table/cells/table-identifier-cell";
67
import { TableRelationshipCell } from "@/entities/nodes/object/ui/object-table/cells/table-relationship-cell";
7-
import { TableRowIdentifier } from "@/entities/nodes/object/ui/object-table/cells/table-row-identifier";
88
import { getAttributesVisibleInListView } from "@/entities/nodes/object/utils/get-attributes-visible-in-list";
9+
import { getNodeLabel } from "@/entities/nodes/object/utils/get-node-label";
910
import { getRelationshipsVisibleInListView } from "@/entities/nodes/object/utils/get-relationships-visible-in-list";
1011
import { NodeObject } from "@/entities/nodes/types";
1112
import { Permission } from "@/entities/permission/types";
@@ -32,11 +33,7 @@ export const getObjectTableColumns = (
3233
return [
3334
{
3435
id: "id",
35-
accessorFn: ({ hfid, display_label, id }): string => {
36-
if (schema.human_friendly_id && hfid) return hfid.join(", ");
37-
if (schema.display_labels && display_label) return display_label;
38-
return id;
39-
},
36+
accessorFn: (node) => getNodeLabel({ node, schema }),
4037
header: () => (
4138
<div className={classNames(cellsStyle, cellHeaderStyle, "left-0 z-10 hover:bg-white")}>
4239
{schema.icon && <Icon icon={schema.icon} className="text-stone-400" />}
@@ -46,10 +43,10 @@ export const getObjectTableColumns = (
4643
cell: ({ row }) => {
4744
const value = (row.getValue("id") ?? "-") as string;
4845
return (
49-
<TableRowIdentifier
46+
<TableIdentifierCell
5047
objectKind={row.original.__typename as string}
5148
objectId={row.original.id as string}
52-
identifier={value}
49+
label={value}
5350
/>
5451
);
5552
},
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { NodeCore } from "@/entities/nodes/types";
2+
import { describe, expect, it } from "vitest";
3+
import { generateNodeSchema } from "../../../../../tests/fake/schema";
4+
import { getNodeLabel } from "./get-node-label";
5+
6+
describe("getNodeLabel", () => {
7+
const baseSchema = generateNodeSchema();
8+
const baseNode: NodeCore = {
9+
id: "test-id",
10+
hfid: null,
11+
display_label: null,
12+
__typename: "TestKind",
13+
};
14+
15+
it("should join multiple hfids with comma when node has hfid array", () => {
16+
// GIVEN
17+
const node = { ...baseNode, hfid: ["hfid-1", "hfid-2"] };
18+
const schema = baseSchema;
19+
20+
// WHEN
21+
const result = getNodeLabel({ node, schema });
22+
23+
// THEN
24+
expect(result).toBe("hfid-1, hfid-2");
25+
});
26+
27+
it("should use display_label when hfid is not present", () => {
28+
// GIVEN
29+
const node = { ...baseNode, display_label: "Display Label" };
30+
const schema = baseSchema;
31+
32+
// WHEN
33+
const result = getNodeLabel({ node, schema });
34+
35+
// THEN
36+
expect(result).toBe("Display Label");
37+
});
38+
39+
it("should fallback to node.id when neither hfid nor display_label are present", () => {
40+
// WHEN
41+
const result = getNodeLabel({ node: baseNode, schema: baseSchema });
42+
43+
// THEN
44+
expect(result).toBe("test-id");
45+
});
46+
47+
it("should prefer hfid over display_label when both are available", () => {
48+
// GIVEN
49+
const node = {
50+
...baseNode,
51+
hfid: ["hfid-1"],
52+
display_label: "Display Label",
53+
};
54+
const schema = baseSchema;
55+
56+
// WHEN
57+
const result = getNodeLabel({ node, schema });
58+
59+
// THEN
60+
expect(result).toBe("hfid-1");
61+
});
62+
63+
it("should fallback to node.id when schema allows special labels but node values are null", () => {
64+
// GIVEN
65+
const schema = baseSchema;
66+
67+
// WHEN
68+
const result = getNodeLabel({ node: baseNode, schema });
69+
70+
// THEN
71+
expect(result).toBe("test-id");
72+
});
73+
74+
it("should use node.id when schema disables special labels even if node has them", () => {
75+
// GIVEN
76+
const node = {
77+
...baseNode,
78+
hfid: ["hfid-1"],
79+
display_label: "Display Label",
80+
};
81+
const schema = {
82+
...baseSchema,
83+
human_friendly_id: null,
84+
display_labels: null,
85+
};
86+
87+
// WHEN
88+
const result = getNodeLabel({ node, schema: schema });
89+
90+
// THEN
91+
expect(result).toBe("test-id");
92+
});
93+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NodeCore } from "@/entities/nodes/types";
2+
import { IModelSchema } from "@/entities/schema/stores/schema.atom";
3+
4+
export function getNodeLabel({ node, schema }: { node: NodeCore; schema: IModelSchema }): string {
5+
if (schema.human_friendly_id && node.hfid) {
6+
return node.hfid.join(", ");
7+
}
8+
9+
if (schema.display_labels && node.display_label) {
10+
return node.display_label;
11+
}
12+
13+
return node.id;
14+
}

frontend/app/src/entities/nodes/types.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ export type RelationshipKind =
88
| "Hierarchy"
99
| "Profile";
1010

11+
export type NodeCore = {
12+
id: string;
13+
hfid?: string[] | null;
14+
display_label?: string | null;
15+
__typename: string;
16+
};
17+
1118
export type NodeAttribute = {
1219
id: string;
1320
value: string | number | boolean | null;
1421
};
1522

1623
export type NodeRelationshipOne = {
17-
node: {
18-
id: string;
19-
display_label: string;
20-
__typename: string;
21-
};
24+
node: NodeCore;
2225
};
2326

2427
export type NodeRelationshipMany = {
@@ -27,11 +30,6 @@ export type NodeRelationshipMany = {
2730

2831
export type NodeRelationship = NodeRelationshipOne | NodeRelationshipMany;
2932

30-
export type NodeObject = {
31-
id: string;
32-
hfid?: string;
33-
display_label?: string;
34-
__typename: string;
35-
} & {
33+
export type NodeObject = NodeCore & {
3634
[key: string]: NodeAttribute | NodeRelationship;
3735
};

frontend/app/src/shared/api/graphql/utils.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ describe("addRelationshipsToRequest", () => {
152152
test: {
153153
node: {
154154
id: true,
155+
hfid: true,
155156
display_label: true,
156157
},
157158
},
@@ -173,6 +174,7 @@ describe("addRelationshipsToRequest", () => {
173174
edges: {
174175
node: {
175176
id: true,
177+
hfid: true,
176178
display_label: true,
177179
},
178180
},
@@ -194,6 +196,7 @@ describe("addRelationshipsToRequest", () => {
194196
test: {
195197
node: {
196198
id: true,
199+
hfid: true,
197200
display_label: true,
198201
},
199202
properties: {
@@ -230,13 +233,15 @@ describe("addRelationshipsToRequest", () => {
230233
one: {
231234
node: {
232235
id: true,
236+
hfid: true,
233237
display_label: true,
234238
},
235239
},
236240
many: {
237241
edges: {
238242
node: {
239243
id: true,
244+
hfid: true,
240245
display_label: true,
241246
},
242247
},

frontend/app/src/shared/api/graphql/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const addRelationshipsToRequest = (
6868
const baseFragment = {
6969
node: {
7070
id: true,
71+
hfid: true,
7172
display_label: true,
7273
},
7374
...(withMetadata && {

frontend/app/tests/e2e/form/select-2-steps.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ test.describe("Verifies the object creation", () => {
9595
test("verifies values in kind and parent selects", async ({ page }) => {
9696
await test.step("got to the edit form", async () => {
9797
await page.goto("/objects/InfraInterfaceL3");
98-
await page.getByRole("link", { name: "dfw1-edge1, Ethernet1", exact: true }).click();
98+
await page
99+
.getByTestId("identifier-cell")
100+
.getByRole("link", { name: "dfw1-edge1, Ethernet1", exact: true })
101+
.click();
99102
await page.getByTestId("edit-button").click();
100103
});
101104

frontend/app/tests/e2e/objects/object-relationships.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ test.describe("/objects/:objectKind/:objectid - relationship tab", () => {
106106

107107
test("should access to the pool selector on relationships add", async ({ page }) => {
108108
await page.goto("/objects/InfraInterfaceL3/");
109-
await page.getByRole("link", { name: "den1-edge2, Ethernet1", exact: true }).click();
109+
await page
110+
.getByTestId("identifier-cell")
111+
.getByRole("link", { name: "den1-edge2, Ethernet1", exact: true })
112+
.click();
110113
await page.getByText("Ip Addresses1").click();
111114
await page.getByTestId("open-relationship-form-button").click();
112115
await page.getByTestId("select-open-pool-option-button").click();

0 commit comments

Comments
 (0)