Skip to content

Commit 45e8440

Browse files
committed
feat: when a catalog has one topology, show only topology card
Fixes #2174 fix: show loading animation, until card is shown or not chore: wip fix: fix incorrect import fix: change how merged pages work
1 parent 0dd788c commit 45e8440

19 files changed

+533
-258
lines changed

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
265265
<Route
266266
path=":id"
267267
element={withAuthorizationAccessCheck(
268-
<TopologyPage />,
268+
<TopologyPage></TopologyPage>,
269269
tables.database,
270270
"read",
271271
true
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { useRef } from "react";
3+
import { useSearchParams } from "react-router-dom";
4+
import { LoadingBarRef } from "react-top-loading-bar";
5+
import { getTopology } from "../services/topology";
6+
7+
export default function useTopologyByIDQuery(id: string) {
8+
const [searchParams] = useSearchParams({
9+
sortBy: "status",
10+
sortOrder: "desc"
11+
});
12+
13+
const selectedLabel = searchParams.get("labels") ?? "All";
14+
const team = searchParams.get("team") ?? "All";
15+
const topologyType = searchParams.get("type") ?? "All";
16+
const healthStatus = searchParams.get("status") ?? "All";
17+
const sortBy = searchParams.get("sortBy") ?? "status";
18+
const sortOrder = searchParams.get("sortOrder") ?? "desc";
19+
const agentId = searchParams.get("agent_id") ?? undefined;
20+
const showHiddenComponents =
21+
searchParams.get("showHiddenComponents") ?? undefined;
22+
23+
const loadingBarRef = useRef<LoadingBarRef>(null);
24+
25+
return useQuery(
26+
[
27+
"topologies",
28+
id,
29+
healthStatus,
30+
team,
31+
selectedLabel,
32+
topologyType,
33+
showHiddenComponents,
34+
sortBy,
35+
sortOrder,
36+
agentId
37+
],
38+
() => {
39+
loadingBarRef.current?.continuousStart();
40+
const apiParams = {
41+
id,
42+
status: healthStatus,
43+
type: topologyType,
44+
team: team,
45+
labels: selectedLabel,
46+
sortBy,
47+
sortOrder,
48+
// only flatten, if topology type is set
49+
...(topologyType &&
50+
topologyType.toString().toLowerCase() !== "all" && {
51+
flatten: true
52+
}),
53+
hidden: showHiddenComponents === "no" ? false : undefined,
54+
agent_id: agentId
55+
};
56+
return getTopology(apiParams);
57+
},
58+
{
59+
onSettled: () => {
60+
loadingBarRef.current?.complete();
61+
}
62+
}
63+
);
64+
}

src/api/services/configs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ConfigChange,
1212
ConfigHealthCheckView,
1313
ConfigItem,
14+
ConfigItemDetails,
1415
ConfigSummary,
1516
ConfigTypeRelationships
1617
} from "../types/configs";
@@ -144,7 +145,7 @@ export const getAllChanges = (
144145
};
145146

146147
export const getConfig = (id: string) =>
147-
resolvePostGrestRequestWithPagination<ConfigItem[]>(
148+
resolvePostGrestRequestWithPagination<ConfigItemDetails[]>(
148149
ConfigDB.get(`/config_detail?id=eq.${id}&select=*,config_scrapers(id,name)`)
149150
);
150151

src/api/types/configs.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Agent, Avatar, CreatedAt, Timestamped } from "../traits";
22
import { HealthCheckSummary } from "./health";
3+
import { Topology } from "./topology";
34

45
export interface ConfigChange extends CreatedAt {
56
id: string;
@@ -69,13 +70,6 @@ export interface ConfigItem extends Timestamped, Avatar, Agent, Costs {
6970
id: string;
7071
name: string;
7172
};
72-
summary?: {
73-
relationships?: number;
74-
analysis?: number;
75-
changes?: number;
76-
playbook_runs?: number;
77-
checks?: number;
78-
};
7973
properties?: {
8074
icon: string;
8175
name: string;
@@ -87,6 +81,17 @@ export interface ConfigItem extends Timestamped, Avatar, Agent, Costs {
8781
last_scraped_time?: string;
8882
}
8983

84+
export interface ConfigItemDetails extends ConfigItem {
85+
summary?: {
86+
relationships?: number;
87+
analysis?: number;
88+
changes?: number;
89+
playbook_runs?: number;
90+
checks?: number;
91+
};
92+
components?: Topology[];
93+
}
94+
9095
export interface ConfigItemGraphData extends ConfigItem {
9196
expanded?: boolean;
9297
expandable?: boolean;

src/api/types/topology.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Agent, Namespaced, Timestamped } from "../traits";
22
import { CostsData, Severity, ValueType } from "./common";
3+
import { ConfigItem } from "./configs";
34
import { HealthCheckSummary } from "./health";
45
import { IncidentType } from "./incident";
56
import { User } from "./users";
@@ -68,6 +69,8 @@ export interface Topology extends Component, CostsData, Agent {
6869
children?: string[];
6970
is_leaf?: boolean;
7071
description?: string;
72+
config_id?: string;
73+
configs?: Pick<ConfigItem, "name" | "id" | "type">[];
7174
}
7275

7376
export type ComponentTeamItem = {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import useTopologyByIDQuery from "@flanksource-ui/api/query-hooks/useTopologyByIDQuery";
2+
import IncidentDetailsPageSkeletonLoader from "@flanksource-ui/ui/SkeletonLoader/IncidentDetailsPageSkeletonLoader";
3+
import { TopologyCard } from "../Topology/TopologyCard";
4+
import { useTopologyCardWidth } from "../Topology/TopologyPopover/topologyPreference";
5+
6+
type ConfigComponentsProps = {
7+
topologyId: string;
8+
};
9+
10+
export default function ConfigComponents({
11+
topologyId
12+
}: ConfigComponentsProps) {
13+
const { data, isLoading } = useTopologyByIDQuery(topologyId);
14+
15+
const [topologyCardSize] = useTopologyCardWidth();
16+
17+
return (
18+
<div className="flex w-full flex-1 overflow-y-auto">
19+
<div className="flex w-full flex-wrap p-4">
20+
{isLoading && data ? (
21+
<IncidentDetailsPageSkeletonLoader />
22+
) : (
23+
data?.components?.[0].components?.map((component) => (
24+
<TopologyCard
25+
key={component.id}
26+
topology={component}
27+
size={topologyCardSize}
28+
menuPosition="absolute"
29+
/>
30+
))
31+
)}
32+
</div>
33+
</div>
34+
);
35+
}

src/components/Configs/ConfigDetailsTabs.tsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SearchLayout } from "@flanksource-ui/ui/Layout/SearchLayout";
2+
import IncidentDetailsPageSkeletonLoader from "@flanksource-ui/ui/SkeletonLoader/IncidentDetailsPageSkeletonLoader";
23
import clsx from "clsx";
34
import { useAtom } from "jotai";
45
import { ReactNode } from "react";
@@ -9,21 +10,24 @@ import { Head } from "../../ui/Head";
910
import { refreshButtonClickedTrigger } from "../../ui/SlidingSideBar/SlidingSideBar";
1011
import TabbedLinks from "../../ui/Tabs/TabbedLinks";
1112
import { ErrorBoundary } from "../ErrorBoundary";
13+
import ConfigComponents from "./ConfigComponents";
1214
import { useConfigDetailsTabs } from "./ConfigTabsLinks";
1315
import ConfigSidebar from "./Sidebar/ConfigSidebar";
1416

15-
type ConfigDetailsTabsProps = {
17+
export type ConfigTab =
18+
| "Catalog"
19+
| "Changes"
20+
| "Insights"
21+
| "Relationships"
22+
| "Playbooks"
23+
| "Checks";
24+
25+
export type ConfigDetailsTabsProps = {
1626
refetch?: () => void;
1727
children: ReactNode;
1828
isLoading?: boolean;
1929
pageTitlePrefix: string;
20-
activeTabName:
21-
| "Catalog"
22-
| "Changes"
23-
| "Insights"
24-
| "Relationships"
25-
| "Playbooks"
26-
| "Checks";
30+
activeTabName: ConfigTab;
2731
className?: string;
2832
};
2933

@@ -69,21 +73,30 @@ export function ConfigDetailsTabs({
6973
loading={isLoading}
7074
contentClass="p-0 h-full overflow-y-hidden"
7175
>
72-
<div className={`flex h-full flex-row`}>
73-
<div className="flex flex-1 flex-col overflow-x-auto">
74-
<TabbedLinks
75-
activeTabName={activeTabName}
76-
tabLinks={configTabList}
77-
contentClassName={clsx(
78-
"bg-white border border-t-0 border-gray-300 flex-1 overflow-y-auto",
79-
className
76+
{isLoadingConfig ? (
77+
<IncidentDetailsPageSkeletonLoader />
78+
) : (
79+
<div className={`flex h-full flex-row bg-gray-100`}>
80+
<div className="flex h-full flex-1 flex-col">
81+
{configItem?.components && configItem.components.length === 1 && (
82+
<ConfigComponents topologyId={configItem.components[0].id} />
8083
)}
81-
>
82-
<ErrorBoundary>{children}</ErrorBoundary>
83-
</TabbedLinks>
84+
<TabbedLinks
85+
activeTabName={activeTabName}
86+
tabLinks={configTabList}
87+
contentClassName={clsx(
88+
"bg-white border border-t-0 border-gray-300 flex-1 overflow-y-auto",
89+
className
90+
)}
91+
>
92+
<ErrorBoundary>{children}</ErrorBoundary>
93+
</TabbedLinks>
94+
</div>
95+
<ConfigSidebar
96+
topologyProperties={configItem?.components?.[0]?.properties}
97+
/>
8498
</div>
85-
<ConfigSidebar />
86-
</div>
99+
)}
87100
</SearchLayout>
88101
</>
89102
);

src/components/Configs/ConfigTabsLinks.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Badge } from "@flanksource-ui/ui/Badge/Badge";
22
import { useParams } from "react-router-dom";
3-
import { ConfigItem } from "../../api/types/configs";
3+
import { ConfigItemDetails } from "../../api/types/configs";
44

5-
export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]) {
5+
export function useConfigDetailsTabs(
6+
countSummary?: ConfigItemDetails["summary"],
7+
basePath: `/${string}` = "/catalog"
8+
) {
69
const { id } = useParams<{ id: string }>();
710

811
return [
9-
{ label: "Config", key: "Catalog", path: `/catalog/${id}` },
12+
{ label: "Config", key: "Catalog", path: `${basePath}/${id}` },
1013
{
1114
label: (
1215
<>
@@ -15,7 +18,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]) {
1518
</>
1619
),
1720
key: "Changes",
18-
path: `/catalog/${id}/changes`
21+
path: `${basePath}/${id}/changes`
1922
},
2023
{
2124
label: (
@@ -25,7 +28,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]) {
2528
</>
2629
),
2730
key: "Insights",
28-
path: `/catalog/${id}/insights`
31+
path: `${basePath}/${id}/insights`
2932
},
3033
{
3134
label: (
@@ -35,7 +38,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]) {
3538
</>
3639
),
3740
key: "Relationships",
38-
path: `/catalog/${id}/relationships`
41+
path: `${basePath}/${id}/relationships`
3942
},
4043
{
4144
label: (
@@ -45,7 +48,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]) {
4548
</>
4649
),
4750
key: "Playbooks",
48-
path: `/catalog/${id}/playbooks`
51+
path: `${basePath}/${id}/playbooks`
4952
},
5053
{
5154
label: (
@@ -55,7 +58,7 @@ export function useConfigDetailsTabs(countSummary?: ConfigItem["summary"]) {
5558
</>
5659
),
5760
key: "Checks",
58-
path: `/catalog/${id}/checks`
61+
path: `${basePath}/${id}/checks`
5962
}
6063
];
6164
}

src/components/Configs/Sidebar/ConfigDetails.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useGetConfigByIdQuery } from "@flanksource-ui/api/query-hooks";
22
import { isCostsEmpty } from "@flanksource-ui/api/types/configs";
3+
import { Topology } from "@flanksource-ui/api/types/topology";
34
import { formatProperties } from "@flanksource-ui/components/Topology/Sidebar/Utils/formatProperties";
45
import { Age } from "@flanksource-ui/ui/Age";
56
import TextSkeletonLoader from "@flanksource-ui/ui/SkeletonLoader/TextSkeletonLoader";
@@ -17,9 +18,10 @@ import { formatConfigLabels } from "./Utils/formatConfigLabels";
1718

1819
type Props = {
1920
configId: string;
21+
topologyProperties?: Topology["properties"];
2022
};
2123

22-
export function ConfigDetails({ configId }: Props) {
24+
export function ConfigDetails({ configId, topologyProperties }: Props) {
2325
const {
2426
data: configDetails,
2527
isLoading,
@@ -67,6 +69,15 @@ export function ConfigDetails({ configId }: Props) {
6769
[configDetails]
6870
);
6971

72+
const formattedTopologyProperties = useMemo(() => {
73+
if (topologyProperties) {
74+
return formatProperties({
75+
properties: topologyProperties
76+
});
77+
}
78+
return undefined;
79+
}, [topologyProperties]);
80+
7081
const isLastScrappedMoreThan1Hour = useMemo(() => {
7182
if (!configDetails?.last_scraped_time) {
7283
return false;
@@ -179,6 +190,10 @@ export function ConfigDetails({ configId }: Props) {
179190
]}
180191
/>
181192

193+
{formattedTopologyProperties && (
194+
<DisplayGroupedProperties items={formattedTopologyProperties} />
195+
)}
196+
182197
<DisplayDetailsRow
183198
items={[
184199
{

0 commit comments

Comments
 (0)