diff --git a/src/api/types/configs.ts b/src/api/types/configs.ts index 326d26f9e..742bdf748 100644 --- a/src/api/types/configs.ts +++ b/src/api/types/configs.ts @@ -56,7 +56,7 @@ export interface ConfigItem extends Timestamped, Avatar, Agent, Costs { config?: Record; description?: string | null; health?: "healthy" | "unhealthy" | "warn" | "unknown"; - related_id?: string; + related_ids?: string[]; agent?: { id: string; name: string; diff --git a/src/components/Configs/ConfigList/ConfigsRelationshipsTable.tsx b/src/components/Configs/ConfigList/ConfigsRelationshipsTable.tsx new file mode 100644 index 000000000..ff13fb437 --- /dev/null +++ b/src/components/Configs/ConfigList/ConfigsRelationshipsTable.tsx @@ -0,0 +1,148 @@ +import { ConfigItem } from "@flanksource-ui/api/types/configs"; +import { DataTable } from "@flanksource-ui/ui/DataTable"; +import { Row, SortingState, Updater } from "@tanstack/react-table"; +import { useCallback, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { configListColumns } from "./ConfigListColumn"; + +export interface Props { + data: ConfigItem[]; + isLoading: boolean; + columnsToHide?: string[]; + groupBy?: string[]; + expandAllRows?: boolean; +} + +export default function ConfigsRelationshipsTable({ + data, + isLoading, + columnsToHide = ["type"], + groupBy = [], + expandAllRows = false +}: Props) { + const [queryParams, setSearchParams] = useSearchParams({ + sortBy: "type", + sortOrder: "asc" + }); + + const navigate = useNavigate(); + + const sortField = queryParams.get("sortBy") ?? "type"; + + const isSortOrderDesc = + queryParams.get("sortOrder") === "desc" ? true : false; + + const determineSortColumnOrder = useCallback( + (sortState: SortingState): SortingState => { + const sortStateWithoutDeletedAt = sortState.filter( + (sort) => sort.id !== "deleted_at" + ); + return [{ id: "deleted_at", desc: false }, ...sortStateWithoutDeletedAt]; + }, + [] + ); + + const [sortBy, setSortBy] = useState(() => { + return sortField + ? determineSortColumnOrder([ + { + id: sortField, + desc: isSortOrderDesc + }, + ...(sortField !== "name" + ? [ + { + id: "name", + desc: isSortOrderDesc + } + ] + : []) + ]) + : determineSortColumnOrder([]); + }); + + const updateSortBy = useCallback( + (newSortBy: Updater) => { + const getSortBy = Array.isArray(newSortBy) + ? newSortBy + : newSortBy(sortBy); + // remove deleted_at from sort state, we don't want it to be save to the + // URL for the purpose of sorting + const sortStateWithoutDeleteAt = getSortBy.filter( + (state) => state.id !== "deleted_at" + ); + const { id: field, desc } = sortStateWithoutDeleteAt[0] ?? {}; + const order = desc ? "desc" : "asc"; + if (field && order && field !== "type" && order !== "asc") { + queryParams.set("sortBy", field); + queryParams.set("sortOrder", order); + } else { + queryParams.delete("sortBy"); + queryParams.delete("sortOrder"); + } + setSearchParams(queryParams); + const sortByValue = + typeof newSortBy === "function" ? newSortBy(sortBy) : newSortBy; + if (sortByValue.length > 0) { + setSortBy(determineSortColumnOrder(sortByValue)); + } + }, + [determineSortColumnOrder, queryParams, setSearchParams, sortBy] + ); + + const hiddenColumns = ["deleted_at", "changed", ...columnsToHide]; + + const determineRowClassNames = useCallback((row: Row) => { + if (row.getIsGrouped()) { + // check if the whole group is deleted + const allDeleted = row.getLeafRows().every((row) => { + if (row.original.deleted_at) { + return true; + } + return false; + }); + + if (allDeleted) { + return "text-gray-500"; + } + } else { + if (row.original.deleted_at) { + return "text-gray-500"; + } + } + return ""; + }, []); + + const handleRowClick = useCallback( + (row?: { original?: { id: string } }) => { + const id = row?.original?.id; + if (id) { + navigate(`/catalog/${id}`); + } + }, + [navigate] + ); + + return ( + + ); +} diff --git a/src/components/Configs/Graph/ConfigRelationshipGraph.tsx b/src/components/Configs/Graph/ConfigRelationshipGraph.tsx index 6bedc9159..defb04b01 100644 --- a/src/components/Configs/Graph/ConfigRelationshipGraph.tsx +++ b/src/components/Configs/Graph/ConfigRelationshipGraph.tsx @@ -13,7 +13,7 @@ export type ConfigGraphNodes = config: Pick< ConfigRelationships, | "id" - | "related_id" + | "related_ids" | "direction" | "type" | "name" @@ -33,7 +33,7 @@ export type ConfigGraphNodes = configs: Pick< ConfigRelationships, | "id" - | "related_id" + | "related_ids" | "direction" | "type" | "name" @@ -61,32 +61,35 @@ export function ConfigRelationshipGraph({ ); const edges: Edge[] = useMemo(() => { - return configsForGraph.map((config) => { + const e: Edge[] = []; + configsForGraph.forEach((config) => { if (config.nodeType === "config") { - return { - id: `${config.config.id}-related-to-${config.config.related_id}`, + config.config.related_ids?.forEach((related_id) => { + e.push({ + id: `${config.config.id}-related-to-${related_id}`, + source: + config.config.direction === "incoming" + ? config.config.id + : related_id, + target: + config.config.direction === "incoming" + ? related_id + : config.config.id + } satisfies Edge); + }); + } else { + e.push({ + id: `${config.id}-related-to-${config.related_id}`, source: - config.config.direction === "incoming" - ? config.config.id - : config.config.related_id!, + config.direction === "incoming" ? config.id : config.related_id!, target: - config.config.direction === "incoming" - ? config.config.related_id! - : config.config.id - } satisfies Edge; + config.direction === "incoming" ? config.related_id! : config.id + } satisfies Edge); } - - return { - id: `${config.id}-related-to-${config.related_id}`, - source: - config.direction === "incoming" ? config.id : config.related_id!, - target: config.direction === "incoming" ? config.related_id! : config.id - } satisfies Edge; }); + return e; }, [configsForGraph]); - console.log("configsForGraph", JSON.stringify(configsForGraph, null, 2)); - const nodes: Node[] = useMemo(() => { // break this down by config types return configsForGraph.map((config) => { diff --git a/src/components/Configs/Graph/__tests__/__snapshots__/formatConfigsForGraph.unit.test.ts.snap b/src/components/Configs/Graph/__tests__/__snapshots__/formatConfigsForGraph.unit.test.ts.snap index d68ddb60d..daa534e69 100644 --- a/src/components/Configs/Graph/__tests__/__snapshots__/formatConfigsForGraph.unit.test.ts.snap +++ b/src/components/Configs/Graph/__tests__/__snapshots__/formatConfigsForGraph.unit.test.ts.snap @@ -12,7 +12,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "f5c71026-fcab-4709-ae00-2edcc7e3086a", "name": "argo", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd", + ], "relation_type": "incoming", "status": "Healthy", "tags": { @@ -32,7 +34,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "33616137-3536-6531-6236-633631376130", "name": "aws", "ready": false, - "related_id": "f5c71026-fcab-4709-ae00-2edcc7e3086a", + "related_ids": [ + "f5c71026-fcab-4709-ae00-2edcc7e3086a", + ], "relation_type": "hard", "tags": { "cluster": "aws", @@ -64,7 +68,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "b9317816-adbb-462e-9fde-0578b6b8d10f", "name": "argo-argocd-applicationset-controller-d6667d499-86bck", "ready": true, - "related_id": "532fb54a-5325-431a-a618-0617020b1ab2", + "related_ids": [ + "532fb54a-5325-431a-a618-0617020b1ab2", + ], "relation_type": "hard", "status": "Running", "tags": { @@ -100,7 +106,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "51a1fccc-8117-4919-b249-3fb7cd8b6057", "name": "argo-argocd-applicationset-controller-764547bfd6", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -131,7 +139,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "492dd2e3-aee3-45aa-91b1-a12661a6d4a8", "name": "argo-argocd-applicationset-controller-5cdc847b88", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -150,7 +160,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "532fb54a-5325-431a-a618-0617020b1ab2", "name": "argo-argocd-applicationset-controller-d6667d499", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd", + ], "relation_type": "outgoing", "status": "Running", "tags": { @@ -181,7 +193,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "00f3ddcd-2f2a-4b3b-af4a-ba5091bd4501", "name": "argo-argocd-applicationset-controller-864b6c68", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -219,7 +233,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "51a1fccc-8117-4919-b249-3fb7cd8b6057", "name": "argo-argocd-applicationset-controller-764547bfd6", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -253,7 +269,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "492dd2e3-aee3-45aa-91b1-a12661a6d4a8", "name": "argo-argocd-applicationset-controller-5cdc847b88", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -275,7 +293,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "532fb54a-5325-431a-a618-0617020b1ab2", "name": "argo-argocd-applicationset-controller-d6667d499", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Running", "tags": { @@ -309,7 +329,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-a "id": "00f3ddcd-2f2a-4b3b-af4a-ba5091bd4501", "name": "argo-argocd-applicationset-controller-864b6c68", "ready": true, - "related_id": "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + "related_ids": [ + "9580a93f-7b74-437b-836f-9a419252f3dd-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -391,7 +413,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "f5c71026-fcab-4709-ae00-2edcc7e3086a", "name": "argo", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136", + ], "relation_type": "incoming", "status": "Healthy", "tags": { @@ -429,7 +453,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "8cc44af9-61f5-452f-94c9-ab33d8cb470c", "name": "argo-argocd-notifications-controller-54747746f8-k966m", "ready": true, - "related_id": "301902be-86af-4791-95fe-3f432c190276", + "related_ids": [ + "301902be-86af-4791-95fe-3f432c190276", + ], "relation_type": "hard", "status": "Running", "tags": { @@ -450,7 +476,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "33616137-3536-6531-6236-633631376130", "name": "aws", "ready": false, - "related_id": "f5c71026-fcab-4709-ae00-2edcc7e3086a", + "related_ids": [ + "f5c71026-fcab-4709-ae00-2edcc7e3086a", + ], "relation_type": "hard", "tags": { "cluster": "aws", @@ -484,7 +512,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "26a39243-80b7-4b82-941b-1ce15758b292", "name": "argo-argocd-notifications-controller-5b445ddccf", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -515,7 +545,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "92c4c176-6570-4a5d-bdcd-b2dbc7c9a26c", "name": "argo-argocd-notifications-controller-777f7984cd", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -546,7 +578,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "a15f91b1-dbee-4884-89af-9cd5370c2644", "name": "argo-argocd-notifications-controller-5f47765dfd", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -572,7 +606,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "301902be-86af-4791-95fe-3f432c190276", "name": "argo-argocd-notifications-controller-54747746f8", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136", + ], "relation_type": "outgoing", "status": "Running", "tags": { @@ -610,7 +646,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "26a39243-80b7-4b82-941b-1ce15758b292", "name": "argo-argocd-notifications-controller-5b445ddccf", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -644,7 +682,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "92c4c176-6570-4a5d-bdcd-b2dbc7c9a26c", "name": "argo-argocd-notifications-controller-777f7984cd", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -678,7 +718,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "a15f91b1-dbee-4884-89af-9cd5370c2644", "name": "argo-argocd-notifications-controller-5f47765dfd", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Scaled to Zero", "tags": { @@ -707,7 +749,9 @@ exports[`prepareConfigsForGraph should return formatted config for argo-argocd-n "id": "301902be-86af-4791-95fe-3f432c190276", "name": "argo-argocd-notifications-controller-54747746f8", "ready": true, - "related_id": "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + "related_ids": [ + "b59c1752-1be4-4a51-9f84-8225ebe7c136-root-Kubernetes::ReplicaSet", + ], "relation_type": "outgoing", "status": "Running", "tags": { diff --git a/src/components/Configs/Graph/__tests__/formatConfigsForGraph.unit.test.ts b/src/components/Configs/Graph/__tests__/formatConfigsForGraph.unit.test.ts index 795016bb2..6f4c0d642 100644 --- a/src/components/Configs/Graph/__tests__/formatConfigsForGraph.unit.test.ts +++ b/src/components/Configs/Graph/__tests__/formatConfigsForGraph.unit.test.ts @@ -15,32 +15,32 @@ import { // Test data for ConfigRelationships const configs: Pick< ConfigRelationships, - "id" | "related_id" | "direction" | "type" + "id" | "related_ids" | "direction" | "type" >[] = [ { id: "1", - related_id: "2", + related_ids: ["2"], direction: "outgoing", type: "type1" }, { id: "2", - related_id: "3", + related_ids: ["3"], direction: "incoming", type: "type2" }, { id: "3", - related_id: "1", + related_ids: ["1"], direction: "outgoing", type: "type3" } ]; // Test data for ConfigItem -const currentConfig: Pick = { +const currentConfig: Pick = { id: "4", - related_id: "1", + related_ids: ["1"], type: "type4" }; @@ -52,7 +52,9 @@ test("prepareConfigsForGraph should return transformedConfigs", () => { "config": { "direction": "outgoing", "id": "1", - "related_id": "2", + "related_ids": [ + "2", + ], "type": "type1", }, "nodeType": "config", @@ -61,7 +63,9 @@ test("prepareConfigsForGraph should return transformedConfigs", () => { "config": { "direction": "incoming", "id": "2", - "related_id": "3", + "related_ids": [ + "3", + ], "type": "type2", }, "nodeType": "config", @@ -70,7 +74,9 @@ test("prepareConfigsForGraph should return transformedConfigs", () => { "config": { "direction": "outgoing", "id": "3", - "related_id": "1", + "related_ids": [ + "1", + ], "type": "type3", }, "nodeType": "config", @@ -78,7 +84,9 @@ test("prepareConfigsForGraph should return transformedConfigs", () => { { "config": { "id": "4", - "related_id": "1", + "related_ids": [ + "1", + ], "type": "type4", }, "nodeType": "config", diff --git a/src/components/Configs/Graph/__tests__/mocks/argo-argocd-applicationset-controller.ts b/src/components/Configs/Graph/__tests__/mocks/argo-argocd-applicationset-controller.ts index e3da62d8e..258c423f6 100644 --- a/src/components/Configs/Graph/__tests__/mocks/argo-argocd-applicationset-controller.ts +++ b/src/components/Configs/Graph/__tests__/mocks/argo-argocd-applicationset-controller.ts @@ -7,7 +7,7 @@ export const applicationSetController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "9580a93f-7b74-437b-836f-9a419252f3dd", + related_ids: ["9580a93f-7b74-437b-836f-9a419252f3dd"], depth: 1, tags: { cluster: "aws", @@ -38,7 +38,7 @@ export const applicationSetController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "9580a93f-7b74-437b-836f-9a419252f3dd", + related_ids: ["9580a93f-7b74-437b-836f-9a419252f3dd"], depth: 1, tags: { cluster: "aws", @@ -70,7 +70,7 @@ export const applicationSetController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "9580a93f-7b74-437b-836f-9a419252f3dd", + related_ids: ["9580a93f-7b74-437b-836f-9a419252f3dd"], depth: 1, tags: { cluster: "aws", @@ -90,7 +90,7 @@ export const applicationSetController = [ type: "Kubernetes::Namespace", relation_type: "incoming", direction: "incoming", - related_id: "9580a93f-7b74-437b-836f-9a419252f3dd", + related_ids: ["9580a93f-7b74-437b-836f-9a419252f3dd"], depth: 1, tags: { cluster: "aws" @@ -109,7 +109,7 @@ export const applicationSetController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "9580a93f-7b74-437b-836f-9a419252f3dd", + related_ids: ["9580a93f-7b74-437b-836f-9a419252f3dd"], depth: 1, tags: { cluster: "aws", @@ -141,7 +141,7 @@ export const applicationSetController = [ type: "Kubernetes::Cluster", relation_type: "hard", direction: "incoming", - related_id: "f5c71026-fcab-4709-ae00-2edcc7e3086a", + related_ids: ["f5c71026-fcab-4709-ae00-2edcc7e3086a"], depth: 2, tags: { cluster: "aws" @@ -159,7 +159,7 @@ export const applicationSetController = [ type: "Kubernetes::Pod", relation_type: "hard", direction: "outgoing", - related_id: "532fb54a-5325-431a-a618-0617020b1ab2", + related_ids: ["532fb54a-5325-431a-a618-0617020b1ab2"], depth: 2, tags: { cluster: "aws", diff --git a/src/components/Configs/Graph/__tests__/mocks/argo-argocd-notifications-controller.ts b/src/components/Configs/Graph/__tests__/mocks/argo-argocd-notifications-controller.ts index d78b94247..bdf2b4822 100644 --- a/src/components/Configs/Graph/__tests__/mocks/argo-argocd-notifications-controller.ts +++ b/src/components/Configs/Graph/__tests__/mocks/argo-argocd-notifications-controller.ts @@ -7,7 +7,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "b59c1752-1be4-4a51-9f84-8225ebe7c136", + related_ids: ["b59c1752-1be4-4a51-9f84-8225ebe7c136"], depth: 1, tags: { cluster: "aws", @@ -39,7 +39,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "b59c1752-1be4-4a51-9f84-8225ebe7c136", + related_ids: ["b59c1752-1be4-4a51-9f84-8225ebe7c136"], depth: 1, tags: { cluster: "aws", @@ -71,7 +71,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "b59c1752-1be4-4a51-9f84-8225ebe7c136", + related_ids: ["b59c1752-1be4-4a51-9f84-8225ebe7c136"], depth: 1, tags: { cluster: "aws", @@ -103,7 +103,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::Namespace", relation_type: "incoming", direction: "incoming", - related_id: "b59c1752-1be4-4a51-9f84-8225ebe7c136", + related_ids: ["b59c1752-1be4-4a51-9f84-8225ebe7c136"], depth: 1, tags: { cluster: "aws" @@ -122,7 +122,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::ReplicaSet", relation_type: "outgoing", direction: "outgoing", - related_id: "b59c1752-1be4-4a51-9f84-8225ebe7c136", + related_ids: ["b59c1752-1be4-4a51-9f84-8225ebe7c136"], depth: 1, tags: { cluster: "aws", @@ -149,7 +149,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::Pod", relation_type: "hard", direction: "outgoing", - related_id: "301902be-86af-4791-95fe-3f432c190276", + related_ids: ["301902be-86af-4791-95fe-3f432c190276"], depth: 2, tags: { cluster: "aws", @@ -186,7 +186,7 @@ export const argoArgocdNotificationsController = [ type: "Kubernetes::Cluster", relation_type: "hard", direction: "incoming", - related_id: "f5c71026-fcab-4709-ae00-2edcc7e3086a", + related_ids: ["f5c71026-fcab-4709-ae00-2edcc7e3086a"], depth: 2, tags: { cluster: "aws" diff --git a/src/components/Configs/Graph/formatConfigsForGraph.ts b/src/components/Configs/Graph/formatConfigsForGraph.ts index 097f1e47d..4abe55ddd 100644 --- a/src/components/Configs/Graph/formatConfigsForGraph.ts +++ b/src/components/Configs/Graph/formatConfigsForGraph.ts @@ -7,36 +7,40 @@ import { ConfigGraphNodes } from "./ConfigRelationshipGraph"; export function checkIfConfigHasMoreThan3Siblings( configList: Pick< ConfigRelationships, - "id" | "related_id" | "direction" | "type" + "id" | "related_ids" | "direction" | "type" >[], configItem: Pick< ConfigRelationships, - "id" | "related_id" | "direction" | "type" + "id" | "related_ids" | "direction" | "type" > ) { return ( - configList.filter( - (config) => - config.related_id === configItem.related_id && - config.direction === configItem.direction - ).length > 3 + configList.filter((config) => { + let relatedIdExistsInBoth = false; + config.related_ids?.forEach((relatedId) => { + relatedIdExistsInBoth = + configItem.related_ids?.includes(relatedId) ?? false; + }); + return relatedIdExistsInBoth && config.direction === configItem.direction; + }).length > 3 ); } export function shouldCreateRootIntermediaryNode( configList: Pick< ConfigRelationships, - "id" | "related_id" | "direction" | "type" + "id" | "related_ids" | "direction" | "type" >[], configItem: Pick< ConfigRelationships, - "id" | "related_id" | "direction" | "type" + "id" | "related_ids" | "direction" | "type" > ) { const types = configList .filter( (config) => - config.related_id === configItem.id && config.direction === "outgoing" + config.related_ids?.includes(configItem.id) && + config.direction === "outgoing" ) .reduce((acc, config) => { acc.add(config.type); @@ -49,9 +53,9 @@ export function shouldCreateRootIntermediaryNode( export function prepareConfigsForGraph( configs: Pick< ConfigRelationships, - "id" | "related_id" | "direction" | "type" + "id" | "related_ids" | "direction" | "type" >[], - currentConfig: Pick + currentConfig: Pick ) { const transformedConfigs: ConfigGraphNodes[] = []; @@ -78,7 +82,7 @@ export function prepareConfigsForGraph( // intermediary node id const childrenConfigs = allConfigs.filter( (configItem) => - configItem.related_id === config.id && + configItem.related_ids?.includes(config.id) && configItem.direction === "outgoing" ); @@ -124,7 +128,7 @@ export function prepareConfigsForGraph( // determine the number of children the config has, based on the type const siblingsConfigByType = allConfigs.filter( (configItem) => - configItem.related_id === config.id && + configItem.related_ids?.includes(config.id) && configItem.direction === "outgoing" && configItem.type === child.type ); @@ -162,7 +166,7 @@ export function prepareConfigsForGraph( nodeType: "config", config: { ...(child as ConfigRelationships), - related_id: intermediaryNodeID + related_ids: [intermediaryNodeID] } }); } else { @@ -172,7 +176,7 @@ export function prepareConfigsForGraph( nodeType: "config", config: { ...(child as ConfigRelationships), - related_id: rootConfigIntermediaryNodeID ?? config.id + related_ids: [rootConfigIntermediaryNodeID ?? config.id] } } satisfies ConfigGraphNodes); } diff --git a/src/pages/config/details/ConfigDetailsRelationshipsPage.tsx b/src/pages/config/details/ConfigDetailsRelationshipsPage.tsx index 374ffd89b..2581be322 100644 --- a/src/pages/config/details/ConfigDetailsRelationshipsPage.tsx +++ b/src/pages/config/details/ConfigDetailsRelationshipsPage.tsx @@ -2,7 +2,7 @@ import { useGetConfigByIdQuery } from "@flanksource-ui/api/query-hooks"; import useConfigRelationshipsQuery from "@flanksource-ui/api/query-hooks/useConfigRelationshipsQuery"; import { ConfigRelationships } from "@flanksource-ui/api/types/configs"; import { ConfigDetailsTabs } from "@flanksource-ui/components/Configs/ConfigDetailsTabs"; -import ConfigsTable from "@flanksource-ui/components/Configs/ConfigList/ConfigsTable"; +import ConfigsRelationshipsTable from "@flanksource-ui/components/Configs/ConfigList/ConfigsRelationshipsTable"; import ConfigRelationshipFilterBar from "@flanksource-ui/components/Configs/ConfigRelationshipFilterBar"; import { useConfigGraphTableToggleViewValue } from "@flanksource-ui/components/Configs/ConfigsListFilters/ConfigGraphTableToggle"; import { ConfigRelationshipGraph } from "@flanksource-ui/components/Configs/Graph/ConfigRelationshipGraph"; @@ -80,12 +80,11 @@ export function ConfigDetailsRelationshipsPage() { )} ) : ( - )} diff --git a/src/ui/Graphs/RelationshipGraph.tsx b/src/ui/Graphs/RelationshipGraph.tsx index a0e1bb1dc..2ec55fa85 100644 --- a/src/ui/Graphs/RelationshipGraph.tsx +++ b/src/ui/Graphs/RelationshipGraph.tsx @@ -47,7 +47,7 @@ export function RelationshipGraph({ nodes: propNodes, edges: propEdges }: ConfigGraphProps) { - const { setViewport, fitView } = useReactFlow(); + const { setViewport, fitView, getZoom } = useReactFlow(); // During the initial layout setup, we want to align the nodes vertically // centered and horizontally aligned to the left. This is done only once and @@ -83,14 +83,19 @@ export function RelationshipGraph({ setIsInitialLayoutSetupDone(true); if (node.data.expandable) { + // Set center is not working as expected, fitView with maxZoom and + // minZoom set is a workaround to center the node without messing up the + // zoom level fitView({ nodes: [node], - duration: 200 + duration: 200, + maxZoom: getZoom(), + minZoom: getZoom() }); } setTimeout(() => setShowScreen(false), 150); }, - [fitView] + [fitView, getZoom] ); const sendNodesEdgePoint = useCallback(() => {