From 90d2f097e80f15db3d8e0e89dea6a0a68b82eacd Mon Sep 17 00:00:00 2001 From: Idan Shamam Date: Tue, 4 Nov 2025 17:20:45 +0200 Subject: [PATCH] fix(workflows): Fix empty results in Workflows query type Fixes #503 - Add nil check for CreatedAt/UpdatedAt timestamps to prevent panics - Add 'None' option to Time Field dropdown (default) to return all workflows - Improve time filtering logic with better nil handling - Add debug logging for troubleshooting --- package.json | 2 +- pkg/github/workflows.go | 52 ++++++++++++++++++++++++++---- pkg/models/workflows.go | 4 ++- src/constants.ts | 1 + src/views/QueryEditorWorkflows.tsx | 6 ++-- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 78fa7cda..db2e429c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grafana-github-datasource", - "version": "2.3.0", + "version": "2.3.1", "private": true, "description": "The GitHub data source plugin for Grafana lets you to query the GitHub API in Grafana so you can visualize your GitHub repositories and projects.", "repository": "github:grafana/github-datasource", diff --git a/pkg/github/workflows.go b/pkg/github/workflows.go index f41c2996..91f4d734 100644 --- a/pkg/github/workflows.go +++ b/pkg/github/workflows.go @@ -63,38 +63,78 @@ func GetWorkflows(ctx context.Context, client models.Client, opts models.ListWor return nil, fmt.Errorf("listing workflows: opts=%+v %w", opts, err) } + backend.Logger.Debug("GetWorkflows", "fetched_workflows", len(data.Workflows), "timeField", opts.TimeField, "timeRange", fmt.Sprintf("%v to %v", timeRange.From, timeRange.To)) + + // If time field is None, return all workflows without filtering + if opts.TimeField == models.WorkflowTimeFieldNone { + backend.Logger.Debug("GetWorkflows", "time_field_none", "returning_all_workflows") + return WorkflowsWrapper(data.Workflows), nil + } + + // Otherwise, apply time filtering based on the selected time field workflows, err := keepWorkflowsInTimeRange(data.Workflows, opts.TimeField, timeRange) if err != nil { return nil, fmt.Errorf("filtering workflows by time range: timeField=%d timeRange=%+v %w", opts.TimeField, timeRange, err) } + backend.Logger.Debug("GetWorkflows", "filtered_workflows", len(workflows)) return WorkflowsWrapper(workflows), nil } func keepWorkflowsInTimeRange(workflows []*googlegithub.Workflow, timeField models.WorkflowTimeField, timeRange backend.TimeRange) ([]*googlegithub.Workflow, error) { + // If time range is empty/unset, return all workflows (similar to Tags, Releases, etc.) + if timeRange.From.Unix() <= 0 && timeRange.To.Unix() <= 0 { + backend.Logger.Debug("keepWorkflowsInTimeRange", "time_range_empty", "returning_all_workflows", len(workflows)) + return workflows, nil + } + out := make([]*googlegithub.Workflow, 0) + nilCount := 0 + excludedCount := 0 for _, workflow := range workflows { + var shouldInclude bool + switch timeField { case models.WorkflowCreatedAt: - if workflow.CreatedAt.Before(timeRange.From) || workflow.CreatedAt.After(timeRange.To) { + if workflow.CreatedAt == nil { + // If filtering by CreatedAt but CreatedAt is nil, exclude the workflow + nilCount++ continue } + // Include if CreatedAt is within the time range (inclusive) + createdAtTime := workflow.CreatedAt.Time + shouldInclude = !createdAtTime.Before(timeRange.From) && !createdAtTime.After(timeRange.To) + if !shouldInclude { + excludedCount++ + backend.Logger.Debug("keepWorkflowsInTimeRange", "workflow_excluded", *workflow.Name, "createdAt", createdAtTime, "timeRange", fmt.Sprintf("%v to %v", timeRange.From, timeRange.To)) + } case models.WorkflowUpdatedAt: - if workflow.UpdatedAt != nil { - if workflow.UpdatedAt.Before(timeRange.From) || workflow.UpdatedAt.After(timeRange.To) { - continue - } + if workflow.UpdatedAt == nil { + // If filtering by UpdatedAt but UpdatedAt is nil, exclude the workflow + nilCount++ + continue + } + // Include if UpdatedAt is within the time range (inclusive) + updatedAtTime := workflow.UpdatedAt.Time + shouldInclude = !updatedAtTime.Before(timeRange.From) && !updatedAtTime.After(timeRange.To) + if !shouldInclude { + excludedCount++ + backend.Logger.Debug("keepWorkflowsInTimeRange", "workflow_excluded", *workflow.Name, "updatedAt", updatedAtTime, "timeRange", fmt.Sprintf("%v to %v", timeRange.From, timeRange.To)) } default: return nil, backend.DownstreamError(fmt.Errorf("unexpected time field: %d", timeField)) } - out = append(out, workflow) + if shouldInclude { + out = append(out, workflow) + } } + backend.Logger.Debug("keepWorkflowsInTimeRange", "total_workflows", len(workflows), "included", len(out), "excluded_nil", nilCount, "excluded_out_of_range", excludedCount) + return out, nil } diff --git a/pkg/models/workflows.go b/pkg/models/workflows.go index 6afdaccf..4f96431b 100644 --- a/pkg/models/workflows.go +++ b/pkg/models/workflows.go @@ -6,8 +6,10 @@ import "time" type WorkflowTimeField uint32 const ( + // WorkflowTimeFieldNone indicates no time filtering should be applied + WorkflowTimeFieldNone WorkflowTimeField = iota // WorkflowCreatedAt is used when filtering when an workflow was created - WorkflowCreatedAt WorkflowTimeField = iota + WorkflowCreatedAt // WorkflowUpdatedAt is used when filtering when an Workflow was updated WorkflowUpdatedAt ) diff --git a/src/constants.ts b/src/constants.ts index bbd2d559..4f86d8ef 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -49,6 +49,7 @@ export enum IssueTimeField { } export enum WorkflowsTimeField { + None, CreatedAt, UpdatedAt, } diff --git a/src/views/QueryEditorWorkflows.tsx b/src/views/QueryEditorWorkflows.tsx index d8103be9..128c05c9 100644 --- a/src/views/QueryEditorWorkflows.tsx +++ b/src/views/QueryEditorWorkflows.tsx @@ -18,7 +18,7 @@ const timeFieldOptions: Array> = Object.keys }; }); -const defaultTimeField = 0 as WorkflowsTimeField; +const defaultTimeField = WorkflowsTimeField.None; const QueryEditorWorkflows = (props: Props) => { return ( @@ -26,12 +26,12 @@ const QueryEditorWorkflows = (props: Props) => {