diff --git a/docs/sources/datasources/graphite/configure/index.md b/docs/sources/datasources/graphite/configure/index.md
index d7e03817c45..ab384c99560 100644
--- a/docs/sources/datasources/graphite/configure/index.md
+++ b/docs/sources/datasources/graphite/configure/index.md
@@ -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.
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.
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.
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.
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:
diff --git a/docs/sources/datasources/graphite/template-variables/index.md b/docs/sources/datasources/graphite/template-variables/index.md
index cb1633225cb..b79feb5e3d0 100644
--- a/docs/sources/datasources/graphite/template-variables/index.md
+++ b/docs/sources/datasources/graphite/template-variables/index.md
@@ -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
diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
index d2e38be4f58..625a4b4b106 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
@@ -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) => 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 = 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[] => {
- const columnsFromFields = (f: Field[], w: number[]): TableColumn[] =>
- f.map((field, i): TableColumn => {
+ interface Schema {
+ columns: TableColumn[];
+ cellRootRenderers: Record;
+ }
+
+ 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): 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 (
+ |
+ );
+ };
+
+ result.cellRootRenderers[displayName] = renderCellRoot;
+
+ // this fires second
+ const renderCellContent = (props: RenderCellProps): 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 && (
+
+ )}
+ >
+ );
+ };
+
+ const column: TableColumn = {
field,
key: displayName,
name: displayName,
width,
headerCellClass,
- renderCell: (props: RenderCellProps): 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 && (
-
- )}
- >
- );
- },
+ renderCell: renderCellContent,
renderHeaderCell: ({ column, sortDirection }): JSX.Element => (
{footerCalcs[i]};
},
};
+
+ 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) => | ;
+
// 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 (
{...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 (
<>
@@ -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): 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
;
};
-/**
- * passed to the top-level `renderCell` prop on DataGrid. This applies all per-cell styles.
- */
-const renderCellFactory =
- (
- columnTypes: Record,
- applyToRowBgFn: ((rowIdx: number) => CellColors) | undefined,
- rowHeight: number | ((row: TableRow) => number),
- textWraps: Record,
- theme: GrafanaTheme2,
- visibleFieldsByDisplayName: Record
- ) =>
- (key: Key, props: CellRendererProps) => {
- 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 | ;
- }
-
- 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 (
- |
- );
- };
-
const getGridStyles = (
theme: GrafanaTheme2,
{ enablePagination, noHeader }: { enablePagination?: boolean; noHeader?: boolean }
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
index d65b1cd9ff5..79afab8b076 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
@@ -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;
}
/**