TableNG: Specialize cell root and cell content renderers (#107427)
This commit is contained in:
@@ -88,52 +88,52 @@ Grafana takes you to the **Settings** tab, where you will set up your Graphite c
|
||||
|
||||
Following is a list of configuration options for Graphite.
|
||||
|
||||
| Setting | Description |
|
||||
|-------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Name** | The display name for the data source. This is how you'll reference it in panels and queries. <br>Examples: `graphite-1`, `graphite-metrics`. |
|
||||
| **Default** | When enabled, sets this data source as the default for dashboard panels. It will be automatically selected when creating new panels. |
|
||||
| Setting | Description |
|
||||
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | The display name for the data source. This is how you'll reference it in panels and queries. <br>Examples: `graphite-1`, `graphite-metrics`. |
|
||||
| **Default** | When enabled, sets this data source as the default for dashboard panels. It will be automatically selected when creating new panels. |
|
||||
|
||||
**HTTP:**
|
||||
|
||||
| Setting | Description |
|
||||
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **URL** | Sets the HTTP protocol, IP, and port of your `graphite-web` or `graphite-api` installation. <br>Since the access method is set to *Server*, the URL must be accessible from the Grafana backend. |
|
||||
| **Allowed cookies**| By default, Grafana removes forwarded cookies. Specify cookie names here to allow them to be forwarded to the data source. |
|
||||
| **Timeout** | Sets the HTTP request timeout in seconds. |
|
||||
| Setting | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **URL** | Sets the HTTP protocol, IP, and port of your `graphite-web` or `graphite-api` installation. <br>Since the access method is set to _Server_, the URL must be accessible from the Grafana backend. |
|
||||
| **Allowed cookies** | By default, Grafana removes forwarded cookies. Specify cookie names here to allow them to be forwarded to the data source. |
|
||||
| **Timeout** | Sets the HTTP request timeout in seconds. |
|
||||
|
||||
**Auth:**
|
||||
|
||||
| **Setting** | **Description** |
|
||||
|------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Basic Auth** | Toggle on to enable basic authentication to the data source. |
|
||||
| **User** | Sets the username used for basic authentication. |
|
||||
| **Password** | Enter the password used for basic authentication. |
|
||||
| **With Credentials** | Toggle on to include cookies and authentication headers in cross-origin requests. |
|
||||
| **TLS Client Auth** | Toggle on to enable TLS client authentication (both server and client are verified). |
|
||||
| **ServerName** | The server name used to verify the hostname on the certificate returned by the server. |
|
||||
| **Client Cert** | Client certificate generated by a Certificate Authority (CA) or self-signed. |
|
||||
| **Client Key** | Private key used to encrypt communication between the client and server. Also generated by a CA or self-signed. |
|
||||
| **With CA Cert** | Toggle on to authenticate with a CA certificate. |
|
||||
| **CA Cert** | CA certificate used to validate the server certificate. |
|
||||
| **Skip TLS Verify** | Toggle on to bypass TLS certificate validation. Not recommended unless necessary or for testing purposes. |
|
||||
| **Forward OAuth Identity** | Toggle on to forward the user's upstream OAuth identity to the data source. Grafana includes the access token in the request. |
|
||||
| **Setting** | **Description** |
|
||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Basic Auth** | Toggle on to enable basic authentication to the data source. |
|
||||
| **User** | Sets the username used for basic authentication. |
|
||||
| **Password** | Enter the password used for basic authentication. |
|
||||
| **With Credentials** | Toggle on to include cookies and authentication headers in cross-origin requests. |
|
||||
| **TLS Client Auth** | Toggle on to enable TLS client authentication (both server and client are verified). |
|
||||
| **ServerName** | The server name used to verify the hostname on the certificate returned by the server. |
|
||||
| **Client Cert** | Client certificate generated by a Certificate Authority (CA) or self-signed. |
|
||||
| **Client Key** | Private key used to encrypt communication between the client and server. Also generated by a CA or self-signed. |
|
||||
| **With CA Cert** | Toggle on to authenticate with a CA certificate. |
|
||||
| **CA Cert** | CA certificate used to validate the server certificate. |
|
||||
| **Skip TLS Verify** | Toggle on to bypass TLS certificate validation. Not recommended unless necessary or for testing purposes. |
|
||||
| **Forward OAuth Identity** | Toggle on to forward the user's upstream OAuth identity to the data source. Grafana includes the access token in the request. |
|
||||
|
||||
**Custom HTTP Headers:**
|
||||
|
||||
Pass along additional information and metadata about the request or response.
|
||||
|
||||
| **Setting** | **Description** |
|
||||
|-------------|--------------------------------------------------------------------------------------------------|
|
||||
| **Setting** | **Description** |
|
||||
| ----------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| **Header** | Add a custom header. This allows custom headers to be passed based on the needs of your Graphite instance. |
|
||||
| **Value** | The value of the header. |
|
||||
| **Value** | The value of the header. |
|
||||
|
||||
**Graphite details:**
|
||||
|
||||
| **Setting** | **Description** |
|
||||
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Version** | Select your Graphite version from the drop-down. This controls which functions are available in the Graphite query editor. Use `1.1.x` for Grafana Cloud Graphite. |
|
||||
| **Graphite backend type**| Select the Graphite backend type. Choosing `Metrictank` enables additional features like query processing metadata. (`Metrictank` is a multi-tenant time series engine compatible with Graphite.) Use `Default` for Grafana Cloud Graphite. |
|
||||
| **Rollup indicator** | Toggle on to display an info icon in panel headers when data aggregation (rollup) occurs. Only available when `Metrictank` is selected. |
|
||||
| **Setting** | **Description** |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Version** | Select your Graphite version from the drop-down. This controls which functions are available in the Graphite query editor. Use `1.1.x` for Grafana Cloud Graphite. |
|
||||
| **Graphite backend type** | Select the Graphite backend type. Choosing `Metrictank` enables additional features like query processing metadata. (`Metrictank` is a multi-tenant time series engine compatible with Graphite.) Use `Default` for Grafana Cloud Graphite. |
|
||||
| **Rollup indicator** | Toggle on to display an info icon in panel headers when data aggregation (rollup) occurs. Only available when `Metrictank` is selected. |
|
||||
|
||||
**Label mappings:**
|
||||
|
||||
@@ -149,10 +149,10 @@ Grafana automatically maps all Graphite tags to labels, even if you haven’t de
|
||||
| `alias(servers.*.{001,002}.*,1,2)` | `{server=~"(001,002)"}` |
|
||||
| `interpolate(seriesByTag('foo=bar', 'server=002'), inf)` | `{foo="bar", server="002"}` |
|
||||
|
||||
| **Setting** | **Description** |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Private data source connect** | _Only for Grafana Cloud users._ Establishes a private, secured connection between a Grafana Cloud stack and data sources within a private network. Use the drop-down to locate the PDC URL. For setup instructions, refer to [Private data source connect (PDC)](ref:private-data-source-connect) and [Configure PDC](ref:configure-pdc). Click **Manage private data source connect** to open your PDC connection page and view your configuration details. |
|
||||
|
||||
| **Setting** | **Description** |
|
||||
|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Private data source connect** | _Only for Grafana Cloud users._ Establishes a private, secured connection between a Grafana Cloud stack and data sources within a private network. Use the drop-down to locate the PDC URL. For setup instructions, refer to [Private data source connect (PDC)](ref:private-data-source-connect) and [Configure PDC](ref:configure-pdc). Click **Manage private data source connect** to open your PDC connection page and view your configuration details.
|
||||
|
|
||||
|
||||
After configuring your Graphite data source options, click **Save & test** at the bottom to test the connection. You should see a confirmation dialog box that says:
|
||||
|
||||
@@ -48,11 +48,11 @@ To view an example templated dashboard, refer to [Graphite Templated Nested dash
|
||||
|
||||
With Graphite data sources, you can only create query variables. Grafana supports three specific query types for Graphite-based variables:
|
||||
|
||||
| Query type | Description | Example usage |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| Query type | Description | Example usage |
|
||||
| --------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| **Default query** | Allows you to dynamically list metrics, nodes, or tag values using Graphite functions. | `tag_values(apps.*.requests.count, app)` |
|
||||
| **Value query** | Returns all the values for a query that includes a metric and function. | `tag_values(apps.*.status.*, status)` |
|
||||
| **Metric name query** | Returns all the names for a query that includes a metric and function. | `apps.*.requests.count` |
|
||||
| **Value query** | Returns all the values for a query that includes a metric and function. | `tag_values(apps.*.status.*, status)` |
|
||||
| **Metric name query** | Returns all the names for a query that includes a metric and function. | `apps.*.requests.count` |
|
||||
|
||||
### Choose a variable syntax
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'react-data-grid/lib/styles.css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
import { Key, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Key, ReactNode, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Cell,
|
||||
CellRendererProps,
|
||||
@@ -22,7 +22,7 @@ import { MenuItem } from '../../Menu/MenuItem';
|
||||
import { Pagination } from '../../Pagination/Pagination';
|
||||
import { PanelContext, usePanelContext } from '../../PanelChrome';
|
||||
import { TableCellInspector, TableCellInspectorMode } from '../TableCellInspector';
|
||||
import { CellColors } from '../types';
|
||||
import { CellColors, TableCellDisplayMode } from '../types';
|
||||
|
||||
import { HeaderCell } from './Cells/HeaderCell';
|
||||
import { RowExpander } from './Cells/RowExpander';
|
||||
@@ -55,6 +55,8 @@ import {
|
||||
getCellOptions,
|
||||
} from './utils';
|
||||
|
||||
type CellRootRenderer = (key: React.Key, props: CellRendererProps<TableRow, TableSummaryRow>) => React.ReactNode;
|
||||
|
||||
export function TableNG(props: TableNGProps) {
|
||||
const {
|
||||
cellHeight,
|
||||
@@ -137,10 +139,6 @@ export function TableNG(props: TableNGProps) {
|
||||
|
||||
// vt scrollbar accounting for column auto-sizing
|
||||
const visibleFields = useMemo(() => getVisibleFields(data.fields), [data.fields]);
|
||||
const visibleFieldsByDisplayName: Record<string, Field> = useMemo(
|
||||
() => visibleFields.reduce((acc, f) => ({ ...acc, [getDisplayName(f)]: f }), {}),
|
||||
[visibleFields]
|
||||
);
|
||||
const availableWidth = useMemo(
|
||||
() => (hasNestedFrames ? width - COLUMN.EXPANDER_WIDTH : width),
|
||||
[width, hasNestedFrames]
|
||||
@@ -175,11 +173,6 @@ export function TableNG(props: TableNGProps) {
|
||||
[data, enableSharedCrosshair, expandedRows, panelContext]
|
||||
);
|
||||
|
||||
const renderCell = useMemo(
|
||||
() => renderCellFactory(columnTypes, applyToRowBgFn, rowHeight, textWraps, theme, visibleFieldsByDisplayName),
|
||||
[columnTypes, applyToRowBgFn, rowHeight, textWraps, theme, visibleFieldsByDisplayName]
|
||||
);
|
||||
|
||||
const commonDataGridProps = useMemo(
|
||||
() =>
|
||||
({
|
||||
@@ -240,9 +233,19 @@ export function TableNG(props: TableNGProps) {
|
||||
]
|
||||
);
|
||||
|
||||
const columns = useMemo<TableColumn[]>((): TableColumn[] => {
|
||||
const columnsFromFields = (f: Field[], w: number[]): TableColumn[] =>
|
||||
f.map((field, i): TableColumn => {
|
||||
interface Schema {
|
||||
columns: TableColumn[];
|
||||
cellRootRenderers: Record<string, CellRootRenderer>;
|
||||
}
|
||||
|
||||
const { columns, cellRootRenderers } = useMemo(() => {
|
||||
const fromFields = (f: Field[], widths: number[]) => {
|
||||
const result: Schema = {
|
||||
columns: [],
|
||||
cellRootRenderers: {},
|
||||
};
|
||||
|
||||
f.forEach((field, i) => {
|
||||
const justifyContent = getTextAlign(field);
|
||||
const footerStyles = getFooterStyles(justifyContent);
|
||||
const displayName = getDisplayName(field);
|
||||
@@ -253,7 +256,7 @@ export function TableNG(props: TableNGProps) {
|
||||
const cellInspect = Boolean(field.config.custom?.inspect);
|
||||
const showFilters = Boolean(field.config.filterable && onCellFilterAdded != null);
|
||||
const showActions = cellInspect || showFilters;
|
||||
const width = w[i];
|
||||
const width = widths[i];
|
||||
const frame = data;
|
||||
|
||||
// helps us avoid string cx and emotion per-cell
|
||||
@@ -265,54 +268,99 @@ export function TableNG(props: TableNGProps) {
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
const cellType = cellOptions.type;
|
||||
const fieldType = columnTypes[displayName];
|
||||
const shouldWrap = textWraps[displayName];
|
||||
const shouldOverflow = shouldTextOverflow(fieldType, cellType, shouldWrap, cellInspect);
|
||||
|
||||
let lastRowIdx = -1;
|
||||
let _rowHeight = 0;
|
||||
|
||||
// this fires first
|
||||
const renderCellRoot = (key: Key, props: CellRendererProps<TableRow, TableSummaryRow>): ReactNode => {
|
||||
const rowIdx = props.row.__index;
|
||||
const value = props.row[props.column.key];
|
||||
|
||||
// meh, this should be cached by the renderRow() call?
|
||||
if (rowIdx !== lastRowIdx) {
|
||||
_rowHeight = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
|
||||
lastRowIdx = rowIdx;
|
||||
}
|
||||
|
||||
let colors: CellColors;
|
||||
|
||||
if (applyToRowBgFn != null) {
|
||||
colors = applyToRowBgFn(props.rowIdx);
|
||||
} else if (cellType !== TableCellDisplayMode.Auto) {
|
||||
const displayValue = field.display!(value); // this fires here to get colors, then again to get rendered value?
|
||||
colors = getCellColors(theme, cellOptions, displayValue);
|
||||
} else {
|
||||
colors = {};
|
||||
}
|
||||
|
||||
const cellStyle = getCellStyles(theme, field, _rowHeight, shouldWrap, shouldOverflow, colors);
|
||||
|
||||
return (
|
||||
<Cell
|
||||
key={key}
|
||||
{...props}
|
||||
className={cx(props.className, cellStyle.cell)}
|
||||
style={{ color: colors.textColor ?? 'inherit' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
result.cellRootRenderers[displayName] = renderCellRoot;
|
||||
|
||||
// this fires second
|
||||
const renderCellContent = (props: RenderCellProps<TableRow, TableSummaryRow>): JSX.Element => {
|
||||
const rowIdx = props.row.__index;
|
||||
const value = props.row[props.column.key];
|
||||
|
||||
// TODO: defer until click?
|
||||
const actions = getActions?.(frame, field, props.row.__index, replaceVariables);
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderFieldCell({
|
||||
actions,
|
||||
cellOptions,
|
||||
frame,
|
||||
field,
|
||||
height,
|
||||
justifyContent,
|
||||
rowIdx,
|
||||
theme,
|
||||
value,
|
||||
width,
|
||||
cellInspect,
|
||||
showFilters,
|
||||
})}
|
||||
{showActions && (
|
||||
<TableCellActions
|
||||
field={field}
|
||||
value={value}
|
||||
cellOptions={cellOptions}
|
||||
displayName={displayName}
|
||||
cellInspect={cellInspect}
|
||||
showFilters={showFilters}
|
||||
className={cellActionClassName}
|
||||
setIsInspecting={setIsInspecting}
|
||||
setContextMenuProps={setContextMenuProps}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const column: TableColumn = {
|
||||
field,
|
||||
key: displayName,
|
||||
name: displayName,
|
||||
width,
|
||||
headerCellClass,
|
||||
renderCell: (props: RenderCellProps<TableRow, TableSummaryRow>): JSX.Element => {
|
||||
// TODO: once per row
|
||||
const height = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
|
||||
// TODO: defer until click?
|
||||
const actions = getActions?.(frame, field, props.row.__index, replaceVariables);
|
||||
|
||||
const rowIdx = props.row.__index;
|
||||
const value = props.row[displayName];
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderFieldCell({
|
||||
actions,
|
||||
cellOptions,
|
||||
frame,
|
||||
field,
|
||||
height,
|
||||
justifyContent,
|
||||
rowIdx,
|
||||
theme,
|
||||
value,
|
||||
width,
|
||||
cellInspect,
|
||||
showFilters,
|
||||
})}
|
||||
{showActions && (
|
||||
<TableCellActions
|
||||
field={field}
|
||||
value={value}
|
||||
cellOptions={cellOptions}
|
||||
displayName={displayName}
|
||||
cellInspect={cellInspect}
|
||||
showFilters={showFilters}
|
||||
className={cellActionClassName}
|
||||
setIsInspecting={setIsInspecting}
|
||||
setContextMenuProps={setContextMenuProps}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
renderCell: renderCellContent,
|
||||
renderHeaderCell: ({ column, sortDirection }): JSX.Element => (
|
||||
<HeaderCell
|
||||
column={column}
|
||||
@@ -340,9 +388,14 @@ export function TableNG(props: TableNGProps) {
|
||||
return <div className={footerStyles.footerCell}>{footerCalcs[i]}</div>;
|
||||
},
|
||||
};
|
||||
|
||||
result.columns.push(column);
|
||||
});
|
||||
|
||||
const result: TableColumn[] = columnsFromFields(visibleFields, widths);
|
||||
return result;
|
||||
};
|
||||
|
||||
const result = fromFields(visibleFields, widths);
|
||||
|
||||
// handle nested frames rendering from here.
|
||||
if (!hasNestedFrames) {
|
||||
@@ -356,13 +409,17 @@ export function TableNG(props: TableNGProps) {
|
||||
}
|
||||
|
||||
const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
|
||||
const expandedColumns = columnsFromFields(
|
||||
const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields(
|
||||
firstNestedData.fields,
|
||||
computeColWidths(firstNestedData.fields, availableWidth)
|
||||
);
|
||||
|
||||
const renderCellRoot: CellRootRenderer = (key, props) => nestedCellRootRenderers[props.column.key](key, props);
|
||||
|
||||
result.cellRootRenderers.expanded = (key, props) => <Cell key={key} {...props} />;
|
||||
|
||||
// If we have nested frames, we need to add a column for the row expansion
|
||||
result.unshift({
|
||||
result.columns.unshift({
|
||||
key: 'expanded',
|
||||
name: '',
|
||||
field: {
|
||||
@@ -372,16 +429,16 @@ export function TableNG(props: TableNGProps) {
|
||||
values: [],
|
||||
},
|
||||
cellClass(row) {
|
||||
if (Number(row.__depth) !== 0) {
|
||||
if (row.__depth !== 0) {
|
||||
return styles.cellNested;
|
||||
}
|
||||
return;
|
||||
},
|
||||
colSpan(args) {
|
||||
return args.type === 'ROW' && Number(args.row.__depth) === 1 ? data.fields.length : 1;
|
||||
return args.type === 'ROW' && args.row.__depth === 1 ? data.fields.length : 1;
|
||||
},
|
||||
renderCell: ({ row }) => {
|
||||
if (Number(row.__depth) === 0) {
|
||||
if (row.__depth === 0) {
|
||||
return (
|
||||
<RowExpander
|
||||
height={defaultRowHeight}
|
||||
@@ -405,9 +462,9 @@ export function TableNG(props: TableNGProps) {
|
||||
<DataGrid<TableRow, TableSummaryRow>
|
||||
{...commonDataGridProps}
|
||||
className={cx(styles.grid, styles.gridNested)}
|
||||
columns={expandedColumns}
|
||||
columns={nestedColumns}
|
||||
rows={expandedRecords}
|
||||
renderers={{ renderRow, renderCell }}
|
||||
renderers={{ renderRow, renderCell: renderCellRoot }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -433,7 +490,6 @@ export function TableNG(props: TableNGProps) {
|
||||
onCellFilterAdded,
|
||||
panelContext,
|
||||
replaceVariables,
|
||||
renderCell,
|
||||
rows,
|
||||
rowHeight,
|
||||
setFilter,
|
||||
@@ -443,6 +499,10 @@ export function TableNG(props: TableNGProps) {
|
||||
theme,
|
||||
visibleFields,
|
||||
widths,
|
||||
applyToRowBgFn,
|
||||
columnTypes,
|
||||
height,
|
||||
textWraps,
|
||||
]);
|
||||
|
||||
// invalidate columns on every structureRev change. this supports width editing in the fieldConfig.
|
||||
@@ -454,6 +514,10 @@ export function TableNG(props: TableNGProps) {
|
||||
const displayedEnd = pageRangeEnd;
|
||||
const numRows = sortedRows.length;
|
||||
|
||||
const renderCellRoot: CellRootRenderer = (key, props) => {
|
||||
return cellRootRenderers[props.column.key](key, props);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataGrid<TableRow, TableSummaryRow>
|
||||
@@ -471,7 +535,7 @@ export function TableNG(props: TableNGProps) {
|
||||
}
|
||||
: null
|
||||
}
|
||||
renderers={{ renderRow, renderCell }}
|
||||
renderers={{ renderRow, renderCell: renderCellRoot }}
|
||||
/>
|
||||
|
||||
{enablePagination && (
|
||||
@@ -538,11 +602,11 @@ const renderRowFactory =
|
||||
) =>
|
||||
(key: React.Key, props: RenderRowProps<TableRow, TableSummaryRow>): React.ReactNode => {
|
||||
const { row } = props;
|
||||
const rowIdx = Number(row.__index);
|
||||
const rowIdx = row.__index;
|
||||
const isExpanded = !!expandedRows[rowIdx];
|
||||
|
||||
// Don't render non expanded child rows
|
||||
if (Number(row.__depth) === 1 && !isExpanded) {
|
||||
if (row.__depth === 1 && !isExpanded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -573,63 +637,6 @@ const renderRowFactory =
|
||||
return <Row key={key} {...props} {...handlers} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* passed to the top-level `renderCell` prop on DataGrid. This applies all per-cell styles.
|
||||
*/
|
||||
const renderCellFactory =
|
||||
(
|
||||
columnTypes: Record<string, FieldType>,
|
||||
applyToRowBgFn: ((rowIdx: number) => CellColors) | undefined,
|
||||
rowHeight: number | ((row: TableRow) => number),
|
||||
textWraps: Record<string, boolean>,
|
||||
theme: GrafanaTheme2,
|
||||
visibleFieldsByDisplayName: Record<string, Field>
|
||||
) =>
|
||||
(key: Key, props: CellRendererProps<TableRow, TableSummaryRow>) => {
|
||||
const displayName = props.column.key;
|
||||
const field = visibleFieldsByDisplayName[displayName];
|
||||
|
||||
// exit early if we fail to look up the field from the column key.
|
||||
if (!field) {
|
||||
return <Cell key={key} {...props} />;
|
||||
}
|
||||
|
||||
const cellOptions = getCellOptions(field);
|
||||
const cellType = cellOptions.type;
|
||||
const value = props.row[props.column.key];
|
||||
|
||||
const colors: CellColors = (() => {
|
||||
if (applyToRowBgFn) {
|
||||
return applyToRowBgFn(props.rowIdx);
|
||||
}
|
||||
const displayValue = field.display?.(value);
|
||||
if (displayValue && cellOptions) {
|
||||
return getCellColors(theme, cellOptions, displayValue);
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
|
||||
const rh = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
|
||||
const shouldOverflow = shouldTextOverflow(
|
||||
displayName,
|
||||
columnTypes,
|
||||
textWraps[getDisplayName(field)],
|
||||
field,
|
||||
cellType
|
||||
);
|
||||
const shouldWrap = textWraps[displayName] ?? false;
|
||||
const cellStyle = getCellStyles(theme, field, rh, shouldWrap, shouldOverflow, colors);
|
||||
|
||||
return (
|
||||
<Cell
|
||||
key={key}
|
||||
{...props}
|
||||
className={cx(props.className, cellStyle.cell)}
|
||||
style={{ color: colors.textColor ?? 'inherit' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getGridStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
{ enablePagination, noHeader }: { enablePagination?: boolean; noHeader?: boolean }
|
||||
|
||||
@@ -74,21 +74,14 @@ export function getDefaultRowHeight(theme: GrafanaTheme2, cellHeight?: TableCell
|
||||
* Returns true if text overflow handling should be applied to the cell.
|
||||
*/
|
||||
export function shouldTextOverflow(
|
||||
key: string,
|
||||
columnTypes: ColumnTypes,
|
||||
fieldType: FieldType,
|
||||
cellType: TableCellDisplayMode,
|
||||
textWrap: boolean,
|
||||
field: Field,
|
||||
cellType: TableCellDisplayMode
|
||||
cellInspect: boolean
|
||||
): boolean {
|
||||
const cellInspect = field.config?.custom?.inspect ?? false;
|
||||
|
||||
// Tech debt: Technically image cells are of type string, which is misleading (kinda?)
|
||||
// so we need to ensure we don't apply overflow hover states fo type image
|
||||
if (textWrap || cellInspect || cellType === TableCellDisplayMode.Image || columnTypes[key] !== FieldType.string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return fieldType === FieldType.string && cellType !== TableCellDisplayMode.Image && !textWrap && !cellInspect;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user