Skip to content
Draft
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
11 changes: 9 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ require (
github.com/Pallinder/sillyname-go v0.0.0-20130730142914-97aeae9e6ba1
github.com/briandowns/spinner v1.19.0
github.com/dapr/dapr v1.17.0-rc.8
github.com/dapr/durabletask-go v0.11.0
github.com/dapr/durabletask-go v0.11.4-0.20260408193502-7e0554e3883e
github.com/dapr/go-sdk v1.13.0
github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d
github.com/dapr/kit v0.17.1-0.20260402173438-be272d92042b
github.com/diagridio/go-etcd-cron v0.12.3
github.com/docker/docker v28.5.2+incompatible
github.com/evanphx/json-patch/v5 v5.9.0
Expand Down Expand Up @@ -104,6 +104,7 @@ require (
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-events v0.0.0-20250808211157-605354379745 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
Expand Down Expand Up @@ -189,6 +190,7 @@ require (
github.com/montanaflynn/stats v0.7.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
Expand All @@ -204,6 +206,7 @@ require (
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/statsd_exporter v0.22.7 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rubenv/sql-migrate v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
Expand Down Expand Up @@ -269,6 +272,10 @@ require (
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/kubectl v0.33.3 // indirect
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect
modernc.org/libc v1.61.9 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.34.5 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/controller-runtime v0.19.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,14 @@ github.com/dapr/dapr v1.17.0-rc.8 h1:CZTPQRwX7sO9H4wKrVnnV6w1foKWLP89wtAJY/bL5KQ
github.com/dapr/dapr v1.17.0-rc.8/go.mod h1:GZq8BjQaX3BbMmZET4HDe6Rrv58yC1pHNVQod3bIAVk=
github.com/dapr/durabletask-go v0.11.0 h1:e9Ns/3a2b6JDKGuvksvx6gCHn7rd+nwZZyAXbg5Ley4=
github.com/dapr/durabletask-go v0.11.0/go.mod h1:0Ts4rXp74JyG19gDWPcwNo5V6NBZzhARzHF5XynmA7Q=
github.com/dapr/durabletask-go v0.11.4-0.20260408193502-7e0554e3883e h1:/ZE9swGMPaaaoo2wJNjC4xzDaQRaAk0m9yddSo7YRlo=
github.com/dapr/durabletask-go v0.11.4-0.20260408193502-7e0554e3883e/go.mod h1:nElJPTX9FmveqErAmvTXZrAmt2nnyTQ7Glj0ahPphSY=
github.com/dapr/go-sdk v1.13.0 h1:Qw2BmUonClQ9yK/rrEEaFL1PyDgq616RrvYj0CT67Lk=
github.com/dapr/go-sdk v1.13.0/go.mod h1:RsffVNZitDApmQqoS68tNKGMXDZUjTviAbKZupJSzts=
github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d h1:csljij9d1IO6u9nqbg+TuSRmTZ+OXT8G49yh6zie1yI=
github.com/dapr/kit v0.16.2-0.20251124175541-3ac186dff64d/go.mod h1:40ZWs5P6xfYf7O59XgwqZkIyDldTIXlhTQhGop8QoSM=
github.com/dapr/kit v0.17.1-0.20260402173438-be272d92042b h1:hXbRlNKvmMGbiMSRy3MX04kH5OiMgH82PRuLN7adtwE=
github.com/dapr/kit v0.17.1-0.20260402173438-be272d92042b/go.mod h1:2v02LZdXzPmOadxoT6EMEt0bsEYe6h1fn2ndYWmylCg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down Expand Up @@ -202,6 +206,8 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
Expand Down Expand Up @@ -540,6 +546,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA=
github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
Expand Down Expand Up @@ -617,6 +625,8 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
Expand Down Expand Up @@ -1167,6 +1177,14 @@ k8s.io/kubectl v0.33.3 h1:r/phHvH1iU7gO/l7tTjQk2K01ER7/OAJi8uFHHyWSac=
k8s.io/kubectl v0.33.3/go.mod h1:euj2bG56L6kUGOE/ckZbCoudPwuj4Kud7BR0GzyNiT0=
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg=
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
modernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM=
modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
Expand Down
41 changes: 41 additions & 0 deletions pkg/workflow/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,12 @@ func HistoryWide(ctx context.Context, opts HistoryOptions) ([]*HistoryOutputWide
row.addAttr("timerName", *t.TimerCreated.Name)
}
row.addAttr("fireAt", t.TimerCreated.FireAt.AsTime().Format(time.RFC3339))
if o := timerOriginInfo(t.TimerCreated); o != nil {
row.addAttr("origin", o.kind)
if o.attrKey != "" {
row.addAttr(o.attrKey, o.attrVal)
}
}
case *protos.HistoryEvent_TimerFired:
row.addAttr("timerId", fmt.Sprintf("%d", t.TimerFired.TimerId))
row.addAttr("fireAt", t.TimerFired.FireAt.AsTime().Format(time.RFC3339))
Expand Down Expand Up @@ -382,6 +388,9 @@ func deriveDetails(first *protos.HistoryEvent, h *protos.HistoryEvent) *string {
if in := t.TimerCreated.RerunParentInstanceInfo; in != nil {
det += fmt.Sprintf(",rerunParent=%s", in.InstanceID)
}
if o := timerOriginString(t.TimerCreated); o != "" {
det += ",origin=" + o
}
return ptr.Of(det)
case *protos.HistoryEvent_EventRaised:
return ptr.Of(fmt.Sprintf("event=%s", t.EventRaised.Name))
Expand Down Expand Up @@ -450,6 +459,38 @@ func flatTags(tags map[string]string, max int) string {
return s
}

type timerOrigin struct {
kind string // e.g. "createTimer", "externalEvent"
attrKey string // extra attr key, empty if none
attrVal string // extra attr value
}

func timerOriginInfo(tc *protos.TimerCreatedEvent) *timerOrigin {
switch x := tc.GetOrigin().(type) {
case *protos.TimerCreatedEvent_CreateTimer:
return &timerOrigin{kind: "createTimer"}
case *protos.TimerCreatedEvent_ExternalEvent:
return &timerOrigin{kind: "externalEvent", attrKey: "eventName", attrVal: x.ExternalEvent.GetName()}
case *protos.TimerCreatedEvent_ActivityRetry:
return &timerOrigin{kind: "activityRetry", attrKey: "taskExecId", attrVal: x.ActivityRetry.GetTaskExecutionId()}
case *protos.TimerCreatedEvent_ChildWorkflowRetry:
return &timerOrigin{kind: "childWorkflowRetry", attrKey: "instanceId", attrVal: x.ChildWorkflowRetry.GetInstanceId()}
default:
return nil
}
}

func timerOriginString(tc *protos.TimerCreatedEvent) string {
o := timerOriginInfo(tc)
if o == nil {
return ""
}
if o.attrVal != "" {
return o.kind + "(" + o.attrVal + ")"
}
return o.kind
}

func trim(ww *wrapperspb.StringValue, limit int) string {
if ww == nil {
return ""
Expand Down
180 changes: 180 additions & 0 deletions pkg/workflow/history_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
Copyright 2026 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package workflow

import (
"testing"

"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/dapr/durabletask-go/api/protos"
)

func TestTimerOriginString(t *testing.T) {
tests := []struct {
name string
event *protos.TimerCreatedEvent
expect string
}{
{
name: "nil origin",
event: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
},
expect: "",
},
{
name: "createTimer",
event: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
Origin: &protos.TimerCreatedEvent_CreateTimer{
CreateTimer: &protos.TimerOriginCreateTimer{},
},
},
expect: "createTimer",
},
{
name: "externalEvent",
event: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
Origin: &protos.TimerCreatedEvent_ExternalEvent{
ExternalEvent: &protos.TimerOriginExternalEvent{
Name: "myEvent",
},
},
},
expect: "externalEvent(myEvent)",
},
{
name: "activityRetry",
event: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
Origin: &protos.TimerCreatedEvent_ActivityRetry{
ActivityRetry: &protos.TimerOriginActivityRetry{
TaskExecutionId: "exec-123",
},
},
},
expect: "activityRetry(exec-123)",
},
{
name: "childWorkflowRetry",
event: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
Origin: &protos.TimerCreatedEvent_ChildWorkflowRetry{
ChildWorkflowRetry: &protos.TimerOriginChildWorkflowRetry{
InstanceId: "wf-456",
},
},
},
expect: "childWorkflowRetry(wf-456)",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := timerOriginString(tc.event)
assert.Equal(t, tc.expect, got)
})
}
}

func TestDeriveDetails_TimerCreated(t *testing.T) {
first := &protos.HistoryEvent{
Timestamp: timestamppb.Now(),
}

tests := []struct {
name string
event *protos.HistoryEvent
contains []string
excludes []string
}{
{
name: "timer with no origin",
event: &protos.HistoryEvent{
EventType: &protos.HistoryEvent_TimerCreated{
TimerCreated: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
},
},
},
contains: []string{"fireAt="},
excludes: []string{"origin="},
},
{
name: "timer with createTimer origin",
event: &protos.HistoryEvent{
EventType: &protos.HistoryEvent_TimerCreated{
TimerCreated: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
Origin: &protos.TimerCreatedEvent_CreateTimer{
CreateTimer: &protos.TimerOriginCreateTimer{},
},
},
},
},
contains: []string{"fireAt=", "origin=createTimer"},
},
{
name: "timer with activityRetry origin",
event: &protos.HistoryEvent{
EventType: &protos.HistoryEvent_TimerCreated{
TimerCreated: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
Origin: &protos.TimerCreatedEvent_ActivityRetry{
ActivityRetry: &protos.TimerOriginActivityRetry{
TaskExecutionId: "exec-abc",
},
},
},
},
},
contains: []string{"fireAt=", "origin=activityRetry(exec-abc)"},
},
{
name: "timer with rerunParent and origin",
event: &protos.HistoryEvent{
EventType: &protos.HistoryEvent_TimerCreated{
TimerCreated: &protos.TimerCreatedEvent{
FireAt: timestamppb.Now(),
RerunParentInstanceInfo: &protos.RerunParentInstanceInfo{
InstanceID: "parent-123",
},
Origin: &protos.TimerCreatedEvent_ExternalEvent{
ExternalEvent: &protos.TimerOriginExternalEvent{
Name: "approval",
},
},
},
},
},
contains: []string{"fireAt=", "rerunParent=parent-123", "origin=externalEvent(approval)"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
details := deriveDetails(first, tc.event)
assert.NotNil(t, details)
for _, s := range tc.contains {
assert.Contains(t, *details, s)
}
for _, s := range tc.excludes {
assert.NotContains(t, *details, s)
}
})
}
}
Loading
Loading