Skip to content

Commit cdee24d

Browse files
CyanCyan
authored andcommitted
Merge branch 'feat/support-grafana-timezone-settings' into dev-baseV1.0.4
2 parents 3b999cc + 0a32e5d commit cdee24d

3 files changed

Lines changed: 42 additions & 63 deletions

File tree

provisioning/dashboards/dashboard.json

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,52 +41,29 @@
4141
},
4242
"targets": [
4343
{
44-
"csvWave": [
45-
{
46-
"timeStep": 78000,
47-
"valuesCSV": "0,0,2,2,1,1"
48-
}
49-
],
5044
"datasource": {
5145
"type": "grafana-testdata-datasource",
52-
"uid": "db84e60d-b92a-4089-82cb-34842fb1754b"
46+
"uid": "trlxrdZVk"
5347
},
5448
"refId": "A",
55-
"scenarioId": "predictable_csv_wave"
49+
"scenarioId": "csv_content",
50+
"csvContent": "time,value\n2026-02-28T00:30:00Z,1\n2026-02-28T16:30:00Z,2\n2026-02-28T23:30:00Z,3\n2026-02-27T00:30:00Z,4\n2026-02-27T16:30:00Z,5\n2026-02-27T23:30:00Z,6\n2026-02-26T00:30:00Z,7\n2026-02-26T16:30:00Z,8\n2026-02-26T23:30:00Z,9"
5651
}
5752
],
58-
"title": "Sample Panel Title",
59-
"type": "tim012432-calendarheatmap-panel"
60-
},
61-
{
62-
"datasource": {
63-
"type": "grafana-testdata-datasource",
64-
"uid": "trlxrdZVk"
65-
},
66-
"gridPos": {
67-
"h": 8,
68-
"w": 12,
69-
"x": 12,
70-
"y": 0
71-
},
72-
"id": 2,
73-
"options": {
74-
"seriesCountSize": "sm",
75-
"showSeriesCount": false,
76-
"text": "Default value of text input option"
77-
},
78-
"targets": [
53+
"title": "Calendar Heatmap Panel (Timezone test)",
54+
"transformations": [
7955
{
80-
"alias": "",
81-
"datasource": {
82-
"type": "grafana-testdata-datasource",
83-
"uid": "db84e60d-b92a-4089-82cb-34842fb1754b"
84-
},
85-
"refId": "A",
86-
"scenarioId": "no_data_points"
56+
"id": "convertFieldType",
57+
"options": {
58+
"conversions": [
59+
{
60+
"destinationType": "time",
61+
"targetField": "time"
62+
}
63+
]
64+
}
8765
}
8866
],
89-
"title": "Sample Panel Title",
9067
"type": "tim012432-calendarheatmap-panel"
9168
}
9269
],
@@ -97,13 +74,13 @@
9774
"list": []
9875
},
9976
"time": {
100-
"from": "now-6h",
101-
"to": "now"
77+
"from": "2026-02-25T00:00:00Z",
78+
"to": "2026-03-02T00:00:00Z"
10279
},
10380
"timepicker": {},
10481
"timezone": "",
105-
"title": "Provisioned calendar-heatmap-panel dashboard",
82+
"title": "Provisioned calendar-heatmap-panel dashboard (Timezone test)",
10683
"uid": "a538aeff-5a8a-42a5-901c-938d896fdd6f",
107-
"version": 1,
84+
"version": 2,
10885
"weekStart": ""
109-
}
86+
}

src/components/CalendarHeatmapPanel.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ function parseAnyYMD(dateStr: string): Date | null {
2929
return new Date(y, m - 1, d);
3030
}
3131

32-
/** Must match dataProcessor.ts formatDate(): YYYY/MM/DD */
3332
function toKey(date: Date): string {
3433
const year = date.getFullYear();
3534
const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -45,21 +44,25 @@ function splitCsv(input: string): string[] {
4544
}
4645

4746
function rotateWeek(labelsSunFirst: string[], weekStart: 'sunday' | 'monday'): string[] {
48-
// Input is always Sun..Sat
4947
if (labelsSunFirst.length !== 7) {
5048
return labelsSunFirst;
5149
}
52-
return weekStart === 'monday'
53-
? [...labelsSunFirst.slice(1), labelsSunFirst[0]] // Mon..Sat + Sun
54-
: labelsSunFirst; // Sun..Sat
50+
return weekStart === 'monday' ? [...labelsSunFirst.slice(1), labelsSunFirst[0]] : labelsSunFirst;
5551
}
5652

57-
export const CalendarHeatmapPanel: React.FC<Props> = ({ data, width, height, options, timeRange }) => {
53+
export const CalendarHeatmapPanel: React.FC<Props> = ({
54+
data,
55+
width,
56+
height,
57+
options,
58+
timeRange,
59+
timeZone,
60+
}) => {
5861
const theme = useTheme2();
5962

6063
const heatmapData = useMemo(() => {
61-
return processTimeSeriesData(data.series, options.aggregation);
62-
}, [data.series, options.aggregation]);
64+
return processTimeSeriesData(data.series, options.aggregation, timeZone);
65+
}, [data.series, options.aggregation, timeZone]);
6366

6467
const countByOriginalDate = useMemo(() => {
6568
const m = new Map<string, number>();
@@ -71,7 +74,6 @@ export const CalendarHeatmapPanel: React.FC<Props> = ({ data, width, height, opt
7174

7275
const rawStartDate = useMemo(() => new Date(timeRange.from.valueOf()), [timeRange.from]);
7376
const rawEndDate = useMemo(() => new Date(timeRange.to.valueOf()), [timeRange.to]);
74-
7577
const availableWidth = useMemo(() => Math.max(0, width - 32), [width]);
7678

7779
// Monday-first: shift render dates by -1 day to rotate weekday rows so Sunday becomes last

src/utils/dataProcessor.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { DataFrame, FieldType, GrafanaTheme2 } from '@grafana/data';
1+
import { DataFrame, FieldType, GrafanaTheme2, dateTime, dateTimeFormat } from '@grafana/data';
22
import { HeatmapValue } from '../types';
33

44
type Aggregation = 'sum' | 'count' | 'avg' | 'max' | 'min';
55

6-
export function processTimeSeriesData(series: DataFrame[], aggregation: Aggregation): HeatmapValue[] {
6+
export function processTimeSeriesData(
7+
series: DataFrame[],
8+
aggregation: Aggregation,
9+
timeZone?: string
10+
): HeatmapValue[] {
711
const dailyData = new Map<string, number[]>();
812

913
// Iterate through all data frames
@@ -15,7 +19,7 @@ export function processTimeSeriesData(series: DataFrame[], aggregation: Aggregat
1519
continue;
1620
}
1721

18-
// Group values by date
22+
// Group values by date (using Grafana timezone passed from panel props)
1923
for (let i = 0; i < frame.length; i++) {
2024
const timestamp = timeField.values[i];
2125
const value = valueField.values[i];
@@ -24,7 +28,11 @@ export function processTimeSeriesData(series: DataFrame[], aggregation: Aggregat
2428
continue;
2529
}
2630

27-
const date = formatDate(new Date(timestamp));
31+
// IMPORTANT: pass Grafana's timezone so day-bucketing follows dashboard/user settings
32+
const date = dateTimeFormat(dateTime(timestamp), {
33+
format: 'YYYY/MM/DD',
34+
timeZone,
35+
});
2836

2937
if (!dailyData.has(date)) {
3038
dailyData.set(date, []);
@@ -68,13 +76,6 @@ function aggregate(values: number[], method: Aggregation): number {
6876
}
6977
}
7078

71-
function formatDate(date: Date): string {
72-
const year = date.getFullYear();
73-
const month = String(date.getMonth() + 1).padStart(2, '0');
74-
const day = String(date.getDate()).padStart(2, '0');
75-
return `${year}/${month}/${day}`;
76-
}
77-
7879
// --------------------
7980
// Custom color helpers
8081
// --------------------
@@ -146,7 +147,6 @@ function buildCustomLevels(base: RGB, theme: GrafanaTheme2): string[] {
146147
rgbToCss(mix(base, black, 0.35)),
147148
];
148149

149-
// Keep behavior aligned with existing logic:
150150
// reverse in dark mode for perceived contrast
151151
return theme.isDark ? [...levels].reverse() : levels;
152152
}

0 commit comments

Comments
 (0)