mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TimeZone: unify the time zone pickers to one that can rule them all. (#24803)
* draft on a unified TimeZonePicker. * most of the data structures is in place. * wip. * wip. * wip: timezone selector in progress.2 * fixed so we have proper data on all timezones. * started to add timezone into time picker. * addeing time zone footer. * footer is working. * fixed so we use the timeZone picker in shared preferences. * Added so we can change timeZone from picker. * did some styling changes. * will update timezone on all places that we need to update it. * removed console.log * removed magic string. * fixed border on calendar. * ignoring eslint cache. * cleaned up the code a bit. * made the default selectable. * corrected so the behaviour about default works as expected. * excluded timezone from change tracker. * revert so default will always be the intial value. * default will always fallback to the one in the config. * do the country mapping on startup. * fixed nit. * updated snapshots for timepicker. * fixed build errors. * updating so snapshot tests is in sync. * removed Date.now from prop since it will change each run in the snapshot tests. * fixed so e2e tests works as before. * moved files into separate folders.
This commit is contained in:
parent
084542a006
commit
1abbb477cf
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,6 +47,7 @@ public/css/*.min.css
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
.vscode/
|
||||||
.vs/
|
.vs/
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
/data/*
|
/data/*
|
||||||
/bin/*
|
/bin/*
|
||||||
|
@ -9,7 +9,7 @@ e2e.scenario({
|
|||||||
scenario: () => {
|
scenario: () => {
|
||||||
e2e.flows.openDashboard('5SdHCasdf');
|
e2e.flows.openDashboard('5SdHCasdf');
|
||||||
|
|
||||||
const fromTimeZone = 'UTC';
|
const fromTimeZone = 'Coordinated Universal Time';
|
||||||
const toTimeZone = 'America/Chicago';
|
const toTimeZone = 'America/Chicago';
|
||||||
const offset = -5;
|
const offset = -5;
|
||||||
|
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
|
import moment from 'moment-timezone';
|
||||||
|
import { memoize } from 'lodash';
|
||||||
import { TimeZone } from '../types';
|
import { TimeZone } from '../types';
|
||||||
import { getTimeZone } from './common';
|
import { getTimeZone } from './common';
|
||||||
|
|
||||||
|
export enum InternalTimeZones {
|
||||||
|
default = '',
|
||||||
|
localBrowserTime = 'browser',
|
||||||
|
utc = 'utc',
|
||||||
|
}
|
||||||
|
|
||||||
export const timeZoneFormatUserFriendly = (timeZone: TimeZone | undefined) => {
|
export const timeZoneFormatUserFriendly = (timeZone: TimeZone | undefined) => {
|
||||||
switch (getTimeZone({ timeZone })) {
|
switch (getTimeZone({ timeZone })) {
|
||||||
case 'browser':
|
case 'browser':
|
||||||
@ -12,392 +20,413 @@ export const timeZoneFormatUserFriendly = (timeZone: TimeZone | undefined) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// List taken from https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript
|
export interface TimeZoneCountry {
|
||||||
export const getTimeZoneGroups = () => {
|
code: string;
|
||||||
const europeZones = [
|
name: string;
|
||||||
'Europe/Amsterdam',
|
}
|
||||||
'Europe/Andorra',
|
export interface TimeZoneInfo {
|
||||||
'Europe/Astrakhan',
|
name: string;
|
||||||
'Europe/Athens',
|
zone: string;
|
||||||
'Europe/Belgrade',
|
countries: TimeZoneCountry[];
|
||||||
'Europe/Berlin',
|
abbreviation: string;
|
||||||
'Europe/Brussels',
|
offsetInMins: number;
|
||||||
'Europe/Bucharest',
|
}
|
||||||
'Europe/Budapest',
|
|
||||||
'Europe/Chisinau',
|
|
||||||
'Europe/Copenhagen',
|
|
||||||
'Europe/Dublin',
|
|
||||||
'Europe/Gibraltar',
|
|
||||||
'Europe/Helsinki',
|
|
||||||
'Europe/Istanbul',
|
|
||||||
'Europe/Kaliningrad',
|
|
||||||
'Europe/Kiev',
|
|
||||||
'Europe/Kirov',
|
|
||||||
'Europe/Lisbon',
|
|
||||||
'Europe/London',
|
|
||||||
'Europe/Luxembourg',
|
|
||||||
'Europe/Madrid',
|
|
||||||
'Europe/Malta',
|
|
||||||
'Europe/Minsk',
|
|
||||||
'Europe/Monaco',
|
|
||||||
'Europe/Moscow',
|
|
||||||
'Europe/Oslo',
|
|
||||||
'Europe/Paris',
|
|
||||||
'Europe/Prague',
|
|
||||||
'Europe/Riga',
|
|
||||||
'Europe/Rome',
|
|
||||||
'Europe/Samara',
|
|
||||||
'Europe/Saratov',
|
|
||||||
'Europe/Simferopol',
|
|
||||||
'Europe/Sofia',
|
|
||||||
'Europe/Stockholm',
|
|
||||||
'Europe/Tallinn',
|
|
||||||
'Europe/Tirane',
|
|
||||||
'Europe/Ulyanovsk',
|
|
||||||
'Europe/Uzhgorod',
|
|
||||||
'Europe/Vienna',
|
|
||||||
'Europe/Vilnius',
|
|
||||||
'Europe/Volgograd',
|
|
||||||
'Europe/Warsaw',
|
|
||||||
'Europe/Zaporozhye',
|
|
||||||
'Europe/Zurich',
|
|
||||||
];
|
|
||||||
|
|
||||||
const africaZones = [
|
export interface GroupedTimeZones {
|
||||||
'Africa/Abidjan',
|
name: string;
|
||||||
'Africa/Accra',
|
zones: TimeZone[];
|
||||||
'Africa/Algiers',
|
}
|
||||||
'Africa/Bissau',
|
|
||||||
'Africa/Cairo',
|
|
||||||
'Africa/Casablanca',
|
|
||||||
'Africa/Ceuta',
|
|
||||||
'Africa/El_Aaiun',
|
|
||||||
'Africa/Johannesburg',
|
|
||||||
'Africa/Juba',
|
|
||||||
'Africa/Khartoum',
|
|
||||||
'Africa/Lagos',
|
|
||||||
'Africa/Maputo',
|
|
||||||
'Africa/Monrovia',
|
|
||||||
'Africa/Nairobi',
|
|
||||||
'Africa/Ndjamena',
|
|
||||||
'Africa/Sao_Tome',
|
|
||||||
'Africa/Tripoli',
|
|
||||||
'Africa/Tunis',
|
|
||||||
'Africa/Windhoek',
|
|
||||||
];
|
|
||||||
|
|
||||||
const asiaZones = [
|
export const getTimeZoneInfo = (zone: string, timestamp: number): TimeZoneInfo | undefined => {
|
||||||
'Asia/Almaty',
|
const internal = mapInternal(zone, timestamp);
|
||||||
'Asia/Amman',
|
|
||||||
'Asia/Anadyr',
|
|
||||||
'Asia/Aqtau',
|
|
||||||
'Asia/Aqtobe',
|
|
||||||
'Asia/Ashgabat',
|
|
||||||
'Asia/Atyrau',
|
|
||||||
'Asia/Baghdad',
|
|
||||||
'Asia/Baku',
|
|
||||||
'Asia/Bangkok',
|
|
||||||
'Asia/Barnaul',
|
|
||||||
'Asia/Beirut',
|
|
||||||
'Asia/Bishkek',
|
|
||||||
'Asia/Brunei',
|
|
||||||
'Asia/Chita',
|
|
||||||
'Asia/Choibalsan',
|
|
||||||
'Asia/Colombo',
|
|
||||||
'Asia/Damascus',
|
|
||||||
'Asia/Dhaka',
|
|
||||||
'Asia/Dili',
|
|
||||||
'Asia/Dubai',
|
|
||||||
'Asia/Dushanbe',
|
|
||||||
'Asia/Famagusta',
|
|
||||||
'Asia/Gaza',
|
|
||||||
'Asia/Hebron',
|
|
||||||
'Asia/Ho_Chi_Minh',
|
|
||||||
'Asia/Hong_Kong',
|
|
||||||
'Asia/Hovd',
|
|
||||||
'Asia/Irkutsk',
|
|
||||||
'Asia/Jakarta',
|
|
||||||
'Asia/Jayapura',
|
|
||||||
'Asia/Jerusalem',
|
|
||||||
'Asia/Kabul',
|
|
||||||
'Asia/Kamchatka',
|
|
||||||
'Asia/Karachi',
|
|
||||||
'Asia/Kathmandu',
|
|
||||||
'Asia/Khandyga',
|
|
||||||
'Asia/Kolkata',
|
|
||||||
'Asia/Krasnoyarsk',
|
|
||||||
'Asia/Kuala_Lumpur',
|
|
||||||
'Asia/Kuching',
|
|
||||||
'Asia/Macau',
|
|
||||||
'Asia/Magadan',
|
|
||||||
'Asia/Makassar',
|
|
||||||
'Asia/Manila',
|
|
||||||
'Asia/Nicosia',
|
|
||||||
'Asia/Novokuznetsk',
|
|
||||||
'Asia/Novosibirsk',
|
|
||||||
'Asia/Omsk',
|
|
||||||
'Asia/Oral',
|
|
||||||
'Asia/Pontianak',
|
|
||||||
'Asia/Pyongyang',
|
|
||||||
'Asia/Qatar',
|
|
||||||
'Asia/Qostanay',
|
|
||||||
'Asia/Qyzylorda',
|
|
||||||
'Asia/Riyadh',
|
|
||||||
'Asia/Sakhalin',
|
|
||||||
'Asia/Samarkand',
|
|
||||||
'Asia/Seoul',
|
|
||||||
'Asia/Shanghai',
|
|
||||||
'Asia/Singapore',
|
|
||||||
'Asia/Srednekolymsk',
|
|
||||||
'Asia/Taipei',
|
|
||||||
'Asia/Tashkent',
|
|
||||||
'Asia/Tbilisi',
|
|
||||||
'Asia/Tehran',
|
|
||||||
'Asia/Thimphu',
|
|
||||||
'Asia/Tokyo',
|
|
||||||
'Asia/Tomsk',
|
|
||||||
'Asia/Ulaanbaatar',
|
|
||||||
'Asia/Urumqi',
|
|
||||||
'Asia/Ust-Nera',
|
|
||||||
'Asia/Vladivostok',
|
|
||||||
'Asia/Yakutsk',
|
|
||||||
'Asia/Yangon',
|
|
||||||
'Asia/Yekaterinburg',
|
|
||||||
'Asia/Yerevan',
|
|
||||||
];
|
|
||||||
|
|
||||||
const antarcticaZones = [
|
if (internal) {
|
||||||
'Antarctica/Casey',
|
return internal;
|
||||||
'Antarctica/Davis',
|
}
|
||||||
'Antarctica/DumontDUrville',
|
|
||||||
'Antarctica/Macquarie',
|
|
||||||
'Antarctica/Mawson',
|
|
||||||
'Antarctica/Palmer',
|
|
||||||
'Antarctica/Rothera',
|
|
||||||
'Antarctica/Syowa',
|
|
||||||
'Antarctica/Troll',
|
|
||||||
'Antarctica/Vostok',
|
|
||||||
];
|
|
||||||
|
|
||||||
const americaZones = [
|
return mapToInfo(zone, timestamp);
|
||||||
'America/Adak',
|
|
||||||
'America/Anchorage',
|
|
||||||
'America/Araguaina',
|
|
||||||
'America/Argentina/Buenos_Aires',
|
|
||||||
'America/Argentina/Catamarca',
|
|
||||||
'America/Argentina/Cordoba',
|
|
||||||
'America/Argentina/Jujuy',
|
|
||||||
'America/Argentina/La_Rioja',
|
|
||||||
'America/Argentina/Mendoza',
|
|
||||||
'America/Argentina/Rio_Gallegos',
|
|
||||||
'America/Argentina/Salta',
|
|
||||||
'America/Argentina/San_Juan',
|
|
||||||
'America/Argentina/San_Luis',
|
|
||||||
'America/Argentina/Tucuman',
|
|
||||||
'America/Argentina/Ushuaia',
|
|
||||||
'America/Asuncion',
|
|
||||||
'America/Atikokan',
|
|
||||||
'America/Bahia',
|
|
||||||
'America/Bahia_Banderas',
|
|
||||||
'America/Barbados',
|
|
||||||
'America/Belem',
|
|
||||||
'America/Belize',
|
|
||||||
'America/Blanc-Sablon',
|
|
||||||
'America/Boa_Vista',
|
|
||||||
'America/Bogota',
|
|
||||||
'America/Boise',
|
|
||||||
'America/Cambridge_Bay',
|
|
||||||
'America/Campo_Grande',
|
|
||||||
'America/Cancun',
|
|
||||||
'America/Caracas',
|
|
||||||
'America/Cayenne',
|
|
||||||
'America/Chicago',
|
|
||||||
'America/Chihuahua',
|
|
||||||
'America/Costa_Rica',
|
|
||||||
'America/Creston',
|
|
||||||
'America/Cuiaba',
|
|
||||||
'America/Curacao',
|
|
||||||
'America/Danmarkshavn',
|
|
||||||
'America/Dawson',
|
|
||||||
'America/Dawson_Creek',
|
|
||||||
'America/Denver',
|
|
||||||
'America/Detroit',
|
|
||||||
'America/Edmonton',
|
|
||||||
'America/Eirunepe',
|
|
||||||
'America/El_Salvador',
|
|
||||||
'America/Fort_Nelson',
|
|
||||||
'America/Fortaleza',
|
|
||||||
'America/Glace_Bay',
|
|
||||||
'America/Godthab',
|
|
||||||
'America/Goose_Bay',
|
|
||||||
'America/Grand_Turk',
|
|
||||||
'America/Guatemala',
|
|
||||||
'America/Guayaquil',
|
|
||||||
'America/Guyana',
|
|
||||||
'America/Halifax',
|
|
||||||
'America/Havana',
|
|
||||||
'America/Hermosillo',
|
|
||||||
'America/Indiana/Indianapolis',
|
|
||||||
'America/Indiana/Knox',
|
|
||||||
'America/Indiana/Marengo',
|
|
||||||
'America/Indiana/Petersburg',
|
|
||||||
'America/Indiana/Tell_City',
|
|
||||||
'America/Indiana/Vevay',
|
|
||||||
'America/Indiana/Vincennes',
|
|
||||||
'America/Indiana/Winamac',
|
|
||||||
'America/Inuvik',
|
|
||||||
'America/Iqaluit',
|
|
||||||
'America/Jamaica',
|
|
||||||
'America/Juneau',
|
|
||||||
'America/Kentucky/Louisville',
|
|
||||||
'America/Kentucky/Monticello',
|
|
||||||
'America/La_Paz',
|
|
||||||
'America/Lima',
|
|
||||||
'America/Los_Angeles',
|
|
||||||
'America/Maceio',
|
|
||||||
'America/Managua',
|
|
||||||
'America/Manaus',
|
|
||||||
'America/Martinique',
|
|
||||||
'America/Matamoros',
|
|
||||||
'America/Mazatlan',
|
|
||||||
'America/Menominee',
|
|
||||||
'America/Merida',
|
|
||||||
'America/Metlakatla',
|
|
||||||
'America/Mexico_City',
|
|
||||||
'America/Miquelon',
|
|
||||||
'America/Moncton',
|
|
||||||
'America/Monterrey',
|
|
||||||
'America/Montevideo',
|
|
||||||
'America/Nassau',
|
|
||||||
'America/New_York',
|
|
||||||
'America/Nipigon',
|
|
||||||
'America/Nome',
|
|
||||||
'America/Noronha',
|
|
||||||
'America/North_Dakota/Beulah',
|
|
||||||
'America/North_Dakota/Center',
|
|
||||||
'America/North_Dakota/New_Salem',
|
|
||||||
'America/Ojinaga',
|
|
||||||
'America/Panama',
|
|
||||||
'America/Pangnirtung',
|
|
||||||
'America/Paramaribo',
|
|
||||||
'America/Phoenix',
|
|
||||||
'America/Port-au-Prince',
|
|
||||||
'America/Port_of_Spain',
|
|
||||||
'America/Porto_Velho',
|
|
||||||
'America/Puerto_Rico',
|
|
||||||
'America/Punta_Arenas',
|
|
||||||
'America/Rainy_River',
|
|
||||||
'America/Rankin_Inlet',
|
|
||||||
'America/Recife',
|
|
||||||
'America/Regina',
|
|
||||||
'America/Resolute',
|
|
||||||
'America/Rio_Branco',
|
|
||||||
'America/Santarem',
|
|
||||||
'America/Santiago',
|
|
||||||
'America/Santo_Domingo',
|
|
||||||
'America/Sao_Paulo',
|
|
||||||
'America/Scoresbysund',
|
|
||||||
'America/Sitka',
|
|
||||||
'America/St_Johns',
|
|
||||||
'America/Swift_Current',
|
|
||||||
'America/Tegucigalpa',
|
|
||||||
'America/Thule',
|
|
||||||
'America/Thunder_Bay',
|
|
||||||
'America/Tijuana',
|
|
||||||
'America/Toronto',
|
|
||||||
'America/Vancouver',
|
|
||||||
'America/Whitehorse',
|
|
||||||
'America/Winnipeg',
|
|
||||||
'America/Yakutat',
|
|
||||||
'America/Yellowknife',
|
|
||||||
];
|
|
||||||
|
|
||||||
const pacificZones = [
|
|
||||||
'Pacific/Apia',
|
|
||||||
'Pacific/Auckland',
|
|
||||||
'Pacific/Bougainville',
|
|
||||||
'Pacific/Chatham',
|
|
||||||
'Pacific/Chuuk',
|
|
||||||
'Pacific/Easter',
|
|
||||||
'Pacific/Efate',
|
|
||||||
'Pacific/Enderbury',
|
|
||||||
'Pacific/Fakaofo',
|
|
||||||
'Pacific/Fiji',
|
|
||||||
'Pacific/Funafuti',
|
|
||||||
'Pacific/Galapagos',
|
|
||||||
'Pacific/Gambier',
|
|
||||||
'Pacific/Guadalcanal',
|
|
||||||
'Pacific/Guam',
|
|
||||||
'Pacific/Honolulu',
|
|
||||||
'Pacific/Kiritimati',
|
|
||||||
'Pacific/Kosrae',
|
|
||||||
'Pacific/Kwajalein',
|
|
||||||
'Pacific/Majuro',
|
|
||||||
'Pacific/Marquesas',
|
|
||||||
'Pacific/Nauru',
|
|
||||||
'Pacific/Niue',
|
|
||||||
'Pacific/Norfolk',
|
|
||||||
'Pacific/Noumea',
|
|
||||||
'Pacific/Pago_Pago',
|
|
||||||
'Pacific/Palau',
|
|
||||||
'Pacific/Pitcairn',
|
|
||||||
'Pacific/Pohnpei',
|
|
||||||
'Pacific/Port_Moresby',
|
|
||||||
'Pacific/Rarotonga',
|
|
||||||
'Pacific/Tahiti',
|
|
||||||
'Pacific/Tarawa',
|
|
||||||
'Pacific/Tongatapu',
|
|
||||||
'Pacific/Wake',
|
|
||||||
'Pacific/Wallis',
|
|
||||||
];
|
|
||||||
|
|
||||||
const australiaZones = [
|
|
||||||
'Australia/Adelaide',
|
|
||||||
'Australia/Brisbane',
|
|
||||||
'Australia/Broken_Hill',
|
|
||||||
'Australia/Currie',
|
|
||||||
'Australia/Darwin',
|
|
||||||
'Australia/Eucla',
|
|
||||||
'Australia/Hobart',
|
|
||||||
'Australia/Lindeman',
|
|
||||||
'Australia/Lord_Howe',
|
|
||||||
'Australia/Melbourne',
|
|
||||||
'Australia/Perth',
|
|
||||||
'Australia/Sydney',
|
|
||||||
];
|
|
||||||
|
|
||||||
const atlanticZones = [
|
|
||||||
'Atlantic/Azores',
|
|
||||||
'Atlantic/Bermuda',
|
|
||||||
'Atlantic/Canary',
|
|
||||||
'Atlantic/Cape_Verde',
|
|
||||||
'Atlantic/Faroe',
|
|
||||||
'Atlantic/Madeira',
|
|
||||||
'Atlantic/Reykjavik',
|
|
||||||
'Atlantic/South_Georgia',
|
|
||||||
'Atlantic/Stanley',
|
|
||||||
];
|
|
||||||
|
|
||||||
const indianZones = [
|
|
||||||
'Indian/Chagos',
|
|
||||||
'Indian/Christmas',
|
|
||||||
'Indian/Cocos',
|
|
||||||
'Indian/Kerguelen',
|
|
||||||
'Indian/Mahe',
|
|
||||||
'Indian/Maldives',
|
|
||||||
'Indian/Mauritius',
|
|
||||||
'Indian/Reunion',
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
|
||||||
{ label: 'Africa', options: africaZones },
|
|
||||||
{ label: 'America', options: americaZones },
|
|
||||||
{ label: 'Antarctica', options: antarcticaZones },
|
|
||||||
{ label: 'Asia', options: asiaZones },
|
|
||||||
{ label: 'Atlantic', options: atlanticZones },
|
|
||||||
{ label: 'Australia', options: australiaZones },
|
|
||||||
{ label: 'Europe', options: europeZones },
|
|
||||||
{ label: 'Indian', options: indianZones },
|
|
||||||
{ label: 'Pacific', options: pacificZones },
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTimeZones = memoize((includeInternal = false): TimeZone[] => {
|
||||||
|
const initial: TimeZone[] = [];
|
||||||
|
|
||||||
|
if (includeInternal) {
|
||||||
|
initial.push.apply(initial, [InternalTimeZones.default, InternalTimeZones.localBrowserTime, InternalTimeZones.utc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return moment.tz.names().reduce((zones: TimeZone[], zone: string) => {
|
||||||
|
const countriesForZone = countriesByTimeZone[zone];
|
||||||
|
|
||||||
|
if (!Array.isArray(countriesForZone) || countriesForZone.length === 0) {
|
||||||
|
return zones;
|
||||||
|
}
|
||||||
|
|
||||||
|
zones.push(zone);
|
||||||
|
return zones;
|
||||||
|
}, initial);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getTimeZoneGroups = memoize((includeInternal = false): GroupedTimeZones[] => {
|
||||||
|
const timeZones = getTimeZones(includeInternal);
|
||||||
|
|
||||||
|
const groups = timeZones.reduce((groups: Record<string, TimeZone[]>, zone: TimeZone) => {
|
||||||
|
const delimiter = zone.indexOf('/');
|
||||||
|
|
||||||
|
if (delimiter === -1) {
|
||||||
|
const group = '';
|
||||||
|
groups[group] = groups[group] ?? [];
|
||||||
|
groups[group].push(zone);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = zone.substr(0, delimiter);
|
||||||
|
groups[group] = groups[group] ?? [];
|
||||||
|
groups[group].push(zone);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return Object.keys(groups).map(name => ({
|
||||||
|
name,
|
||||||
|
zones: groups[name],
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapInternal = (zone: string, timestamp: number): TimeZoneInfo | undefined => {
|
||||||
|
switch (zone) {
|
||||||
|
case InternalTimeZones.utc: {
|
||||||
|
return {
|
||||||
|
name: 'Coordinated Universal Time',
|
||||||
|
zone,
|
||||||
|
countries: [],
|
||||||
|
abbreviation: 'UTC, GMT',
|
||||||
|
offsetInMins: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case InternalTimeZones.default: {
|
||||||
|
const tz = getTimeZone();
|
||||||
|
const isInternal = tz === 'browser' || tz === 'utc';
|
||||||
|
const info = (isInternal ? mapInternal(tz, timestamp) : mapToInfo(tz, timestamp)) ?? {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
countries: countriesByTimeZone[tz] ?? [],
|
||||||
|
abbreviation: '',
|
||||||
|
offsetInMins: 0,
|
||||||
|
...info,
|
||||||
|
name: 'Default',
|
||||||
|
zone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case InternalTimeZones.localBrowserTime: {
|
||||||
|
const tz = moment.tz.guess(true);
|
||||||
|
const info = mapToInfo(tz, timestamp) ?? {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
countries: countriesByTimeZone[tz] ?? [],
|
||||||
|
abbreviation: 'Your local time',
|
||||||
|
offsetInMins: new Date().getTimezoneOffset(),
|
||||||
|
...info,
|
||||||
|
name: 'Browser Time',
|
||||||
|
zone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const abbrevationWithoutOffset = (abbrevation: string): string => {
|
||||||
|
if (/^(\+|\-).+/.test(abbrevation)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return abbrevation;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapToInfo = (timeZone: TimeZone, timestamp: number): TimeZoneInfo | undefined => {
|
||||||
|
const momentTz = moment.tz.zone(timeZone);
|
||||||
|
|
||||||
|
if (!momentTz) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: timeZone,
|
||||||
|
zone: timeZone,
|
||||||
|
countries: countriesByTimeZone[timeZone] ?? [],
|
||||||
|
abbreviation: abbrevationWithoutOffset(momentTz.abbr(timestamp)),
|
||||||
|
offsetInMins: momentTz.utcOffset(timestamp),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Country names by ISO 3166-1-alpha-2 code
|
||||||
|
const countryByCode: Record<string, string> = {
|
||||||
|
AF: 'Afghanistan',
|
||||||
|
AX: 'Aland Islands',
|
||||||
|
AL: 'Albania',
|
||||||
|
DZ: 'Algeria',
|
||||||
|
AS: 'American Samoa',
|
||||||
|
AD: 'Andorra',
|
||||||
|
AO: 'Angola',
|
||||||
|
AI: 'Anguilla',
|
||||||
|
AQ: 'Antarctica',
|
||||||
|
AG: 'Antigua And Barbuda',
|
||||||
|
AR: 'Argentina',
|
||||||
|
AM: 'Armenia',
|
||||||
|
AW: 'Aruba',
|
||||||
|
AU: 'Australia',
|
||||||
|
AT: 'Austria',
|
||||||
|
AZ: 'Azerbaijan',
|
||||||
|
BS: 'Bahamas',
|
||||||
|
BH: 'Bahrain',
|
||||||
|
BD: 'Bangladesh',
|
||||||
|
BB: 'Barbados',
|
||||||
|
BY: 'Belarus',
|
||||||
|
BE: 'Belgium',
|
||||||
|
BZ: 'Belize',
|
||||||
|
BJ: 'Benin',
|
||||||
|
BM: 'Bermuda',
|
||||||
|
BT: 'Bhutan',
|
||||||
|
BO: 'Bolivia',
|
||||||
|
BA: 'Bosnia And Herzegovina',
|
||||||
|
BW: 'Botswana',
|
||||||
|
BV: 'Bouvet Island',
|
||||||
|
BR: 'Brazil',
|
||||||
|
IO: 'British Indian Ocean Territory',
|
||||||
|
BN: 'Brunei Darussalam',
|
||||||
|
BG: 'Bulgaria',
|
||||||
|
BF: 'Burkina Faso',
|
||||||
|
BI: 'Burundi',
|
||||||
|
KH: 'Cambodia',
|
||||||
|
CM: 'Cameroon',
|
||||||
|
CA: 'Canada',
|
||||||
|
CV: 'Cape Verde',
|
||||||
|
KY: 'Cayman Islands',
|
||||||
|
CF: 'Central African Republic',
|
||||||
|
TD: 'Chad',
|
||||||
|
CL: 'Chile',
|
||||||
|
CN: 'China',
|
||||||
|
CX: 'Christmas Island',
|
||||||
|
CC: 'Cocos (Keeling) Islands',
|
||||||
|
CO: 'Colombia',
|
||||||
|
KM: 'Comoros',
|
||||||
|
CG: 'Congo',
|
||||||
|
CD: 'Congo, Democratic Republic',
|
||||||
|
CK: 'Cook Islands',
|
||||||
|
CR: 'Costa Rica',
|
||||||
|
CI: "Cote D'Ivoire",
|
||||||
|
HR: 'Croatia',
|
||||||
|
CU: 'Cuba',
|
||||||
|
CY: 'Cyprus',
|
||||||
|
CZ: 'Czech Republic',
|
||||||
|
DK: 'Denmark',
|
||||||
|
DJ: 'Djibouti',
|
||||||
|
DM: 'Dominica',
|
||||||
|
DO: 'Dominican Republic',
|
||||||
|
EC: 'Ecuador',
|
||||||
|
EG: 'Egypt',
|
||||||
|
SV: 'El Salvador',
|
||||||
|
GQ: 'Equatorial Guinea',
|
||||||
|
ER: 'Eritrea',
|
||||||
|
EE: 'Estonia',
|
||||||
|
ET: 'Ethiopia',
|
||||||
|
FK: 'Falkland Islands (Malvinas)',
|
||||||
|
FO: 'Faroe Islands',
|
||||||
|
FJ: 'Fiji',
|
||||||
|
FI: 'Finland',
|
||||||
|
FR: 'France',
|
||||||
|
GF: 'French Guiana',
|
||||||
|
PF: 'French Polynesia',
|
||||||
|
TF: 'French Southern Territories',
|
||||||
|
GA: 'Gabon',
|
||||||
|
GM: 'Gambia',
|
||||||
|
GE: 'Georgia',
|
||||||
|
DE: 'Germany',
|
||||||
|
GH: 'Ghana',
|
||||||
|
GI: 'Gibraltar',
|
||||||
|
GR: 'Greece',
|
||||||
|
GL: 'Greenland',
|
||||||
|
GD: 'Grenada',
|
||||||
|
GP: 'Guadeloupe',
|
||||||
|
GU: 'Guam',
|
||||||
|
GT: 'Guatemala',
|
||||||
|
GG: 'Guernsey',
|
||||||
|
GN: 'Guinea',
|
||||||
|
GW: 'Guinea-Bissau',
|
||||||
|
GY: 'Guyana',
|
||||||
|
HT: 'Haiti',
|
||||||
|
HM: 'Heard Island & Mcdonald Islands',
|
||||||
|
VA: 'Holy See (Vatican City State)',
|
||||||
|
HN: 'Honduras',
|
||||||
|
HK: 'Hong Kong',
|
||||||
|
HU: 'Hungary',
|
||||||
|
IS: 'Iceland',
|
||||||
|
IN: 'India',
|
||||||
|
ID: 'Indonesia',
|
||||||
|
IR: 'Iran (Islamic Republic Of)',
|
||||||
|
IQ: 'Iraq',
|
||||||
|
IE: 'Ireland',
|
||||||
|
IM: 'Isle Of Man',
|
||||||
|
IL: 'Israel',
|
||||||
|
IT: 'Italy',
|
||||||
|
JM: 'Jamaica',
|
||||||
|
JP: 'Japan',
|
||||||
|
JE: 'Jersey',
|
||||||
|
JO: 'Jordan',
|
||||||
|
KZ: 'Kazakhstan',
|
||||||
|
KE: 'Kenya',
|
||||||
|
KI: 'Kiribati',
|
||||||
|
KR: 'Korea',
|
||||||
|
KW: 'Kuwait',
|
||||||
|
KG: 'Kyrgyzstan',
|
||||||
|
LA: "Lao People's Democratic Republic",
|
||||||
|
LV: 'Latvia',
|
||||||
|
LB: 'Lebanon',
|
||||||
|
LS: 'Lesotho',
|
||||||
|
LR: 'Liberia',
|
||||||
|
LY: 'Libyan Arab Jamahiriya',
|
||||||
|
LI: 'Liechtenstein',
|
||||||
|
LT: 'Lithuania',
|
||||||
|
LU: 'Luxembourg',
|
||||||
|
MO: 'Macao',
|
||||||
|
MK: 'Macedonia',
|
||||||
|
MG: 'Madagascar',
|
||||||
|
MW: 'Malawi',
|
||||||
|
MY: 'Malaysia',
|
||||||
|
MV: 'Maldives',
|
||||||
|
ML: 'Mali',
|
||||||
|
MT: 'Malta',
|
||||||
|
MH: 'Marshall Islands',
|
||||||
|
MQ: 'Martinique',
|
||||||
|
MR: 'Mauritania',
|
||||||
|
MU: 'Mauritius',
|
||||||
|
YT: 'Mayotte',
|
||||||
|
MX: 'Mexico',
|
||||||
|
FM: 'Micronesia (Federated States Of)',
|
||||||
|
MD: 'Moldova',
|
||||||
|
MC: 'Monaco',
|
||||||
|
MN: 'Mongolia',
|
||||||
|
ME: 'Montenegro',
|
||||||
|
MS: 'Montserrat',
|
||||||
|
MA: 'Morocco',
|
||||||
|
MZ: 'Mozambique',
|
||||||
|
MM: 'Myanmar',
|
||||||
|
NA: 'Namibia',
|
||||||
|
NR: 'Nauru',
|
||||||
|
NP: 'Nepal',
|
||||||
|
NL: 'Netherlands',
|
||||||
|
AN: 'Netherlands Antilles',
|
||||||
|
NC: 'New Caledonia',
|
||||||
|
NZ: 'New Zealand',
|
||||||
|
NI: 'Nicaragua',
|
||||||
|
NE: 'Niger',
|
||||||
|
NG: 'Nigeria',
|
||||||
|
NU: 'Niue',
|
||||||
|
NF: 'Norfolk Island',
|
||||||
|
MP: 'Northern Mariana Islands',
|
||||||
|
NO: 'Norway',
|
||||||
|
OM: 'Oman',
|
||||||
|
PK: 'Pakistan',
|
||||||
|
PW: 'Palau',
|
||||||
|
PS: 'Palestinian Territory (Occupied)',
|
||||||
|
PA: 'Panama',
|
||||||
|
PG: 'Papua New Guinea',
|
||||||
|
PY: 'Paraguay',
|
||||||
|
PE: 'Peru',
|
||||||
|
PH: 'Philippines',
|
||||||
|
PN: 'Pitcairn',
|
||||||
|
PL: 'Poland',
|
||||||
|
PT: 'Portugal',
|
||||||
|
PR: 'Puerto Rico',
|
||||||
|
QA: 'Qatar',
|
||||||
|
RE: 'Reunion',
|
||||||
|
RO: 'Romania',
|
||||||
|
RU: 'Russian Federation',
|
||||||
|
RW: 'Rwanda',
|
||||||
|
BL: 'Saint Barthelemy',
|
||||||
|
SH: 'Saint Helena',
|
||||||
|
KN: 'Saint Kitts And Nevis',
|
||||||
|
LC: 'Saint Lucia',
|
||||||
|
MF: 'Saint Martin',
|
||||||
|
PM: 'Saint Pierre And Miquelon',
|
||||||
|
VC: 'Saint Vincent And Grenadines',
|
||||||
|
WS: 'Samoa',
|
||||||
|
SM: 'San Marino',
|
||||||
|
ST: 'Sao Tome And Principe',
|
||||||
|
SA: 'Saudi Arabia',
|
||||||
|
SN: 'Senegal',
|
||||||
|
RS: 'Serbia',
|
||||||
|
SC: 'Seychelles',
|
||||||
|
SL: 'Sierra Leone',
|
||||||
|
SG: 'Singapore',
|
||||||
|
SK: 'Slovakia',
|
||||||
|
SI: 'Slovenia',
|
||||||
|
SB: 'Solomon Islands',
|
||||||
|
SO: 'Somalia',
|
||||||
|
ZA: 'South Africa',
|
||||||
|
GS: 'South Georgia And Sandwich Isl.',
|
||||||
|
ES: 'Spain',
|
||||||
|
LK: 'Sri Lanka',
|
||||||
|
SD: 'Sudan',
|
||||||
|
SR: 'Suriname',
|
||||||
|
SJ: 'Svalbard And Jan Mayen',
|
||||||
|
SZ: 'Swaziland',
|
||||||
|
SE: 'Sweden',
|
||||||
|
CH: 'Switzerland',
|
||||||
|
SY: 'Syrian Arab Republic',
|
||||||
|
TW: 'Taiwan',
|
||||||
|
TJ: 'Tajikistan',
|
||||||
|
TZ: 'Tanzania',
|
||||||
|
TH: 'Thailand',
|
||||||
|
TL: 'Timor-Leste',
|
||||||
|
TG: 'Togo',
|
||||||
|
TK: 'Tokelau',
|
||||||
|
TO: 'Tonga',
|
||||||
|
TT: 'Trinidad And Tobago',
|
||||||
|
TN: 'Tunisia',
|
||||||
|
TR: 'Turkey',
|
||||||
|
TM: 'Turkmenistan',
|
||||||
|
TC: 'Turks And Caicos Islands',
|
||||||
|
TV: 'Tuvalu',
|
||||||
|
UG: 'Uganda',
|
||||||
|
UA: 'Ukraine',
|
||||||
|
AE: 'United Arab Emirates',
|
||||||
|
GB: 'United Kingdom',
|
||||||
|
US: 'United States',
|
||||||
|
UM: 'United States Outlying Islands',
|
||||||
|
UY: 'Uruguay',
|
||||||
|
UZ: 'Uzbekistan',
|
||||||
|
VU: 'Vanuatu',
|
||||||
|
VE: 'Venezuela',
|
||||||
|
VN: 'Viet Nam',
|
||||||
|
VG: 'Virgin Islands, British',
|
||||||
|
VI: 'Virgin Islands, U.S.',
|
||||||
|
WF: 'Wallis And Futuna',
|
||||||
|
EH: 'Western Sahara',
|
||||||
|
YE: 'Yemen',
|
||||||
|
ZM: 'Zambia',
|
||||||
|
ZW: 'Zimbabwe',
|
||||||
|
};
|
||||||
|
|
||||||
|
const countriesByTimeZone = ((): Record<string, TimeZoneCountry[]> => {
|
||||||
|
return moment.tz.countries().reduce((all: Record<string, TimeZoneCountry[]>, code) => {
|
||||||
|
const timeZones = moment.tz.zonesForCountry(code);
|
||||||
|
return timeZones.reduce((all: Record<string, TimeZoneCountry[]>, timeZone) => {
|
||||||
|
if (!all[timeZone]) {
|
||||||
|
all[timeZone] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = countryByCode[code];
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
all[timeZone].push({ code, name });
|
||||||
|
return all;
|
||||||
|
}, all);
|
||||||
|
}, {});
|
||||||
|
})();
|
||||||
|
@ -17,7 +17,7 @@ export interface SelectCommonProps<T> {
|
|||||||
components?: any;
|
components?: any;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
filterOption?: (option: SelectableValue, searchQuery: string) => void;
|
filterOption?: (option: SelectableValue, searchQuery: string) => boolean;
|
||||||
/** Function for formatting the text that is displayed when creating a new value*/
|
/** Function for formatting the text that is displayed when creating a new value*/
|
||||||
formatCreateLabel?: (input: string) => string;
|
formatCreateLabel?: (input: string) => string;
|
||||||
getOptionLabel?: (item: SelectableValue<T>) => string;
|
getOptionLabel?: (item: SelectableValue<T>) => string;
|
||||||
|
@ -1,344 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`TimePickerContent renders correctly in full screen 1`] = `
|
|
||||||
<div
|
|
||||||
className="css-1py091y"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-dlnzj7"
|
|
||||||
>
|
|
||||||
<FullScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomScrollbar
|
|
||||||
autoHeightMax="100%"
|
|
||||||
autoHeightMin="0"
|
|
||||||
autoHide={false}
|
|
||||||
autoHideDuration={200}
|
|
||||||
autoHideTimeout={200}
|
|
||||||
className="css-1o1b8dr"
|
|
||||||
hideTracksWhenNotNeeded={false}
|
|
||||||
setScrollTop={[Function]}
|
|
||||||
>
|
|
||||||
<NarrowScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={false}
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Relative time ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="css-1ogeuxc"
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Other quick ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`TimePickerContent renders correctly in narrow screen 1`] = `
|
|
||||||
<div
|
|
||||||
className="css-1py091y"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-dlnzj7"
|
|
||||||
>
|
|
||||||
<FullScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={false}
|
|
||||||
onChange={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomScrollbar
|
|
||||||
autoHeightMax="100%"
|
|
||||||
autoHeightMin="0"
|
|
||||||
autoHide={false}
|
|
||||||
autoHideDuration={200}
|
|
||||||
autoHideTimeout={200}
|
|
||||||
className="css-1o1b8dr"
|
|
||||||
hideTracksWhenNotNeeded={false}
|
|
||||||
setScrollTop={[Function]}
|
|
||||||
>
|
|
||||||
<NarrowScreenForm
|
|
||||||
historyOptions={Array []}
|
|
||||||
isFullscreen={false}
|
|
||||||
onChange={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={true}
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Relative time ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="css-1ogeuxc"
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Other quick ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
|
||||||
<div
|
|
||||||
className="css-1py091y"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="css-dlnzj7"
|
|
||||||
>
|
|
||||||
<FullScreenForm
|
|
||||||
history={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
historyOptions={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
|
||||||
"from": "2019-12-17T07:48:27Z",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-12-18T07:48:27Z",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
|
||||||
"from": "2019-10-17T07:48:27Z",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-10-18T07:48:27Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomScrollbar
|
|
||||||
autoHeightMax="100%"
|
|
||||||
autoHeightMin="0"
|
|
||||||
autoHide={false}
|
|
||||||
autoHideDuration={200}
|
|
||||||
autoHideTimeout={200}
|
|
||||||
className="css-1o1b8dr"
|
|
||||||
hideTracksWhenNotNeeded={false}
|
|
||||||
setScrollTop={[Function]}
|
|
||||||
>
|
|
||||||
<NarrowScreenForm
|
|
||||||
history={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-10-17T07:48:27.433Z",
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-10-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
historyOptions={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
|
||||||
"from": "2019-12-17T07:48:27Z",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-12-18T07:48:27Z",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
|
||||||
"from": "2019-10-17T07:48:27Z",
|
|
||||||
"section": 3,
|
|
||||||
"to": "2019-10-18T07:48:27Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
isFullscreen={true}
|
|
||||||
onChange={[Function]}
|
|
||||||
timeZone="utc"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visible={false}
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Relative time ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="css-1ogeuxc"
|
|
||||||
/>
|
|
||||||
<Component
|
|
||||||
onSelect={[Function]}
|
|
||||||
options={Array []}
|
|
||||||
timeZone="utc"
|
|
||||||
title="Other quick ranges"
|
|
||||||
value={
|
|
||||||
Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"raw": Object {
|
|
||||||
"from": "2019-12-17T07:48:27.433Z",
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
},
|
|
||||||
"to": "2019-12-18T07:48:27.433Z",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -1,17 +0,0 @@
|
|||||||
import { GrafanaTheme } from '@grafana/data';
|
|
||||||
import { selectThemeVariant } from '../../../themes/selectThemeVariant';
|
|
||||||
|
|
||||||
export const getThemeColors = (theme: GrafanaTheme) => {
|
|
||||||
return {
|
|
||||||
border: theme.colors.border1,
|
|
||||||
background: theme.colors.bodyBg,
|
|
||||||
shadow: theme.colors.dropdownShadow,
|
|
||||||
formBackground: selectThemeVariant(
|
|
||||||
{
|
|
||||||
dark: theme.palette.gray15,
|
|
||||||
light: theme.palette.gray98,
|
|
||||||
},
|
|
||||||
theme.type
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
@ -24,6 +24,7 @@ export const basic = () => {
|
|||||||
{(value, updateValue) => {
|
{(value, updateValue) => {
|
||||||
return (
|
return (
|
||||||
<TimeRangePicker
|
<TimeRangePicker
|
||||||
|
onChangeTimeZone={() => {}}
|
||||||
timeZone="browser"
|
timeZone="browser"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={timeRange => {
|
onChange={timeRange => {
|
||||||
|
@ -17,6 +17,7 @@ describe('TimePicker', () => {
|
|||||||
it('renders buttons correctly', () => {
|
it('renders buttons correctly', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<UnthemedTimeRangePicker
|
<UnthemedTimeRangePicker
|
||||||
|
onChangeTimeZone={() => {}}
|
||||||
onChange={value => {}}
|
onChange={value => {}}
|
||||||
value={value}
|
value={value}
|
||||||
onMoveBackward={() => {}}
|
onMoveBackward={() => {}}
|
||||||
|
@ -5,7 +5,7 @@ import { css, cx } from 'emotion';
|
|||||||
// Components
|
// Components
|
||||||
import { Tooltip } from '../Tooltip/Tooltip';
|
import { Tooltip } from '../Tooltip/Tooltip';
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { TimePickerContent } from './TimePickerContent/TimePickerContent';
|
import { TimePickerContent } from './TimeRangePicker/TimePickerContent';
|
||||||
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
|
import { ClickOutsideWrapper } from '../ClickOutsideWrapper/ClickOutsideWrapper';
|
||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
@ -98,6 +98,7 @@ export interface Props extends Themeable {
|
|||||||
timeSyncButton?: JSX.Element;
|
timeSyncButton?: JSX.Element;
|
||||||
isSynced?: boolean;
|
isSynced?: boolean;
|
||||||
onChange: (timeRange: TimeRange) => void;
|
onChange: (timeRange: TimeRange) => void;
|
||||||
|
onChangeTimeZone: (timeZone: TimeZone) => void;
|
||||||
onMoveBackward: () => void;
|
onMoveBackward: () => void;
|
||||||
onMoveForward: () => void;
|
onMoveForward: () => void;
|
||||||
onZoom: () => void;
|
onZoom: () => void;
|
||||||
@ -139,6 +140,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
|
|||||||
isSynced,
|
isSynced,
|
||||||
theme,
|
theme,
|
||||||
history,
|
history,
|
||||||
|
onChangeTimeZone,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { isOpen } = this.state;
|
const { isOpen } = this.state;
|
||||||
@ -168,7 +170,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<ClickOutsideWrapper onClick={this.onClose}>
|
<ClickOutsideWrapper includeButtonPress={false} onClick={this.onClose}>
|
||||||
<TimePickerContent
|
<TimePickerContent
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
value={value}
|
value={value}
|
||||||
@ -176,6 +178,7 @@ export class UnthemedTimeRangePicker extends PureComponent<Props, State> {
|
|||||||
otherOptions={otherOptions}
|
otherOptions={otherOptions}
|
||||||
quickOptions={quickOptions}
|
quickOptions={quickOptions}
|
||||||
history={history}
|
history={history}
|
||||||
|
onChangeTimeZone={onChangeTimeZone}
|
||||||
/>
|
/>
|
||||||
</ClickOutsideWrapper>
|
</ClickOutsideWrapper>
|
||||||
)}
|
)}
|
||||||
|
@ -7,31 +7,32 @@ import { TimePickerTitle } from './TimePickerTitle';
|
|||||||
import { Button } from '../../Button';
|
import { Button } from '../../Button';
|
||||||
import { Icon } from '../../Icon/Icon';
|
import { Icon } from '../../Icon/Icon';
|
||||||
import { Portal } from '../../Portal/Portal';
|
import { Portal } from '../../Portal/Portal';
|
||||||
import { getThemeColors } from './colors';
|
|
||||||
import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper';
|
import { ClickOutsideWrapper } from '../../ClickOutsideWrapper/ClickOutsideWrapper';
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
const containerBorder = theme.isDark ? theme.palette.dark9 : theme.palette.gray5;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
top: 0;
|
top: -1px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 546px;
|
right: 544px;
|
||||||
box-shadow: 0px 0px 20px ${colors.shadow};
|
box-shadow: 0px 0px 20px ${theme.colors.dropdownShadow};
|
||||||
background-color: ${colors.background};
|
background-color: ${theme.colors.bodyBg};
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
border: 1px solid ${containerBorder};
|
||||||
|
border-radius: 2px 0 0 2px;
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: ${colors.background};
|
background-color: ${theme.colors.bodyBg};
|
||||||
width: 19px;
|
width: 19px;
|
||||||
height: 381px;
|
height: 100%;
|
||||||
content: ' ';
|
content: ' ';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: -19px;
|
right: -19px;
|
||||||
border-left: 1px solid ${colors.border};
|
border-left: 1px solid ${theme.colors.border1};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
modal: css`
|
modal: css`
|
||||||
@ -59,11 +60,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getFooterStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getFooterStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
background-color: ${colors.background};
|
background-color: ${theme.colors.bodyBg};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -78,12 +77,10 @@ const getFooterStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getBodyStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getBodyStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: css`
|
title: css`
|
||||||
color: ${theme.colors.text};
|
color: ${theme.colors.text};
|
||||||
background-color: ${colors.background};
|
background-color: ${theme.colors.bodyBg};
|
||||||
font-size: ${theme.typography.size.md};
|
font-size: ${theme.typography.size.md};
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|
||||||
@ -93,7 +90,7 @@ const getBodyStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
`,
|
`,
|
||||||
body: css`
|
body: css`
|
||||||
z-index: ${theme.zIndex.modal};
|
z-index: ${theme.zIndex.modal};
|
||||||
background-color: ${colors.background};
|
background-color: ${theme.colors.bodyBg};
|
||||||
width: 268px;
|
width: 268px;
|
||||||
|
|
||||||
.react-calendar__navigation__label,
|
.react-calendar__navigation__label,
|
||||||
@ -177,11 +174,9 @@ const getBodyStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getHeaderStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getHeaderStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
background-color: ${colors.background};
|
background-color: ${theme.colors.bodyBg};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 7px;
|
padding: 7px;
|
@ -7,7 +7,13 @@ describe('TimePickerContent', () => {
|
|||||||
it('renders correctly in full screen', () => {
|
it('renders correctly in full screen', () => {
|
||||||
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<TimePickerContentWithScreenSize onChange={value => {}} timeZone="utc" value={value} isFullscreen={true} />
|
<TimePickerContentWithScreenSize
|
||||||
|
onChangeTimeZone={() => {}}
|
||||||
|
onChange={value => {}}
|
||||||
|
timeZone="utc"
|
||||||
|
value={value}
|
||||||
|
isFullscreen={true}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@ -15,7 +21,13 @@ describe('TimePickerContent', () => {
|
|||||||
it('renders correctly in narrow screen', () => {
|
it('renders correctly in narrow screen', () => {
|
||||||
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
const value = createTimeRange('2019-12-17T07:48:27.433Z', '2019-12-18T07:48:27.433Z');
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<TimePickerContentWithScreenSize onChange={value => {}} timeZone="utc" value={value} isFullscreen={false} />
|
<TimePickerContentWithScreenSize
|
||||||
|
onChangeTimeZone={() => {}}
|
||||||
|
onChange={value => {}}
|
||||||
|
timeZone="utc"
|
||||||
|
value={value}
|
||||||
|
isFullscreen={false}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@ -29,6 +41,7 @@ describe('TimePickerContent', () => {
|
|||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<TimePickerContentWithScreenSize
|
<TimePickerContentWithScreenSize
|
||||||
|
onChangeTimeZone={() => {}}
|
||||||
onChange={value => {}}
|
onChange={value => {}}
|
||||||
timeZone="utc"
|
timeZone="utc"
|
||||||
value={value}
|
value={value}
|
@ -5,26 +5,26 @@ import { useMedia } from 'react-use';
|
|||||||
import { stylesFactory, useTheme } from '../../../themes';
|
import { stylesFactory, useTheme } from '../../../themes';
|
||||||
import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar';
|
import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar';
|
||||||
import { Icon } from '../../Icon/Icon';
|
import { Icon } from '../../Icon/Icon';
|
||||||
import { getThemeColors } from './colors';
|
|
||||||
import { mapRangeToTimeOption } from './mapper';
|
import { mapRangeToTimeOption } from './mapper';
|
||||||
import { TimePickerTitle } from './TimePickerTitle';
|
import { TimePickerTitle } from './TimePickerTitle';
|
||||||
import { TimeRangeForm } from './TimeRangeForm';
|
import { TimeRangeForm } from './TimeRangeForm';
|
||||||
import { TimeRangeList } from './TimeRangeList';
|
import { TimeRangeList } from './TimeRangeList';
|
||||||
|
import { TimePickerFooter } from './TimePickerFooter';
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
const containerBorder = theme.isDark ? theme.palette.dark9 : theme.palette.gray5;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
display: flex;
|
background: ${theme.colors.bodyBg};
|
||||||
background: ${colors.background};
|
box-shadow: 0px 0px 20px ${theme.colors.dropdownShadow};
|
||||||
box-shadow: 0px 0px 20px ${colors.shadow};
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: ${theme.zIndex.modal};
|
z-index: ${theme.zIndex.modal};
|
||||||
width: 546px;
|
width: 546px;
|
||||||
height: 381px;
|
|
||||||
top: 116%;
|
top: 116%;
|
||||||
margin-left: -322px;
|
margin-left: -322px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid ${containerBorder};
|
||||||
|
|
||||||
@media only screen and (max-width: ${theme.breakpoints.lg}) {
|
@media only screen and (max-width: ${theme.breakpoints.lg}) {
|
||||||
width: 218px;
|
width: 218px;
|
||||||
@ -36,10 +36,14 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
margin-left: -100px;
|
margin-left: -100px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
body: css`
|
||||||
|
display: flex;
|
||||||
|
height: 381px;
|
||||||
|
`,
|
||||||
leftSide: css`
|
leftSide: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-right: 1px solid ${colors.border};
|
border-right: 1px solid ${theme.colors.border1};
|
||||||
width: 60%;
|
width: 60%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@ -61,7 +65,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
const formBackground = theme.isDark ? theme.palette.gray15 : theme.palette.gray98;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
header: css`
|
header: css`
|
||||||
@ -69,13 +73,13 @@ const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid ${colors.border};
|
border-bottom: 1px solid ${theme.colors.border1};
|
||||||
padding: 7px 9px 7px 9px;
|
padding: 7px 9px 7px 9px;
|
||||||
`,
|
`,
|
||||||
body: css`
|
body: css`
|
||||||
border-bottom: 1px solid ${colors.border};
|
border-bottom: 1px solid ${theme.colors.border1};
|
||||||
background: ${colors.formBackground};
|
background: ${formBackground};
|
||||||
box-shadow: inset 0px 2px 2px ${colors.shadow};
|
box-shadow: inset 0px 2px 2px ${theme.colors.dropdownShadow};
|
||||||
`,
|
`,
|
||||||
form: css`
|
form: css`
|
||||||
padding: 7px 9px 7px 9px;
|
padding: 7px 9px 7px 9px;
|
||||||
@ -103,11 +107,11 @@ const getFullScreenStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getEmptyListStyles = stylesFactory((theme: GrafanaTheme) => {
|
const getEmptyListStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
const colors = getThemeColors(theme);
|
const formBackground = theme.isDark ? theme.palette.gray15 : theme.palette.gray98;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
background-color: ${colors.formBackground};
|
background-color: ${formBackground};
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
|
|
||||||
@ -125,6 +129,7 @@ const getEmptyListStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
interface Props {
|
interface Props {
|
||||||
value: TimeRange;
|
value: TimeRange;
|
||||||
onChange: (timeRange: TimeRange) => void;
|
onChange: (timeRange: TimeRange) => void;
|
||||||
|
onChangeTimeZone: (timeZone: TimeZone) => void;
|
||||||
timeZone?: TimeZone;
|
timeZone?: TimeZone;
|
||||||
quickOptions?: TimeOption[];
|
quickOptions?: TimeOption[];
|
||||||
otherOptions?: TimeOption[];
|
otherOptions?: TimeOption[];
|
||||||
@ -148,27 +153,30 @@ export const TimePickerContentWithScreenSize: React.FC<PropsWithScreenSize> = pr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.leftSide}>
|
<div className={styles.body}>
|
||||||
<FullScreenForm {...props} visible={isFullscreen} historyOptions={historyOptions} />
|
<div className={styles.leftSide}>
|
||||||
|
<FullScreenForm {...props} visible={isFullscreen} historyOptions={historyOptions} />
|
||||||
|
</div>
|
||||||
|
<CustomScrollbar className={styles.rightSide}>
|
||||||
|
<NarrowScreenForm {...props} visible={!isFullscreen} historyOptions={historyOptions} />
|
||||||
|
<TimeRangeList
|
||||||
|
title="Relative time ranges"
|
||||||
|
options={quickOptions}
|
||||||
|
onSelect={props.onChange}
|
||||||
|
value={props.value}
|
||||||
|
timeZone={props.timeZone}
|
||||||
|
/>
|
||||||
|
<div className={styles.spacing} />
|
||||||
|
<TimeRangeList
|
||||||
|
title="Other quick ranges"
|
||||||
|
options={otherOptions}
|
||||||
|
onSelect={props.onChange}
|
||||||
|
value={props.value}
|
||||||
|
timeZone={props.timeZone}
|
||||||
|
/>
|
||||||
|
</CustomScrollbar>
|
||||||
</div>
|
</div>
|
||||||
<CustomScrollbar className={styles.rightSide}>
|
{isFullscreen && <TimePickerFooter timeZone={props.timeZone} onChangeTimeZone={props.onChangeTimeZone} />}
|
||||||
<NarrowScreenForm {...props} visible={!isFullscreen} historyOptions={historyOptions} />
|
|
||||||
<TimeRangeList
|
|
||||||
title="Relative time ranges"
|
|
||||||
options={quickOptions}
|
|
||||||
onSelect={props.onChange}
|
|
||||||
value={props.value}
|
|
||||||
timeZone={props.timeZone}
|
|
||||||
/>
|
|
||||||
<div className={styles.spacing} />
|
|
||||||
<TimeRangeList
|
|
||||||
title="Other quick ranges"
|
|
||||||
options={otherOptions}
|
|
||||||
onSelect={props.onChange}
|
|
||||||
value={props.value}
|
|
||||||
timeZone={props.timeZone}
|
|
||||||
/>
|
|
||||||
</CustomScrollbar>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -0,0 +1,113 @@
|
|||||||
|
import React, { FC, useState, useCallback } from 'react';
|
||||||
|
import { css, cx } from 'emotion';
|
||||||
|
import { TimeZone, GrafanaTheme, getTimeZoneInfo } from '@grafana/data';
|
||||||
|
import { stylesFactory, useTheme } from '../../../themes';
|
||||||
|
import { TimeZoneTitle } from '../TimeZonePicker/TimeZoneTitle';
|
||||||
|
import { TimeZoneDescription } from '../TimeZonePicker/TimeZoneDescription';
|
||||||
|
import { TimeZoneOffset } from '../TimeZonePicker/TimeZoneOffset';
|
||||||
|
import { Button } from '../../Button';
|
||||||
|
import { TimeZonePicker } from '../TimeZonePicker';
|
||||||
|
import isString from 'lodash/isString';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
timeZone?: TimeZone;
|
||||||
|
timestamp?: number;
|
||||||
|
onChangeTimeZone: (timeZone: TimeZone) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimePickerFooter: FC<Props> = props => {
|
||||||
|
const { timeZone, timestamp = Date.now(), onChangeTimeZone } = props;
|
||||||
|
const [isEditing, setEditing] = useState(false);
|
||||||
|
|
||||||
|
const onToggleChangeTz = useCallback(
|
||||||
|
(event?: React.MouseEvent) => {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
setEditing(!isEditing);
|
||||||
|
},
|
||||||
|
[isEditing, setEditing]
|
||||||
|
);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const style = getStyle(theme);
|
||||||
|
|
||||||
|
if (!isString(timeZone)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = getTimeZoneInfo(timeZone, timestamp);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
return (
|
||||||
|
<div className={cx(style.container, style.editContainer)}>
|
||||||
|
<div className={style.timeZoneContainer}>
|
||||||
|
<TimeZonePicker
|
||||||
|
onChange={timeZone => {
|
||||||
|
onToggleChangeTz();
|
||||||
|
|
||||||
|
if (isString(timeZone)) {
|
||||||
|
onChangeTimeZone(timeZone);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
autoFocus={true}
|
||||||
|
onBlur={onToggleChangeTz}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.container}>
|
||||||
|
<div className={style.timeZoneContainer}>
|
||||||
|
<div className={style.timeZone}>
|
||||||
|
<TimeZoneTitle title={info.name} />
|
||||||
|
<div className={style.spacer} />
|
||||||
|
<TimeZoneDescription info={info} />
|
||||||
|
</div>
|
||||||
|
<TimeZoneOffset timeZone={timeZone} timestamp={timestamp} />
|
||||||
|
</div>
|
||||||
|
<div className={style.spacer} />
|
||||||
|
<Button variant="secondary" onClick={onToggleChangeTz} size="sm">
|
||||||
|
Change time zone
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyle = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
border-top: 1px solid ${theme.colors.border1};
|
||||||
|
padding: 11px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`,
|
||||||
|
editContainer: css`
|
||||||
|
padding: 7px;
|
||||||
|
`,
|
||||||
|
spacer: css`
|
||||||
|
margin-left: 7px;
|
||||||
|
`,
|
||||||
|
timeZoneContainer: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
`,
|
||||||
|
timeZone: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-grow: 1;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,370 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`TimePickerContent renders correctly in full screen 1`] = `
|
||||||
|
<div
|
||||||
|
className="css-1py9bjs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-ooqtr4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-dlnzj7"
|
||||||
|
>
|
||||||
|
<FullScreenForm
|
||||||
|
historyOptions={Array []}
|
||||||
|
isFullscreen={true}
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomScrollbar
|
||||||
|
autoHeightMax="100%"
|
||||||
|
autoHeightMin="0"
|
||||||
|
autoHide={false}
|
||||||
|
autoHideDuration={200}
|
||||||
|
autoHideTimeout={200}
|
||||||
|
className="css-1o1b8dr"
|
||||||
|
hideTracksWhenNotNeeded={false}
|
||||||
|
setScrollTop={[Function]}
|
||||||
|
>
|
||||||
|
<NarrowScreenForm
|
||||||
|
historyOptions={Array []}
|
||||||
|
isFullscreen={true}
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible={false}
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
onSelect={[Function]}
|
||||||
|
options={Array []}
|
||||||
|
timeZone="utc"
|
||||||
|
title="Relative time ranges"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="css-1ogeuxc"
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
onSelect={[Function]}
|
||||||
|
options={Array []}
|
||||||
|
timeZone="utc"
|
||||||
|
title="Other quick ranges"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</CustomScrollbar>
|
||||||
|
</div>
|
||||||
|
<Component
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`TimePickerContent renders correctly in narrow screen 1`] = `
|
||||||
|
<div
|
||||||
|
className="css-1py9bjs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-ooqtr4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-dlnzj7"
|
||||||
|
>
|
||||||
|
<FullScreenForm
|
||||||
|
historyOptions={Array []}
|
||||||
|
isFullscreen={false}
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomScrollbar
|
||||||
|
autoHeightMax="100%"
|
||||||
|
autoHeightMin="0"
|
||||||
|
autoHide={false}
|
||||||
|
autoHideDuration={200}
|
||||||
|
autoHideTimeout={200}
|
||||||
|
className="css-1o1b8dr"
|
||||||
|
hideTracksWhenNotNeeded={false}
|
||||||
|
setScrollTop={[Function]}
|
||||||
|
>
|
||||||
|
<NarrowScreenForm
|
||||||
|
historyOptions={Array []}
|
||||||
|
isFullscreen={false}
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
onSelect={[Function]}
|
||||||
|
options={Array []}
|
||||||
|
timeZone="utc"
|
||||||
|
title="Relative time ranges"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="css-1ogeuxc"
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
onSelect={[Function]}
|
||||||
|
options={Array []}
|
||||||
|
timeZone="utc"
|
||||||
|
title="Other quick ranges"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</CustomScrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`TimePickerContent renders recent absolute ranges correctly 1`] = `
|
||||||
|
<div
|
||||||
|
className="css-1py9bjs"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-ooqtr4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="css-dlnzj7"
|
||||||
|
>
|
||||||
|
<FullScreenForm
|
||||||
|
history={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"from": "2019-10-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-10-17T07:48:27.433Z",
|
||||||
|
"to": "2019-10-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-10-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
historyOptions={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
||||||
|
"from": "2019-12-17T07:48:27Z",
|
||||||
|
"section": 3,
|
||||||
|
"to": "2019-12-18T07:48:27Z",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
||||||
|
"from": "2019-10-17T07:48:27Z",
|
||||||
|
"section": 3,
|
||||||
|
"to": "2019-10-18T07:48:27Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
isFullscreen={true}
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomScrollbar
|
||||||
|
autoHeightMax="100%"
|
||||||
|
autoHeightMin="0"
|
||||||
|
autoHide={false}
|
||||||
|
autoHideDuration={200}
|
||||||
|
autoHideTimeout={200}
|
||||||
|
className="css-1o1b8dr"
|
||||||
|
hideTracksWhenNotNeeded={false}
|
||||||
|
setScrollTop={[Function]}
|
||||||
|
>
|
||||||
|
<NarrowScreenForm
|
||||||
|
history={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"from": "2019-10-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-10-17T07:48:27.433Z",
|
||||||
|
"to": "2019-10-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-10-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
historyOptions={
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"display": "2019-12-17 07:48:27 to 2019-12-18 07:48:27",
|
||||||
|
"from": "2019-12-17T07:48:27Z",
|
||||||
|
"section": 3,
|
||||||
|
"to": "2019-12-18T07:48:27Z",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"display": "2019-10-17 07:48:27 to 2019-10-18 07:48:27",
|
||||||
|
"from": "2019-10-17T07:48:27Z",
|
||||||
|
"section": 3,
|
||||||
|
"to": "2019-10-18T07:48:27Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
isFullscreen={true}
|
||||||
|
onChange={[Function]}
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visible={false}
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
onSelect={[Function]}
|
||||||
|
options={Array []}
|
||||||
|
timeZone="utc"
|
||||||
|
title="Relative time ranges"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="css-1ogeuxc"
|
||||||
|
/>
|
||||||
|
<Component
|
||||||
|
onSelect={[Function]}
|
||||||
|
options={Array []}
|
||||||
|
timeZone="utc"
|
||||||
|
title="Other quick ranges"
|
||||||
|
value={
|
||||||
|
Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"raw": Object {
|
||||||
|
"from": "2019-12-17T07:48:27.433Z",
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
},
|
||||||
|
"to": "2019-12-18T07:48:27.433Z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</CustomScrollbar>
|
||||||
|
</div>
|
||||||
|
<Component
|
||||||
|
onChangeTimeZone={[Function]}
|
||||||
|
timeZone="utc"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -23,6 +23,9 @@ export const basic = () => {
|
|||||||
<TimeZonePicker
|
<TimeZonePicker
|
||||||
value={value.value}
|
value={value.value}
|
||||||
onChange={newValue => {
|
onChange={newValue => {
|
||||||
|
if (!newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
action('on selected')(newValue);
|
action('on selected')(newValue);
|
||||||
updateValue({ value: newValue });
|
updateValue({ value: newValue });
|
||||||
}}
|
}}
|
||||||
|
@ -1,47 +1,150 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { getTimeZoneGroups } from '@grafana/data';
|
import { toLower, isEmpty, isString } from 'lodash';
|
||||||
import { Cascader } from '../index';
|
import {
|
||||||
|
SelectableValue,
|
||||||
|
getTimeZoneInfo,
|
||||||
|
TimeZoneInfo,
|
||||||
|
getTimeZoneGroups,
|
||||||
|
GroupedTimeZones,
|
||||||
|
TimeZone,
|
||||||
|
InternalTimeZones,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { Select } from '../Select/Select';
|
||||||
|
import { CompactTimeZoneOption, WideTimeZoneOption, SelectableZone } from './TimeZonePicker/TimeZoneOption';
|
||||||
|
import { TimeZoneGroup } from './TimeZonePicker/TimeZoneGroup';
|
||||||
|
import { formatUtcOffset } from './TimeZonePicker/TimeZoneOffset';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
value: string;
|
value?: TimeZone;
|
||||||
width?: number;
|
width?: number;
|
||||||
|
autoFocus?: boolean;
|
||||||
onChange: (newValue: string) => void;
|
onChange: (timeZone: TimeZone | undefined) => void;
|
||||||
|
onBlur?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TimeZonePicker: FC<Props> = ({ onChange, value, width }) => {
|
export const TimeZonePicker: React.FC<Props> = props => {
|
||||||
const timeZoneGroups = getTimeZoneGroups();
|
const { onChange, width, autoFocus = false, onBlur, value } = props;
|
||||||
|
const groupedTimeZones = useTimeZones();
|
||||||
|
const selected = useSelectedTimeZone(groupedTimeZones, value);
|
||||||
|
const filterBySearchIndex = useFilterBySearchIndex();
|
||||||
|
const TimeZoneOption = width && width <= 45 ? CompactTimeZoneOption : WideTimeZoneOption;
|
||||||
|
|
||||||
const groupOptions = timeZoneGroups.map(group => {
|
const onChangeTz = useCallback(
|
||||||
const options = group.options.map(timeZone => {
|
(selectable: SelectableValue<string>) => {
|
||||||
return {
|
if (!selectable || !isString(selectable.value)) {
|
||||||
label: timeZone,
|
return onChange(value);
|
||||||
value: timeZone,
|
}
|
||||||
};
|
onChange(selectable.value);
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: group.label,
|
|
||||||
value: group.label,
|
|
||||||
items: options,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedValue = groupOptions.reduce(
|
|
||||||
(acc, group) => {
|
|
||||||
const found = group.items.find(option => option.value === value);
|
|
||||||
return found || acc;
|
|
||||||
},
|
},
|
||||||
{ value: '' }
|
[onChange, value]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cascader
|
<Select
|
||||||
options={groupOptions}
|
value={selected}
|
||||||
initialValue={selectedValue?.value}
|
placeholder="Type to search (country, city, abbreviation)"
|
||||||
onSelect={(newValue: string) => onChange(newValue)}
|
autoFocus={autoFocus}
|
||||||
|
openMenuOnFocus={true}
|
||||||
width={width}
|
width={width}
|
||||||
placeholder="Select timezone"
|
filterOption={filterBySearchIndex}
|
||||||
|
options={groupedTimeZones}
|
||||||
|
onChange={onChangeTz}
|
||||||
|
onBlur={onBlur}
|
||||||
|
components={{ Option: TimeZoneOption, Group: TimeZoneGroup }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface SelectableZoneGroup extends SelectableValue<string> {
|
||||||
|
options: SelectableZone[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useTimeZones = (): SelectableZoneGroup[] => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
return getTimeZoneGroups(true).map((group: GroupedTimeZones) => {
|
||||||
|
const options = group.zones.reduce((options: SelectableZone[], zone) => {
|
||||||
|
const info = getTimeZoneInfo(zone, now);
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
label: info.name,
|
||||||
|
value: info.zone,
|
||||||
|
searchIndex: useSearchIndex(info, now),
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: group.name,
|
||||||
|
options,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSelectedTimeZone = (
|
||||||
|
groups: SelectableZoneGroup[],
|
||||||
|
timeZone: TimeZone | undefined
|
||||||
|
): SelectableZone | undefined => {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (timeZone === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = groups.find(group => {
|
||||||
|
if (!group.label) {
|
||||||
|
return isInternal(timeZone);
|
||||||
|
}
|
||||||
|
return timeZone.startsWith(group.label);
|
||||||
|
});
|
||||||
|
|
||||||
|
return group?.options.find(option => {
|
||||||
|
if (isEmpty(timeZone)) {
|
||||||
|
return option.value === InternalTimeZones.default;
|
||||||
|
}
|
||||||
|
return toLower(option.value) === timeZone;
|
||||||
|
});
|
||||||
|
}, [groups, timeZone]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isInternal = (timeZone: TimeZone): boolean => {
|
||||||
|
switch (timeZone) {
|
||||||
|
case InternalTimeZones.default:
|
||||||
|
case InternalTimeZones.localBrowserTime:
|
||||||
|
case InternalTimeZones.utc:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useFilterBySearchIndex = () => {
|
||||||
|
return useCallback((option: SelectableValue, searchQuery: string) => {
|
||||||
|
if (!searchQuery || !option.data || !option.data.searchIndex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return option.data.searchIndex.indexOf(toLower(searchQuery)) > -1;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSearchIndex = (info: TimeZoneInfo, timestamp: number): string => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const parts: string[] = [
|
||||||
|
toLower(info.name),
|
||||||
|
toLower(info.abbreviation),
|
||||||
|
toLower(formatUtcOffset(timestamp, info.zone)),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const country of info.countries) {
|
||||||
|
parts.push(toLower(country.name));
|
||||||
|
parts.push(toLower(country.code));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join('|');
|
||||||
|
}, [info.zone, info.abbreviation, info.offsetInMins]);
|
||||||
|
};
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import React, { PropsWithChildren, useMemo } from 'react';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { GrafanaTheme, TimeZoneInfo } from '@grafana/data';
|
||||||
|
import { useTheme, stylesFactory } from '../../../themes';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
info?: TimeZoneInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeZoneDescription: React.FC<PropsWithChildren<Props>> = ({ info }) => {
|
||||||
|
if (!info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
const description = useDescription(info);
|
||||||
|
|
||||||
|
return <div className={styles.description}>{description}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useDescription = (info: TimeZoneInfo): string => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
if (info.countries.length > 0) {
|
||||||
|
const country = info.countries[0];
|
||||||
|
parts.push(country.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.abbreviation) {
|
||||||
|
parts.push(info.abbreviation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(', ');
|
||||||
|
}, [info.zone]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
description: css`
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
|
color: ${theme.colors.textWeak};
|
||||||
|
white-space: normal;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { useTheme, stylesFactory } from '../../../themes';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopPropagation = (event: React.MouseEvent) => event.stopPropagation();
|
||||||
|
|
||||||
|
export const TimeZoneGroup: React.FC<PropsWithChildren<Props>> = props => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { children, label } = props;
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
if (!label) {
|
||||||
|
return <div onClick={stopPropagation}>{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onClick={stopPropagation}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<span className={styles.label}>{label}</span>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
header: css`
|
||||||
|
padding: 7px 10px;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid ${theme.colors.border1};
|
||||||
|
text-transform: capitalize;
|
||||||
|
`,
|
||||||
|
label: css`
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
|
color: ${theme.colors.textWeak};
|
||||||
|
font-weight: ${theme.typography.weight.semibold};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,72 @@
|
|||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import { css, cx } from 'emotion';
|
||||||
|
import { GrafanaTheme, TimeZone, dateTimeFormat } from '@grafana/data';
|
||||||
|
import { useTheme, stylesFactory } from '../../../themes';
|
||||||
|
import isString from 'lodash/isString';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
timestamp: number;
|
||||||
|
timeZone: TimeZone | undefined;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeZoneOffset: React.FC<PropsWithChildren<Props>> = props => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { timestamp, timeZone, className } = props;
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
if (!isString(timeZone)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className={styles.localTime}>{formatLocalTime(timestamp, timeZone)}</span>
|
||||||
|
<span className={cx(styles.offset, className)}>{formatUtcOffset(timestamp, timeZone)}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatUtcOffset = (timestamp: number, timeZone: TimeZone): string => {
|
||||||
|
const offset = dateTimeFormat(timestamp, {
|
||||||
|
timeZone,
|
||||||
|
format: 'Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (offset === '+00:00') {
|
||||||
|
return 'UTC';
|
||||||
|
}
|
||||||
|
return `UTC${offset}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatLocalTime = (timestamp: number, timeZone: TimeZone): string => {
|
||||||
|
return dateTimeFormat(timestamp, {
|
||||||
|
timeZone,
|
||||||
|
format: 'HH:mm',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const textBase = css`
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
|
color: ${theme.colors.textWeak};
|
||||||
|
white-space: normal;
|
||||||
|
`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
localTime: css`
|
||||||
|
display: none;
|
||||||
|
${textBase};
|
||||||
|
color: ${theme.colors.text};
|
||||||
|
`,
|
||||||
|
offset: css`
|
||||||
|
${textBase};
|
||||||
|
color: ${theme.colors.text};
|
||||||
|
background: ${theme.colors.bg2};
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-left: 4px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,159 @@
|
|||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import { css, cx } from 'emotion';
|
||||||
|
import { GrafanaTheme, SelectableValue, getTimeZoneInfo } from '@grafana/data';
|
||||||
|
import { useTheme } from '../../../themes/ThemeContext';
|
||||||
|
import { stylesFactory } from '../../../themes/stylesFactory';
|
||||||
|
import { Icon } from '../../Icon/Icon';
|
||||||
|
import { TimeZoneOffset } from './TimeZoneOffset';
|
||||||
|
import { TimeZoneDescription } from './TimeZoneDescription';
|
||||||
|
import { TimeZoneTitle } from './TimeZoneTitle';
|
||||||
|
import isString from 'lodash/isString';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isFocused: boolean;
|
||||||
|
isSelected: boolean;
|
||||||
|
innerProps: any;
|
||||||
|
data: SelectableZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offsetClassName = 'tz-utc-offset';
|
||||||
|
|
||||||
|
export interface SelectableZone extends SelectableValue<string> {
|
||||||
|
searchIndex: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WideTimeZoneOption: React.FC<PropsWithChildren<Props>> = (props, ref) => {
|
||||||
|
const { children, innerProps, data, isSelected, isFocused } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const containerStyles = cx(styles.container, isFocused && styles.containerFocused);
|
||||||
|
|
||||||
|
if (!isString(data.value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={containerStyles} {...innerProps} aria-label="Select option">
|
||||||
|
<div className={cx(styles.leftColumn, styles.row)}>
|
||||||
|
<div className={cx(styles.leftColumn, styles.wideRow)}>
|
||||||
|
<TimeZoneTitle title={children} />
|
||||||
|
<div className={styles.spacer} />
|
||||||
|
<TimeZoneDescription info={getTimeZoneInfo(data.value, timestamp)} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColumn}>
|
||||||
|
<TimeZoneOffset timeZone={data.value} timestamp={timestamp} className={offsetClassName} />
|
||||||
|
{isSelected && (
|
||||||
|
<span>
|
||||||
|
<Icon name="check" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CompactTimeZoneOption: React.FC<PropsWithChildren<Props>> = (props, ref) => {
|
||||||
|
const { children, innerProps, data, isSelected, isFocused } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const containerStyles = cx(styles.container, isFocused && styles.containerFocused);
|
||||||
|
|
||||||
|
if (!isString(data.value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={containerStyles} {...innerProps} aria-label="Select option">
|
||||||
|
<div className={styles.body}>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.leftColumn}>
|
||||||
|
<TimeZoneTitle title={children} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColumn}>
|
||||||
|
{isSelected && (
|
||||||
|
<span>
|
||||||
|
<Icon name="check" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.row}>
|
||||||
|
<div className={styles.leftColumn}>
|
||||||
|
<TimeZoneDescription info={getTimeZoneInfo(data.value, timestamp)} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColumn}>
|
||||||
|
<TimeZoneOffset timestamp={timestamp} timeZone={data.value} className={offsetClassName} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
const offsetHoverBg = theme.isDark ? theme.palette.gray05 : theme.palette.white;
|
||||||
|
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
padding: 6px 8px 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: ${theme.colors.dropdownOptionHoverBg};
|
||||||
|
|
||||||
|
span.${offsetClassName} {
|
||||||
|
background: ${offsetHoverBg};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
containerFocused: css`
|
||||||
|
background: ${theme.colors.dropdownOptionHoverBg};
|
||||||
|
border-image: linear-gradient(#f05a28 30%, #fbca0a 99%);
|
||||||
|
border-image-slice: 1;
|
||||||
|
border-style: solid;
|
||||||
|
border-top: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
border-left-width: 2px;
|
||||||
|
|
||||||
|
span.${offsetClassName} {
|
||||||
|
background: ${offsetHoverBg};
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
body: css`
|
||||||
|
display: flex;
|
||||||
|
font-weight: ${theme.typography.weight.semibold};
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
`,
|
||||||
|
row: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
`,
|
||||||
|
leftColumn: css`
|
||||||
|
flex-grow: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`,
|
||||||
|
rightColumn: css`
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
`,
|
||||||
|
wideRow: css`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
`,
|
||||||
|
spacer: css`
|
||||||
|
margin-left: 6px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { useTheme, stylesFactory } from '../../../themes';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string | ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeZoneTitle: React.FC<Props> = ({ title }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className={styles.title}>{title}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
title: css`
|
||||||
|
font-weight: ${theme.typography.weight.regular};
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -23,8 +23,8 @@ export { UnitPicker } from './UnitPicker/UnitPicker';
|
|||||||
export { StatsPicker } from './StatsPicker/StatsPicker';
|
export { StatsPicker } from './StatsPicker/StatsPicker';
|
||||||
export { RefreshPicker } from './RefreshPicker/RefreshPicker';
|
export { RefreshPicker } from './RefreshPicker/RefreshPicker';
|
||||||
export { TimeRangePicker } from './TimePicker/TimeRangePicker';
|
export { TimeRangePicker } from './TimePicker/TimeRangePicker';
|
||||||
export { TimeZonePicker } from './TimePicker/TimeZonePicker';
|
|
||||||
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
|
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
|
||||||
|
export { TimeZonePicker } from './TimePicker/TimeZonePicker';
|
||||||
export { List } from './List/List';
|
export { List } from './List/List';
|
||||||
export { TagsInput } from './TagsInput/TagsInput';
|
export { TagsInput } from './TagsInput/TagsInput';
|
||||||
export { Pagination } from './Pagination/Pagination';
|
export { Pagination } from './Pagination/Pagination';
|
||||||
|
@ -94,7 +94,7 @@ export class GrafanaApp {
|
|||||||
|
|
||||||
addClassIfNoOverlayScrollbar();
|
addClassIfNoOverlayScrollbar();
|
||||||
setLocale(config.bootData.user.locale);
|
setLocale(config.bootData.user.locale);
|
||||||
setTimeZoneResolver(() => config.bootData.user.timeZone);
|
setTimeZoneResolver(() => config.bootData.user.timezone);
|
||||||
|
|
||||||
setMarkdownOptions({ sanitize: !config.disableSanitizeHtml });
|
setMarkdownOptions({ sanitize: !config.disableSanitizeHtml });
|
||||||
|
|
||||||
|
@ -12,8 +12,9 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
RadioButtonGroup,
|
RadioButtonGroup,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
|
TimeZonePicker,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { getTimeZoneGroups, SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types';
|
import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types';
|
||||||
@ -36,18 +37,6 @@ const themes: SelectableValue[] = [
|
|||||||
{ value: 'light', label: 'Light' },
|
{ value: 'light', label: 'Light' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const grafanaTimeZones = [
|
|
||||||
{ value: '', label: 'Default' },
|
|
||||||
{ value: 'browser', label: 'Local browser time' },
|
|
||||||
{ value: 'utc', label: 'UTC' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const timeZones = getTimeZoneGroups().reduce((tzs, group) => {
|
|
||||||
const options = group.options.map(tz => ({ value: tz, label: tz }));
|
|
||||||
tzs.push.apply(tzs, options);
|
|
||||||
return tzs;
|
|
||||||
}, grafanaTimeZones);
|
|
||||||
|
|
||||||
export class SharedPreferences extends PureComponent<Props, State> {
|
export class SharedPreferences extends PureComponent<Props, State> {
|
||||||
backendSrv = backendSrv;
|
backendSrv = backendSrv;
|
||||||
|
|
||||||
@ -112,11 +101,11 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
|||||||
this.setState({ theme: value });
|
this.setState({ theme: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onTimeZoneChanged = (timezone: SelectableValue<string>) => {
|
onTimeZoneChanged = (timezone: string) => {
|
||||||
if (!timezone || typeof timezone.value !== 'string') {
|
if (!timezone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({ timezone: timezone.value });
|
this.setState({ timezone: timezone });
|
||||||
};
|
};
|
||||||
|
|
||||||
onHomeDashboardChanged = (dashboardId: number) => {
|
onHomeDashboardChanged = (dashboardId: number) => {
|
||||||
@ -168,12 +157,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Timezone" aria-label={selectors.components.TimeZonePicker.container}>
|
<Field label="Timezone" aria-label={selectors.components.TimeZonePicker.container}>
|
||||||
<Select
|
<TimeZonePicker value={timezone} onChange={this.onTimeZoneChanged} />
|
||||||
isSearchable={true}
|
|
||||||
value={timeZones.find(item => item.value === timezone)}
|
|
||||||
onChange={this.onTimeZoneChanged}
|
|
||||||
options={timeZones}
|
|
||||||
/>
|
|
||||||
</Field>
|
</Field>
|
||||||
<div className="gf-form-button-row">
|
<div className="gf-form-button-row">
|
||||||
<Button variant="primary">Save</Button>
|
<Button variant="primary">Save</Button>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Libaries
|
// Libaries
|
||||||
import React, { PureComponent, FC, ReactNode } from 'react';
|
import React, { PureComponent, FC, ReactNode } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect, MapDispatchToProps } from 'react-redux';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { appEvents } from 'app/core/app_events';
|
import { appEvents } from 'app/core/app_events';
|
||||||
@ -13,6 +13,7 @@ import { textUtil } from '@grafana/data';
|
|||||||
import { BackButton } from 'app/core/components/BackButton/BackButton';
|
import { BackButton } from 'app/core/components/BackButton/BackButton';
|
||||||
// State
|
// State
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
|
||||||
// Types
|
// Types
|
||||||
import { DashboardModel } from '../../state';
|
import { DashboardModel } from '../../state';
|
||||||
import { CoreEvents, StoreState } from 'app/types';
|
import { CoreEvents, StoreState } from 'app/types';
|
||||||
@ -23,10 +24,14 @@ export interface OwnProps {
|
|||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
$injector: any;
|
$injector: any;
|
||||||
updateLocation: typeof updateLocation;
|
|
||||||
onAddPanel: () => void;
|
onAddPanel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
updateTimeZoneForSession: typeof updateTimeZoneForSession;
|
||||||
|
updateLocation: typeof updateLocation;
|
||||||
|
}
|
||||||
|
|
||||||
interface DashNavButtonModel {
|
interface DashNavButtonModel {
|
||||||
show: (props: Props) => boolean;
|
show: (props: Props) => boolean;
|
||||||
component: FC<Partial<Props>>;
|
component: FC<Partial<Props>>;
|
||||||
@ -48,7 +53,7 @@ export interface StateProps {
|
|||||||
location: any;
|
location: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = StateProps & OwnProps;
|
type Props = StateProps & OwnProps & DispatchProps;
|
||||||
|
|
||||||
class DashNav extends PureComponent<Props> {
|
class DashNav extends PureComponent<Props> {
|
||||||
playlistSrv: PlaylistSrv;
|
playlistSrv: PlaylistSrv;
|
||||||
@ -277,7 +282,7 @@ class DashNav extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, location, isFullscreen } = this.props;
|
const { dashboard, location, isFullscreen, updateTimeZoneForSession } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="navbar">
|
<div className="navbar">
|
||||||
@ -315,7 +320,11 @@ class DashNav extends PureComponent<Props> {
|
|||||||
|
|
||||||
{!dashboard.timepicker.hidden && (
|
{!dashboard.timepicker.hidden && (
|
||||||
<div className="navbar-buttons">
|
<div className="navbar-buttons">
|
||||||
<DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} />
|
<DashNavTimeControls
|
||||||
|
dashboard={dashboard}
|
||||||
|
location={location}
|
||||||
|
onChangeTimeZone={updateTimeZoneForSession}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -327,8 +336,9 @@ const mapStateToProps = (state: StoreState) => ({
|
|||||||
location: state.location,
|
location: state.location,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||||
updateLocation,
|
updateLocation,
|
||||||
|
updateTimeZoneForSession,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(DashNav);
|
export default connect(mapStateToProps, mapDispatchToProps)(DashNav);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Libaries
|
// Libaries
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { dateMath, GrafanaTheme } from '@grafana/data';
|
import { dateMath, GrafanaTheme, TimeZone } from '@grafana/data';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
@ -9,7 +9,7 @@ import { LocationState, CoreEvents } from 'app/types';
|
|||||||
import { TimeRange } from '@grafana/data';
|
import { TimeRange } from '@grafana/data';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { RefreshPicker, withTheme, stylesFactory, Themeable } from '@grafana/ui';
|
import { RefreshPicker, withTheme, stylesFactory, Themeable } from '@grafana/ui';
|
||||||
@ -31,8 +31,8 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
updateLocation: typeof updateLocation;
|
|
||||||
location: LocationState;
|
location: LocationState;
|
||||||
|
onChangeTimeZone: typeof updateTimeZoneForSession;
|
||||||
}
|
}
|
||||||
class UnthemedDashNavTimeControls extends Component<Props> {
|
class UnthemedDashNavTimeControls extends Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -87,6 +87,12 @@ class UnthemedDashNavTimeControls extends Component<Props> {
|
|||||||
getTimeSrv().setTime(nextRange);
|
getTimeSrv().setTime(nextRange);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onChangeTimeZone = (timeZone: TimeZone) => {
|
||||||
|
this.props.dashboard.timezone = timeZone;
|
||||||
|
this.props.onChangeTimeZone(timeZone);
|
||||||
|
this.onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
onZoom = () => {
|
onZoom = () => {
|
||||||
appEvents.emit(CoreEvents.zoomOut, 2);
|
appEvents.emit(CoreEvents.zoomOut, 2);
|
||||||
};
|
};
|
||||||
@ -109,6 +115,7 @@ class UnthemedDashNavTimeControls extends Component<Props> {
|
|||||||
onMoveBackward={this.onMoveBack}
|
onMoveBackward={this.onMoveBack}
|
||||||
onMoveForward={this.onMoveForward}
|
onMoveForward={this.onMoveForward}
|
||||||
onZoom={this.onZoom}
|
onZoom={this.onZoom}
|
||||||
|
onChangeTimeZone={this.onChangeTimeZone}
|
||||||
/>
|
/>
|
||||||
<RefreshPicker
|
<RefreshPicker
|
||||||
onIntervalChanged={this.onChangeRefreshInterval}
|
onIntervalChanged={this.onChangeRefreshInterval}
|
||||||
|
@ -1,24 +1,12 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Select, Input, Tooltip, LegacyForms } from '@grafana/ui';
|
import { TimeZonePicker, Input, Tooltip, LegacyForms } from '@grafana/ui';
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
import { DashboardModel } from '../../state/DashboardModel';
|
||||||
import { getTimeZoneGroups, TimeZone, rangeUtil, SelectableValue } from '@grafana/data';
|
import { TimeZone, rangeUtil } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
const grafanaTimeZones = [
|
|
||||||
{ value: '', label: 'Default' },
|
|
||||||
{ value: 'browser', label: 'Local browser time' },
|
|
||||||
{ value: 'utc', label: 'UTC' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const timeZones = getTimeZoneGroups().reduce((tzs, group) => {
|
|
||||||
const options = group.options.map(tz => ({ value: tz, label: tz }));
|
|
||||||
tzs.push.apply(tzs, options);
|
|
||||||
return tzs;
|
|
||||||
}, grafanaTimeZones);
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
getDashboard: () => DashboardModel;
|
getDashboard: () => DashboardModel;
|
||||||
onTimeZoneChange: (timeZone: TimeZone) => void;
|
onTimeZoneChange: (timeZone: TimeZone) => void;
|
||||||
@ -97,17 +85,16 @@ export class TimePickerSettings extends PureComponent<Props, State> {
|
|||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
onTimeZoneChange = (timeZone: SelectableValue<string>) => {
|
onTimeZoneChange = (timeZone: string) => {
|
||||||
if (!timeZone || typeof timeZone.value !== 'string') {
|
if (typeof timeZone !== 'string') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.props.onTimeZoneChange(timeZone.value);
|
this.props.onTimeZoneChange(timeZone);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const dashboard = this.props.getDashboard();
|
const dashboard = this.props.getDashboard();
|
||||||
const value = timeZones.find(item => item.value === dashboard.timezone);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="editor-row">
|
<div className="editor-row">
|
||||||
@ -115,7 +102,7 @@ export class TimePickerSettings extends PureComponent<Props, State> {
|
|||||||
<div className="gf-form-group">
|
<div className="gf-form-group">
|
||||||
<div className="gf-form" aria-label={selectors.components.TimeZonePicker.container}>
|
<div className="gf-form" aria-label={selectors.components.TimeZonePicker.container}>
|
||||||
<label className="gf-form-label width-7">Timezone</label>
|
<label className="gf-form-label width-7">Timezone</label>
|
||||||
<Select isSearchable={true} value={value} onChange={this.onTimeZoneChange} options={timeZones} width={40} />
|
<TimeZonePicker value={dashboard.timezone} onChange={this.onTimeZoneChange} width={40} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
|
@ -24,6 +24,7 @@ import { PanelEditorUIState, setDiscardChanges } from './state/reducers';
|
|||||||
import { getPanelEditorTabs } from './state/selectors';
|
import { getPanelEditorTabs } from './state/selectors';
|
||||||
import { getPanelStateById } from '../../state/selectors';
|
import { getPanelStateById } from '../../state/selectors';
|
||||||
import { OptionsPaneContent } from './OptionsPaneContent';
|
import { OptionsPaneContent } from './OptionsPaneContent';
|
||||||
|
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
|
||||||
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
|
import { DashNavButton } from 'app/features/dashboard/components/DashNav/DashNavButton';
|
||||||
import { VariableModel } from 'app/features/variables/types';
|
import { VariableModel } from 'app/features/variables/types';
|
||||||
import { getVariables } from 'app/features/variables/state/selectors';
|
import { getVariables } from 'app/features/variables/state/selectors';
|
||||||
@ -54,6 +55,7 @@ interface DispatchProps {
|
|||||||
panelEditorCleanUp: typeof panelEditorCleanUp;
|
panelEditorCleanUp: typeof panelEditorCleanUp;
|
||||||
setDiscardChanges: typeof setDiscardChanges;
|
setDiscardChanges: typeof setDiscardChanges;
|
||||||
updatePanelEditorUIState: typeof updatePanelEditorUIState;
|
updatePanelEditorUIState: typeof updatePanelEditorUIState;
|
||||||
|
updateTimeZoneForSession: typeof updateTimeZoneForSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OwnProps & ConnectedProps & DispatchProps;
|
type Props = OwnProps & ConnectedProps & DispatchProps;
|
||||||
@ -220,7 +222,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPanelToolbar(styles: EditorStyles) {
|
renderPanelToolbar(styles: EditorStyles) {
|
||||||
const { dashboard, location, uiState, variables } = this.props;
|
const { dashboard, location, uiState, variables, updateTimeZoneForSession } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={styles.panelToolbar}>
|
<div className={styles.panelToolbar}>
|
||||||
<HorizontalGroup justify={variables.length > 0 ? 'space-between' : 'flex-end'} align="flex-start">
|
<HorizontalGroup justify={variables.length > 0 ? 'space-between' : 'flex-end'} align="flex-start">
|
||||||
@ -228,7 +230,11 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
|
|||||||
|
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<RadioButtonGroup value={uiState.mode} options={displayModes} onChange={this.onDiplayModeChange} />
|
<RadioButtonGroup value={uiState.mode} options={displayModes} onChange={this.onDiplayModeChange} />
|
||||||
<DashNavTimeControls dashboard={dashboard} location={location} updateLocation={updateLocation} />
|
<DashNavTimeControls
|
||||||
|
dashboard={dashboard}
|
||||||
|
location={location}
|
||||||
|
onChangeTimeZone={updateTimeZoneForSession}
|
||||||
|
/>
|
||||||
{!uiState.isPanelOptionsVisible && (
|
{!uiState.isPanelOptionsVisible && (
|
||||||
<DashNavButton
|
<DashNavButton
|
||||||
onClick={this.onTogglePanelOptions}
|
onClick={this.onTogglePanelOptions}
|
||||||
@ -362,6 +368,7 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
|||||||
panelEditorCleanUp,
|
panelEditorCleanUp,
|
||||||
setDiscardChanges,
|
setDiscardChanges,
|
||||||
updatePanelEditorUIState,
|
updatePanelEditorUIState,
|
||||||
|
updateTimeZoneForSession,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected);
|
export const PanelEditor = connect(mapStateToProps, mapDispatchToProps)(PanelEditorUnconnected);
|
||||||
|
@ -112,6 +112,7 @@ export class ChangeTracker {
|
|||||||
dash.time = 0;
|
dash.time = 0;
|
||||||
dash.refresh = 0;
|
dash.refresh = 0;
|
||||||
dash.schemaVersion = 0;
|
dash.schemaVersion = 0;
|
||||||
|
dash.timezone = 0;
|
||||||
|
|
||||||
// ignore iteration property
|
// ignore iteration property
|
||||||
delete dash.iteration;
|
delete dash.iteration;
|
||||||
|
@ -23,6 +23,7 @@ export interface Props {
|
|||||||
syncedTimes: boolean;
|
syncedTimes: boolean;
|
||||||
onChangeTimeSync: () => void;
|
onChangeTimeSync: () => void;
|
||||||
onChangeTime: (range: RawTimeRange) => void;
|
onChangeTime: (range: RawTimeRange) => void;
|
||||||
|
onChangeTimeZone: (timeZone: TimeZone) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExploreTimeControls extends Component<Props> {
|
export class ExploreTimeControls extends Component<Props> {
|
||||||
@ -56,7 +57,7 @@ export class ExploreTimeControls extends Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { range, timeZone, splitted, syncedTimes, onChangeTimeSync, hideText } = this.props;
|
const { range, timeZone, splitted, syncedTimes, onChangeTimeSync, hideText, onChangeTimeZone } = this.props;
|
||||||
const timeSyncButton = splitted ? <TimeSyncButton onClick={onChangeTimeSync} isSynced={syncedTimes} /> : undefined;
|
const timeSyncButton = splitted ? <TimeSyncButton onClick={onChangeTimeSync} isSynced={syncedTimes} /> : undefined;
|
||||||
const timePickerCommonProps = {
|
const timePickerCommonProps = {
|
||||||
value: range,
|
value: range,
|
||||||
@ -73,6 +74,7 @@ export class ExploreTimeControls extends Component<Props> {
|
|||||||
timeSyncButton={timeSyncButton}
|
timeSyncButton={timeSyncButton}
|
||||||
isSynced={syncedTimes}
|
isSynced={syncedTimes}
|
||||||
onChange={this.onChangeTimePicker}
|
onChange={this.onChangeTimePicker}
|
||||||
|
onChangeTimeZone={onChangeTimeZone}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ function createToolbar(supportedModes: ExploreMode[]) {
|
|||||||
setDashboardQueriesToUpdateOnLoad={(() => {}) as any}
|
setDashboardQueriesToUpdateOnLoad={(() => {}) as any}
|
||||||
exploreId={ExploreId.left}
|
exploreId={ExploreId.left}
|
||||||
onChangeTime={(() => {}) as any}
|
onChangeTime={(() => {}) as any}
|
||||||
|
onChangeTimeZone={(() => {}) as any}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
} from './state/actions';
|
} from './state/actions';
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
import { getTimeZone } from '../profile/state/selectors';
|
import { getTimeZone } from '../profile/state/selectors';
|
||||||
|
import { updateTimeZoneForSession } from '../profile/state/reducers';
|
||||||
import { getDashboardSrv } from '../dashboard/services/DashboardSrv';
|
import { getDashboardSrv } from '../dashboard/services/DashboardSrv';
|
||||||
import kbn from '../../core/utils/kbn';
|
import kbn from '../../core/utils/kbn';
|
||||||
import { ExploreTimeControls } from './ExploreTimeControls';
|
import { ExploreTimeControls } from './ExploreTimeControls';
|
||||||
@ -83,6 +84,7 @@ interface DispatchProps {
|
|||||||
changeMode: typeof changeMode;
|
changeMode: typeof changeMode;
|
||||||
updateLocation: typeof updateLocation;
|
updateLocation: typeof updateLocation;
|
||||||
setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad;
|
setDashboardQueriesToUpdateOnLoad: typeof setDashboardQueriesToUpdateOnLoad;
|
||||||
|
onChangeTimeZone: typeof updateTimeZoneForSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = StateProps & DispatchProps & OwnProps;
|
type Props = StateProps & DispatchProps & OwnProps;
|
||||||
@ -180,6 +182,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
|||||||
originPanelId,
|
originPanelId,
|
||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
|
onChangeTimeZone,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const styles = getStyles();
|
const styles = getStyles();
|
||||||
@ -303,6 +306,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
|
|||||||
syncedTimes={syncedTimes}
|
syncedTimes={syncedTimes}
|
||||||
onChangeTimeSync={this.onChangeTimeSync}
|
onChangeTimeSync={this.onChangeTimeSync}
|
||||||
hideText={showSmallTimePicker}
|
hideText={showSmallTimePicker}
|
||||||
|
onChangeTimeZone={onChangeTimeZone}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -410,6 +414,7 @@ const mapDispatchToProps: DispatchProps = {
|
|||||||
syncTimes,
|
syncTimes,
|
||||||
changeMode: changeMode,
|
changeMode: changeMode,
|
||||||
setDashboardQueriesToUpdateOnLoad,
|
setDashboardQueriesToUpdateOnLoad,
|
||||||
|
onChangeTimeZone: updateTimeZoneForSession,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExploreToolbar = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedExploreToolbar));
|
export const ExploreToolbar = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedExploreToolbar));
|
||||||
|
@ -1,15 +1,40 @@
|
|||||||
import { UserState } from 'app/types';
|
import _ from 'lodash';
|
||||||
|
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { UserState, ThunkResult } from 'app/types';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
import { TimeZone } from '@grafana/data';
|
||||||
|
import { contextSrv } from 'app/core/core';
|
||||||
|
|
||||||
export const initialState: UserState = {
|
export const initialState: UserState = {
|
||||||
orgId: config.bootData.user.orgId,
|
orgId: config.bootData.user.orgId,
|
||||||
timeZone: config.bootData.user.timezone,
|
timeZone: config.bootData.user.timezone,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userReducer = (state = initialState, action: any): UserState => {
|
export const slice = createSlice({
|
||||||
return state;
|
name: 'user/profile',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
updateTimeZone: (state, action: PayloadAction<TimeZone>): UserState => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
timeZone: action.payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateTimeZoneForSession = (timeZone: TimeZone): ThunkResult<void> => {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const { updateTimeZone } = slice.actions;
|
||||||
|
|
||||||
|
if (!_.isString(timeZone) || _.isEmpty(timeZone)) {
|
||||||
|
timeZone = config?.bootData?.user?.timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.set(contextSrv, 'user.timezone', timeZone);
|
||||||
|
dispatch(updateTimeZone(timeZone));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export const userReducer = slice.reducer;
|
||||||
user: userReducer,
|
export default { user: slice.reducer };
|
||||||
};
|
|
||||||
|
Loading…
Reference in New Issue
Block a user