Skip to content

Commit

Permalink
refactor: use MRT for config relationships
Browse files Browse the repository at this point in the history
Fixes #2285

fix: typescript error
  • Loading branch information
mainawycliffe committed Oct 4, 2024
1 parent b86e615 commit 50f8d87
Show file tree
Hide file tree
Showing 6 changed files with 476 additions and 117 deletions.
14 changes: 10 additions & 4 deletions src/components/Configs/ConfigList/Cells/ConfigListCostCell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CellContext, Row } from "@tanstack/react-table";
import { MRT_Row } from "mantine-react-table";
import {
ConfigItem,
ConfigSummary,
Expand All @@ -16,14 +17,19 @@ export default function ConfigListCostCell({
* Recursively aggregate costs for a given row and its children, and its children's children, etc.
*
*/
const aggregatedCosts = (
rows: Row<ConfigItem>,
export const aggregatedCosts = (
rows: Row<ConfigItem> | MRT_Row<ConfigItem>,
data: Required<Costs>
): Required<Costs> => {
if (rows.subRows.length === 0) {
const subRows = rows.subRows;
if (!subRows) {
return data;
}
return rows.subRows.reduce((acc, row) => {
if (subRows.length === 0) {
return data;
}
// @ts-ignore
return subRows.reduce((acc, row) => {
if (row.original) {
acc.cost_total_30d! += row.original.cost_total_30d ?? 0;
acc.cost_total_7d! += row.original.cost_total_7d ?? 0;
Expand Down
22 changes: 22 additions & 0 deletions src/components/Configs/ConfigList/Cells/ConfigListDateCell.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MRTCellProps } from "@flanksource-ui/ui/MRTDataTable/MRTCellProps";
import { CellContext } from "@tanstack/react-table";
import { FaTrash } from "react-icons/fa";
import { Age } from "../../../../ui/Age";
Expand All @@ -22,3 +23,24 @@ export default function ConfigListDateCell<T extends Record<string, any>>({
</div>
);
}

export function MRTConfigListDateCell<T extends Record<string, any>>({
column,
row,
cell
}: MRTCellProps<T>) {
const dateString = cell.getValue();
if (dateString === "0001-01-01T00:00:00") {
return null;
}
const isDeleted = !!row.original.deleted_at;
const value = isDeleted ? row.original.deleted_at : dateString;
return (
<div className="text-xs">
<Age from={value} />
{column.id === "updated_at" && isDeleted && (
<FaTrash className="mx-2 inline h-4 w-4 align-middle text-gray-400" />
)}
</div>
);
}
117 changes: 117 additions & 0 deletions src/components/Configs/ConfigList/Cells/MRTConfigListTagsCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { MRTCellProps } from "@flanksource-ui/ui/MRTDataTable/MRTCellProps";
import { Tag } from "@flanksource-ui/ui/Tags/Tag";
import { useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { ConfigItem } from "../../../../api/types/configs";

type MRTConfigListTagsCellProps<
T extends {
tags?: Record<string, any>;
id: string;
}
> = MRTCellProps<T> & {
hideGroupByView?: boolean;
enableFilterByTag?: boolean;
};

export default function MRTConfigListTagsCell<
T extends { tags?: Record<string, any>; id: string }
>({
row,
cell,
hideGroupByView = false,
enableFilterByTag = false
}: MRTConfigListTagsCellProps<T>): JSX.Element | null {
const [params, setParams] = useSearchParams();

const tagMap = cell.getValue<ConfigItem["tags"]>() || {};
const tagKeys = Object.keys(tagMap)
.sort()
.filter((key) => key !== "toString");

const onFilterByTag = useCallback(
(
e: React.MouseEvent<HTMLButtonElement>,
tag: {
key: string;
value: string;
},
action: "include" | "exclude"
) => {
if (!enableFilterByTag) {
return;
}

e.preventDefault();
e.stopPropagation();

// Get the current tags from the URL
const currentTags = params.get("tags");
const currentTagsArray = (
currentTags ? currentTags.split(",") : []
).filter((value) => {
const tagKey = value.split("____")[0];
const tagAction = value.split(":")[1] === "1" ? "include" : "exclude";

if (tagKey === tag.key && tagAction !== action) {
return false;
}
return true;
});

// Append the new value, but for same tags, don't allow including and excluding at the same time
const updatedValue = currentTagsArray
.concat(`${tag.key}____${tag.value}:${action === "include" ? 1 : -1}`)
.filter((value, index, self) => self.indexOf(value) === index)
.join(",");

// Update the URL
params.set("tags", updatedValue);
setParams(params);
},
[enableFilterByTag, params, setParams]
);

const groupByProp = decodeURIComponent(params.get("groupByProp") ?? "");

if (tagKeys.length === 0) {
return null;
}

if (!hideGroupByView && groupByProp) {
if (!tagMap[groupByProp]) {
return null;
}

return (
<div className="flex w-full max-w-full flex-wrap space-y-1 pl-1 font-mono">
<div
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"
key={groupByProp}
>
{groupByProp}:{" "}
<span className="font-light">{tagMap[groupByProp]}</span>
</div>
</div>
);
}

return (
<div className="flex flex-wrap gap-1">
{Object.entries(tagMap).map(([key, value]) => (
<Tag
tag={{
key,
value
}}
id={row.original.id}
key={value}
variant="gray"
onFilterByTag={enableFilterByTag ? onFilterByTag : undefined}
>
{value}
</Tag>
))}
</div>
);
}
127 changes: 16 additions & 111 deletions src/components/Configs/ConfigList/ConfigsRelationshipsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,121 +1,35 @@
import { ConfigItem } from "@flanksource-ui/api/types/configs";
import { DataTable } from "@flanksource-ui/ui/DataTable";
import { Row, SortingState, Updater } from "@tanstack/react-table";
import { useCallback, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { configListColumns } from "./ConfigListColumn";
import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
import { useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { mrtConfigListColumns } from "./MRTConfigListColumn";

export interface Props {
data: ConfigItem[];
isLoading: boolean;
columnsToHide?: string[];
groupBy?: string[];
expandAllRows?: boolean;
totalEntries?: number;
pageCount?: number;
}

export default function ConfigsRelationshipsTable({
data,
isLoading,
columnsToHide = ["type"],
groupBy = [],
expandAllRows = false
expandAllRows = false,
totalEntries,
pageCount
}: Props) {
const [queryParams, setSearchParams] = useSearchParams({
sortBy: "type",
sortOrder: "asc"
});

const navigate = useNavigate();

const sortField = queryParams.get("sortBy") ?? "type";

const isSortOrderDesc =
queryParams.get("sortOrder") === "desc" ? true : false;

const determineSortColumnOrder = useCallback(
(sortState: SortingState): SortingState => {
const sortStateWithoutDeletedAt = sortState.filter(
(sort) => sort.id !== "deleted_at"
);
return [{ id: "deleted_at", desc: false }, ...sortStateWithoutDeletedAt];
},
[]
);

const [sortBy, setSortBy] = useState<SortingState>(() => {
return sortField
? determineSortColumnOrder([
{
id: sortField,
desc: isSortOrderDesc
},
...(sortField !== "name"
? [
{
id: "name",
desc: isSortOrderDesc
}
]
: [])
])
: determineSortColumnOrder([]);
});

const updateSortBy = useCallback(
(newSortBy: Updater<SortingState>) => {
const getSortBy = Array.isArray(newSortBy)
? newSortBy
: newSortBy(sortBy);
// remove deleted_at from sort state, we don't want it to be save to the
// URL for the purpose of sorting
const sortStateWithoutDeleteAt = getSortBy.filter(
(state) => state.id !== "deleted_at"
);
const { id: field, desc } = sortStateWithoutDeleteAt[0] ?? {};
const order = desc ? "desc" : "asc";
if (field && order && field !== "type" && order !== "asc") {
queryParams.set("sortBy", field);
queryParams.set("sortOrder", order);
} else {
queryParams.delete("sortBy");
queryParams.delete("sortOrder");
}
setSearchParams(queryParams);
const sortByValue =
typeof newSortBy === "function" ? newSortBy(sortBy) : newSortBy;
if (sortByValue.length > 0) {
setSortBy(determineSortColumnOrder(sortByValue));
}
},
[determineSortColumnOrder, queryParams, setSearchParams, sortBy]
);

const hiddenColumns = ["deleted_at", "changed", ...columnsToHide];

const determineRowClassNames = useCallback((row: Row<ConfigItem>) => {
if (row.getIsGrouped()) {
// check if the whole group is deleted
const allDeleted = row.getLeafRows().every((row) => {
if (row.original.deleted_at) {
return true;
}
return false;
});

if (allDeleted) {
return "text-gray-500";
}
} else {
if (row.original.deleted_at) {
return "text-gray-500";
}
}
return "";
}, []);

const handleRowClick = useCallback(
(row?: { original?: { id: string } }) => {
const id = row?.original?.id;
(row?: ConfigItem) => {
const id = row?.id;
if (id) {
navigate(`/catalog/${id}`);
}
Expand All @@ -124,25 +38,16 @@ export default function ConfigsRelationshipsTable({
);

return (
<DataTable
stickyHead
isVirtualized
columns={configListColumns}
<MRTDataTable
columns={mrtConfigListColumns}
data={data}
handleRowClick={handleRowClick}
tableStyle={{ borderSpacing: "0" }}
onRowClick={handleRowClick}
isLoading={isLoading}
groupBy={groupBy}
hiddenColumns={hiddenColumns}
className="max-w-full table-auto table-fixed"
tableSortByState={sortBy}
enableServerSideSorting
onTableSortByChanged={updateSortBy}
determineRowClassNamesCallback={determineRowClassNames}
preferencesKey="config-list"
virtualizedRowEstimatedHeight={37}
overScan={20}
expandAllRows={expandAllRows}
totalRowCount={totalEntries}
manualPageCount={pageCount}
/>
);
}
Loading

0 comments on commit 50f8d87

Please sign in to comment.