mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-08 23:50:40 +01:00
* feat(tanstack-table): add showPageSize flag and callbacks to pagination * chore(signozhq/ui): bump to v0.0.19 * feat(components): added shared components for alerts * feat(hooks): add shared hooks for alerts * feat(time-utils): add little helper to get elapsed time in ms * feat(alerts-components): add badge map colors * feat(list-alerts): rewrite page to use new table component (#11276) * feat(triggered-alerts): rewrite page to use new table component (#11260) * feat(triggered-alerts): rewrite page * chore(triggered-alerts): move reason tooltip content to own component * chore(pr-comments): address PR comments * chore(lint): fix signozhq/ui imports * refactor(alerts): removed stats card * refactor(alerts): move table to use pagination instead of infinity load * refactor(alerts): remove extra columns & add back info tooltip * refactor(alerts): disable move columns * refactor(alerts): cleanup dead components * refactor(alerts): standardize errors and empty states * refactor(alerts): missing pagination class on group * fix(tanstack): preserve page from URL on refresh page * refactor(alerts): ensure no empty has correct colors * refactor(alerts): remove barrel imports * fix(alerts): missing including error empty state * fix(alerts): ensure the params are removed on page change * fix(alerts): address comments related to UI/UX * feat(tanstack): add auto page size * fix(alert): use calculated page size * fix(alerts): address tiny issues in ui * fix(tests): label column tests * fix(actions-menu): ensure callbacks are memoized * fix(alerts): ensure tabs reset the url state (#11380) * fix(pr): address issue with expand button * fix(list-alerts): reduce size of state/severity, add line clamp for table * fix(triggered-alerts): reduce size of state/severity, add line clamp for table * fix(expanded-alerts): remove min-height when no page * fix(triggered-alerts): increase row height for triggered alerts * fix(list-alerts-triggered-alerts): reset page to 1 when change search * fix(use-url-search): add missing test case for clear search
177 lines
4.7 KiB
TypeScript
177 lines
4.7 KiB
TypeScript
import type { CSSProperties, ReactNode } from 'react';
|
|
import type { ColumnDef } from '@tanstack/react-table';
|
|
|
|
import { RowKeyData, TableColumnDef } from './types';
|
|
import { ComboboxSimpleItem } from '@signozhq/ui/combobox';
|
|
|
|
export const getColumnId = <TData>(column: TableColumnDef<TData>): string =>
|
|
column.id;
|
|
|
|
const DEFAULT_MIN_WIDTH = 192; // 12rem * 16px
|
|
|
|
/**
|
|
* Parse a numeric pixel value from a number or string (e.g., 200 or "200px").
|
|
* Returns undefined for non-pixel strings like "100%" or "10rem".
|
|
*/
|
|
const parsePixelValue = (
|
|
value: number | string | undefined,
|
|
): number | undefined => {
|
|
if (value == null) {
|
|
return undefined;
|
|
}
|
|
if (typeof value === 'number') {
|
|
return value;
|
|
}
|
|
// Only parse pixel values, ignore %, rem, em, etc.
|
|
const match = /^(\d+(?:\.\d+)?)px$/.exec(value);
|
|
return match ? parseFloat(match[1]) : undefined;
|
|
};
|
|
|
|
export const getColumnWidthStyle = <TData>(
|
|
column: TableColumnDef<TData>,
|
|
/** Persisted width from user resizing (overrides defined width) */
|
|
persistedWidth?: number,
|
|
/** Last column always gets width: 100% and ignores other width settings */
|
|
isLastColumn?: boolean,
|
|
): CSSProperties => {
|
|
// Last column always fills remaining space
|
|
if (isLastColumn && column?.width?.ignoreLastColumnFill !== true) {
|
|
return {
|
|
width: '100%',
|
|
minWidth: persistedWidth ?? column?.width?.min,
|
|
};
|
|
}
|
|
|
|
const { width } = column;
|
|
if (!width) {
|
|
return {
|
|
width: persistedWidth ?? DEFAULT_MIN_WIDTH,
|
|
minWidth: DEFAULT_MIN_WIDTH,
|
|
};
|
|
}
|
|
if (width.fixed != null) {
|
|
return {
|
|
width: width.fixed,
|
|
minWidth: width.fixed,
|
|
maxWidth: width.fixed,
|
|
};
|
|
}
|
|
return {
|
|
width: persistedWidth ?? width.default ?? width.min,
|
|
minWidth: width.min ?? DEFAULT_MIN_WIDTH,
|
|
maxWidth: width.max,
|
|
};
|
|
};
|
|
|
|
const isSkeletonRow = (row: unknown): boolean => {
|
|
const r = row as Record<string, unknown>;
|
|
return typeof r?.id === 'string' && r.id.startsWith('skeleton-');
|
|
};
|
|
|
|
const buildAccessorFn = <TData>(
|
|
colDef: TableColumnDef<TData>,
|
|
): ((row: TData) => unknown) => {
|
|
return (row: TData): unknown => {
|
|
// Skip accessor for skeleton rows to avoid errors with missing properties
|
|
if (isSkeletonRow(row)) {
|
|
return undefined;
|
|
}
|
|
if (colDef.accessorFn) {
|
|
return colDef.accessorFn(row);
|
|
}
|
|
if (colDef.accessorKey) {
|
|
return (row as Record<string, unknown>)[colDef.accessorKey];
|
|
}
|
|
return undefined;
|
|
};
|
|
};
|
|
|
|
export function buildTanstackColumnDef<TData>(
|
|
colDef: TableColumnDef<TData>,
|
|
isRowActive?: (row: TData) => boolean,
|
|
getRowKeyData?: (index: number) => RowKeyData | undefined,
|
|
): ColumnDef<TData> {
|
|
const isFixed = colDef.width?.fixed != null;
|
|
const headerFn =
|
|
typeof colDef.header === 'function' ? colDef.header : undefined;
|
|
|
|
// Extract numeric size/minSize/maxSize for TanStack's resize behavior
|
|
// This ensures TanStack's internal resize state stays in sync with CSS constraints
|
|
let size: number | undefined;
|
|
let minSize: number | undefined;
|
|
let maxSize: number | undefined;
|
|
|
|
const fixedWidth = parsePixelValue(colDef.width?.fixed);
|
|
if (isFixed && fixedWidth != null) {
|
|
size = fixedWidth;
|
|
minSize = fixedWidth;
|
|
maxSize = fixedWidth;
|
|
} else {
|
|
// Match the logic in getColumnWidthStyle for initial size
|
|
const defaultSize = parsePixelValue(colDef.width?.default);
|
|
const minWidth = parsePixelValue(colDef.width?.min) ?? DEFAULT_MIN_WIDTH;
|
|
size = defaultSize ?? minWidth;
|
|
minSize = minWidth;
|
|
maxSize = parsePixelValue(colDef.width?.max);
|
|
}
|
|
|
|
return {
|
|
id: colDef.id,
|
|
size,
|
|
minSize,
|
|
maxSize,
|
|
header:
|
|
typeof colDef.header === 'string'
|
|
? colDef.header
|
|
: (): ReactNode => headerFn?.() ?? null,
|
|
accessorFn: buildAccessorFn(colDef),
|
|
enableResizing: colDef.enableResize !== false && !isFixed,
|
|
enableSorting: colDef.enableSort === true,
|
|
cell: ({ row, getValue }): ReactNode => {
|
|
const rowData = row.original;
|
|
const keyData = getRowKeyData?.(row.index);
|
|
return colDef.cell({
|
|
row: rowData,
|
|
value: getValue() as TData[any],
|
|
isActive: isRowActive?.(rowData) ?? false,
|
|
rowIndex: row.index,
|
|
isExpanded: row.getIsExpanded(),
|
|
canExpand: row.getCanExpand(),
|
|
toggleExpanded: (): void => {
|
|
row.toggleExpanded();
|
|
},
|
|
itemKey: keyData?.itemKey ?? '',
|
|
groupMeta: keyData?.groupMeta,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
const DEFAULT_PAGE_SIZES = [10, 20, 30, 50, 100];
|
|
|
|
export function buildPageSizeItems(
|
|
calculatedSize?: number | null,
|
|
): ComboboxSimpleItem[] {
|
|
const items: ComboboxSimpleItem[] = [];
|
|
|
|
if (calculatedSize) {
|
|
items.push({
|
|
value: calculatedSize.toString(),
|
|
label: `Auto (${calculatedSize})`,
|
|
displayValue: calculatedSize.toString(),
|
|
});
|
|
}
|
|
|
|
for (const size of DEFAULT_PAGE_SIZES) {
|
|
if (size !== calculatedSize) {
|
|
items.push({
|
|
value: size.toString(),
|
|
label: size.toString(),
|
|
displayValue: size.toString(),
|
|
});
|
|
}
|
|
}
|
|
|
|
return items;
|
|
}
|