Skip to content
Merged
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
125 changes: 108 additions & 17 deletions src/pages/audit-report/components/DynamicDataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { formatDate } from "../utils";

import MRTDataTable from "@flanksource-ui/ui/MRTDataTable/MRTDataTable";
import { MRT_ColumnDef } from "mantine-react-table";
import { SortingState } from "@tanstack/react-table";
import HealthBadge, { HealthType } from "./HealthBadge";
import GaugeCell from "./GaugeCell";
import { Link, useSearchParams } from "react-router-dom";
Expand All @@ -29,6 +30,8 @@ interface DynamicDataTableProps {
totalRowCount?: number;
isLoading?: boolean;
tablePrefix?: string;
defaultPageSize?: number;
defaultSorting?: SortingState;
}

interface RowAttributes {
Expand All @@ -50,21 +53,99 @@ const DynamicDataTable: React.FC<DynamicDataTableProps> = ({
pageCount,
totalRowCount,
isLoading,
tablePrefix
tablePrefix,
defaultPageSize,
defaultSorting
}) => {
const columnDef: MRT_ColumnDef<any>[] = columns
.filter((col) => !col.hidden && !hiddenColumnTypes.includes(col.type))
.map((col) => {
const visibleColumnsWithRatio = React.useMemo(() => {
return columns.reduce<
{
column: ViewColumnDef;
width?: string;
}[]
>((acc, col) => {
if (col.hidden || hiddenColumnTypes.includes(col.type)) {
return acc;
}
const width = col.width;
acc.push({ column: col, width });
return acc;
}, []);
}, [columns]);

const baseWidth = React.useMemo(
() => Math.max(visibleColumnsWithRatio.length * 120, 120),
[visibleColumnsWithRatio.length]
);

const { customWidths, widthError } = React.useMemo(() => {
const widths: Record<string, number> = {};
let totalWeight = 0;
const weightColumns: { name: string; weight: number }[] = [];

for (const { column: col, width } of visibleColumnsWithRatio) {
if (!width) {
continue;
}
const widthStr = String(width).trim();
const match = widthStr.match(/^([0-9]+(?:\.[0-9]+)?)(px)?$/);
if (!match) {
return {
customWidths: {},
widthError: `Invalid column width "${widthStr}" for column "${col.name}". Use weight (e.g. "2") or px (e.g. "150px").`
};
}
const value = parseFloat(match[1]);
const unit = match[2];
if (Number.isNaN(value) || value <= 0) {
return {
customWidths: {},
widthError: `Invalid column width "${widthStr}" for column "${col.name}". Use positive values like "2" or "150px".`
};
}

if (unit === "px") {
widths[col.name] = Math.max(20, Math.round(value));
} else {
weightColumns.push({ name: col.name, weight: value });
totalWeight += value;
}
}

if (weightColumns.length > 0 && totalWeight > 0) {
weightColumns.forEach(({ name, weight }) => {
widths[name] = Math.max(
20,
Math.round((weight / totalWeight) * baseWidth)
);
});
}
return {
customWidths: widths,
widthError: undefined as string | undefined
};
}, [baseWidth, visibleColumnsWithRatio]);

const columnDef: MRT_ColumnDef<any>[] = visibleColumnsWithRatio.map(
({ column: col }) => {
const calculatedSize = widthError ? undefined : customWidths[col.name];

return {
accessorKey: col.name,
minSize: 15,
maxSize: minWidthForColumnType(col.type),
...(calculatedSize === undefined
? {
minSize: 15,
maxSize: minWidthForColumnType(col.type)
}
: {}),
size: calculatedSize,
header: formatDisplayLabel(col.name),
enableSorting: col.type !== "labels",
Cell: ({ cell, row }: { cell: any; row: any }) =>
renderCellValue(cell.getValue(), col, row.original, tablePrefix)
};
});
}
);

const adaptedData = rows.map((row) => {
const rowObj: { [key: string]: any } = {};
Expand Down Expand Up @@ -97,16 +178,26 @@ const DynamicDataTable: React.FC<DynamicDataTableProps> = ({
});

return (
<MRTDataTable
enableColumnActions={false}
columns={columnDef}
isLoading={isLoading}
data={adaptedData}
enableServerSideSorting
enableServerSidePagination
manualPageCount={pageCount}
totalRowCount={totalRowCount}
/>
<>
{widthError && (
<div className="mb-2 text-sm text-red-600">
{widthError} Falling back to default column widths.
</div>
)}
<MRTDataTable
enableColumnActions={false}
columns={columnDef}
isLoading={isLoading}
data={adaptedData}
enableServerSideSorting
enableServerSidePagination
manualPageCount={pageCount}
totalRowCount={totalRowCount}
urlParamPrefix={tablePrefix}
defaultPageSize={defaultPageSize}
defaultSorting={defaultSorting}
/>
</>
);
};

Expand Down
73 changes: 69 additions & 4 deletions src/pages/audit-report/components/View/View.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useMemo } from "react";
import React, { useEffect, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { Box, Table2, LayoutGrid } from "lucide-react";
import { useSearchParams } from "react-router-dom";
import DynamicDataTable from "../DynamicDataTable";
import { formatDisplayLabel } from "./panels/utils";
import {
ColumnFilterOptions,
DisplayTable,
PanelResult,
ViewColumnDef,
ViewRow,
Expand Down Expand Up @@ -38,6 +39,7 @@ interface ViewProps {
name: string;
columns?: ViewColumnDef[];
columnOptions?: Record<string, ColumnFilterOptions>;
table?: DisplayTable;
variables?: ViewVariable[];
card?: {
columns: number;
Expand All @@ -55,17 +57,48 @@ const View: React.FC<ViewProps> = ({
columns,
columnOptions,
panels,
table,
variables,
card,
requestFingerprint,
currentVariables,
hideVariables
}) => {
const { pageSize } = useReactTablePaginationState();
const tablePrefix = `view_${namespace}_${name}`;

const defaultPageSize = table?.size;

const defaultSorting = useMemo(() => {
const sort = table?.sort?.trim();
if (!sort) {
return undefined;
}

const desc = sort.startsWith("-");
const id = sort.replace(/^[-+]/, "");

if (!id) {
return undefined;
}

return [
{
id,
desc
}
];
}, [table?.sort]);

const { pageSize } = useReactTablePaginationState({
paramPrefix: tablePrefix,
defaultPageSize: defaultPageSize
});

// Create unique prefix for this view's table
const tablePrefix = `view_${namespace}_${name}`;
const [tableSearchParams] = usePrefixedSearchParams(tablePrefix);
const [tableSearchParams, setTableSearchParams] = usePrefixedSearchParams(
tablePrefix,
false
);

// Separate display mode state (frontend only, not sent to backend)
const [searchParams, setSearchParams] = useSearchParams();
Expand Down Expand Up @@ -118,6 +151,36 @@ const View: React.FC<ViewProps> = ({
return columnFilterFields;
}, [columnFilterFields]);

useEffect(() => {
setTableSearchParams((current) => {
const updated = new URLSearchParams(current);
let changed = false;

if (defaultPageSize && defaultPageSize > 0 && !updated.get("pageSize")) {
updated.set("pageSize", defaultPageSize.toString());
changed = true;
}

if (!updated.get("pageIndex")) {
updated.set("pageIndex", "0");
changed = true;
}

if (
defaultSorting &&
defaultSorting.length > 0 &&
!updated.get("sortBy")
) {
const sort = defaultSorting[0];
updated.set("sortBy", sort.id);
updated.set("sortOrder", sort.desc ? "desc" : "asc");
changed = true;
}

return changed ? updated : current;
});
}, [defaultPageSize, defaultSorting, setTableSearchParams]);

// Fetch table data with only column filters (no global filters)
const {
data: tableResponse,
Expand Down Expand Up @@ -281,6 +344,8 @@ const View: React.FC<ViewProps> = ({
pageCount={totalEntries ? Math.ceil(totalEntries / pageSize) : 1}
totalRowCount={totalEntries}
tablePrefix={tablePrefix}
defaultPageSize={defaultPageSize}
defaultSorting={defaultSorting}
/>
</div>
))}
Expand Down
11 changes: 11 additions & 0 deletions src/pages/audit-report/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ export interface ViewColumnDef {
*/
filter?: ViewColumnDefFilter;

/**
* Width of the column (weight like "2" or fixed like "150px")
*/
width?: string;

/**
* The data type of the column
*/
Expand Down Expand Up @@ -396,6 +401,11 @@ export interface DisplayCard {
default?: boolean;
}

export interface DisplayTable {
sort?: string;
size?: number;
}

/**
* Filter options for a column.
* For regular columns, list contains distinct values.
Expand Down Expand Up @@ -430,6 +440,7 @@ export interface ViewResult {
columnOptions?: Record<string, ColumnFilterOptions>;
variables?: ViewVariable[];
card?: DisplayCard;
table?: DisplayTable;
requestFingerprint: string;
sections?: ViewSection[];
}
Expand Down
1 change: 1 addition & 0 deletions src/pages/config/details/ConfigDetailsViewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function ConfigDetailsViewPage() {
panels={viewResult.panels}
columns={viewResult.columns}
card={viewResult.card}
table={viewResult.table}
requestFingerprint={viewResult.requestFingerprint}
columnOptions={viewResult.columnOptions}
/>
Expand Down
1 change: 1 addition & 0 deletions src/pages/views/components/ViewSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const ViewSection: React.FC<ViewSectionProps> = ({
columns={sectionViewResult?.columns}
columnOptions={sectionViewResult?.columnOptions}
panels={sectionViewResult?.panels}
table={sectionViewResult?.table}
variables={sectionViewResult?.variables}
card={sectionViewResult?.card}
requestFingerprint={sectionViewResult.requestFingerprint}
Expand Down
Loading
Loading