Skip to content

Commit b49cae2

Browse files
committed
feat: time viewer for config changes
Fixes #93
1 parent 171bb82 commit b49cae2

File tree

8 files changed

+1279
-112
lines changed

8 files changed

+1279
-112
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",

src/components/Configs/Changes/ConfigChangeTable.tsx

Lines changed: 14 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useGetConfigChangesById } from "@flanksource-ui/api/query-hooks/useGetConfigChangesByConfigChangeIdQuery";
21
import { ConfigChange } from "@flanksource-ui/api/types/configs";
32
import GetUserAvatar from "@flanksource-ui/components/Users/GetUserAvatar";
43
import { Age } from "@flanksource-ui/ui/Age";
@@ -7,10 +6,8 @@ import { ChangeIcon } from "@flanksource-ui/ui/Icons/ChangeIcon";
76
import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
87
import { CellContext } from "@tanstack/react-table";
98
import { MRT_ColumnDef } from "mantine-react-table";
10-
import { useState } from "react";
119
import ConfigLink from "../ConfigLink/ConfigLink";
1210
import MRTConfigListTagsCell from "../ConfigList/Cells/MRTConfigListTagsCell";
13-
import { ConfigDetailChangeModal } from "./ConfigDetailsChanges/ConfigDetailsChanges";
1411

1512
export const paramsToReset = {
1613
configChanges: ["pageIndex", "pageSize"]
@@ -190,53 +187,27 @@ type ConfigChangeTableProps = {
190187
isLoading?: boolean;
191188
totalRecords: number;
192189
numberOfPages: number;
190+
onRowClick?: (row: ConfigChange) => void;
193191
};
194192

195193
export function ConfigChangeTable({
196194
data,
197195
isLoading,
198196
totalRecords,
199-
numberOfPages
197+
numberOfPages,
198+
onRowClick = () => {}
200199
}: ConfigChangeTableProps) {
201-
const [selectedConfigChange, setSelectedConfigChange] =
202-
useState<ConfigChange>();
203-
const [modalIsOpen, setModalIsOpen] = useState(false);
204-
205-
const { data: configChange, isLoading: changeLoading } =
206-
useGetConfigChangesById(
207-
selectedConfigChange?.id!,
208-
selectedConfigChange?.config_id!,
209-
{
210-
enabled: !!selectedConfigChange
211-
}
212-
);
213-
214200
return (
215-
<>
216-
<MRTDataTable
217-
columns={configChangesColumn}
218-
data={data}
219-
isLoading={isLoading}
220-
enableServerSideSorting
221-
totalRowCount={totalRecords}
222-
manualPageCount={numberOfPages}
223-
enableServerSidePagination
224-
disableHiding
225-
onRowClick={(row) => {
226-
setSelectedConfigChange(row);
227-
setModalIsOpen(true);
228-
}}
229-
/>
230-
{configChange && (
231-
<ConfigDetailChangeModal
232-
isLoading={changeLoading}
233-
open={modalIsOpen}
234-
setOpen={(open) => {
235-
setModalIsOpen(open);
236-
}}
237-
changeDetails={configChange}
238-
/>
239-
)}
240-
</>
201+
<MRTDataTable
202+
columns={configChangesColumn}
203+
data={data}
204+
isLoading={isLoading}
205+
enableServerSideSorting
206+
totalRowCount={totalRecords}
207+
manualPageCount={numberOfPages}
208+
enableServerSidePagination
209+
disableHiding
210+
onRowClick={onRowClick}
211+
/>
241212
);
242213
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 { relativeDateTime } from "@flanksource-ui/utils/date";
5+
import dayjs from "dayjs";
6+
import { useMemo } from "react";
7+
import {
8+
ChartShallowDataShape,
9+
ChartTooltip,
10+
ChartZoomPan,
11+
LinearXAxis,
12+
LinearXAxisTickLabel,
13+
LinearXAxisTickLine,
14+
LinearXAxisTickSeries,
15+
LinearYAxis,
16+
LinearYAxisTickLabel,
17+
LinearYAxisTickSeries,
18+
ScatterPlot,
19+
ScatterPoint,
20+
ScatterSeries
21+
} from "reaviz";
22+
import ConfigsTypeIcon from "../ConfigsTypeIcon";
23+
24+
type ConfigChangesGraphProps = {
25+
changes: ConfigChange[];
26+
onItemClicked?: (change: ConfigChange) => void;
27+
};
28+
29+
export default function ConfigChangesGraph({
30+
changes,
31+
onItemClicked = () => {}
32+
}: ConfigChangesGraphProps) {
33+
const data: ChartShallowDataShape[] = useMemo(() => {
34+
// group changes by config_id, then map to a scatter plot point
35+
return changes.map((change) => {
36+
return {
37+
key: dayjs(change.first_observed).toDate(),
38+
data: change.config?.name!,
39+
metadata: change
40+
};
41+
});
42+
}, [changes]);
43+
44+
return (
45+
<div className="h-full w-full">
46+
<ScatterPlot
47+
data={data}
48+
zoomPan={<ChartZoomPan />}
49+
yAxis={
50+
<LinearYAxis
51+
type="category"
52+
tickSeries={
53+
<LinearYAxisTickSeries
54+
label={
55+
<LinearYAxisTickLabel
56+
padding={3}
57+
fontSize={12}
58+
format={(v) => {
59+
return v;
60+
}}
61+
/>
62+
}
63+
/>
64+
}
65+
/>
66+
}
67+
xAxis={
68+
<LinearXAxis
69+
type="time"
70+
scale={{ type: "time" }}
71+
tickSeries={
72+
<LinearXAxisTickSeries
73+
line={<LinearXAxisTickLine position="center" />}
74+
label={
75+
<LinearXAxisTickLabel
76+
padding={3}
77+
format={(v) => {
78+
return relativeDateTime(v);
79+
}}
80+
/>
81+
}
82+
/>
83+
}
84+
/>
85+
}
86+
series={
87+
<ScatterSeries
88+
point={
89+
<ScatterPoint
90+
tooltip={
91+
<ChartTooltip
92+
followCursor={true}
93+
content={(data: any) => {
94+
const count = (data.metadata as ConfigChange).count;
95+
const firstObserved = (data.metadata as ConfigChange)
96+
.first_observed;
97+
const summary = (data.metadata as ConfigChange).summary;
98+
99+
return (
100+
<div className="flex flex-col gap-1 rounded-lg bg-gray-100 p-2 text-black shadow-sm">
101+
<ConfigsTypeIcon
102+
config={(data.metadata as ConfigChange).config}
103+
>
104+
{(data.metadata as ConfigChange).config?.name}
105+
</ConfigsTypeIcon>
106+
<div className="flex flex-col gap-1">
107+
<div className="flex flex-row items-center justify-center gap-2 text-xs">
108+
<span className="flex flex-row items-center gap-1 font-semibold">
109+
<ChangeIcon change={data.metadata} />
110+
{(data.metadata as ConfigChange).change_type}
111+
</span>
112+
<span className="font-semibold">
113+
<Age
114+
from={
115+
(data.metadata as ConfigChange)
116+
.first_observed
117+
}
118+
/>
119+
{(count || 1) > 1 && (
120+
<span className="inline-block pl-1 text-gray-500">
121+
(x{count} over <Age from={firstObserved} />)
122+
</span>
123+
)}
124+
</span>
125+
</div>
126+
<p>{summary}</p>
127+
</div>
128+
</div>
129+
);
130+
}}
131+
/>
132+
}
133+
className={"bg-gray-500"}
134+
size={20}
135+
symbol={(data) => {
136+
const change = data.metadata;
137+
return <ChangeIcon change={change} />;
138+
}}
139+
onClick={(data) => {
140+
onItemClicked(data.metadata as ConfigChange);
141+
}}
142+
/>
143+
}
144+
/>
145+
}
146+
/>
147+
</div>
148+
);
149+
}
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}

0 commit comments

Comments
 (0)