diff --git a/packages/grafana-data/src/field/fieldColor.ts b/packages/grafana-data/src/field/fieldColor.ts index 8c7702c2bb0..a7957f9f44e 100644 --- a/packages/grafana-data/src/field/fieldColor.ts +++ b/packages/grafana-data/src/field/fieldColor.ts @@ -218,7 +218,7 @@ export class FieldColorSchemeMode implements FieldColorMode { } } else if (this.useSeriesName) { return (_: number, _percent: number, _threshold?: Threshold) => { - return colors[Math.abs(stringHash(field.state?.displayName ?? field.name)) % colors.length]; + return getColorByStringHash(colors, field.state?.displayName ?? field.name); }; } else { return (_: number, _percent: number, _threshold?: Threshold) => { @@ -263,6 +263,10 @@ export function getFieldSeriesColor(field: Field, theme: GrafanaTheme2): ColorSc return scale(value); } +export function getColorByStringHash(colors: string[], string: string) { + return colors[Math.abs(stringHash(string)) % colors.length]; +} + function getFixedColor(field: Field, theme: GrafanaTheme2) { return () => { return theme.visualization.getColorByName(field.config.color?.fixedColor ?? FALLBACK_COLOR); diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index bc9e4d245db..c51f2f17c7c 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -137,6 +137,8 @@ export { fieldColorModeRegistry, type FieldColorMode, getFieldSeriesColor, + /** @internal */ + getColorByStringHash, } from './field/fieldColor'; export { FieldConfigOptionsRegistry } from './field/FieldConfigOptionsRegistry'; export { sortThresholds, getActiveThreshold } from './field/thresholds'; diff --git a/packages/grafana-schema/src/common/common.gen.ts b/packages/grafana-schema/src/common/common.gen.ts index ff937656095..37273bee0cf 100644 --- a/packages/grafana-schema/src/common/common.gen.ts +++ b/packages/grafana-schema/src/common/common.gen.ts @@ -977,8 +977,6 @@ export enum ComparisonOperation { } export interface TablePillCellOptions { - color?: string; - colorMode?: ('auto' | 'fixed' | 'mapped'); type: TableCellDisplayMode.Pill; } diff --git a/packages/grafana-schema/src/common/table.cue b/packages/grafana-schema/src/common/table.cue index 0858150ae4c..acf4a6b7480 100644 --- a/packages/grafana-schema/src/common/table.cue +++ b/packages/grafana-schema/src/common/table.cue @@ -111,7 +111,4 @@ TableFieldOptions: { TablePillCellOptions: { type: TableCellDisplayMode & "pill" - color?: string - colorMode?: "auto" | "fixed" | "mapped" } @cuetsy(kind="interface") - diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx index a216a9221d1..b7249e88b2b 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.test.tsx @@ -1,16 +1,18 @@ -import { render, screen } from '@testing-library/react'; +import { render, RenderResult } from '@testing-library/react'; import { DataFrame, Field, FieldType, GrafanaTheme2, MappingType, createTheme } from '@grafana/data'; import { TableCellDisplayMode, TablePillCellOptions } from '@grafana/schema'; import { mockThemeContext } from '../../../../themes/ThemeContext'; -import { PillCell, inferPills } from './PillCell'; +import { PillCell, getStyles } from './PillCell'; describe('PillCell', () => { + let pillClass: string; let restoreThemeContext: () => void; beforeEach(() => { + pillClass = getStyles(createTheme()).pill; restoreThemeContext = mockThemeContext(createTheme()); }); @@ -20,7 +22,6 @@ describe('PillCell', () => { const mockCellOptions: TablePillCellOptions = { type: TableCellDisplayMode.Pill, - colorMode: 'auto', }; const mockField: Field = { @@ -37,7 +38,6 @@ describe('PillCell', () => { }; const defaultProps = { - value: 'test-value', field: mockField, justifyContent: 'flex-start' as const, cellOptions: mockCellOptions, @@ -50,170 +50,102 @@ describe('PillCell', () => { showFilters: false, }; - describe('pill parsing', () => { - it('should render pills for single values', () => { - render(); - expect(screen.getByText('test-value')).toBeInTheDocument(); - }); + const ser = new XMLSerializer(); - it('should render pills for CSV values', () => { - render(); - expect(screen.getByText('value1')).toBeInTheDocument(); - expect(screen.getByText('value2')).toBeInTheDocument(); - expect(screen.getByText('value3')).toBeInTheDocument(); - }); + const expectHTML = (result: RenderResult, expected: string) => { + let actual = ser.serializeToString(result.asFragment()).replace(/xmlns=".*?" /g, ''); + expect(actual).toEqual(expected.replace(/^\s*|\n/gm, '')); + }; - it('should render pills for JSON array values', () => { - render(); - expect(screen.getByText('item1')).toBeInTheDocument(); - expect(screen.getByText('item2')).toBeInTheDocument(); - expect(screen.getByText('item3')).toBeInTheDocument(); - }); + // one class for lightTextPill, darkTextPill - it('should show dash for empty values', () => { - render(); - expect(screen.getByText('-')).toBeInTheDocument(); - }); + describe('Color by hash (classic palette)', () => { + const props = { ...defaultProps }; - it('should show dash for null values', () => { - render(); - expect(screen.getByText('-')).toBeInTheDocument(); + it('single value', () => { + expectHTML( + render(), + `value1` + ); }); - }); - - describe('color mapping', () => { - // These tests primarily ensure the color logic executes without throwing. - // For true color verification, visual regression tests would be needed. - it('should use mapped colors when colorMode is mapped', () => { - const mappedOptions: TablePillCellOptions = { - type: TableCellDisplayMode.Pill, - colorMode: 'mapped', - }; - - render(); - - const successPill = screen.getByText('success'); - const errorPill = screen.getByText('error'); - const warningPill = screen.getByText('warning'); - const unknownPill = screen.getByText('unknown'); - - expect(successPill).toBeInTheDocument(); - expect(errorPill).toBeInTheDocument(); - expect(warningPill).toBeInTheDocument(); - expect(unknownPill).toBeInTheDocument(); + it('empty string', () => { + expectHTML(render(), ''); }); - it('should use field-level value mappings when available', () => { - const mappedOptions: TablePillCellOptions = { - type: TableCellDisplayMode.Pill, - colorMode: 'mapped', - }; - - // Mock field with value mappings - const fieldWithMappings: Field = { - ...mockField, - config: { - ...mockField.config, - mappings: [ - { - type: MappingType.ValueToText, - options: { - success: { color: '#00FF00' }, - error: { color: '#FF0000' }, - warning: { color: '#FFFF00' }, - }, - }, - ], - }, - display: (value: unknown) => ({ - text: String(value), - color: - String(value) === 'success' - ? '#00FF00' - : String(value) === 'error' - ? '#FF0000' - : String(value) === 'warning' - ? '#FFFF00' - : '#FF780A', - numeric: 0, - }), - }; - - render( - + // it('null', () => { + // expectHTML( + // render(), + // 'value1' + // ); + // }); + + it('CSV values', () => { + expectHTML( + render(), + ` + value1 + value2 + value3 + ` ); - - const successPill = screen.getByText('success'); - const errorPill = screen.getByText('error'); - const warningPill = screen.getByText('warning'); - const unknownPill = screen.getByText('unknown'); - - expect(successPill).toBeInTheDocument(); - expect(errorPill).toBeInTheDocument(); - expect(warningPill).toBeInTheDocument(); - expect(unknownPill).toBeInTheDocument(); - }); - - it('should use fixed color when colorMode is fixed', () => { - const fixedOptions: TablePillCellOptions = { - type: TableCellDisplayMode.Pill, - colorMode: 'fixed', - color: '#FF00FF', - }; - - render(); - expect(screen.getByText('test-value')).toBeInTheDocument(); }); - it('should use auto color when colorMode is auto', () => { - const autoOptions: TablePillCellOptions = { - type: TableCellDisplayMode.Pill, - colorMode: 'auto', - }; - - render(); - expect(screen.getByText('test-value')).toBeInTheDocument(); + it('JSON array values', () => { + expectHTML( + render(), + ` + value1 + value2 + value3 + ` + ); }); - }); -}); -describe('inferPills', () => { - // These tests verify the pill parsing logic handles various input formats correctly. - // They ensure the function can extract pill values from different data structures. - - it('should return empty array for null/undefined values', () => { - expect(inferPills(null)).toEqual([]); - expect(inferPills(undefined)).toEqual([]); - expect(inferPills('')).toEqual([]); + // TODO: handle null values? }); - it('should parse single values', () => { - expect(inferPills('test')).toEqual(['test']); - expect(inferPills('"quoted"')).toEqual(['quoted']); - expect(inferPills("'quoted'")).toEqual(['quoted']); - }); - - it('should parse CSV strings', () => { - expect(inferPills('value1,value2,value3')).toEqual(['value1', 'value2', 'value3']); - expect(inferPills(' value1 , value2 , value3 ')).toEqual(['value1', 'value2', 'value3']); - expect(inferPills('value1, ,value3')).toEqual(['value1', 'value3']); - }); - - it('should parse JSON arrays', () => { - expect(inferPills('["item1","item2","item3"]')).toEqual(['item1', 'item2', 'item3']); - expect(inferPills('["item1", "item2", "item3"]')).toEqual(['item1', 'item2', 'item3']); - expect(inferPills('["item1", null, "item3"]')).toEqual(['item1', 'item3']); - }); + describe('Color by value mappings', () => { + const field: Field = { + ...mockField, + config: { + ...mockField.config, + mappings: [ + { + type: MappingType.ValueToText, + options: { + success: { color: '#00FF00' }, + error: { color: '#FF0000' }, + warning: { color: '#FFFF00' }, + }, + }, + ], + }, + display: (value: unknown) => ({ + text: String(value), + color: + value === 'success' ? '#00FF00' : value === 'error' ? '#FF0000' : value === 'warning' ? '#FFFF00' : '#FF780A', + numeric: 0, + }), + }; + + const props = { + ...defaultProps, + field, + }; + + it('CSV values', () => { + expectHTML( + render(), + ` + success + error + warning + unknown + ` + ); + }); - it('should handle mixed content', () => { - // When JSON parsing fails, it falls back to CSV parsing - expect(inferPills('["item1", "item2"],extra')).toEqual(['["item1"', '"item2"]', 'extra']); - expect(inferPills('not-json,value')).toEqual(['not-json', 'value']); + // TODO: handle null values? }); }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx index aaa103c3dc7..c1ca23b7f69 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/PillCell.tsx @@ -1,15 +1,19 @@ import { css } from '@emotion/css'; -import { Property } from 'csstype'; import { useMemo } from 'react'; -import { GrafanaTheme2, isDataFrame, classicColors, colorManipulator, Field } from '@grafana/data'; -import { TablePillCellOptions } from '@grafana/schema'; - -import { useStyles2 } from '../../../../themes/ThemeContext'; +import { + GrafanaTheme2, + classicColors, + colorManipulator, + Field, + getColorByStringHash, + FALLBACK_COLOR, +} from '@grafana/data'; +import { FieldColorModeId } from '@grafana/schema'; + +import { useStyles2, useTheme2 } from '../../../../themes/ThemeContext'; import { TableCellRendererProps } from '../types'; -const DEFAULT_PILL_BG_COLOR = '#FF780A'; - interface Pill { value: string; key: string; @@ -17,9 +21,9 @@ interface Pill { color: string; } -function createPills(pillValues: string[], cellOptions: TableCellRendererProps['cellOptions'], field: Field): Pill[] { +function createPills(pillValues: string[], field: Field, theme: GrafanaTheme2): Pill[] { return pillValues.map((pill, index) => { - const bgColor = getPillColor(pill, cellOptions, field); + const bgColor = getPillColor(pill, field, theme); const textColor = colorManipulator.getContrastRatio('#FFFFFF', bgColor) >= 4.5 ? '#FFFFFF' : '#000000'; return { value: pill, @@ -30,156 +34,73 @@ function createPills(pillValues: string[], cellOptions: TableCellRendererProps[' }); } -export function PillCell({ value, field, justifyContent, cellOptions }: TableCellRendererProps) { - const styles = useStyles2(getStyles, justifyContent); +export function PillCell({ value, field }: TableCellRendererProps) { + const styles = useStyles2(getStyles); + const theme = useTheme2(); const pills: Pill[] = useMemo(() => { - const pillValues = inferPills(value); - return createPills(pillValues, cellOptions, field); - }, [value, cellOptions, field]); - - if (pills.length === 0) { - return
-
; - } - - return ( -
-
- {pills.map((pill) => ( - - {pill.value} - - ))} -
-
- ); + const pillValues = inferPills(String(value)); + return createPills(pillValues, field, theme); + }, [value, field, theme]); + + return pills.map((pill) => ( + + {pill.value} + + )); } -export function inferPills(value: unknown): string[] { - if (!value) { - return []; - } +const SPLIT_RE = /\s*,\s*/; +const TRANSPARENT = 'rgba(0,0,0,0)'; - // Handle DataFrame - not supported for pills - if (isDataFrame(value)) { +export function inferPills(value: string): string[] { + if (value === '') { return []; } - // Handle different value types - const stringValue = String(value); - - // Try to parse as JSON first - try { - const parsed = JSON.parse(stringValue); - if (Array.isArray(parsed)) { - // JSON array of strings - return parsed - .filter((item) => item != null && item !== '') - .map(String) - .map((text) => text.trim()) - .filter((item) => item !== ''); + if (value[0] === '[') { + try { + return JSON.parse(value); + } catch { + return value.trim().split(SPLIT_RE); } - } catch { - // Not valid JSON, continue with other parsing - } - - // Handle CSV string - if (stringValue.includes(',')) { - return stringValue - .split(',') - .map((text) => text.trim()) - .filter((item) => item !== ''); } - // Single value - strip quotes - return [stringValue.replace(/["'`]/g, '').trim()]; + return value.trim().split(SPLIT_RE); } -function isPillCellOptions(cellOptions: TableCellRendererProps['cellOptions']): cellOptions is TablePillCellOptions { - return cellOptions?.type === 'pill'; -} +function getPillColor(value: string, field: Field, theme: GrafanaTheme2): string { + const cfg = field.config; -function getPillColor(pill: string, cellOptions: TableCellRendererProps['cellOptions'], field: Field): string { - if (!isPillCellOptions(cellOptions)) { - return getDeterministicColor(pill); + if (cfg.mappings?.length ?? 0 > 0) { + return field.display!(value).color ?? FALLBACK_COLOR; } - const colorMode = cellOptions.colorMode || 'auto'; - - // Fixed color mode (highest priority) - if (colorMode === 'fixed' && cellOptions.color) { - return cellOptions.color; + if (cfg.color?.mode === FieldColorModeId.Fixed) { + return theme.visualization.getColorByName(cfg.color?.fixedColor ?? FALLBACK_COLOR); } - // Mapped color mode - use field's value mappings - if (colorMode === 'mapped') { - // Check if field has value mappings - if (field.config.mappings && field.config.mappings.length > 0) { - // Use the field's display processor to get the mapped value - const displayValue = field.display!(pill); - if (displayValue.color) { - return displayValue.color; - } - } - // Fallback to default color for unmapped values - return cellOptions.color || DEFAULT_PILL_BG_COLOR; - } - - // Auto mode - deterministic color assignment based on string hash - if (colorMode === 'auto') { - return getDeterministicColor(pill); - } - - // Default color for unknown values or fallback - return DEFAULT_PILL_BG_COLOR; + // TODO: instead of classicColors we need to pull colors from theme, same way as FieldColorModeId.PaletteClassicByName (see fieldColor.ts) + return getColorByStringHash(classicColors, value); } -function getDeterministicColor(text: string): string { - // Create a simple hash of the string to get consistent colors - let hash = 0; - for (let i = 0; i < text.length; i++) { - const char = text.charCodeAt(i); - hash = (hash << 5) - hash + char; - hash = hash & hash; // Convert to 32-bit integer - } - - // Use absolute value and modulo to get a consistent index - const colorValues = Object.values(classicColors); - const index = Math.abs(hash) % colorValues.length; - - return colorValues[index]; -} - -const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent | undefined) => ({ - cell: css({ - display: 'flex', - justifyContent: justifyContent || 'flex-start', - alignItems: 'center', - height: '100%', - padding: theme.spacing(0.5), - }), - pillsContainer: css({ - display: 'flex', - flexWrap: 'wrap', - gap: theme.spacing(0.5), - maxWidth: '100%', - }), +export const getStyles = (theme: GrafanaTheme2) => ({ pill: css({ display: 'inline-block', padding: theme.spacing(0.25, 0.75), + marginInlineEnd: theme.spacing(0.5), + marginBlock: theme.spacing(0.5), borderRadius: theme.shape.radius.default, fontSize: theme.typography.bodySmall.fontSize, lineHeight: theme.typography.bodySmall.lineHeight, - fontWeight: theme.typography.fontWeightMedium, whiteSpace: 'nowrap', - textAlign: 'center', - minWidth: 'fit-content', }), }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx index 74c773f28ff..d1bdcdad4a2 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/renderers.tsx @@ -109,6 +109,12 @@ export function getCellRenderer(field: Field, cellOptions: TableCellOptions): Ta if (cellType === TableCellDisplayMode.Auto) { return getAutoRendererResult(field); } + + // TODO: add support boolean, enum, (maybe int). but for now just string fields + if (cellType === TableCellDisplayMode.Pill && field.type !== FieldType.string) { + return AUTO_RENDERER; + } + return CELL_RENDERERS[cellType] ?? AUTO_RENDERER; } diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index dbe5dd3f1a0..5590dad8e3e 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -14,9 +14,18 @@ import { SortColumn, } from 'react-data-grid'; -import { DataHoverClearEvent, DataHoverEvent, Field, FieldType, GrafanaTheme2, ReducerID } from '@grafana/data'; +import { + DataHoverClearEvent, + DataHoverEvent, + FALLBACK_COLOR, + Field, + FieldType, + getDisplayProcessor, + GrafanaTheme2, + ReducerID, +} from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; -import { TableCellHeight } from '@grafana/schema'; +import { FieldColorModeId, TableCellHeight } from '@grafana/schema'; import { useStyles2, useTheme2 } from '../../../themes/ThemeContext'; import { ContextMenu } from '../../ContextMenu/ContextMenu'; @@ -272,11 +281,30 @@ export function TableNG(props: TableNGProps) { let _rowHeight = 0; f.forEach((field, i) => { + const cellOptions = getCellOptions(field); + const cellType = cellOptions.type; + + // make sure we use mappings exclusively if they exist, ignore default thresholds mode + // we hack this by using the single color mode calculator + if (cellType === TableCellDisplayMode.Pill && (field.config.mappings?.length ?? 0 > 0)) { + field = { + ...field, + config: { + ...field.config, + color: { + ...field.config.color, + mode: FieldColorModeId.Fixed, + fixedColor: field.config.color?.fixedColor ?? FALLBACK_COLOR, + }, + }, + }; + field.display = getDisplayProcessor({ field, theme }); + } + const justifyContent = getTextAlign(field); const footerStyles = getFooterStyles(justifyContent); const displayName = getDisplayName(field); const headerCellClass = getHeaderCellStyles(theme, justifyContent).headerCell; - const cellOptions = getCellOptions(field); const renderFieldCell = getCellRenderer(field, cellOptions); const cellInspect = isCellInspectEnabled(field); @@ -293,7 +321,6 @@ export function TableNG(props: TableNGProps) { ) : undefined; - const cellType = cellOptions.type; const shouldOverflow = shouldTextOverflow(field); const shouldWrap = shouldTextWrap(field); const withTooltip = withDataLinksActionsTooltip(field, cellType); diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index 90004837874..a99ec3edab7 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -128,11 +128,14 @@ export function getMaxWrapCell( * Returns true if text overflow handling should be applied to the cell. */ export function shouldTextOverflow(field: Field): boolean { + let type = getCellOptions(field).type; + return ( field.type === FieldType.string && // 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 for type image - getCellOptions(field).type !== TableCellDisplayMode.Image && + type !== TableCellDisplayMode.Image && + type !== TableCellDisplayMode.Pill && !shouldTextWrap(field) && !isCellInspectEnabled(field) ); diff --git a/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx b/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx index d8752c8e503..ee4d69c1add 100644 --- a/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx +++ b/public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx @@ -10,7 +10,6 @@ import { AutoCellOptionsEditor } from './cells/AutoCellOptionsEditor'; import { BarGaugeCellOptionsEditor } from './cells/BarGaugeCellOptionsEditor'; import { ColorBackgroundCellOptionsEditor } from './cells/ColorBackgroundCellOptionsEditor'; import { ImageCellOptionsEditor } from './cells/ImageCellOptionsEditor'; -import { PillCellOptionsEditor } from './cells/PillCellOptionsEditor'; import { SparklineCellOptionsEditor } from './cells/SparklineCellOptionsEditor'; // The props that any cell type editor are expected @@ -78,9 +77,6 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => { {cellType === TableCellDisplayMode.Image && ( )} - {cellType === TableCellDisplayMode.Pill && ( - - )} ); }; diff --git a/public/app/plugins/panel/table/table-new/cells/PillCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/PillCellOptionsEditor.tsx deleted file mode 100644 index 0ff38f33ea9..00000000000 --- a/public/app/plugins/panel/table/table-new/cells/PillCellOptionsEditor.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { t } from '@grafana/i18n'; -import { TablePillCellOptions } from '@grafana/schema'; -import { Field, ColorPicker, RadioButtonGroup, Stack } from '@grafana/ui'; - -import { TableCellEditorProps } from '../TableCellOptionEditor'; - -const colorModeOptions: Array<{ value: 'auto' | 'fixed' | 'mapped'; label: string }> = [ - { value: 'auto', label: 'Auto' }, - { value: 'fixed', label: 'Fixed color' }, - { value: 'mapped', label: 'Value mapping' }, -]; - -export const PillCellOptionsEditor = ({ cellOptions, onChange }: TableCellEditorProps) => { - const colorMode = cellOptions.colorMode || 'auto'; - - const onColorModeChange = (mode: 'auto' | 'fixed' | 'mapped') => { - const updatedOptions = { ...cellOptions, colorMode: mode }; - onChange(updatedOptions); - }; - - const onColorChange = (color: string) => { - const updatedOptions = { ...cellOptions, color }; - onChange(updatedOptions); - }; - - return ( - - - - - - {colorMode === 'fixed' && ( - - - - )} - - {colorMode === 'mapped' && ( - -
 
-
- )} -
- ); -}; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index ea3d5595fbb..c518ead9368 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11871,14 +11871,6 @@ "name-show-table-footer": "Show table footer", "name-show-table-header": "Show table header", "name-wrap-header-text": "Wrap header text", - "pill-cell-options-editor": { - "description-color-mode": "Choose how colors are assigned to pills", - "description-fixed-color": "All pills in this column will use this color", - "description-value-mappings-info": "For Value Mappings either use the global table Value Mappings or the Field overrides Value Mappings. The default will fall back to the Color Scheme. ", - "label-color-mode": "Color Mode", - "label-fixed-color": "Fixed Color", - "label-value-mappings-info": "Value Mappings" - }, "placeholder-column-width": "auto", "placeholder-fields": "All Numeric Fields" },