1import { e2e } from '@grafana/e2e'; 2import { 3 addDays, 4 addHours, 5 differenceInCalendarDays, 6 differenceInMinutes, 7 format, 8 isBefore, 9 parseISO, 10 toDate, 11} from 'date-fns'; 12 13e2e.scenario({ 14 describeName: 'Dashboard time zone support', 15 itName: 'Tests dashboard time zone scenarios', 16 addScenarioDataSource: false, 17 addScenarioDashBoard: false, 18 skipScenario: false, 19 scenario: () => { 20 e2e.flows.openDashboard({ uid: '5SdHCasdf' }); 21 22 const fromTimeZone = 'UTC'; 23 const toTimeZone = 'America/Chicago'; 24 const offset = offsetBetweenTimeZones(toTimeZone, fromTimeZone); 25 26 const panelsToCheck = [ 27 'Random walk series', 28 'Millisecond res x-axis and tooltip', 29 '2 yaxis and axis labels', 30 'Stacking value ontop of nulls', 31 'Null between points', 32 'Legend Table No Scroll Visible', 33 ]; 34 35 const timesInUtc: Record<string, string> = {}; 36 37 for (const title of panelsToCheck) { 38 e2e.components.Panels.Panel.containerByTitle(title) 39 .should('be.visible') 40 .within(() => 41 e2e.components.Panels.Visualization.Graph.xAxis 42 .labels() 43 .should('be.visible') 44 .last() 45 .should((element) => { 46 timesInUtc[title] = element.text(); 47 }) 48 ); 49 } 50 51 e2e.components.PageToolbar.item('Dashboard settings').click(); 52 53 e2e.components.TimeZonePicker.containerV2() 54 .should('be.visible') 55 .within(() => { 56 e2e.components.Select.singleValue().should('be.visible').should('have.text', 'Coordinated Universal Time'); 57 e2e.components.Select.input().should('be.visible').click(); 58 }); 59 60 e2e.components.Select.option().should('be.visible').contains(toTimeZone).click(); 61 62 // click to go back to the dashboard. 63 e2e.components.BackButton.backArrow().click({ force: true }).wait(2000); 64 65 for (const title of panelsToCheck) { 66 e2e.components.Panels.Panel.containerByTitle(title) 67 .should('be.visible') 68 .within(() => 69 e2e.components.Panels.Visualization.Graph.xAxis 70 .labels() 71 .should('be.visible') 72 .last() 73 .should((element) => { 74 const inUtc = timesInUtc[title]; 75 const inTz = element.text(); 76 const isCorrect = isTimeCorrect(inUtc, inTz, offset); 77 assert.isTrue(isCorrect, `Panel with title: "${title}"`); 78 }) 79 ); 80 } 81 }, 82}); 83 84const isTimeCorrect = (inUtc: string, inTz: string, offset: number): boolean => { 85 if (inUtc === inTz) { 86 // we need to catch issues when timezone isn't changed for some reason like https://github.com/grafana/grafana/issues/35504 87 return false; 88 } 89 90 const reference = format(new Date(), 'yyyy-LL-dd'); 91 92 const utcDate = toDate(parseISO(`${reference} ${inUtc}`)); 93 const utcDateWithOffset = addHours(toDate(parseISO(`${reference} ${inUtc}`)), offset); 94 const dayDifference = differenceInCalendarDays(utcDate, utcDateWithOffset); // if the utcDate +/- offset is the day before/after then we need to adjust reference 95 const dayOffset = isBefore(utcDateWithOffset, utcDate) ? dayDifference * -1 : dayDifference; 96 const tzDate = addDays(toDate(parseISO(`${reference} ${inTz}`)), dayOffset); // adjust tzDate with any dayOffset 97 const diff = Math.abs(differenceInMinutes(utcDate, tzDate)); // use Math.abs if tzDate is in future 98 99 return diff <= Math.abs(offset * 60); 100}; 101 102const offsetBetweenTimeZones = (timeZone1: string, timeZone2: string, when: Date = new Date()): number => { 103 const t1 = convertDateToAnotherTimeZone(when, timeZone1); 104 const t2 = convertDateToAnotherTimeZone(when, timeZone2); 105 return (t1.getTime() - t2.getTime()) / (1000 * 60 * 60); 106}; 107 108const convertDateToAnotherTimeZone = (date: Date, timeZone: string): Date => { 109 const dateString = date.toLocaleString('en-US', { 110 timeZone: timeZone, 111 }); 112 return new Date(dateString); 113}; 114