diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index b6835ec82ac..6fa4169a8b7 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -136,21 +136,13 @@ interface ExpressionResultProps { } export const PAGE_SIZE = 20; export const ExpressionResult: FC = ({ series, isAlertCondition }) => { - const { page, pageItems, onPageChange, numberOfPages, pageStart, pageEnd } = usePagination(series, 1, PAGE_SIZE); + const { pageItems, previousPage, nextPage, numberOfPages, pageStart, pageEnd } = usePagination(series, 1, PAGE_SIZE); const styles = useStyles2(getStyles); // sometimes we receive results where every value is just "null" when noData occurs const emptyResults = isEmptySeries(series); const isTimeSeriesResults = !emptyResults && isTimeSeries(series); - const previousPage = useCallback(() => { - onPageChange(page - 1); - }, [page, onPageChange]); - - const nextPage = useCallback(() => { - onPageChange(page + 1); - }, [page, onPageChange]); - const shouldShowPagination = numberOfPages > 1; return ( @@ -327,8 +319,11 @@ const FrameRow: FC = ({ frame, index, isAlertCondition }) => { const TimeseriesRow: FC = ({ frame, index }) => { const styles = useStyles2(getStyles); - const hasLabels = frame.fields[1].labels; - const name = hasLabels ? formatLabels(frame.fields[1].labels ?? {}) : 'Series ' + index; + const valueField = frame.fields[1]; // field 0 is "time", field 1 is "value" + + const hasLabels = valueField.labels; + const displayNameFromDS = valueField.config?.displayNameFromDS; + const name = displayNameFromDS ?? (hasLabels ? formatLabels(valueField.labels ?? {}) : 'Series ' + index); const timestamps = frame.fields[0].values.toArray(); diff --git a/public/app/features/alerting/unified/components/expressions/util.test.ts b/public/app/features/alerting/unified/components/expressions/util.test.ts new file mode 100644 index 00000000000..30b022791da --- /dev/null +++ b/public/app/features/alerting/unified/components/expressions/util.test.ts @@ -0,0 +1,99 @@ +import { DataFrame, FieldType, toDataFrame } from '@grafana/data'; + +import { getSeriesName, formatLabels, getSeriesValue, isEmptySeries } from './util'; + +const EMPTY_FRAME: DataFrame = toDataFrame([]); +const NAMED_FRAME: DataFrame = { + name: 'MyFrame', + ...toDataFrame([]), +}; + +const DATA_FRAME: DataFrame = toDataFrame({ + fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3] }], +}); + +const DATA_FRAME_LARGE_DECIMAL: DataFrame = toDataFrame({ + fields: [{ name: 'value', type: FieldType.number, values: [1.23456789] }], +}); + +const DATA_FRAME_WITH_LABELS: DataFrame = toDataFrame({ + fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3], labels: { foo: 'bar' } }], +}); + +describe('formatLabels', () => { + it('should work with no labels', () => { + expect(formatLabels({})).toBe(''); + }); + + it('should work with 1 label', () => { + expect(formatLabels({ foo: 'bar' })).toBe('foo=bar'); + }); + + it('should work with multiple labels', () => { + expect(formatLabels({ foo: 'bar', baz: 'qux' })).toBe('foo=bar, baz=qux'); + }); +}); + +describe('isEmptySeries', () => { + it('should be true for empty series', () => { + expect(isEmptySeries([EMPTY_FRAME])).toBe(true); + expect(isEmptySeries([EMPTY_FRAME, EMPTY_FRAME])).toBe(true); + + expect(isEmptySeries([DATA_FRAME])).toBe(false); + expect(isEmptySeries([EMPTY_FRAME, DATA_FRAME])).toBe(false); + }); +}); + +describe('getSeriesName', () => { + it('should work with named data frame', () => { + const name = getSeriesName(NAMED_FRAME); + expect(name).toBe('MyFrame'); + }); + + it('should work with empty data frame', () => { + expect(getSeriesName(EMPTY_FRAME)).toBe(''); + }); + + it('should work with labeled frame', () => { + const name = getSeriesName(DATA_FRAME_WITH_LABELS); + expect(name).toBe('foo=bar'); + }); + + it('should work with NoData frames', () => { + expect(getSeriesName(EMPTY_FRAME)).toBe(''); + }); + + it('should give preference to displayNameFromDS', () => { + const frame: DataFrame = { + name: 'MyFrame', + ...toDataFrame({ + fields: [ + { + name: 'value', + type: FieldType.number, + values: [1, 2, 3], + labels: { foo: 'bar' }, + config: { displayNameFromDS: 'series-name-override' }, + }, + ], + }), + }; + + expect(getSeriesName(frame)).toBe('series-name-override'); + }); +}); + +describe('getSeriesValue', () => { + it('should work with empty data frame', () => { + expect(getSeriesValue(EMPTY_FRAME)).toBe(undefined); + }); + + it('should work with data frame', () => { + const name = getSeriesValue(DATA_FRAME); + expect(name).toBe(1); + }); + + it('should round values', () => { + expect(getSeriesValue(DATA_FRAME_LARGE_DECIMAL)).toBe(1.23457); + }); +}); diff --git a/public/app/features/alerting/unified/components/expressions/util.ts b/public/app/features/alerting/unified/components/expressions/util.ts index 2a8407a6416..ffa21753f28 100644 --- a/public/app/features/alerting/unified/components/expressions/util.ts +++ b/public/app/features/alerting/unified/components/expressions/util.ts @@ -10,7 +10,10 @@ import { DataFrame, Labels, roundDecimals } from '@grafana/data'; */ const getSeriesName = (frame: DataFrame): string => { - return frame.name ?? formatLabels(frame.fields[0]?.labels ?? {}); + const firstField = frame.fields[0]; + + const displayNameFromDS = firstField?.config?.displayNameFromDS; + return displayNameFromDS ?? frame.name ?? formatLabels(firstField?.labels ?? {}); }; const getSeriesValue = (frame: DataFrame) => {