[release-12.2.2] Table: Pill and JSON Cells should allow formatting (#113130)

Table: Pill and JSON Cells should allow formatting (#111951)

* Table: PillCell should use formatted text inside pills

* Table: JSONCell should use formatted text

* remove unused imports

(cherry picked from commit 237ab6c1b4)

Co-authored-by: Paul Marbach <paul.marbach@grafana.com>
This commit is contained in:
grafana-delivery-bot[bot]
2025-11-03 08:37:35 -05:00
committed by GitHub
co-authored by Paul Marbach
parent 75d12036b8
commit dc12aeb4ab
5 changed files with 73 additions and 24 deletions
@@ -14,6 +14,7 @@ describe('PillCell', () => {
type: FieldType.string,
values: values,
config: {},
display: (value: unknown) => ({ text: String(value), color: '#FF780A', numeric: NaN }),
});
const ser = new XMLSerializer();
@@ -119,6 +120,29 @@ describe('PillCell', () => {
`
);
});
it('custom display text', () => {
const mockField = fieldWithValues(['value1,value2,value3']);
const field = {
...mockField,
display: (value: unknown) => ({
text: `${value} lbs`,
color: '#FF780A',
numeric: 0,
}),
} satisfies Field;
expectHTML(
render(
<PillCell getTextColorForBackground={getTextColorForBackground} field={field} rowIdx={0} theme={theme} />
),
`
<span style=\"background-color: rgb(207, 250, 255); color: rgb(32, 34, 38);\">value1 lbs</span>
<span style=\"background-color: rgb(229, 172, 14); color: rgb(247, 248, 250);\">value2 lbs</span>
<span style=\"background-color: rgb(63, 104, 51); color: rgb(247, 248, 250);\">value3 lbs</span>
`
);
});
});
describe('Color by value mappings', () => {
@@ -8,6 +8,7 @@ import {
getColorByStringHash,
FALLBACK_COLOR,
fieldColorModeRegistry,
formattedValueToString,
} from '@grafana/data';
import { FieldColorModeId } from '@grafana/schema';
@@ -20,10 +21,11 @@ export function PillCell({ rowIdx, field, theme, getTextColorForBackground }: Pi
const pillValues = inferPills(value);
return pillValues.length > 0
? pillValues.map((pill, index) => {
const bgColor = getPillColor(pill, field, theme);
const renderedValue = formattedValueToString(field.display!(pill));
const bgColor = getPillColor(renderedValue, field, theme);
const textColor = getTextColorForBackground(bgColor);
return {
value: String(pill),
value: renderedValue,
key: `${pill}-${index}`,
bgColor,
color: textColor,
@@ -438,7 +438,7 @@ export function TableNG(props: TableNGProps) {
// attach JSONCell custom display function to JSONView cell type
if (cellType === TableCellDisplayMode.JSONView || field.type === FieldType.other) {
field.display = displayJsonValue;
field.display = displayJsonValue(field);
}
// For some cells, "aligning" the cell will mean aligning the inline contents of the cell with
@@ -47,6 +47,7 @@ import {
getDisplayName,
predicateByName,
calculateFooterHeight,
displayJsonValue,
} from './utils';
describe('TableNG utils', () => {
@@ -1354,10 +1355,35 @@ describe('TableNG utils', () => {
});
describe('displayJsonValue', () => {
it.todo('should parse and then stringify string values');
it.todo('should not throw for non-serializable string values');
it.todo('should stringify non-string values');
it.todo('should not throw for non-serializable non-string values');
let field: Field;
beforeEach(() => {
field = {
name: 'test',
type: FieldType.string,
config: {},
state: { displayName: 'Test Display Name' },
values: [],
display: (val: unknown) => ({ text: String(val), numeric: NaN }),
};
});
it('should parse and then stringify string values', () => {
expect(displayJsonValue(field)('{"valid": "json"}').text).toBe('{\n "valid": "json"\n}');
});
it('should not throw for non-serializable string values', () => {
expect(displayJsonValue(field)('{"invalid": "json').text).toBe('{"invalid": "json');
});
it('should stringify non-string values', () => {
expect(displayJsonValue(field)(42).text).toBe('42');
});
it('should use the underlying field.display method to format values and return numeric values', () => {
field.display = (val: unknown) => ({ text: `**${val}**`, numeric: Number(val), suffix: 'ms' });
expect(displayJsonValue(field)(42).text).toBe('**42**ms');
expect(displayJsonValue(field)(42).numeric).toBe(42);
});
});
describe('applySort', () => {
@@ -15,6 +15,7 @@ import {
DisplayValueAlignmentFactors,
DataFrame,
DisplayProcessor,
DecimalCount,
} from '@grafana/data';
import {
BarGaugeDisplayMode,
@@ -970,28 +971,24 @@ export function canFieldBeColorized(
);
}
export const displayJsonValue: DisplayProcessor = (value: unknown): DisplayValue => {
let displayValue: string;
export const displayJsonValue: (field: Field) => DisplayProcessor = (field: Field, decimals?: DecimalCount) => {
const origDisplay = field.display!;
return (value: unknown): DisplayValue => {
let jsonText: string;
// Handle string values that might be JSON
if (typeof value === 'string') {
const displayValue = origDisplay(value, decimals);
const formattedValue = formattedValueToString(displayValue);
// Handle string values that might be JSON
try {
const parsed = JSON.parse(value);
displayValue = JSON.stringify(parsed, null, ' ');
const parsed = JSON.parse(formattedValue);
jsonText = JSON.stringify(parsed, null, ' ');
} catch {
displayValue = value; // Keep original if not valid JSON
jsonText = formattedValue; // Keep original if not valid JSON
}
} else {
// For non-string values, stringify them
try {
displayValue = JSON.stringify(value, null, ' ');
} catch (error) {
// Handle circular references or other stringify errors
displayValue = String(value);
}
}
return { text: displayValue, numeric: Number.NaN };
return { ...displayValue, text: jsonText };
};
};
export function getSummaryCellTextAlign(textAlign: TextAlign, cellType: TableCellDisplayMode): TextAlign {