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
34 changes: 34 additions & 0 deletions internal/temporalcli/commands.gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,7 @@ func NewTemporalScheduleCommand(cctx *CommandContext, parent *TemporalCommand) *
s.Command.AddCommand(&NewTemporalScheduleDeleteCommand(cctx, &s).Command)
s.Command.AddCommand(&NewTemporalScheduleDescribeCommand(cctx, &s).Command)
s.Command.AddCommand(&NewTemporalScheduleListCommand(cctx, &s).Command)
s.Command.AddCommand(&NewTemporalScheduleListMatchingTimesCommand(cctx, &s).Command)
s.Command.AddCommand(&NewTemporalScheduleToggleCommand(cctx, &s).Command)
s.Command.AddCommand(&NewTemporalScheduleTriggerCommand(cctx, &s).Command)
s.Command.AddCommand(&NewTemporalScheduleUpdateCommand(cctx, &s).Command)
Expand Down Expand Up @@ -2270,6 +2271,39 @@ func NewTemporalScheduleListCommand(cctx *CommandContext, parent *TemporalSchedu
return &s
}

type TemporalScheduleListMatchingTimesCommand struct {
Parent *TemporalScheduleCommand
Command cobra.Command
ScheduleIdOptions
StartTime cliext.FlagTimestamp
EndTime cliext.FlagTimestamp
}

func NewTemporalScheduleListMatchingTimesCommand(cctx *CommandContext, parent *TemporalScheduleCommand) *TemporalScheduleListMatchingTimesCommand {
var s TemporalScheduleListMatchingTimesCommand
s.Parent = parent
s.Command.DisableFlagsInUseLine = true
s.Command.Use = "list-matching-times [flags]"
s.Command.Short = "List matching times for a Schedule (Experimental feature)"
if hasHighlighting {
s.Command.Long = "\nNote: This is an experimental feature and may change in the future.\n\nList the times a Schedule's spec would match within a given time\nrange. The time range may be in the past or future. Use this\ncommand to preview when a Schedule will take actions without\nactually running them.\n\nFor example:\n\n\x1b[1m temporal schedule list-matching-times \\\n --schedule-id \"YourScheduleId\" \\\n --start-time \"2024-01-01T00:00:00Z\" \\\n --end-time \"2024-01-31T23:59:59Z\"\x1b[0m"
} else {
s.Command.Long = "\nNote: This is an experimental feature and may change in the future.\n\nList the times a Schedule's spec would match within a given time\nrange. The time range may be in the past or future. Use this\ncommand to preview when a Schedule will take actions without\nactually running them.\n\nFor example:\n\n```\n temporal schedule list-matching-times \\\n --schedule-id \"YourScheduleId\" \\\n --start-time \"2024-01-01T00:00:00Z\" \\\n --end-time \"2024-01-31T23:59:59Z\"\n```"
}
s.Command.Args = cobra.NoArgs
s.Command.Flags().Var(&s.StartTime, "start-time", "Start of time range to list matching times. Required.")
_ = cobra.MarkFlagRequired(s.Command.Flags(), "start-time")
s.Command.Flags().Var(&s.EndTime, "end-time", "End of time range to list matching times. Required.")
_ = cobra.MarkFlagRequired(s.Command.Flags(), "end-time")
s.ScheduleIdOptions.BuildFlags(s.Command.Flags())
s.Command.Run = func(c *cobra.Command, args []string) {
if err := s.run(cctx, args); err != nil {
cctx.Options.Fail(err)
}
}
return &s
}

type TemporalScheduleToggleCommand struct {
Parent *TemporalScheduleCommand
Command cobra.Command
Expand Down
34 changes: 34 additions & 0 deletions internal/temporalcli/commands.schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/temporalio/cli/cliext"
"github.com/temporalio/cli/internal/printer"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/timestamppb"

commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
Expand Down Expand Up @@ -606,3 +607,36 @@ func formatDuration(d time.Duration) string {
s = strings.TrimSpace(s)
return s
}

func (c *TemporalScheduleListMatchingTimesCommand) run(cctx *CommandContext, args []string) error {
cl, err := dialClient(cctx, &c.Parent.ClientOptions)
if err != nil {
return err
}
defer cl.Close()

res, err := cl.WorkflowService().ListScheduleMatchingTimes(cctx, &workflowservice.ListScheduleMatchingTimesRequest{
Namespace: c.Parent.Namespace,
ScheduleId: c.ScheduleId,
StartTime: timestamppb.New(c.StartTime.Time()),
EndTime: timestamppb.New(c.EndTime.Time()),
})
if err != nil {
return err
}

if cctx.JSONOutput {
return cctx.Printer.PrintStructured(res, printer.StructuredOptions{})
}

type matchingTime struct {
Time string `json:"time"`
}
var rows []matchingTime
for _, t := range res.StartTime {
rows = append(rows, matchingTime{Time: t.AsTime().Format(time.RFC3339)})
}
return cctx.Printer.PrintStructured(rows, printer.StructuredOptions{
Table: &printer.TableOptions{},
})
}
53 changes: 53 additions & 0 deletions internal/temporalcli/commands.schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"math/rand"
"regexp"
"strings"
"time"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -553,3 +554,55 @@ func (s *SharedServerSuite) TestSchedule_Memo_Update() {
return j.Schedule.Action.StartWorkflow.Memo.Fields.Bar.Data == "Mg=="
}, 10*time.Second, 100*time.Millisecond)
}

func (s *SharedServerSuite) TestSchedule_ListMatchingTimes() {
// use a calendar spec with known hours so results are deterministic
schedId, _, res := s.createSchedule("--calendar", `{"hour":"3,6,9"}`)
s.NoError(res.Err)

// query a full day - should match exactly 3 times
res = s.Execute(
"schedule", "list-matching-times",
"--address", s.Address(),
"-s", schedId,
"--start-time", "2025-01-01T00:00:00Z",
"--end-time", "2025-01-01T23:59:59Z",
)
s.NoError(res.Err)
// text output should contain parseable RFC3339 timestamps
for _, line := range strings.Split(res.Stdout.String(), "\n") {
line = strings.TrimSpace(line)
if line == "" || line == "Time" {
continue
}
_, err := time.Parse(time.RFC3339, line)
s.NoError(err, "should parse text line as time: %q", line)
}

// json output
res = s.Execute(
"schedule", "list-matching-times",
"--address", s.Address(),
"-s", schedId,
"--start-time", "2025-01-01T00:00:00Z",
"--end-time", "2025-01-01T23:59:59Z",
"-o", "json",
)
s.NoError(res.Err)
var resp struct {
StartTime []string `json:"startTime"`
}
s.NoError(json.Unmarshal(res.Stdout.Bytes(), &resp))
s.Equal(3, len(resp.StartTime))
}

func (s *SharedServerSuite) TestSchedule_ListMatchingTimes_NotFound() {
res := s.Execute(
"schedule", "list-matching-times",
"--address", s.Address(),
"-s", "nonexistent-schedule-id",
"--start-time", "2025-01-01T00:00:00Z",
"--end-time", "2025-01-01T23:59:59Z",
)
s.Error(res.Err)
}
31 changes: 31 additions & 0 deletions internal/temporalcli/commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2320,6 +2320,37 @@ commands:
- overlap-policy
- schedule-id

- name: temporal schedule list-matching-times
summary: List matching times for a Schedule (Experimental feature)
description: |

Note: This is an experimental feature and may change in the future.

List the times a Schedule's spec would match within a given time
range. The time range may be in the past or future. Use this
command to preview when a Schedule will take actions without
actually running them.

For example:

```
temporal schedule list-matching-times \
--schedule-id "YourScheduleId" \
--start-time "2024-01-01T00:00:00Z" \
--end-time "2024-01-31T23:59:59Z"
```
options:
- name: start-time
type: timestamp
description: Start of time range to list matching times.
required: true
- name: end-time
type: timestamp
description: End of time range to list matching times.
required: true
option-sets:
- schedule-id

- name: temporal schedule create
summary: Create a new Schedule
description: |
Expand Down
Loading