Skip to content

Commit 9044b56

Browse files
committed
feat: time viewer for config changes
Fixes #93
1 parent 31c2216 commit 9044b56

File tree

7 files changed

+1206
-68
lines changed

7 files changed

+1206
-68
lines changed

package-lock.json

Lines changed: 1015 additions & 56 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"react-use-size": "^3.0.3",
9191
"react-windowed-select": "^5.2.0",
9292
"reactflow": "^11.11.3",
93+
"reaviz": "^15.18.5",
9394
"recharts": "2.1.12",
9495
"strip-ansi": "^7.1.0",
9596
"tailwindcss": "^3.4.1",
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { ConfigChange } from "@flanksource-ui/api/types/configs";
2+
import { Age } from "@flanksource-ui/ui/Age";
3+
import { ChangeIcon } from "@flanksource-ui/ui/Icons/ChangeIcon";
4+
import dayjs from "dayjs";
5+
import { useMemo } from "react";
6+
import {
7+
ChartShallowDataShape,
8+
ChartTooltip,
9+
LinearXAxis,
10+
LinearXAxisTickLabel,
11+
LinearXAxisTickLine,
12+
LinearXAxisTickSeries,
13+
LinearYAxis,
14+
LinearYAxisTickLabel,
15+
LinearYAxisTickSeries,
16+
ScatterPlot,
17+
ScatterPoint,
18+
ScatterSeries
19+
} from "reaviz";
20+
import ConfigsTypeIcon from "../ConfigsTypeIcon";
21+
22+
type ConfigChangesGraphProps = {
23+
changes: ConfigChange[];
24+
};
25+
26+
export default function ConfigChangesGraph({
27+
changes
28+
}: ConfigChangesGraphProps) {
29+
const data: ChartShallowDataShape[] = useMemo(() => {
30+
// group changes by config_id, then map to a scatter plot point
31+
return changes.map((change) => {
32+
return {
33+
key: dayjs(change.first_observed).toDate(),
34+
data: change.config?.name!,
35+
metadata: change
36+
};
37+
});
38+
}, [changes]);
39+
40+
return (
41+
<div className="h-full w-full">
42+
<ScatterPlot
43+
data={data}
44+
yAxis={
45+
<LinearYAxis
46+
type="category"
47+
tickSeries={
48+
<LinearYAxisTickSeries
49+
label={<LinearYAxisTickLabel padding={3} fontSize={12} />}
50+
/>
51+
}
52+
/>
53+
}
54+
xAxis={
55+
<LinearXAxis
56+
type="category"
57+
tickSeries={
58+
<LinearXAxisTickSeries
59+
line={<LinearXAxisTickLine position="center" />}
60+
label={<LinearXAxisTickLabel padding={3} />}
61+
/>
62+
}
63+
/>
64+
}
65+
series={
66+
<ScatterSeries
67+
point={
68+
<ScatterPoint
69+
tooltip={
70+
<ChartTooltip
71+
followCursor={true}
72+
content={(data: any) => {
73+
const count = (data.metadata as ConfigChange).count;
74+
const firstObserved = (data.metadata as ConfigChange)
75+
.first_observed;
76+
const summary = (data.metadata as ConfigChange).summary;
77+
78+
return (
79+
<div className="flex flex-col gap-1 rounded-lg bg-gray-100 p-2 text-black shadow-sm">
80+
<ConfigsTypeIcon
81+
config={(data.metadata as ConfigChange).config}
82+
>
83+
{(data.metadata as ConfigChange).config?.name}
84+
</ConfigsTypeIcon>
85+
<div className="flex flex-col gap-1">
86+
<div className="flex flex-row items-center justify-center gap-2 text-xs">
87+
<span className="flex flex-row items-center gap-1 font-semibold">
88+
<ChangeIcon change={data.metadata} />
89+
{(data.metadata as ConfigChange).change_type}
90+
</span>
91+
<span className="font-semibold">
92+
<Age
93+
from={
94+
(data.metadata as ConfigChange)
95+
.first_observed
96+
}
97+
/>
98+
{(count || 1) > 1 && (
99+
<span className="inline-block pl-1 text-gray-500">
100+
(x{count} over <Age from={firstObserved} />)
101+
</span>
102+
)}
103+
</span>
104+
</div>
105+
<p>{summary}</p>
106+
</div>
107+
</div>
108+
);
109+
}}
110+
/>
111+
}
112+
className={"bg-gray-500"}
113+
size={20}
114+
symbol={(data) => {
115+
const change = data.metadata;
116+
return <ChangeIcon change={change} />;
117+
}}
118+
/>
119+
}
120+
/>
121+
}
122+
/>
123+
</div>
124+
);
125+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Switch } from "@flanksource-ui/ui/FormControls/Switch";
2+
import { useAtom } from "jotai";
3+
import { atomWithStorage } from "jotai/utils";
4+
5+
export type GraphType = "Table" | "Graph";
6+
export const configChangesViewToggle = atomWithStorage<GraphType>(
7+
"configChangesViewToggleState",
8+
"Table",
9+
undefined,
10+
{
11+
getOnInit: true
12+
}
13+
);
14+
15+
export function useConfigChangesViewToggleState() {
16+
const [hideDeletedConfigs] = useAtom(configChangesViewToggle);
17+
return hideDeletedConfigs;
18+
}
19+
20+
export default function ConfigChangesViewToggle() {
21+
const [toggleValue, setToggleValue] = useAtom(configChangesViewToggle);
22+
23+
return (
24+
<div className="ml-auto flex flex-row items-center gap-2 px-2">
25+
<Switch
26+
options={["Table", "Graph"]}
27+
onChange={(v) => {
28+
setToggleValue(v as GraphType);
29+
}}
30+
value={toggleValue}
31+
/>
32+
</div>
33+
);
34+
}

src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { FilterBadge } from "../../ConfigChangesFilters/ConfigChangesFilters";
88
import { ConfigRelatedChangesToggles } from "../../ConfigChangesFilters/ConfigRelatedChangesToggles";
99
import { ConfigTagsDropdown } from "../../ConfigChangesFilters/ConfigTagsDropdown";
1010
import ConfigTypesTristateDropdown from "../../ConfigChangesFilters/ConfigTypesTristateDropdown";
11+
import ConfigChangesViewToggle from "../../ConfigChangesViewToggle";
1112
import { ConfigChangesToggledDeletedItems } from "./ConfigChangesToggledDeletedItems";
1213

1314
type ConfigChangeFiltersProps = {
@@ -22,19 +23,27 @@ export function ConfigRelatedChangesFilters({
2223
const arbitraryFilters = useConfigChangesArbitraryFilters();
2324

2425
return (
25-
<div className="flex flex-col gap-2">
26+
<div className="flex w-full flex-col gap-2">
2627
<FormikFilterForm
2728
paramsToReset={paramsToReset}
2829
filterFields={["configTypes", "changeType", "severity", "tags"]}
2930
>
30-
<div className={clsx("flex flex-wrap items-center gap-2", className)}>
31+
<div
32+
className={clsx(
33+
"flex w-full flex-wrap items-center gap-2",
34+
className
35+
)}
36+
>
3137
<ConfigTypesTristateDropdown />
3238
<ChangesTypesDropdown />
3339
<ConfigChangeSeverity />
3440
<ConfigTagsDropdown />
3541
<ConfigRelatedChangesToggles />
3642
<ConfigChangesDateRangeFilter paramsToReset={paramsToReset} />
3743
<ConfigChangesToggledDeletedItems />
44+
<div className="ml-auto flex flex-row gap-2">
45+
<ConfigChangesViewToggle />
46+
</div>
3847
</div>
3948
</FormikFilterForm>
4049
<div className="flex flex-wrap gap-2">

src/pages/config/ConfigChangesPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export function ConfigChangesPage() {
9999
) : (
100100
<>
101101
<ConfigChangeFilters paramsToReset={["page"]} />
102+
102103
<ConfigChangeTable
103104
data={changes}
104105
isLoading={isLoading || isRefetching}

src/pages/config/details/ConfigDetailsChangesPage.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { useGetConfigChangesByIDQuery } from "@flanksource-ui/api/query-hooks/useConfigChangesHooks";
2+
import ConfigChangesGraph from "@flanksource-ui/components/Configs/Changes/ConfigChangesGraph";
3+
import { useConfigChangesViewToggleState } from "@flanksource-ui/components/Configs/Changes/ConfigChangesViewToggle";
24
import { ConfigChangeTable } from "@flanksource-ui/components/Configs/Changes/ConfigChangeTable";
35
import { ConfigRelatedChangesFilters } from "@flanksource-ui/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters";
46
import { ConfigDetailsTabs } from "@flanksource-ui/components/Configs/ConfigDetailsTabs";
@@ -16,10 +18,13 @@ export function ConfigDetailsChangesPage() {
1618
const page = params.get("page") ?? "1";
1719
const pageSize = params.get("pageSize") ?? "200";
1820

19-
const { data, isLoading, error, refetch } = useGetConfigChangesByIDQuery({
20-
keepPreviousData: true,
21-
enabled: !!id
22-
});
21+
const { data, isLoading, error, refetch, isRefetching } =
22+
useGetConfigChangesByIDQuery({
23+
keepPreviousData: true,
24+
enabled: !!id
25+
});
26+
27+
const view = useConfigChangesViewToggleState();
2328

2429
const changes = (data?.changes ?? []).map((changes) => ({
2530
...changes,
@@ -77,12 +82,16 @@ export function ConfigDetailsChangesPage() {
7782
<div className="flex w-full flex-1 flex-col items-start gap-2 overflow-y-auto">
7883
<ConfigRelatedChangesFilters paramsToReset={["page"]} />
7984
<div className="flex w-full flex-1 flex-col overflow-y-auto">
80-
<ConfigChangeTable
81-
data={changes}
82-
isLoading={isLoading}
83-
numberOfPages={totalChangesPages}
84-
totalRecords={totalChanges}
85-
/>
85+
{view === "Graph" ? (
86+
<ConfigChangesGraph changes={changes} />
87+
) : (
88+
<ConfigChangeTable
89+
data={changes}
90+
isLoading={isLoading || isRefetching}
91+
totalRecords={totalChanges}
92+
numberOfPages={totalChangesPages}
93+
/>
94+
)}
8695
</div>
8796
</div>
8897
</div>

0 commit comments

Comments
 (0)