Skip to content

Commit fb7364a

Browse files
committed
refactor: use mantime react table for config changes
Fixes #2285 fix: fix issue with config changes table
1 parent e215ff7 commit fb7364a

File tree

6 files changed

+161
-75
lines changed

6 files changed

+161
-75
lines changed

src/components/Configs/Changes/ConfigChangeTable.tsx

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ import { useGetConfigChangesById } from "@flanksource-ui/api/query-hooks/useGetC
22
import { ConfigChange } from "@flanksource-ui/api/types/configs";
33
import GetUserAvatar from "@flanksource-ui/components/Users/GetUserAvatar";
44
import { Age } from "@flanksource-ui/ui/Age";
5-
import { DataTable, PaginationOptions } from "@flanksource-ui/ui/DataTable";
65
import FilterByCellValue from "@flanksource-ui/ui/DataTable/FilterByCellValue";
7-
import useReactTableSortState from "@flanksource-ui/ui/DataTable/Hooks/useReactTableSortState";
86
import { ChangeIcon } from "@flanksource-ui/ui/Icons/ChangeIcon";
7+
import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
98
import { CellContext } from "@tanstack/react-table";
10-
import { ColumnDef } from "@tanstack/table-core";
11-
import React, { useState } from "react";
9+
import { MRT_ColumnDef } from "mantine-react-table";
10+
import { useState } from "react";
1211
import ConfigLink from "../ConfigLink/ConfigLink";
13-
import ConfigListTagsCell from "../ConfigList/Cells/ConfigListTagsCell";
14-
import { ConfigSummaryAnalysisCell } from "../ConfigSummary/ConfigSummaryList";
12+
import MRTConfigListTagsCell from "../ConfigList/Cells/MRTConfigListTagsCell";
1513
import { ConfigDetailChangeModal } from "./ConfigDetailsChanges/ConfigDetailsChanges";
1614

1715
export const paramsToReset = {
@@ -38,7 +36,7 @@ export function ConfigChangeDateCell({
3836
);
3937
}
4038

41-
const configChangesColumn: ColumnDef<ConfigChange>[] = [
39+
const configChangesColumn: MRT_ColumnDef<ConfigChange>[] = [
4240
{
4341
header: "Last Seen",
4442
id: "created_at",
@@ -48,15 +46,30 @@ const configChangesColumn: ColumnDef<ConfigChange>[] = [
4846
cellClassName: "text-ellipsis overflow-hidden"
4947
},
5048
maxSize: 70,
51-
cell: ConfigChangeDateCell
49+
Cell: ({ cell, row, column }) => {
50+
const dateString = row?.getValue<string>(column.id);
51+
const firstObserved = row?.original.first_observed;
52+
const count = row.original.count;
53+
54+
return (
55+
<div className="text-xs">
56+
<Age from={dateString} />
57+
{(count || 1) > 1 && (
58+
<span className="inline-block pl-1 text-gray-500">
59+
(x{count} over <Age from={firstObserved} />)
60+
</span>
61+
)}
62+
</div>
63+
);
64+
}
5265
},
5366
{
5467
header: "Catalog",
5568
id: "config_id",
5669
accessorKey: "config_id",
5770
enableHiding: true,
5871
enableSorting: false,
59-
cell: function ConfigLinkCell({ row }) {
72+
Cell: ({ row }) => {
6073
const configId = row.original.config_id;
6174
return (
6275
<FilterByCellValue
@@ -77,7 +90,7 @@ const configChangesColumn: ColumnDef<ConfigChange>[] = [
7790
{
7891
header: "Type",
7992
accessorKey: "change_type",
80-
cell: function ConfigChangeTypeCell({ row, column }) {
93+
Cell: function ConfigChangeTypeCell({ row, column }) {
8194
const changeType = row?.getValue(column.id) as string;
8295
return (
8396
<FilterByCellValue
@@ -103,8 +116,8 @@ const configChangesColumn: ColumnDef<ConfigChange>[] = [
103116
},
104117
maxSize: 500,
105118
minSize: 250,
106-
cell: ({ getValue }) => {
107-
const summary = getValue<string>();
119+
Cell: ({ cell }) => {
120+
const summary = cell.getValue<string>();
108121

109122
return (
110123
<FilterByCellValue
@@ -120,17 +133,14 @@ const configChangesColumn: ColumnDef<ConfigChange>[] = [
120133
{
121134
header: "Tags",
122135
accessorKey: "tags",
123-
cell: React.memo((props) => (
124-
<ConfigListTagsCell {...props} enableFilterByTag />
125-
)),
126-
aggregatedCell: "",
136+
Cell: (props) => <MRTConfigListTagsCell {...props} enableFilterByTag />,
127137
size: 100
128138
},
129139
{
130140
header: "Created By",
131141
size: 100,
132142
enableSorting: false,
133-
cell: ({ row }) => {
143+
Cell: ({ row }) => {
134144
const userID = row.original.created_by;
135145
if (userID) {
136146
return (
@@ -178,17 +188,15 @@ const configChangesColumn: ColumnDef<ConfigChange>[] = [
178188
type ConfigChangeTableProps = {
179189
data: ConfigChange[];
180190
isLoading?: boolean;
181-
className?: string;
182-
tableStyle?: React.CSSProperties;
183-
pagination?: PaginationOptions;
191+
totalRecords: number;
192+
numberOfPages: number;
184193
};
185194

186195
export function ConfigChangeTable({
187196
data,
188197
isLoading,
189-
className = "table-auto table-fixed",
190-
pagination,
191-
tableStyle
198+
totalRecords,
199+
numberOfPages
192200
}: ConfigChangeTableProps) {
193201
const [selectedConfigChange, setSelectedConfigChange] =
194202
useState<ConfigChange>();
@@ -203,26 +211,18 @@ export function ConfigChangeTable({
203211
}
204212
);
205213

206-
const [sortBy, onTableSortByChanged] = useReactTableSortState();
207-
208214
return (
209215
<>
210-
<DataTable
211-
className={className}
216+
<MRTDataTable
212217
columns={configChangesColumn}
213218
data={data}
214219
isLoading={isLoading}
215-
stickyHead
216-
isVirtualized={false}
217-
tableStyle={tableStyle}
218-
pagination={pagination}
219-
preferencesKey="config-change-history"
220-
savePreferences={false}
221220
enableServerSideSorting
222-
tableSortByState={sortBy}
223-
onTableSortByChanged={onTableSortByChanged}
224-
handleRowClick={(row) => {
225-
setSelectedConfigChange(row.original);
221+
totalRowCount={totalRecords}
222+
manualPageCount={numberOfPages}
223+
enableServerSidePagination
224+
onRowClick={(row) => {
225+
setSelectedConfigChange(row);
226226
setModalIsOpen(true);
227227
}}
228228
/>

src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function ConfigChangeFilters({
8484
}, [configType]);
8585

8686
return (
87-
<div className="flex flex-col gap-2">
87+
<div className="flex w-full flex-col gap-2">
8888
<FormikFilterForm
8989
paramsToReset={paramsToReset}
9090
filterFields={["configTypes", "changeType", "severity", "tags"]}

src/components/Configs/ConfigList/Cells/ConfigListTagsCell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ type ConfigListTagsCellProps<T extends Pick<ConfigItem, "tags" | "id">> = Pick<
1414
};
1515

1616
export default function ConfigListTagsCell<
17-
T extends { tags?: Record<string, any>; id: string }
17+
T extends { tags?: Record<string, any>; id: string }
1818
>({
1919
row,
2020
getValue,
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { MRTCellProps } from "@flanksource-ui/ui/MRTDataTable/MRTCellProps";
2+
import { Tag } from "@flanksource-ui/ui/Tags/Tag";
3+
import { useCallback } from "react";
4+
import { useSearchParams } from "react-router-dom";
5+
import { ConfigItem } from "../../../../api/types/configs";
6+
7+
type MRTConfigListTagsCellProps<T extends Pick<ConfigItem, "tags" | "id">> =
8+
Pick<MRTCellProps<T>, "cell" | "row"> & {
9+
hideGroupByView?: boolean;
10+
label?: string;
11+
enableFilterByTag?: boolean;
12+
};
13+
14+
export default function MRTConfigListTagsCell<
15+
T extends { tags?: Record<string, any>; id: string }
16+
>({
17+
row,
18+
cell,
19+
hideGroupByView = false,
20+
enableFilterByTag = false
21+
}: MRTConfigListTagsCellProps<T>): JSX.Element | null {
22+
const [params, setParams] = useSearchParams();
23+
24+
const tagMap = cell.getValue<ConfigItem["tags"]>() || {};
25+
const tagKeys = Object.keys(tagMap)
26+
.sort()
27+
.filter((key) => key !== "toString");
28+
29+
const onFilterByTag = useCallback(
30+
(
31+
e: React.MouseEvent<HTMLButtonElement>,
32+
tag: {
33+
key: string;
34+
value: string;
35+
},
36+
action: "include" | "exclude"
37+
) => {
38+
if (!enableFilterByTag) {
39+
return;
40+
}
41+
42+
e.preventDefault();
43+
e.stopPropagation();
44+
45+
// Get the current tags from the URL
46+
const currentTags = params.get("tags");
47+
const currentTagsArray = (
48+
currentTags ? currentTags.split(",") : []
49+
).filter((value) => {
50+
const tagKey = value.split("____")[0];
51+
const tagAction = value.split(":")[1] === "1" ? "include" : "exclude";
52+
53+
if (tagKey === tag.key && tagAction !== action) {
54+
return false;
55+
}
56+
return true;
57+
});
58+
59+
// Append the new value, but for same tags, don't allow including and excluding at the same time
60+
const updatedValue = currentTagsArray
61+
.concat(`${tag.key}____${tag.value}:${action === "include" ? 1 : -1}`)
62+
.filter((value, index, self) => self.indexOf(value) === index)
63+
.join(",");
64+
65+
// Update the URL
66+
params.set("tags", updatedValue);
67+
setParams(params);
68+
},
69+
[enableFilterByTag, params, setParams]
70+
);
71+
72+
const groupByProp = decodeURIComponent(params.get("groupByProp") ?? "");
73+
74+
if (tagKeys.length === 0) {
75+
return null;
76+
}
77+
78+
if (!hideGroupByView && groupByProp) {
79+
if (!tagMap[groupByProp]) {
80+
return null;
81+
}
82+
83+
return (
84+
<div className="flex w-full max-w-full flex-wrap space-y-1 pl-1 font-mono">
85+
<div
86+
className="mr-1 max-w-full overflow-hidden text-ellipsis rounded-md border border-gray-300 bg-gray-200 px-1 py-0.75 text-xs font-semibold text-gray-600"
87+
key={groupByProp}
88+
>
89+
{groupByProp}:{" "}
90+
<span className="font-light">{tagMap[groupByProp]}</span>
91+
</div>
92+
</div>
93+
);
94+
}
95+
96+
return (
97+
<div className="flex flex-wrap gap-1">
98+
{Object.entries(tagMap).map(([key, value]) => (
99+
<Tag
100+
tag={{
101+
key,
102+
value
103+
}}
104+
id={row.original.id}
105+
key={value}
106+
variant="gray"
107+
onFilterByTag={enableFilterByTag ? onFilterByTag : undefined}
108+
>
109+
{value}
110+
</Tag>
111+
))}
112+
</div>
113+
);
114+
}

src/pages/config/ConfigChangesPage.tsx

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,18 @@ import {
88
BreadcrumbNav,
99
BreadcrumbRoot
1010
} from "@flanksource-ui/ui/BreadcrumbNav";
11-
import { PaginationOptions } from "@flanksource-ui/ui/DataTable";
1211
import { Head } from "@flanksource-ui/ui/Head";
1312
import { SearchLayout } from "@flanksource-ui/ui/Layout/SearchLayout";
1413
import { refreshButtonClickedTrigger } from "@flanksource-ui/ui/SlidingSideBar/SlidingSideBar";
1514
import { useAtom } from "jotai";
16-
import { useMemo } from "react";
1715
import { useSearchParams } from "react-router-dom";
1816

1917
export function ConfigChangesPage() {
2018
const [, setRefreshButtonClickedTrigger] = useAtom(
2119
refreshButtonClickedTrigger
2220
);
23-
const [params, setParams] = useSearchParams({
24-
sortBy: "created_at",
25-
sortDirection: "desc"
26-
});
21+
const [params] = useSearchParams({});
2722

28-
const page = params.get("page") ?? "1";
2923
const pageSize = params.get("pageSize") ?? "200";
3024

3125
const { data, isLoading, error, isRefetching, refetch } =
@@ -45,30 +39,6 @@ export function ConfigChangesPage() {
4539
const totalChanges = data?.total ?? 0;
4640
const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize));
4741

48-
const pagination = useMemo(() => {
49-
const pagination: PaginationOptions = {
50-
setPagination: (updater) => {
51-
const newParams =
52-
typeof updater === "function"
53-
? updater({
54-
pageIndex: parseInt(page) - 1,
55-
pageSize: parseInt(pageSize)
56-
})
57-
: updater;
58-
params.set("page", (newParams.pageIndex + 1).toString());
59-
params.set("pageSize", newParams.pageSize.toString());
60-
setParams(params);
61-
},
62-
pageIndex: parseInt(page) - 1,
63-
pageSize: parseInt(pageSize),
64-
pageCount: totalChangesPages,
65-
remote: true,
66-
enable: true,
67-
loading: isLoading
68-
};
69-
return pagination;
70-
}, [page, pageSize, totalChangesPages, isLoading, params, setParams]);
71-
7242
const errorMessage =
7343
typeof error === "string"
7444
? error
@@ -98,7 +68,7 @@ export function ConfigChangesPage() {
9868
refetch();
9969
}}
10070
loading={isLoading || isRefetching}
101-
contentClass="p-0 h-full"
71+
contentClass="p-0 h-full flex flex-col flex-1"
10272
>
10373
<ConfigPageTabs activeTab="Changes">
10474
{error ? (
@@ -109,7 +79,8 @@ export function ConfigChangesPage() {
10979
<ConfigChangeTable
11080
data={changes}
11181
isLoading={isLoading}
112-
pagination={pagination}
82+
totalRecords={totalChanges}
83+
numberOfPages={totalChangesPages}
11384
/>
11485
</>
11586
)}

src/pages/config/details/ConfigDetailsChangesPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,14 @@ export function ConfigDetailsChangesPage() {
7474
activeTabName="Changes"
7575
>
7676
<div className={`flex h-full flex-1 flex-col overflow-y-auto`}>
77-
<div className="flex flex-1 flex-col items-start gap-2 overflow-y-auto">
77+
<div className="flex w-full flex-1 flex-col items-start gap-2 overflow-y-auto">
7878
<ConfigRelatedChangesFilters paramsToReset={["page"]} />
79-
<div className="flex flex-1 flex-col overflow-y-auto">
79+
<div className="flex w-full flex-1 flex-col overflow-y-auto">
8080
<ConfigChangeTable
8181
data={changes}
8282
isLoading={isLoading}
83-
pagination={pagination}
83+
numberOfPages={totalChangesPages}
84+
totalRecords={totalChanges}
8485
/>
8586
</div>
8687
</div>

0 commit comments

Comments
 (0)