diff --git a/internal/temporalcli/commands.workflow.go b/internal/temporalcli/commands.workflow.go index 20ebec8d1..e03047714 100644 --- a/internal/temporalcli/commands.workflow.go +++ b/internal/temporalcli/commands.workflow.go @@ -569,13 +569,19 @@ func (s *SingleWorkflowOrBatchOptions) workflowExecOrBatch( return nil, nil, fmt.Errorf("cannot set run ID when query is set") } - // Count the workflows that will be affected - count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: s.Query}) - if err != nil { - return nil, nil, fmt.Errorf("failed counting workflows from query: %w", err) + // The count is only used in the confirmation prompt; skip the request when --yes + // bypasses it, so batch jobs can still proceed if the visibility API is timing out. + var promptMessage string + if s.Yes { + promptMessage = fmt.Sprintf("Start batch against workflows matching query %q? y/N", s.Query) + } else { + count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: s.Query}) + if err != nil { + return nil, nil, fmt.Errorf("failed counting workflows from query: %w", err) + } + promptMessage = fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count) } - yes, err := cctx.promptYes( - fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count), s.Yes) + yes, err := cctx.promptYes(promptMessage, s.Yes) if err != nil { return nil, nil, err } else if !yes { diff --git a/internal/temporalcli/commands.workflow_reset.go b/internal/temporalcli/commands.workflow_reset.go index 4c33065ce..aee402891 100644 --- a/internal/temporalcli/commands.workflow_reset.go +++ b/internal/temporalcli/commands.workflow_reset.go @@ -134,12 +134,19 @@ func (c *TemporalWorkflowResetCommand) runBatchResetWithPostOps(cctx *CommandCon PostResetOperations: postOps, }, } - count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: c.Query}) - if err != nil { - return fmt.Errorf("failed counting workflows from query: %w", err) + // The count is only used in the confirmation prompt; skip the request when --yes + // bypasses it, so batch jobs can still proceed if the visibility API is timing out. + var promptMessage string + if c.Yes { + promptMessage = fmt.Sprintf("Start batch against workflows matching query %q? y/N", c.Query) + } else { + count, err := cl.CountWorkflow(cctx, &workflowservice.CountWorkflowExecutionsRequest{Query: c.Query}) + if err != nil { + return fmt.Errorf("failed counting workflows from query: %w", err) + } + promptMessage = fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count) } - yes, err := cctx.promptYes( - fmt.Sprintf("Start batch against approximately %v workflow(s)? y/N", count.Count), c.Yes) + yes, err := cctx.promptYes(promptMessage, c.Yes) if err != nil { return err } diff --git a/internal/temporalcli/commands.workflow_reset_test.go b/internal/temporalcli/commands.workflow_reset_test.go index 33b3cc525..5dc87604c 100644 --- a/internal/temporalcli/commands.workflow_reset_test.go +++ b/internal/temporalcli/commands.workflow_reset_test.go @@ -927,3 +927,42 @@ func (sut *batchResetTestData) getWorkflowHistory() ([]*history.HistoryEvent, er return events, nil } + +func (s *SharedServerSuite) TestWorkflow_ResetBatch_SkipsCountWhenYes() { + s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { + ctx.Done().Receive(ctx, nil) + return nil, ctx.Err() + }) + + searchAttr := "keyword-" + uuid.NewString() + _, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{ + TaskQueue: s.Worker().Options.TaskQueue, + SearchAttributes: map[string]any{"CustomKeywordField": searchAttr}, + }, + DevWorkflow, + "ignored", + ) + s.NoError(err) + s.Eventually(func() bool { + resp, err := s.Client.ListWorkflow(s.Context, &workflowservice.ListWorkflowExecutionsRequest{ + Query: "CustomKeywordField = '" + searchAttr + "'", + }) + s.NoError(err) + return len(resp.Executions) == 1 + }, 3*time.Second, 100*time.Millisecond) + + res := s.Execute( + "workflow", "reset", + "--address", s.Address(), + "--query", "CustomKeywordField = '"+searchAttr+"'", + "--type", "FirstWorkflowTask", + "--reason", "skip-count-test", + "--yes", + ) + s.NoError(res.Err) + + s.NotContains(res.Stdout.String(), "approximately") + s.Contains(res.Stdout.String(), "matching query") +} diff --git a/internal/temporalcli/commands.workflow_test.go b/internal/temporalcli/commands.workflow_test.go index 20d6d256b..aedf331f7 100644 --- a/internal/temporalcli/commands.workflow_test.go +++ b/internal/temporalcli/commands.workflow_test.go @@ -7,6 +7,7 @@ import ( "math/rand" "regexp" "strconv" + "strings" "sync" "time" @@ -250,7 +251,7 @@ func (s *SharedServerSuite) TestWorkflow_Delete_BatchWorkflowSuccess() { "-y", ) s.NoError(res.Err) - s.Contains(res.Stdout.String(), "Start batch against approximately 2 workflow(s)") + s.Contains(res.Stdout.String(), "Start batch against workflows matching query") s.NotContains(res.Stdout.String(), "Deleting Workflow Executions in a global Namespace") s.NotContains(res.Stderr.String(), "Deleting Workflow Executions in a global Namespace") @@ -387,6 +388,51 @@ func (s *SharedServerSuite) TestWorkflow_Terminate_BatchWorkflowSuccess_JSON() { s.NotEmpty(jsonRes["batchJobId"]) } +func (s *SharedServerSuite) TestWorkflow_Terminate_BatchWorkflow_SkipsCountWhenYes() { + s.Worker().OnDevWorkflow(func(ctx workflow.Context, a any) (any, error) { + ctx.Done().Receive(ctx, nil) + return nil, ctx.Err() + }) + + searchAttr := "keyword-" + uuid.NewString() + run, err := s.Client.ExecuteWorkflow( + s.Context, + client.StartWorkflowOptions{ + TaskQueue: s.Worker().Options.TaskQueue, + SearchAttributes: map[string]any{"CustomKeywordField": searchAttr}, + }, + DevWorkflow, + "ignored", + ) + s.NoError(err) + s.Eventually(func() bool { + resp, err := s.Client.ListWorkflow(s.Context, &workflowservice.ListWorkflowExecutionsRequest{ + Query: "CustomKeywordField = '" + searchAttr + "'", + }) + s.NoError(err) + return len(resp.Executions) == 1 + }, 3*time.Second, 100*time.Millisecond) + + res := s.Execute( + "workflow", "terminate", + "--address", s.Address(), + "--query", "CustomKeywordField = '"+searchAttr+"'", + "--reason", "skip-count-test", + "--yes", + ) + s.NoError(res.Err) + + // Prompt text should show the query, not the count + s.NotContains(res.Stdout.String(), "approximately") + s.Contains(res.Stdout.String(), "matching query") + + // Confirm workflow was terminated + s.Eventually(func() bool { + err := run.Get(s.Context, nil) + return err != nil && strings.Contains(err.Error(), "terminated") + }, 5*time.Second, 100*time.Millisecond) +} + func (s *SharedServerSuite) testTerminateBatchWorkflow( total int, rps float32,