diff --git a/packages/grafana-data/src/field/displayProcessor.ts b/packages/grafana-data/src/field/displayProcessor.ts index b59f5743f38..f189bbdd2fe 100644 --- a/packages/grafana-data/src/field/displayProcessor.ts +++ b/packages/grafana-data/src/field/displayProcessor.ts @@ -6,7 +6,7 @@ import { Field, FieldType } from '../types/dataFrame'; import { DisplayProcessor, DisplayValue } from '../types/displayValue'; import { getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats'; import { getValueMappingResult } from '../utils/valueMappings'; -import { dateTime } from '../datetime'; +import { dateTime, dateTimeParse } from '../datetime'; import { KeyValue, TimeZone } from '../types'; import { getScaleCalculator } from './scale'; import { GrafanaTheme2 } from '../themes/types'; @@ -45,10 +45,23 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP let unit = config.unit; let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:')); + let showMs = false; if (field.type === FieldType.time && !hasDateUnit) { unit = `dateTimeAsSystem`; hasDateUnit = true; + if (field.values && field.values.length > 1) { + let start = field.values.get(0); + let end = field.values.get(field.values.length - 1); + if (typeof start === 'string') { + start = dateTimeParse(start).unix(); + end = dateTimeParse(end).unix(); + } else { + start /= 1e3; + end /= 1e3; + } + showMs = end - start < 60; //show ms when minute or less + } } else if (field.type === FieldType.boolean) { if (!isBooleanUnit(unit)) { unit = 'bool'; @@ -93,7 +106,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP if (!isNaN(numeric)) { if (shouldFormat && !isBoolean(value)) { - const v = formatFunc(numeric, config.decimals, null, options.timeZone); + const v = formatFunc(numeric, config.decimals, null, options.timeZone, showMs); text = v.text; suffix = v.suffix; prefix = v.prefix; diff --git a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx index 010fdafd3ce..685824158c5 100644 --- a/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx +++ b/packages/grafana-data/src/field/getFieldDisplayValuesProxy.test.tsx @@ -4,28 +4,25 @@ import { toDataFrame } from '../dataframe'; import { createTheme } from '../themes'; describe('getFieldDisplayValuesProxy', () => { - const data = applyFieldOverrides({ - data: [ - toDataFrame({ - fields: [ - { name: 'Time', values: [1, 2, 3] }, - { - name: 'power', - values: [100, 200, 300], - labels: { - name: 'POWAH!', - }, - config: { - displayName: 'The Power', - }, - }, - { - name: 'Last', - values: ['a', 'b', 'c'], - }, - ], - }), - ], + const shortTimeField = [{ name: 'Time', values: [1, 2, 3] }]; + + const longTimeField = [{ name: 'Time', values: [1000, 2000, 61000] }]; + + const dataFields = [ + { + name: 'power', + values: [100, 200, 300], + labels: { + name: 'POWAH!', + }, + config: { + displayName: 'The Power', + }, + }, + { name: 'Last', values: ['a', 'b', 'c'] }, + ]; + + const overrides = { fieldConfig: { defaults: {}, overrides: [], @@ -33,29 +30,45 @@ describe('getFieldDisplayValuesProxy', () => { replaceVariables: (val: string) => val, timeZone: 'utc', theme: createTheme(), + }; + + const dataShortTimeRange = applyFieldOverrides({ + ...{ data: [toDataFrame({ fields: [...shortTimeField, ...dataFields] })] }, + ...overrides, + })[0]; + + const dataLongTimeRange = applyFieldOverrides({ + ...{ data: [toDataFrame({ fields: [...longTimeField, ...dataFields] })] }, + ...overrides, })[0]; it('should define all display functions', () => { // Field display should be set - for (const field of data.fields) { + for (const field of dataShortTimeRange.fields) { expect(field.display).toBeDefined(); } }); - it('should format the time values in UTC', () => { + it('should format the time values in UTC with ms when time range is minute or less', () => { // Test Proxies in general - const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 0 }); + const p = getFieldDisplayValuesProxy({ frame: dataShortTimeRange, rowIndex: 0 }); const time = p.Time; expect(time.numeric).toEqual(1); - expect(time.text).toEqual('1970-01-01 00:00:00'); + expect(time.text).toEqual('1970-01-01 00:00:00.001'); // Should get to the same values by name or index const time2 = p[0]; expect(time2.toString()).toEqual(time.toString()); }); + it('should format the time values in UTC without ms when time range is over a minute', () => { + const p = getFieldDisplayValuesProxy({ frame: dataLongTimeRange, rowIndex: 0 }); + const time = p.Time; + expect(time.text).toEqual('1970-01-01 00:00:01'); + }); + it('Lookup by name, index, or displayName', () => { - const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 2 }); + const p = getFieldDisplayValuesProxy({ frame: dataShortTimeRange, rowIndex: 2 }); expect(p.power.numeric).toEqual(300); expect(p['power'].numeric).toEqual(300); expect(p['POWAH!'].numeric).toEqual(300); @@ -64,7 +77,7 @@ describe('getFieldDisplayValuesProxy', () => { }); it('should return undefined when missing', () => { - const p = getFieldDisplayValuesProxy({ frame: data, rowIndex: 0 }); + const p = getFieldDisplayValuesProxy({ frame: dataShortTimeRange, rowIndex: 0 }); expect(p.xyz).toBeUndefined(); expect(p[100]).toBeUndefined(); }); diff --git a/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts b/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts index 93f9a815943..c56dd5fe34a 100644 --- a/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts +++ b/packages/grafana-data/src/valueFormats/dateTimeFormatters.ts @@ -405,9 +405,15 @@ export function dateTimeSystemFormatter( value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, - timeZone?: TimeZone + timeZone?: TimeZone, + showMs?: boolean ): FormattedValue { - return { text: dateTimeFormat(value, { format: systemDateFormats.fullDate, timeZone }) }; + return { + text: dateTimeFormat(value, { + format: showMs ? systemDateFormats.fullDateMS : systemDateFormats.fullDate, + timeZone, + }), + }; } export function dateTimeFromNow( diff --git a/packages/grafana-data/src/valueFormats/valueFormats.ts b/packages/grafana-data/src/valueFormats/valueFormats.ts index 2753426170a..a23253b0c2c 100644 --- a/packages/grafana-data/src/valueFormats/valueFormats.ts +++ b/packages/grafana-data/src/valueFormats/valueFormats.ts @@ -18,7 +18,8 @@ export type ValueFormatter = ( value: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount, - timeZone?: TimeZone + timeZone?: TimeZone, + showMs?: boolean ) => FormattedValue; export interface ValueFormat { diff --git a/packages/grafana-ui/src/components/Graph/utils.test.ts b/packages/grafana-ui/src/components/Graph/utils.test.ts index 9f85d14237b..f1209a088d5 100644 --- a/packages/grafana-ui/src/components/Graph/utils.test.ts +++ b/packages/grafana-ui/src/components/Graph/utils.test.ts @@ -45,11 +45,11 @@ function passThroughFieldOverrides(frame: DataFrame) { const aSeries = passThroughFieldOverrides( toDataFrame({ fields: [ - { name: 'time', type: FieldType.time, values: [10000, 20000, 30000] }, + { name: 'time', type: FieldType.time, values: [10000, 20000, 30000, 80000] }, { name: 'value', type: FieldType.number, - values: [10, 20, 10], + values: [10, 20, 10, 25], config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'red' } }, }, ], @@ -59,11 +59,11 @@ const aSeries = passThroughFieldOverrides( const bSeries = passThroughFieldOverrides( toDataFrame({ fields: [ - { name: 'time', type: FieldType.time, values: [10000, 20000, 30000] }, + { name: 'time', type: FieldType.time, values: [10000, 20000, 30000, 80000] }, { name: 'value', type: FieldType.number, - values: [30, 60, 30], + values: [30, 60, 30, 40], config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'blue' } }, }, ], @@ -74,11 +74,11 @@ const bSeries = passThroughFieldOverrides( const cSeries = passThroughFieldOverrides( toDataFrame({ fields: [ - { name: 'time', type: FieldType.time, values: [10000, 30000] }, + { name: 'time', type: FieldType.time, values: [10000, 30000, 80000] }, { name: 'value', type: FieldType.number, - values: [30, 30], + values: [30, 30, 30], config: { color: { mode: FieldColorModeId.Fixed, fixedColor: 'yellow' } }, }, ],