Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: time viewer for config changes #2355

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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,071 changes: 1,015 additions & 56 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"react-use-size": "^3.0.3",
"react-windowed-select": "^5.2.0",
"reactflow": "^11.11.3",
"reaviz": "^15.18.5",
"recharts": "2.1.12",
"strip-ansi": "^7.1.0",
"tailwindcss": "^3.4.1",
Expand Down
57 changes: 14 additions & 43 deletions src/components/Configs/Changes/ConfigChangeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useGetConfigChangesById } from "@flanksource-ui/api/query-hooks/useGetConfigChangesByConfigChangeIdQuery";
import { ConfigChange } from "@flanksource-ui/api/types/configs";
import GetUserAvatar from "@flanksource-ui/components/Users/GetUserAvatar";
import { Age } from "@flanksource-ui/ui/Age";
Expand All @@ -7,10 +6,8 @@ import { ChangeIcon } from "@flanksource-ui/ui/Icons/ChangeIcon";
import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
import { CellContext } from "@tanstack/react-table";
import { MRT_ColumnDef } from "mantine-react-table";
import { useState } from "react";
import ConfigLink from "../ConfigLink/ConfigLink";
import MRTConfigListTagsCell from "../ConfigList/Cells/MRTConfigListTagsCell";
import { ConfigDetailChangeModal } from "./ConfigDetailsChanges/ConfigDetailsChanges";

export const paramsToReset = {
configChanges: ["pageIndex", "pageSize"]
Expand Down Expand Up @@ -190,53 +187,27 @@ type ConfigChangeTableProps = {
isLoading?: boolean;
totalRecords: number;
numberOfPages: number;
onRowClick?: (row: ConfigChange) => void;
};

export function ConfigChangeTable({
data,
isLoading,
totalRecords,
numberOfPages
numberOfPages,
onRowClick = () => {}
}: ConfigChangeTableProps) {
const [selectedConfigChange, setSelectedConfigChange] =
useState<ConfigChange>();
const [modalIsOpen, setModalIsOpen] = useState(false);

const { data: configChange, isLoading: changeLoading } =
useGetConfigChangesById(
selectedConfigChange?.id!,
selectedConfigChange?.config_id!,
{
enabled: !!selectedConfigChange
}
);

return (
<>
<MRTDataTable
columns={configChangesColumn}
data={data}
isLoading={isLoading}
enableServerSideSorting
totalRowCount={totalRecords}
manualPageCount={numberOfPages}
enableServerSidePagination
disableHiding
onRowClick={(row) => {
setSelectedConfigChange(row);
setModalIsOpen(true);
}}
/>
{configChange && (
<ConfigDetailChangeModal
isLoading={changeLoading}
open={modalIsOpen}
setOpen={(open) => {
setModalIsOpen(open);
}}
changeDetails={configChange}
/>
)}
</>
<MRTDataTable
columns={configChangesColumn}
data={data}
isLoading={isLoading}
enableServerSideSorting
totalRowCount={totalRecords}
manualPageCount={numberOfPages}
enableServerSidePagination
disableHiding
onRowClick={onRowClick}
/>
);
}
149 changes: 149 additions & 0 deletions src/components/Configs/Changes/ConfigChangesGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { ConfigChange } from "@flanksource-ui/api/types/configs";
import { Age } from "@flanksource-ui/ui/Age";
import { ChangeIcon } from "@flanksource-ui/ui/Icons/ChangeIcon";
import { relativeDateTime } from "@flanksource-ui/utils/date";
import dayjs from "dayjs";
import { useMemo } from "react";
import {
ChartShallowDataShape,
ChartTooltip,
ChartZoomPan,
LinearXAxis,
LinearXAxisTickLabel,
LinearXAxisTickLine,
LinearXAxisTickSeries,
LinearYAxis,
LinearYAxisTickLabel,
LinearYAxisTickSeries,
ScatterPlot,
ScatterPoint,
ScatterSeries
} from "reaviz";
import ConfigsTypeIcon from "../ConfigsTypeIcon";

type ConfigChangesGraphProps = {
changes: ConfigChange[];
onItemClicked?: (change: ConfigChange) => void;
};

export default function ConfigChangesGraph({
changes,
onItemClicked = () => {}
}: ConfigChangesGraphProps) {
const data: ChartShallowDataShape[] = useMemo(() => {
// group changes by config_id, then map to a scatter plot point
return changes.map((change) => {
return {
key: dayjs(change.first_observed).toDate(),
data: change.config?.name!,
metadata: change
};
});
}, [changes]);

return (
<div className="h-full w-full">
<ScatterPlot
data={data}
zoomPan={<ChartZoomPan />}
yAxis={
<LinearYAxis
type="category"
tickSeries={
<LinearYAxisTickSeries
label={
<LinearYAxisTickLabel
padding={3}
fontSize={12}
format={(v) => {
return v;
}}
/>
}
/>
}
/>
}
xAxis={
<LinearXAxis
type="time"
scale={{ type: "time" }}
tickSeries={
<LinearXAxisTickSeries
line={<LinearXAxisTickLine position="center" />}
label={
<LinearXAxisTickLabel
padding={3}
format={(v) => {
return relativeDateTime(v);
}}
/>
}
/>
}
/>
}
series={
<ScatterSeries
point={
<ScatterPoint
tooltip={
<ChartTooltip
followCursor={true}
content={(data: any) => {
const count = (data.metadata as ConfigChange).count;
const firstObserved = (data.metadata as ConfigChange)
.first_observed;
const summary = (data.metadata as ConfigChange).summary;

return (
<div className="flex flex-col gap-1 rounded-lg bg-gray-100 p-2 text-black shadow-sm">
<ConfigsTypeIcon
config={(data.metadata as ConfigChange).config}
>
{(data.metadata as ConfigChange).config?.name}
</ConfigsTypeIcon>
<div className="flex flex-col gap-1">
<div className="flex flex-row items-center justify-center gap-2 text-xs">
<span className="flex flex-row items-center gap-1 font-semibold">
<ChangeIcon change={data.metadata} />
{(data.metadata as ConfigChange).change_type}
</span>
<span className="font-semibold">
<Age
from={
(data.metadata as ConfigChange)
.first_observed
}
/>
{(count || 1) > 1 && (
<span className="inline-block pl-1 text-gray-500">
(x{count} over <Age from={firstObserved} />)
</span>
)}
</span>
</div>
<p>{summary}</p>
</div>
</div>
);
}}
/>
}
className={"bg-gray-500"}
size={20}
symbol={(data) => {
const change = data.metadata;
return <ChangeIcon change={change} />;
}}
onClick={(data) => {
onItemClicked(data.metadata as ConfigChange);
}}
/>
}
/>
}
/>
</div>
);
}
34 changes: 34 additions & 0 deletions src/components/Configs/Changes/ConfigChangesViewToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Switch } from "@flanksource-ui/ui/FormControls/Switch";
import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";

export type GraphType = "Table" | "Graph";
export const configChangesViewToggle = atomWithStorage<GraphType>(
"configChangesViewToggleState",
"Table",
undefined,
{
getOnInit: true
}
);

export function useConfigChangesViewToggleState() {
const [hideDeletedConfigs] = useAtom(configChangesViewToggle);
return hideDeletedConfigs;
}

export default function ConfigChangesViewToggle() {
const [toggleValue, setToggleValue] = useAtom(configChangesViewToggle);

return (
<div className="ml-auto flex flex-row items-center gap-2 px-2">
<Switch
options={["Table", "Graph"]}
onChange={(v) => {
setToggleValue(v as GraphType);
}}
value={toggleValue}
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FilterBadge } from "../../ConfigChangesFilters/ConfigChangesFilters";
import { ConfigRelatedChangesToggles } from "../../ConfigChangesFilters/ConfigRelatedChangesToggles";
import { ConfigTagsDropdown } from "../../ConfigChangesFilters/ConfigTagsDropdown";
import ConfigTypesTristateDropdown from "../../ConfigChangesFilters/ConfigTypesTristateDropdown";
import ConfigChangesViewToggle from "../../ConfigChangesViewToggle";
import { ConfigChangesToggledDeletedItems } from "./ConfigChangesToggledDeletedItems";

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

return (
<div className="flex flex-col gap-2">
<div className="flex w-full flex-col gap-2">
<FormikFilterForm
paramsToReset={paramsToReset}
filterFields={["configTypes", "changeType", "severity", "tags"]}
>
<div className={clsx("flex flex-wrap items-center gap-2", className)}>
<div
className={clsx(
"flex w-full flex-wrap items-center gap-2",
className
)}
>
<ConfigTypesTristateDropdown />
<ChangesTypesDropdown />
<ConfigChangeSeverity />
<ConfigTagsDropdown />
<ConfigRelatedChangesToggles />
<ConfigChangesDateRangeFilter paramsToReset={paramsToReset} />
<ConfigChangesToggledDeletedItems />
<div className="ml-auto flex flex-row gap-2">
<ConfigChangesViewToggle />
</div>
</div>
</FormikFilterForm>
<div className="flex flex-wrap gap-2">
Expand Down
1 change: 1 addition & 0 deletions src/pages/config/ConfigChangesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function ConfigChangesPage() {
) : (
<>
<ConfigChangeFilters paramsToReset={["page"]} />

<ConfigChangeTable
data={changes}
isLoading={isLoading || isRefetching}
Expand Down
Loading
Loading