Skip to content

Commit 4857e40

Browse files
authored
Fix date parsing bug with grafana report updates (#2261)
* fix issue converting date formats during report updates * fix reporting acceptance terst
1 parent 9163296 commit 4857e40

File tree

2 files changed

+213
-8
lines changed

2 files changed

+213
-8
lines changed

internal/resources/grafana/resource_report.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -592,25 +592,24 @@ func validateDate(i interface{}, _ cty.Path) diag.Diagnostics {
592592
func formatDate(date string, timezone *time.Location) (*strfmt.DateTime, error) {
593593
parsedDate, err := time.Parse(timeDateShortFormat, date)
594594
if err != nil {
595-
return checkTimezoneFormatDate(date, timezone)
595+
return CheckTimezoneFormatDate(date, timezone)
596596
}
597597

598598
dateTime := strfmt.DateTime(parsedDate.In(timezone))
599599
return &dateTime, nil
600600
}
601601

602-
func checkTimezoneFormatDate(date string, timezone *time.Location) (*strfmt.DateTime, error) {
602+
// CheckTimezoneFormatDate is exported for testing purposes
603+
func CheckTimezoneFormatDate(date string, timezone *time.Location) (*strfmt.DateTime, error) {
603604
parsedDate, err := time.Parse(time.RFC3339, date)
604605
if err != nil {
605606
return nil, err
606607
}
607608

608-
// Fail if timezone isn't GMT. GMT is skipped to avoid to break old implementations.
609-
if timezone.String() != "GMT" {
610-
return nil, fmt.Errorf("date formatted with timezone isn't compatible: %s. Please, remove timezone from the date if you want to set a timezone", date)
611-
}
612-
613-
dateTime := strfmt.DateTime(parsedDate.UTC())
609+
// If the date is already in RFC3339 format (contains timezone info),
610+
// just convert it to the target timezone instead of rejecting it.
611+
// This handles the case where dates from the API (in state) are being re-processed.
612+
dateTime := strfmt.DateTime(parsedDate.In(timezone))
614613
return &dateTime, nil
615614
}
616615

internal/resources/grafana/resource_report_test.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,114 @@ import (
44
"fmt"
55
"strconv"
66
"testing"
7+
"time"
78

89
"github.com/grafana/grafana-openapi-client-go/models"
10+
"github.com/grafana/terraform-provider-grafana/v3/internal/resources/grafana"
911
"github.com/grafana/terraform-provider-grafana/v3/internal/testutils"
1012
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
1113
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1214
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
1315
)
1416

17+
func TestCheckTimezoneFormatDate(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
date string
21+
timezone string
22+
shouldError bool
23+
expectedTime string // Expected time in the target timezone
24+
}{
25+
{
26+
name: "UTC to America/New_York",
27+
date: "2024-01-15T15:00:00Z",
28+
timezone: "America/New_York",
29+
shouldError: false,
30+
expectedTime: "2024-01-15T10:00:00-05:00", // EST offset
31+
},
32+
{
33+
name: "UTC to UTC",
34+
date: "2024-01-15T15:00:00Z",
35+
timezone: "UTC",
36+
shouldError: false,
37+
expectedTime: "2024-01-15T15:00:00Z",
38+
},
39+
{
40+
name: "America/New_York to UTC",
41+
date: "2024-01-15T10:00:00-05:00",
42+
timezone: "UTC",
43+
shouldError: false,
44+
expectedTime: "2024-01-15T15:00:00Z",
45+
},
46+
{
47+
name: "America/New_York to Europe/London",
48+
date: "2024-01-15T10:00:00-05:00",
49+
timezone: "Europe/London",
50+
shouldError: false,
51+
expectedTime: "2024-01-15T15:00:00Z", // London is UTC in January
52+
},
53+
{
54+
name: "Invalid RFC3339 date",
55+
date: "invalid-date",
56+
timezone: "UTC",
57+
shouldError: true,
58+
},
59+
{
60+
name: "Invalid timezone",
61+
date: "2024-01-15T15:00:00Z",
62+
timezone: "Invalid/Timezone",
63+
shouldError: true,
64+
},
65+
}
66+
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
// Load the target timezone
70+
tz, err := time.LoadLocation(tt.timezone)
71+
if err != nil && !tt.shouldError {
72+
t.Fatalf("Failed to load timezone %s: %v", tt.timezone, err)
73+
}
74+
if err != nil && tt.shouldError {
75+
return // Expected error for invalid timezone
76+
}
77+
78+
// Call the exported function for testing
79+
result, err := grafana.CheckTimezoneFormatDate(tt.date, tz)
80+
81+
if tt.shouldError {
82+
if err == nil {
83+
t.Errorf("Expected error but got none")
84+
}
85+
return
86+
}
87+
88+
if err != nil {
89+
t.Errorf("Unexpected error: %v", err)
90+
return
91+
}
92+
93+
if result == nil {
94+
t.Errorf("Expected result but got nil")
95+
return
96+
}
97+
98+
// Parse the expected time for comparison
99+
expectedTime, err := time.Parse(time.RFC3339, tt.expectedTime)
100+
if err != nil {
101+
t.Fatalf("Failed to parse expected time: %v", err)
102+
}
103+
104+
// Convert result back to time for comparison
105+
resultTime := time.Time(*result)
106+
107+
// Compare times (they should represent the same instant)
108+
if !resultTime.Equal(expectedTime) {
109+
t.Errorf("Expected %s, got %s", expectedTime.Format(time.RFC3339), resultTime.Format(time.RFC3339))
110+
}
111+
})
112+
}
113+
}
114+
15115
func TestAccResourceReport_Multiple_Dashboards(t *testing.T) {
16116
testutils.CheckEnterpriseTestsEnabled(t, ">=9.0.0")
17117

@@ -208,3 +308,109 @@ resource "grafana_report" "test" {
208308
}
209309
}`, name)
210310
}
311+
312+
func TestAccResourceReport_DashboardUIDChange_WithTimezone(t *testing.T) {
313+
testutils.CheckEnterpriseTestsEnabled(t, ">=9.0.0")
314+
315+
var report models.Report
316+
var randomUID1 = acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
317+
var randomUID2 = acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
318+
319+
resource.ParallelTest(t, resource.TestCase{
320+
ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories,
321+
CheckDestroy: reportCheckExists.destroyed(&report, nil),
322+
Steps: []resource.TestStep{
323+
{
324+
// Create report with non-GMT timezone and explicit start/end times
325+
Config: testAccReportWithTimezoneStep1(randomUID1, randomUID2),
326+
Check: resource.ComposeTestCheckFunc(
327+
reportCheckExists.exists("grafana_report.test", &report),
328+
resource.TestCheckResourceAttr("grafana_report.test", "name", "timezone test report"),
329+
resource.TestCheckResourceAttr("grafana_report.test", "schedule.0.timezone", "America/New_York"),
330+
resource.TestCheckResourceAttr("grafana_report.test", "dashboards.0.uid", randomUID1),
331+
),
332+
},
333+
{
334+
// Update dashboard UID - this was triggering the timezone error before the fix
335+
Config: testAccReportWithTimezoneStep2(randomUID1, randomUID2),
336+
Check: resource.ComposeTestCheckFunc(
337+
reportCheckExists.exists("grafana_report.test", &report),
338+
resource.TestCheckResourceAttr("grafana_report.test", "name", "timezone test report"),
339+
resource.TestCheckResourceAttr("grafana_report.test", "schedule.0.timezone", "America/New_York"),
340+
// Dashboard UID should be updated
341+
resource.TestCheckResourceAttr("grafana_report.test", "dashboards.0.uid", randomUID2),
342+
),
343+
},
344+
},
345+
})
346+
}
347+
348+
func testAccReportWithTimezoneStep1(dashboardUID1, dashboardUID2 string) string {
349+
return fmt.Sprintf(`
350+
resource "grafana_dashboard" "test1" {
351+
config_json = <<EOD
352+
{
353+
"title": "Test Dashboard %[1]s",
354+
"uid": "%[1]s"
355+
}
356+
EOD
357+
}
358+
359+
resource "grafana_dashboard" "test2" {
360+
config_json = <<EOD
361+
{
362+
"title": "Test Dashboard %[2]s",
363+
"uid": "%[2]s"
364+
}
365+
EOD
366+
}
367+
368+
resource "grafana_report" "test" {
369+
name = "timezone test report"
370+
recipients = ["[email protected]"]
371+
schedule {
372+
frequency = "monthly"
373+
start_time = "2024-02-10T15:00:00" # Short format, no timezone
374+
end_time = "2024-02-15T10:00:00" # Short format, no timezone
375+
timezone = "America/New_York" # Non-GMT timezone
376+
}
377+
dashboards {
378+
uid = grafana_dashboard.test1.uid
379+
}
380+
}`, dashboardUID1, dashboardUID2)
381+
}
382+
383+
func testAccReportWithTimezoneStep2(dashboardUID1, dashboardUID2 string) string {
384+
return fmt.Sprintf(`
385+
resource "grafana_dashboard" "test1" {
386+
config_json = <<EOD
387+
{
388+
"title": "Test Dashboard %[1]s",
389+
"uid": "%[1]s"
390+
}
391+
EOD
392+
}
393+
394+
resource "grafana_dashboard" "test2" {
395+
config_json = <<EOD
396+
{
397+
"title": "Test Dashboard %[2]s",
398+
"uid": "%[2]s"
399+
}
400+
EOD
401+
}
402+
403+
resource "grafana_report" "test" {
404+
name = "timezone test report"
405+
recipients = ["[email protected]"]
406+
schedule {
407+
frequency = "monthly"
408+
start_time = "2024-02-10T15:00:00" # Short format, no timezone
409+
end_time = "2024-02-15T10:00:00" # Short format, no timezone
410+
timezone = "America/New_York" # Non-GMT timezone
411+
}
412+
dashboards {
413+
uid = grafana_dashboard.test2.uid
414+
}
415+
}`, dashboardUID1, dashboardUID2)
416+
}

0 commit comments

Comments
 (0)