mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
System: Date formating options (#27216)
* Add support for local time formats in graph panel * Enfore 24h format for backward compatibility * Use existing Intl.DateTimeFormatOptions * Pre-generate time scale, add tests * Move localTimeFormat, add local format to units * updated default fallback * #25602, use navigator.languages to enforce locale in formatting * Making options * Worked new system settings * things are working * Local browser time formats working * Support parsing dates in different formats * settings updated * Settings starting to work * Fixed graph issue * Logs fix * refactored settings a bit * Updated and name change * Progress * Changed config names * Updated * Updated * Updated test * Synced description * fixed ts issue * Added version notice * Ts fix * Updated heatmap and test * Updated snapshot * Updated * fixed ts issue * Fixes Co-authored-by: Alex Shpak <alex-shpak@users.noreply.github.com>
This commit is contained in:
parent
783391a861
commit
61bd33c241
@ -803,3 +803,22 @@ license_path =
|
||||
[feature_toggles]
|
||||
# enable features, separated by spaces
|
||||
enable =
|
||||
|
||||
[date_formats]
|
||||
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||
|
||||
# Default system date format used in time range picker and other places where full time is displayed
|
||||
full_date = YYYY-MM-DD HH:mm:ss
|
||||
|
||||
# Used by graph and other places where we only show small intervals
|
||||
interval_second = HH:mm:ss
|
||||
interval_minute = HH:mm
|
||||
interval_hour = MM/DD HH:mm
|
||||
interval_day = MM/DD
|
||||
interval_month = YYYY-MM
|
||||
interval_year = YYYY
|
||||
|
||||
# Experimental feature
|
||||
use_browser_locale = false
|
||||
|
||||
|
||||
|
@ -794,3 +794,20 @@
|
||||
[feature_toggles]
|
||||
# enable features, separated by spaces
|
||||
;enable =
|
||||
|
||||
[date_formats]
|
||||
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||
|
||||
# Default system date format used in time range picker and other places where full time is displayed
|
||||
;full_date = YYYY-MM-DD HH:mm:ss
|
||||
|
||||
# Used by graph and other places where we only show small intervals
|
||||
;interval_second = HH:mm:ss
|
||||
;interval_minute = HH:mm
|
||||
;interval_hour = MM/DD HH:mm
|
||||
;interval_day = MM/DD
|
||||
;interval_month = YYYY-MM
|
||||
;interval_year = YYYY
|
||||
|
||||
# Experimental feature
|
||||
;use_browser_locale = false
|
||||
|
@ -189,8 +189,6 @@ Folder that contains [provisioning]({{< relref "provisioning.md" >}}) config fil
|
||||
|
||||
`http`,`https`,`h2` or `socket`
|
||||
|
||||
> **Note:** Grafana versions earlier than 3.0 are vulnerable to [POODLE](https://en.wikipedia.org/wiki/POODLE). So we strongly recommend to upgrade to 3.x or use a reverse proxy for SSL termination.
|
||||
|
||||
### http_addr
|
||||
|
||||
The IP address to bind to. If empty will bind to all interfaces
|
||||
@ -231,8 +229,6 @@ callback URL to be correct).
|
||||
|
||||
### serve_from_sub_path
|
||||
|
||||
> Available in Grafana 6.3+.
|
||||
|
||||
Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
|
||||
|
||||
By enabling this setting and using a subpath in `root_url` above, e.g.
|
||||
@ -1387,3 +1383,37 @@ For more information about Grafana Enterprise, refer to [Grafana Enterprise]({{<
|
||||
### enable
|
||||
|
||||
Keys of alpha features to enable, separated by space. Available alpha features are: `transformations`, `standaloneAlerts`
|
||||
|
||||
## [date_formats]
|
||||
|
||||
> The date format options below are only available in Grafana v7.2+
|
||||
|
||||
This section controls system wide defaults for date formats used in time ranges, graphs and date input boxes.
|
||||
|
||||
The format patterns use [Moment.js](https://momentjs.com/docs/#/displaying/) formatting tokens.
|
||||
|
||||
### full_date
|
||||
|
||||
Full date format used by time range picker and in other places where a full date is rendered.
|
||||
|
||||
### intervals
|
||||
|
||||
These intervals formats are used in the graph to show only a partial date or time. For example if there are only
|
||||
minutes between y-axis tick labels then the `interval_minute` format is used.
|
||||
|
||||
Defaults
|
||||
|
||||
```
|
||||
interval_second = HH:mm:ss
|
||||
interval_minute = HH:mm
|
||||
interval_hour = MM/DD HH:mm
|
||||
interval_day = MM/DD
|
||||
interval_month = YYYY-MM
|
||||
interval_year = YYYY
|
||||
```
|
||||
|
||||
### use_browser_locale
|
||||
|
||||
Set this to `true` to have date formats be automatically be derived from browser locale. Defaults to `false`. This
|
||||
is an experimental feature right now with a few problems that remain unsolved.
|
||||
|
||||
|
@ -13,6 +13,13 @@ export interface DateTimeOptions {
|
||||
* user is used.
|
||||
*/
|
||||
timeZone?: TimeZone;
|
||||
|
||||
/**
|
||||
* Specify a {@link https://momentjs.com/docs/#/displaying/format | momentjs} format to
|
||||
* use a custom formatting pattern or parsing pattern. If no format is set,
|
||||
* then system configured default format is used.
|
||||
*/
|
||||
format?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
19
packages/grafana-data/src/datetime/formats.test.ts
Normal file
19
packages/grafana-data/src/datetime/formats.test.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { localTimeFormat } from './formats';
|
||||
|
||||
describe('Date Formats', () => {
|
||||
it('localTimeFormat', () => {
|
||||
const format = localTimeFormat(
|
||||
{
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
},
|
||||
''
|
||||
);
|
||||
|
||||
expect(format).toBe('MM/DD/YYYY, HH:mm:ss A');
|
||||
});
|
||||
});
|
@ -1,2 +1,117 @@
|
||||
export const DEFAULT_DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
export const MS_DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||
export interface SystemDateFormatSettings {
|
||||
fullDate: string;
|
||||
interval: {
|
||||
second: string;
|
||||
minute: string;
|
||||
hour: string;
|
||||
day: string;
|
||||
month: string;
|
||||
year: string;
|
||||
};
|
||||
useBrowserLocale: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SYSTEM_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
export class SystemDateFormatsState {
|
||||
fullDate = DEFAULT_SYSTEM_DATE_FORMAT;
|
||||
interval = {
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'MM/DD HH:mm',
|
||||
day: 'MM/DD',
|
||||
month: 'YYYY-MM',
|
||||
year: 'YYYY',
|
||||
};
|
||||
|
||||
update(settings: SystemDateFormatSettings) {
|
||||
this.fullDate = settings.fullDate;
|
||||
this.interval = settings.interval;
|
||||
|
||||
if (settings.useBrowserLocale) {
|
||||
this.useBrowserLocale();
|
||||
}
|
||||
}
|
||||
|
||||
get fullDateMS() {
|
||||
// Add millisecond to seconds part
|
||||
return this.fullDate.replace('ss', 'ss.SSS');
|
||||
}
|
||||
|
||||
useBrowserLocale() {
|
||||
this.fullDate = localTimeFormat({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
this.interval.second = localTimeFormat(
|
||||
{ hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false },
|
||||
null,
|
||||
this.interval.second
|
||||
);
|
||||
this.interval.minute = localTimeFormat(
|
||||
{ hour: '2-digit', minute: '2-digit', hour12: false },
|
||||
null,
|
||||
this.interval.minute
|
||||
);
|
||||
this.interval.hour = localTimeFormat(
|
||||
{ month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false },
|
||||
null,
|
||||
this.interval.hour
|
||||
);
|
||||
this.interval.day = localTimeFormat({ month: '2-digit', day: '2-digit', hour12: false }, null, this.interval.day);
|
||||
this.interval.month = localTimeFormat(
|
||||
{ year: 'numeric', month: '2-digit', hour12: false },
|
||||
null,
|
||||
this.interval.month
|
||||
);
|
||||
}
|
||||
|
||||
getTimeFieldUnit(useMsResolution?: boolean) {
|
||||
return `time:${useMsResolution ? this.fullDateMS : this.fullDate}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* localTimeFormat helps to generate date formats for momentjs based on browser's locale
|
||||
*
|
||||
* @param locale browser locale, or default
|
||||
* @param options DateTimeFormatOptions to format date
|
||||
* @param fallback default format if Intl API is not present
|
||||
*/
|
||||
export function localTimeFormat(
|
||||
options: Intl.DateTimeFormatOptions,
|
||||
locale?: string | string[] | null,
|
||||
fallback?: string
|
||||
): string {
|
||||
if (!window.Intl) {
|
||||
return fallback ?? DEFAULT_SYSTEM_DATE_FORMAT;
|
||||
}
|
||||
|
||||
if (!locale) {
|
||||
locale = [...navigator.languages];
|
||||
}
|
||||
|
||||
// https://momentjs.com/docs/#/displaying/format/
|
||||
const parts = new Intl.DateTimeFormat(locale, options).formatToParts(new Date());
|
||||
const mapping: { [key: string]: string } = {
|
||||
year: 'YYYY',
|
||||
month: 'MM',
|
||||
day: 'DD',
|
||||
hour: 'HH',
|
||||
minute: 'mm',
|
||||
second: 'ss',
|
||||
weekday: 'ddd',
|
||||
era: 'N',
|
||||
dayPeriod: 'A',
|
||||
timeZoneName: 'Z',
|
||||
};
|
||||
|
||||
return parts.map(part => mapping[part.type] || part.value).join('');
|
||||
}
|
||||
|
||||
export const systemDateFormats = new SystemDateFormatsState();
|
||||
|
@ -2,7 +2,7 @@
|
||||
import moment, { MomentInput, Moment } from 'moment-timezone';
|
||||
import { TimeZone } from '../types';
|
||||
import { DateTimeInput } from './moment_wrapper';
|
||||
import { DEFAULT_DATE_TIME_FORMAT, MS_DATE_TIME_FORMAT } from './formats';
|
||||
import { systemDateFormats } from './formats';
|
||||
import { DateTimeOptions, getTimeZone } from './common';
|
||||
|
||||
/**
|
||||
@ -13,16 +13,8 @@ import { DateTimeOptions, getTimeZone } from './common';
|
||||
* @public
|
||||
*/
|
||||
export interface DateTimeOptionsWithFormat extends DateTimeOptions {
|
||||
/**
|
||||
* Specify a {@link https://momentjs.com/docs/#/displaying/format | momentjs} format to
|
||||
* use a custom formatting pattern of the date and time value. If no format is set,
|
||||
* then {@link DEFAULT_DATE_TIME_FORMAT} is used.
|
||||
*/
|
||||
format?: string;
|
||||
|
||||
/**
|
||||
* Set this value to `true` if you want to include milliseconds when formatting date and time
|
||||
* values in the default {@link DEFAULT_DATE_TIME_FORMAT} format.
|
||||
*/
|
||||
defaultWithMS?: boolean;
|
||||
}
|
||||
@ -77,7 +69,7 @@ export const dateTimeFormatTimeAgo: DateTimeFormatter = (dateInUtc, options?) =>
|
||||
* @public
|
||||
*/
|
||||
export const dateTimeFormatWithAbbrevation: DateTimeFormatter = (dateInUtc, options?) =>
|
||||
toTz(dateInUtc, getTimeZone(options)).format(`${DEFAULT_DATE_TIME_FORMAT} z`);
|
||||
toTz(dateInUtc, getTimeZone(options)).format(`${systemDateFormats.fullDate} z`);
|
||||
|
||||
/**
|
||||
* Helper function to return only the time zone abbreviation for a given date and time value. If no options
|
||||
@ -93,9 +85,9 @@ export const timeZoneAbbrevation: DateTimeFormatter = (dateInUtc, options?) =>
|
||||
|
||||
const getFormat = <T extends DateTimeOptionsWithFormat>(options?: T): string => {
|
||||
if (options?.defaultWithMS) {
|
||||
return options?.format ?? MS_DATE_TIME_FORMAT;
|
||||
return options?.format ?? systemDateFormats.fullDateMS;
|
||||
}
|
||||
return options?.format ?? DEFAULT_DATE_TIME_FORMAT;
|
||||
return options?.format ?? systemDateFormats.fullDate;
|
||||
};
|
||||
|
||||
const toTz = (dateInUtc: DateTimeInput, timeZone: TimeZone): Moment => {
|
||||
|
25
packages/grafana-data/src/datetime/parser.test.ts
Normal file
25
packages/grafana-data/src/datetime/parser.test.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { dateTimeParse } from './parser';
|
||||
import { systemDateFormats } from './formats';
|
||||
|
||||
describe('dateTimeParse', () => {
|
||||
it('should be able to parse using default format', () => {
|
||||
const date = dateTimeParse('2020-03-02 15:00:22', { timeZone: 'utc' });
|
||||
expect(date.format()).toEqual('2020-03-02T15:00:22Z');
|
||||
});
|
||||
|
||||
it('should be able to parse using default format', () => {
|
||||
systemDateFormats.update({
|
||||
fullDate: 'MMMM D, YYYY, h:mm:ss a',
|
||||
interval: {} as any,
|
||||
useBrowserLocale: false,
|
||||
});
|
||||
|
||||
const date = dateTimeParse('Aug 20, 2020 10:30:20 am', { timeZone: 'utc' });
|
||||
expect(date.format()).toEqual('2020-08-20T10:30:20Z');
|
||||
});
|
||||
|
||||
it('should be able to parse array formats used by calendar', () => {
|
||||
const date = dateTimeParse([2020, 5, 10, 10, 30, 20], { timeZone: 'utc' });
|
||||
expect(date.format()).toEqual('2020-06-10T10:30:20Z');
|
||||
});
|
||||
});
|
@ -4,6 +4,7 @@ import { DateTimeInput, DateTime, isDateTime } from './moment_wrapper';
|
||||
import { DateTimeOptions, getTimeZone } from './common';
|
||||
import { parse, isValid } from './datemath';
|
||||
import { lowerCase } from 'lodash';
|
||||
import { systemDateFormats } from './formats';
|
||||
|
||||
/**
|
||||
* The type that describes options that can be passed when parsing a date and time value.
|
||||
@ -25,7 +26,7 @@ type DateTimeParser<T extends DateTimeOptions = DateTimeOptions> = (value: DateT
|
||||
/**
|
||||
* Helper function to parse a number, text or Date to a DateTime value. If a timeZone is supplied the incoming value
|
||||
* is parsed with that timeZone as a base. The only exception to this is if the passed value is in a UTC-based
|
||||
* format. Then it will use UTC as the base. Examples on UTC-based values are Unix epoch and ISO formatted strings.
|
||||
* format. Then it will use UTC as the base. If no format is specified the current system format will be assumed.
|
||||
*
|
||||
* It can also parse the Grafana quick date and time format, e.g. now-6h will be parsed as Date.now() - 6 hours and
|
||||
* returned as a valid DateTime value.
|
||||
@ -59,7 +60,20 @@ const parseString = (value: string, options?: DateTimeOptionsWhenParsing): DateT
|
||||
return parsed || (moment() as DateTime);
|
||||
}
|
||||
|
||||
return parseOthers(value, options);
|
||||
const timeZone = getTimeZone(options);
|
||||
const zone = moment.tz.zone(timeZone);
|
||||
const format = options?.format ?? systemDateFormats.fullDate;
|
||||
|
||||
if (zone && zone.name) {
|
||||
return moment.tz(value, format, zone.name) as DateTime;
|
||||
}
|
||||
|
||||
switch (lowerCase(timeZone)) {
|
||||
case 'utc':
|
||||
return moment.utc(value, format) as DateTime;
|
||||
default:
|
||||
return moment(value, format) as DateTime;
|
||||
}
|
||||
};
|
||||
|
||||
const parseOthers = (value: DateTimeInput, options?: DateTimeOptionsWhenParsing): DateTime => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DataSourceInstanceSettings } from './datasource';
|
||||
import { PanelPluginMeta } from './panel';
|
||||
import { GrafanaTheme } from './theme';
|
||||
import { SystemDateFormatSettings } from '../datetime';
|
||||
|
||||
/**
|
||||
* Describes the build information that will be available via the Grafana configuration.
|
||||
@ -100,4 +101,5 @@ export interface GrafanaConfig {
|
||||
featureToggles: FeatureToggles;
|
||||
licenseInfo: LicenseInfo;
|
||||
http2Enabled: boolean;
|
||||
dateFormats?: SystemDateFormatSettings;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
dateTimeAsIsoNoDateIfToday,
|
||||
dateTimeAsUS,
|
||||
dateTimeAsUSNoDateIfToday,
|
||||
dateTimeAsLocal,
|
||||
dateTimeFromNow,
|
||||
toClockMilliseconds,
|
||||
toClockSeconds,
|
||||
@ -175,6 +176,7 @@ export const getCategories = (): ValueFormatCategory[] => [
|
||||
{ name: 'Datetime ISO (No date if today)', id: 'dateTimeAsIsoNoDateIfToday', fn: dateTimeAsIsoNoDateIfToday },
|
||||
{ name: 'Datetime US', id: 'dateTimeAsUS', fn: dateTimeAsUS },
|
||||
{ name: 'Datetime US (No date if today)', id: 'dateTimeAsUSNoDateIfToday', fn: dateTimeAsUSNoDateIfToday },
|
||||
{ name: 'Datetime local', id: 'dateTimeAsLocal', fn: dateTimeAsLocal },
|
||||
{ name: 'From Now', id: 'dateTimeFromNow', fn: dateTimeFromNow },
|
||||
],
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrap
|
||||
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
|
||||
import { DecimalCount } from '../types/displayValue';
|
||||
import { TimeZone } from '../types';
|
||||
import { dateTimeFormat, dateTimeFormatTimeAgo } from '../datetime';
|
||||
import { dateTimeFormat, dateTimeFormatTimeAgo, localTimeFormat } from '../datetime';
|
||||
|
||||
interface IntervalsInSeconds {
|
||||
[interval: string]: number;
|
||||
@ -369,6 +369,16 @@ export const dateTimeAsIso = toDateTimeValueFormatter('YYYY-MM-DD HH:mm:ss');
|
||||
export const dateTimeAsIsoNoDateIfToday = toDateTimeValueFormatter('YYYY-MM-DD HH:mm:ss', 'HH:mm:ss');
|
||||
export const dateTimeAsUS = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a');
|
||||
export const dateTimeAsUSNoDateIfToday = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a', 'h:mm:ss a');
|
||||
export const dateTimeAsLocal = toDateTimeValueFormatter(
|
||||
localTimeFormat({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})
|
||||
);
|
||||
|
||||
export function dateTimeFromNow(
|
||||
value: number,
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
GrafanaThemeType,
|
||||
LicenseInfo,
|
||||
PanelPluginMeta,
|
||||
systemDateFormats,
|
||||
SystemDateFormatSettings,
|
||||
} from '@grafana/data';
|
||||
|
||||
export class GrafanaBootConfig implements GrafanaConfig {
|
||||
@ -59,6 +61,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
||||
rendererAvailable = false;
|
||||
http2Enabled = false;
|
||||
dateFormats?: SystemDateFormatSettings;
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
this.theme = options.bootData.user.lightTheme ? getTheme(GrafanaThemeType.Light) : getTheme(GrafanaThemeType.Dark);
|
||||
@ -84,6 +87,10 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
};
|
||||
|
||||
merge(this, defaults, options);
|
||||
|
||||
if (this.dateFormats) {
|
||||
systemDateFormats.update(this.dateFormats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
GrafanaThemeType,
|
||||
Field,
|
||||
} from '@grafana/data';
|
||||
import { getMultiSeriesGraphHoverInfo, findHoverIndexFromData } from './utils';
|
||||
import { getMultiSeriesGraphHoverInfo, findHoverIndexFromData, graphTimeFormat } from './utils';
|
||||
|
||||
const mockResult = (
|
||||
value: string,
|
||||
@ -196,4 +196,14 @@ describe('Graph utils', () => {
|
||||
expect(findHoverIndexFromData(timeField!, 300)).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('graphTimeFormat', () => {
|
||||
it('graphTimeFormat', () => {
|
||||
expect(graphTimeFormat(5, 1, 45 * 5 * 1000)).toBe('HH:mm:ss');
|
||||
expect(graphTimeFormat(5, 1, 7200 * 5 * 1000)).toBe('HH:mm');
|
||||
expect(graphTimeFormat(5, 1, 80000 * 5 * 1000)).toBe('MM/DD HH:mm');
|
||||
expect(graphTimeFormat(5, 1, 2419200 * 5 * 1000)).toBe('MM/DD');
|
||||
expect(graphTimeFormat(5, 1, 12419200 * 5 * 1000)).toBe('YYYY-MM');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
getFieldDisplayName,
|
||||
TimeZone,
|
||||
dateTimeFormat,
|
||||
systemDateFormats,
|
||||
} from '@grafana/data';
|
||||
|
||||
/**
|
||||
@ -120,19 +121,22 @@ export const graphTimeFormat = (ticks: number | null, min: number | null, max: n
|
||||
const oneYear = 31536000000;
|
||||
|
||||
if (secPerTick <= 45) {
|
||||
return 'HH:mm:ss';
|
||||
return systemDateFormats.interval.second;
|
||||
}
|
||||
if (secPerTick <= 7200 || range <= oneDay) {
|
||||
return 'HH:mm';
|
||||
return systemDateFormats.interval.minute;
|
||||
}
|
||||
if (secPerTick <= 80000) {
|
||||
return 'MM/DD HH:mm';
|
||||
return systemDateFormats.interval.hour;
|
||||
}
|
||||
if (secPerTick <= 2419200 || range <= oneYear) {
|
||||
return 'MM/DD';
|
||||
return systemDateFormats.interval.day;
|
||||
}
|
||||
return 'YYYY-MM';
|
||||
if (secPerTick <= 31536000) {
|
||||
return systemDateFormats.interval.month;
|
||||
}
|
||||
return systemDateFormats.interval.year;
|
||||
}
|
||||
|
||||
return 'HH:mm';
|
||||
return systemDateFormats.interval.minute;
|
||||
};
|
||||
|
@ -127,6 +127,12 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
renderTimeStamp(epochMs: number) {
|
||||
return dateTimeFormat(epochMs, {
|
||||
timeZone: this.props.timeZone,
|
||||
});
|
||||
}
|
||||
|
||||
renderLogRow(
|
||||
context?: LogRowContextRows,
|
||||
errors?: LogRowContextQueryErrors,
|
||||
@ -143,7 +149,6 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
allowDetails,
|
||||
row,
|
||||
showDuplicates,
|
||||
timeZone,
|
||||
showContextToggle,
|
||||
showLabels,
|
||||
showTime,
|
||||
@ -176,7 +181,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
|
||||
</td>
|
||||
)}
|
||||
{showTime && <td className={style.logsRowLocalTime}>{dateTimeFormat(row.timeEpochMs, { timeZone })}</td>}
|
||||
{showTime && <td className={style.logsRowLocalTime}>{this.renderTimeStamp(row.timeEpochMs)}</td>}
|
||||
{showLabels && row.uniqueLabels && (
|
||||
<td className={style.logsRowLabels}>
|
||||
<LogLabels labels={row.uniqueLabels} />
|
||||
|
@ -126,7 +126,6 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
logsRowLocalTime: css`
|
||||
label: logs-row__localtime;
|
||||
white-space: nowrap;
|
||||
max-width: 12.5em;
|
||||
`,
|
||||
logsRowLabels: css`
|
||||
label: logs-row__labels;
|
||||
|
@ -233,15 +233,15 @@ exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
||||
"from": "2019-12-17T07:48:27Z",
|
||||
"from": "2019-12-17 07:48:27",
|
||||
"section": 3,
|
||||
"to": "2019-12-18T07:48:27Z",
|
||||
"to": "2019-12-18 07:48:27",
|
||||
},
|
||||
Object {
|
||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
||||
"from": "2019-10-17T07:48:27Z",
|
||||
"from": "2019-10-17 07:48:27",
|
||||
"section": 3,
|
||||
"to": "2019-10-18T07:48:27Z",
|
||||
"to": "2019-10-18 07:48:27",
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -297,15 +297,15 @@ exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
||||
"from": "2019-12-17T07:48:27Z",
|
||||
"from": "2019-12-17 07:48:27",
|
||||
"section": 3,
|
||||
"to": "2019-12-18T07:48:27Z",
|
||||
"to": "2019-12-18 07:48:27",
|
||||
},
|
||||
Object {
|
||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
||||
"from": "2019-10-17T07:48:27Z",
|
||||
"from": "2019-10-17 07:48:27",
|
||||
"section": 3,
|
||||
"to": "2019-10-18T07:48:27Z",
|
||||
"to": "2019-10-18 07:48:27",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TimeOption, TimeRange, TimeZone, rangeUtil, dateTimeFormat, dateTimeFormatISO } from '@grafana/data';
|
||||
import { TimeOption, TimeRange, TimeZone, rangeUtil, dateTimeFormat } from '@grafana/data';
|
||||
|
||||
export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): TimeRange => {
|
||||
return rangeUtil.convertRawToRange({ from: option.from, to: option.to }, timeZone);
|
||||
@ -9,8 +9,8 @@ export const mapRangeToTimeOption = (range: TimeRange, timeZone?: TimeZone): Tim
|
||||
const to = dateTimeFormat(range.to, { timeZone });
|
||||
|
||||
return {
|
||||
from: dateTimeFormatISO(range.from, { timeZone }),
|
||||
to: dateTimeFormatISO(range.to, { timeZone }),
|
||||
from,
|
||||
to,
|
||||
section: 3,
|
||||
display: `${from} to ${to}`,
|
||||
};
|
||||
|
@ -54,6 +54,8 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
settings["dateFormats"] = hs.Cfg.DateFormats
|
||||
|
||||
var data = dtos.IndexViewData{
|
||||
User: &dtos.CurrentUser{
|
||||
Id: c.UserId,
|
||||
|
@ -19,6 +19,7 @@ func init() {
|
||||
func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) error {
|
||||
params := make([]interface{}, 0)
|
||||
filter := ""
|
||||
|
||||
if len(query.User.Teams) > 0 {
|
||||
filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.User.Teams)-1) + ")) OR "
|
||||
params = append(params, query.User.OrgId)
|
||||
@ -26,6 +27,7 @@ func GetPreferencesWithDefaults(query *models.GetPreferencesWithDefaultsQuery) e
|
||||
params = append(params, v)
|
||||
}
|
||||
}
|
||||
|
||||
filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)"
|
||||
params = append(params, query.User.OrgId)
|
||||
params = append(params, query.User.UserId)
|
||||
|
28
pkg/setting/date_formats.go
Normal file
28
pkg/setting/date_formats.go
Normal file
@ -0,0 +1,28 @@
|
||||
package setting
|
||||
|
||||
type DateFormats struct {
|
||||
FullDate string `json:"fullDate"`
|
||||
UseBrowserLocale bool `json:"useBrowserLocale"`
|
||||
Interval DateFormatIntervals `json:"interval"`
|
||||
}
|
||||
|
||||
type DateFormatIntervals struct {
|
||||
Second string `json:"second"`
|
||||
Minute string `json:"minute"`
|
||||
Hour string `json:"hour"`
|
||||
Day string `json:"day"`
|
||||
Month string `json:"month"`
|
||||
Year string `json:"year"`
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readDateFormats() {
|
||||
dateFormats := cfg.Raw.Section("date_formats")
|
||||
cfg.DateFormats.FullDate, _ = valueAsString(dateFormats, "full_date", "YYYY-MM-DD HH:mm:ss")
|
||||
cfg.DateFormats.Interval.Second, _ = valueAsString(dateFormats, "interval_second", "HH:mm:ss")
|
||||
cfg.DateFormats.Interval.Minute, _ = valueAsString(dateFormats, "interval_minute", "HH:mm")
|
||||
cfg.DateFormats.Interval.Hour, _ = valueAsString(dateFormats, "interval_hour", "MM-DD HH:mm")
|
||||
cfg.DateFormats.Interval.Day, _ = valueAsString(dateFormats, "interval_day", "YYYY-MM-DD")
|
||||
cfg.DateFormats.Interval.Month, _ = valueAsString(dateFormats, "interval_month", "YYYY-MM")
|
||||
cfg.DateFormats.Interval.Year = "YYYY"
|
||||
cfg.DateFormats.UseBrowserLocale = dateFormats.Key("date_format_use_browser_locale").MustBool(false)
|
||||
}
|
@ -300,10 +300,11 @@ type Cfg struct {
|
||||
ApiKeyMaxSecondsToLive int64
|
||||
|
||||
// Use to enable new features which may still be in alpha/beta stage.
|
||||
FeatureToggles map[string]bool
|
||||
|
||||
FeatureToggles map[string]bool
|
||||
AnonymousHideVersion bool
|
||||
|
||||
DateFormats DateFormats
|
||||
|
||||
// Annotations
|
||||
AlertingAnnotationCleanupSetting AnnotationCleanupSettings
|
||||
DashboardAnnotationCleanupSettings AnnotationCleanupSettings
|
||||
@ -835,6 +836,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
ConnStr: connStr,
|
||||
}
|
||||
|
||||
cfg.readDateFormats()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { textUtil } from '@grafana/data';
|
||||
import { textUtil, systemDateFormats } from '@grafana/data';
|
||||
|
||||
export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) {
|
||||
const self = this;
|
||||
@ -233,9 +233,9 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
|
||||
}
|
||||
|
||||
if (seriesList[0].hasMsResolution) {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss.SSS';
|
||||
tooltipFormat = systemDateFormats.fullDateMS;
|
||||
} else {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
tooltipFormat = systemDateFormats.fullDate;
|
||||
}
|
||||
|
||||
if (allSeriesMode) {
|
||||
|
@ -12,8 +12,7 @@ import {
|
||||
getSeriesTimeStep,
|
||||
TimeZone,
|
||||
hasMsResolution,
|
||||
MS_DATE_TIME_FORMAT,
|
||||
DEFAULT_DATE_TIME_FORMAT,
|
||||
systemDateFormats,
|
||||
FieldColor,
|
||||
FieldColorMode,
|
||||
FieldConfigSource,
|
||||
@ -117,7 +116,7 @@ export const getGraphSeriesModel = (
|
||||
...timeField,
|
||||
type: timeField.type,
|
||||
config: {
|
||||
unit: `time:${useMsDateFormat ? MS_DATE_TIME_FORMAT : DEFAULT_DATE_TIME_FORMAT}`,
|
||||
unit: systemDateFormats.getTimeFieldUnit(useMsDateFormat),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
formattedValueToString,
|
||||
dateTimeFormat,
|
||||
} from '@grafana/data';
|
||||
import { graphTimeFormat } from '@grafana/ui';
|
||||
import { CoreEvents } from 'app/types';
|
||||
|
||||
const MIN_CARD_SIZE = 1,
|
||||
@ -155,9 +156,13 @@ export class HeatmapRenderer {
|
||||
.range([0, this.chartWidth]);
|
||||
|
||||
const ticks = this.chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
||||
const format = ticksUtils.grafanaTimeFormat(ticks, this.timeRange.from, this.timeRange.to);
|
||||
const format = graphTimeFormat(ticks, this.timeRange.from.valueOf(), this.timeRange.to.valueOf());
|
||||
const timeZone = this.ctrl.dashboard.getTimezone();
|
||||
const formatter = (date: Date) => dateTimeFormat(date, { format, timeZone });
|
||||
const formatter = (date: Date) =>
|
||||
dateTimeFormat(date.valueOf(), {
|
||||
format: format,
|
||||
timeZone: timeZone,
|
||||
});
|
||||
|
||||
const xAxis = d3
|
||||
.axisBottom(this.xScale)
|
||||
|
Loading…
Reference in New Issue
Block a user