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
1 change: 1 addition & 0 deletions changelog/9892.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Following an IP prefix or address link from the relationship tabs of an IPAM namespace now keeps you in that namespace, instead of redirecting to the default namespace.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LinkButton } from "@infrahub/ui";
import type { PressEvent } from "react-aria-components";

import type { overrideQueryParams } from "@/shared/api/rest/fetch";
import { Checkbox } from "@/shared/components/aria/checkbox";

import { useAuth } from "@/entities/authentication/ui/useAuth";
Expand All @@ -13,6 +14,7 @@ export interface TableIdentifierCellProps {
label: React.ReactNode;
isSelected?: boolean;
onClickCheckbox?: (e: PressEvent) => void;
overrideParams?: overrideQueryParams[];
}

export function TableIdentifierCell({
Expand All @@ -21,6 +23,7 @@ export function TableIdentifierCell({
label,
isSelected,
onClickCheckbox,
overrideParams,
}: TableIdentifierCellProps) {
const { isAuthenticated } = useAuth();
return (
Expand All @@ -36,7 +39,7 @@ export function TableIdentifierCell({
<LinkButton
variant="ghost"
size="sm"
href={getObjectDetailsUrl(objectKind, objectId)}
href={getObjectDetailsUrl(objectKind, objectId, overrideParams)}
className="-mx-1 truncate rounded-xl px-2 text-custom-blue-700 hover:underline"
>
{label}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PopoverTriggerProps } from "@radix-ui/react-popover";
import { type ColumnDef, createColumnHelper } from "@tanstack/react-table";

import type { overrideQueryParams } from "@/shared/api/rest/fetch";
import { FROM_RESOURCE_POOL_SUFFIX } from "@/shared/components/form/constants";
import { TableCell } from "@/shared/components/table/table-cell";
import { sortByOrderWeight } from "@/shared/utils/common";
Expand Down Expand Up @@ -34,7 +35,8 @@ import { isGenericSchema } from "@/entities/schema/utils/is-generic-schema";
const columnHelper = createColumnHelper<NodeObject>();

export function getObjectIdentifierColumns(
schema: ModelSchema
schema: ModelSchema,
identifierOverrideParams?: overrideQueryParams[]
): Array<ColumnDef<NodeObject, string>> {
return [
columnHelper.accessor((node) => getNodeLabel(node), {
Expand All @@ -59,6 +61,7 @@ export function getObjectIdentifierColumns(
label={label}
isSelected={row.getIsSelected()}
onClickCheckbox={getToggleSelectedRowHandler({ row, table })}
overrideParams={identifierOverrideParams}
/>
);
},
Expand Down Expand Up @@ -145,10 +148,11 @@ export function getObjectFieldsColumns(

export const getObjectTableColumns = (
schema: ModelSchema,
headerProps?: PopoverTriggerProps
headerProps?: PopoverTriggerProps,
identifierOverrideParams?: overrideQueryParams[]
): Array<ColumnDef<NodeObject>> => {
return [
...getObjectIdentifierColumns(schema),
...getObjectIdentifierColumns(schema, identifierOverrideParams),
...getObjectGenericColumns(schema),
...getObjectFieldsColumns(schema, headerProps),
] as Array<ColumnDef<NodeObject>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DataTable } from "@/shared/components/table/data-table";
import { InfiniteScroll } from "@/shared/components/utils/infinite-scroll";
import useFilters from "@/shared/hooks/useFilters";

import { IP_NAMESPACE_GENERIC, IPAM_QSP } from "@/entities/ipam/constants";
import { ObjectTableEmpty } from "@/entities/nodes/object/ui/object-table/object-table-empty";
import { getObjectTableColumns } from "@/entities/nodes/object/ui/object-table/utils/get-object-table-columns";
import {
Expand All @@ -15,6 +16,7 @@ import { ToolbarDissociateAction } from "@/entities/nodes/relationships/ui/relat
import { canDissociateRelationship } from "@/entities/nodes/relationships/utils/can-dissociate-relationship";
import { useGetObjectPermissions } from "@/entities/permission/ui/queries/get-object-permissions.query";
import { useSchema } from "@/entities/schema/ui/hooks/useSchema";
import { isOfKind } from "@/entities/schema/utils/is-of-kind";

export interface RelationshipTableProps extends UseObjectRelationshipsParams {}

Expand Down Expand Up @@ -49,8 +51,18 @@ export function RelationshipTable({
}

const flatData = data?.pages?.flat() ?? [];

// On an IP namespace's relationship tabs (e.g. its prefixes/addresses), the active
// namespace lives in the route path, not the `namespace` query param that IPAM links
// rely on. Forward it explicitly so child IP links stay in this namespace instead of
// falling back to the default one.
const identifierOverrideParams =
parentSchema && isOfKind(IP_NAMESPACE_GENERIC, parentSchema)
? [{ name: IPAM_QSP.NAMESPACE, value: parentId }]
: undefined;

const columns = [
...getObjectTableColumns(relationshipSchema, { disabled: true }),
...getObjectTableColumns(relationshipSchema, { disabled: true }, identifierOverrideParams),
getRelationshipActionsColumn({
parentId,
parentKind,
Expand Down
49 changes: 49 additions & 0 deletions frontend/app/src/entities/nodes/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { afterEach, describe, expect, it } from "vitest";

import { store } from "@/shared/stores";

import { IP_PREFIX_GENERIC, IPAM_QSP } from "@/entities/ipam/constants";
import { getObjectDetailsUrl } from "@/entities/nodes/utils";
import {
genericSchemasAtom,
nodeSchemasAtom,
profileSchemasAtom,
templateSchemasAtom,
} from "@/entities/schema/stores/schema.atom";

import { generateNodeSchema } from "../../../tests/fake/schema";

const ipPrefixSchema = generateNodeSchema({
kind: "BuiltinIPPrefix",
name: "IPPrefix",
namespace: "Builtin",
inherit_from: [IP_PREFIX_GENERIC],
});

describe("getObjectDetailsUrl", () => {
afterEach(() => {
store.set(nodeSchemasAtom, []);
store.set(genericSchemasAtom, []);
store.set(profileSchemasAtom, []);
store.set(templateSchemasAtom, []);
});

it("keeps the namespace context for an IP prefix link when passed as an override param", () => {
store.set(nodeSchemasAtom, [ipPrefixSchema]);

const url = getObjectDetailsUrl("BuiltinIPPrefix", "prefix-1", [
{ name: IPAM_QSP.NAMESPACE, value: "namespace-1" },
]);

expect(url).toContain("/ipam/BuiltinIPPrefix/prefix-1");
expect(url).toContain(`${IPAM_QSP.NAMESPACE}=namespace-1`);
});

it("does not add a namespace param to an IP prefix link when none is provided", () => {
store.set(nodeSchemasAtom, [ipPrefixSchema]);

const url = getObjectDetailsUrl("BuiltinIPPrefix", "prefix-1");

expect(url).toBe("/ipam/BuiltinIPPrefix/prefix-1");
});
});
Loading