Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions cmd/rum.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,21 @@ func runRumRetentionFiltersDelete(cmd *cobra.Command, args []string) error {

// RUM Sessions Implementation
func runRumSessionsList(cmd *cobra.Command, args []string) error {
// Convert relative time strings to absolute timestamps (validate input first)
fromTime, err := parseTimeString(rumFrom)
if err != nil {
return fmt.Errorf("invalid --from time: %w\n\nSupported formats:\n- Relative: 1h, 30m, 7d, 1w (hour, minute, day, week)\n- Absolute: Unix timestamp in milliseconds\n- now: Current time", err)
}

toTime, err := parseTimeString(rumTo)
if err != nil {
return fmt.Errorf("invalid --to time: %w\n\nSupported formats:\n- Relative: 1h, 30m, 7d, 1w (hour, minute, day, week)\n- Absolute: Unix timestamp in milliseconds\n- now: Current time", err)
}

// Convert timestamps to strings for RUM API
from := fmt.Sprintf("%d", fromTime)
to := fmt.Sprintf("%d", toTime)

client, err := getClient()
if err != nil {
return err
Expand All @@ -665,8 +680,8 @@ func runRumSessionsList(cmd *cobra.Command, args []string) error {
api := datadogV2.NewRUMApi(client.V2())
body := datadogV2.RUMSearchEventsRequest{
Filter: &datadogV2.RUMQueryFilter{
From: &rumFrom,
To: &rumTo,
From: &from,
To: &to,
},
Page: &datadogV2.RUMQueryPageOptions{
Limit: datadog.PtrInt32(int32(rumLimit)),
Expand All @@ -676,7 +691,7 @@ func runRumSessionsList(cmd *cobra.Command, args []string) error {
resp, r, err := api.SearchRUMEvents(client.Context(), body)
if err != nil {
if r != nil {
return fmt.Errorf("failed to list RUM sessions: %w (status: %d)", err, r.StatusCode)
return fmt.Errorf("failed to list RUM sessions: %w (status: %d)\n\nRequest Details:\n- From: %s (parsed from: %s)\n- To: %s (parsed from: %s)\n- Limit: %d\n\nTroubleshooting:\n- Verify your time range is valid and --from is before --to\n- Ensure you have proper permissions for RUM data access\n- Check that RUM data exists for your selected time range", err, r.StatusCode, from, rumFrom, to, rumTo, rumLimit)
}
return fmt.Errorf("failed to list RUM sessions: %w", err)
}
Expand All @@ -690,6 +705,21 @@ func runRumSessionsList(cmd *cobra.Command, args []string) error {
}

func runRumSessionsSearch(cmd *cobra.Command, args []string) error {
// Convert relative time strings to absolute timestamps (validate input first)
fromTime, err := parseTimeString(rumFrom)
if err != nil {
return fmt.Errorf("invalid --from time: %w\n\nSupported formats:\n- Relative: 1h, 30m, 7d, 1w (hour, minute, day, week)\n- Absolute: Unix timestamp in milliseconds\n- now: Current time", err)
}

toTime, err := parseTimeString(rumTo)
if err != nil {
return fmt.Errorf("invalid --to time: %w\n\nSupported formats:\n- Relative: 1h, 30m, 7d, 1w (hour, minute, day, week)\n- Absolute: Unix timestamp in milliseconds\n- now: Current time", err)
}

// Convert timestamps to strings for RUM API
from := fmt.Sprintf("%d", fromTime)
to := fmt.Sprintf("%d", toTime)

client, err := getClient()
if err != nil {
return err
Expand All @@ -699,8 +729,8 @@ func runRumSessionsSearch(cmd *cobra.Command, args []string) error {
body := datadogV2.RUMSearchEventsRequest{
Filter: &datadogV2.RUMQueryFilter{
Query: &rumQuery,
From: &rumFrom,
To: &rumTo,
From: &from,
To: &to,
},
Page: &datadogV2.RUMQueryPageOptions{
Limit: datadog.PtrInt32(int32(rumLimit)),
Expand All @@ -710,7 +740,7 @@ func runRumSessionsSearch(cmd *cobra.Command, args []string) error {
resp, r, err := api.SearchRUMEvents(client.Context(), body)
if err != nil {
if r != nil {
return fmt.Errorf("failed to search RUM sessions: %w (status: %d)", err, r.StatusCode)
return fmt.Errorf("failed to search RUM sessions: %w (status: %d)\n\nRequest Details:\n- Query: %s\n- From: %s (parsed from: %s)\n- To: %s (parsed from: %s)\n- Limit: %d\n\nTroubleshooting:\n- Verify your time range is valid and --from is before --to\n- Check that your query syntax is correct\n- Ensure you have proper permissions for RUM data access\n- Check that RUM data exists for your selected time range and query", err, r.StatusCode, rumQuery, from, rumFrom, to, rumTo, rumLimit)
}
return fmt.Errorf("failed to search RUM sessions: %w", err)
}
Expand Down
185 changes: 185 additions & 0 deletions cmd/rum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package cmd
import (
"testing"

"github.com/DataDog/pup/pkg/config"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -207,3 +208,187 @@ func TestRumAppsListCmd(t *testing.T) {
t.Error("RunE is nil")
}
}

// TestRumSessionsList_TimeConversion tests that relative time strings are converted properly
func TestRumSessionsList_TimeConversion(t *testing.T) {
// Save originals
origCfg := cfg
origClient := ddClient
origFrom := rumFrom
origTo := rumTo
origLimit := rumLimit

// Cleanup
defer func() {
cfg = origCfg
ddClient = origClient
rumFrom = origFrom
rumTo = origTo
rumLimit = origLimit
}()

tests := []struct {
name string
from string
to string
wantError bool
errorMsg string
}{
{
name: "valid relative time 1h",
from: "1h",
to: "now",
wantError: true, // Will fail on getClient, but time parsing should work
},
{
name: "valid relative time 30m",
from: "30m",
to: "now",
wantError: true, // Will fail on getClient, but time parsing should work
},
{
name: "valid relative time 7d",
from: "7d",
to: "now",
wantError: true, // Will fail on getClient, but time parsing should work
},
{
name: "invalid time format",
from: "invalid",
to: "now",
wantError: true,
errorMsg: "invalid --from time",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup: set test values
cfg = &config.Config{}
ddClient = nil // Don't create real client (avoids keychain)
rumFrom = tt.from
rumTo = tt.to
rumLimit = 10

// Run the command
err := runRumSessionsList(nil, nil)

// Verify error behavior
if tt.wantError && err == nil {
t.Errorf("Expected error but got none")
}
if !tt.wantError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if tt.errorMsg != "" && err != nil {
if !containsString(err.Error(), tt.errorMsg) {
t.Errorf("Error message %q does not contain %q", err.Error(), tt.errorMsg)
}
}
})
}
}

// TestRumSessionsSearch_TimeConversion tests that relative time strings are converted properly
func TestRumSessionsSearch_TimeConversion(t *testing.T) {
// Save originals
origCfg := cfg
origClient := ddClient
origFrom := rumFrom
origTo := rumTo
origLimit := rumLimit
origQuery := rumQuery

// Cleanup
defer func() {
cfg = origCfg
ddClient = origClient
rumFrom = origFrom
rumTo = origTo
rumLimit = origLimit
rumQuery = origQuery
}()

tests := []struct {
name string
query string
from string
to string
wantError bool
errorMsg string
}{
{
name: "valid relative time 1h",
query: "@type:view",
from: "1h",
to: "now",
wantError: true, // Will fail on getClient, but time parsing should work
},
{
name: "valid relative time 2h",
query: "status:error",
from: "2h",
to: "1h",
wantError: true, // Will fail on getClient, but time parsing should work
},
{
name: "invalid from time format",
query: "@type:view",
from: "invalid",
to: "now",
wantError: true,
errorMsg: "invalid --from time",
},
{
name: "invalid to time format",
query: "@type:view",
from: "1h",
to: "bad-time",
wantError: true,
errorMsg: "invalid --to time",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup: set test values
cfg = &config.Config{}
ddClient = nil // Don't create real client (avoids keychain)
rumQuery = tt.query
rumFrom = tt.from
rumTo = tt.to
rumLimit = 10

// Run the command
err := runRumSessionsSearch(nil, nil)

// Verify error behavior
if tt.wantError && err == nil {
t.Errorf("Expected error but got none")
}
if !tt.wantError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if tt.errorMsg != "" && err != nil {
if !containsString(err.Error(), tt.errorMsg) {
t.Errorf("Error message %q does not contain %q", err.Error(), tt.errorMsg)
}
}
})
}
}

// Helper function for string contains check
func containsString(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
(len(s) > 0 && len(substr) > 0 && findSubstring(s, substr)))
}

func findSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}