diff --git a/go.mod b/go.mod index 704f97fe6..dc7c88274 100644 --- a/go.mod +++ b/go.mod @@ -17,14 +17,14 @@ require ( github.com/stretchr/testify v1.11.1 github.com/temporalio/cli/cliext v0.0.0 github.com/temporalio/ui-server/v2 v2.52.0 - go.temporal.io/api v1.63.0 - go.temporal.io/sdk v1.45.0 + go.temporal.io/api v1.63.4-0.20260720155646-7a3ca3626ec2 + go.temporal.io/sdk v1.46.1-0.20260720184640-f34dc3da35ab go.temporal.io/sdk/contrib/envconfig v1.0.2 - go.temporal.io/server v1.32.0-158.0 - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f - golang.org/x/mod v0.35.0 - golang.org/x/term v0.43.0 - golang.org/x/tools v0.44.0 + go.temporal.io/server v1.29.0-135.0.0.20260720172935-768cd1f81135 + golang.org/x/exp v0.0.0-20260611194520-c48552f49976 + golang.org/x/mod v0.37.0 + golang.org/x/term v0.45.0 + golang.org/x/tools v0.47.0 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 @@ -78,6 +78,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c // indirect github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect + github.com/caio/go-tdigest/v5 v5.0.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect @@ -129,7 +130,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.9.2 // indirect + github.com/jackc/pgx/v5 v5.10.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -182,17 +183,18 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect - go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.57.0 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/log v0.20.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - go.temporal.io/auto-scaled-workers v0.0.0-20260622220320-9b1e3849116d // indirect + go.temporal.io/auto-scaled-workers v0.0.0-20260706201056-4320b34799ee // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect @@ -201,12 +203,12 @@ require ( go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.52.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.276.0 // indirect google.golang.org/genproto v0.0.0-20260420184626-e10c466a9529 // indirect diff --git a/go.sum b/go.sum index 783a6d133..095697fe8 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go. github.com/cactus/go-statsd-client/v4 v4.0.0/go.mod h1:m73kwJp6TN0Ja9P6ycdZhWM1MlfxY/95WZ//IptPQ+Y= github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4= github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= +github.com/caio/go-tdigest/v5 v5.0.0 h1:XQKgYSazZPbWFDAJ51dKqoZoDrISmTrB8UcWwCmfo6Y= +github.com/caio/go-tdigest/v5 v5.0.0/go.mod h1:wI618wZoAYzIDZlpX2CfyTQdrdGtwEZOJuXdrI3zk/Y= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -268,8 +270,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -300,6 +302,8 @@ github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcX github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= +github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= @@ -447,8 +451,8 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= @@ -459,26 +463,28 @@ go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25 go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs= +go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.temporal.io/api v1.63.0 h1:YZFOTA0/thRUIUC4qunAWdHhPh/IG4vy/+WjfEvT+ZE= -go.temporal.io/api v1.63.0/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= -go.temporal.io/auto-scaled-workers v0.0.0-20260622220320-9b1e3849116d h1:f7+FCJHSrYWz9zvJp2OxKo8Fu/dsBUdnZZA+m5CEOS0= -go.temporal.io/auto-scaled-workers v0.0.0-20260622220320-9b1e3849116d/go.mod h1:HOnbQTZCW18EPcutFHTkZrDcGO4tUjQ8N2pjDyrGhrY= -go.temporal.io/sdk v1.45.0 h1:kvsczo3SHTS60+zBWH9lljLmBLWSXcyGkBxr88z8iQI= -go.temporal.io/sdk v1.45.0/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= +go.temporal.io/api v1.63.4-0.20260720155646-7a3ca3626ec2 h1:s0EE5uB7xQkAYPe2Lb59zo+5y5AF5uqtyYHoo4t9V3s= +go.temporal.io/api v1.63.4-0.20260720155646-7a3ca3626ec2/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ= +go.temporal.io/auto-scaled-workers v0.0.0-20260706201056-4320b34799ee h1:y6A65Iml06cR3CpxW2Zn8FQLjniPDTnN4jtr69UZxXI= +go.temporal.io/auto-scaled-workers v0.0.0-20260706201056-4320b34799ee/go.mod h1:hhHijO9XRPIkAflLJJHix61M9FzbRPqk8fSydkcLkqw= +go.temporal.io/sdk v1.46.1-0.20260720184640-f34dc3da35ab h1:iF9QRS220GeFyNWdiKx+jvQl6LAvZdXGStRCWnU5pZw= +go.temporal.io/sdk v1.46.1-0.20260720184640-f34dc3da35ab/go.mod h1:x3v/9ImVh469kiHspoq1xgLdPnetbfuCAm+Y1+sUtIo= go.temporal.io/sdk/contrib/envconfig v1.0.2 h1:MGHfsuPUtsf7X9M6WYn3zYJj/mWsuYHnA1uuiL0KEuE= go.temporal.io/sdk/contrib/envconfig v1.0.2/go.mod h1:MuMiH7hksps2uXnmKuAWaP9P6WbkSDy62kl64t1VJVg= -go.temporal.io/server v1.32.0-158.0 h1:M+rXAmFztzBrIXNYPZyUPJFZO6yHPKP1lRTrQNUKvbs= -go.temporal.io/server v1.32.0-158.0/go.mod h1:5HHOkpEzSikoCT1CL54dtJC+qpyAwHKz47bHZHJAIp4= +go.temporal.io/server v1.29.0-135.0.0.20260720172935-768cd1f81135 h1:8dmfUYYNLD+C5IiJ/g6cEl7m5ZjHNgWvujMg/Gf64m8= +go.temporal.io/server v1.29.0-135.0.0.20260720172935-768cd1f81135/go.mod h1:93eqJ9osXy/XBq5Wcev1+fPUt84JNKssQFskr7ICAr4= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -509,15 +515,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -530,8 +536,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -543,8 +549,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -553,8 +559,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -571,15 +577,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -587,8 +593,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -605,8 +611,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/temporalcli/commands.activity.go b/internal/temporalcli/commands.activity.go index b772869dd..ccaa79519 100644 --- a/internal/temporalcli/commands.activity.go +++ b/internal/temporalcli/commands.activity.go @@ -1,6 +1,7 @@ package temporalcli import ( + "cmp" "context" "encoding/json" "errors" @@ -8,6 +9,7 @@ import ( "time" "github.com/fatih/color" + "github.com/google/uuid" "github.com/temporalio/cli/internal/printer" activitypb "go.temporal.io/api/activity/v1" "go.temporal.io/api/batch/v1" @@ -40,6 +42,8 @@ type ( } ) +const activityDeleteWarning = "WARNING: Deleting Standalone Activity Executions in a global Namespace removes them from all replicas. Requests sent to a passive cluster are forwarded to the active cluster by default; to target the passive cluster directly, specify `--grpc-meta xdc-redirection=false`." + func (c *TemporalActivityStartCommand) run(cctx *CommandContext, args []string) error { cl, err := dialClient(cctx, &c.Parent.ClientOptions) if err != nil { @@ -562,6 +566,65 @@ func (c *TemporalActivityCountCommand) run(cctx *CommandContext, args []string) return nil } +func (s *ActivityReferenceOrBatchOptions) activityExecOrBatch( + cctx *CommandContext, + namespace string, + cl client.Client, + yesFlag bool, + overrides singleOrBatchOverrides, +) (*client.GetActivityHandleOptions, *workflowservice.StartBatchOperationRequest, error) { + // If activity is set, we return activity handle options with activity ID and run ID + if s.ActivityId != "" { + if s.Query != "" { + return nil, nil, fmt.Errorf("cannot set query when activity ID is set") + } else if yesFlag && !overrides.AllowYesWithActivityID { + return nil, nil, fmt.Errorf("cannot set 'yes' when activity ID is set") + } else if s.Rps != 0 { + return nil, nil, fmt.Errorf("cannot set rps when activity ID is set") + } + return &client.GetActivityHandleOptions{ + ActivityID: s.ActivityId, + RunID: s.RunId, + }, nil, nil + } + + // Check query is set properly + if s.Query == "" { + return nil, nil, fmt.Errorf("must set either activity ID or query") + } else if s.ActivityId != "" { // This is redundant, but kept for completeness + return nil, nil, fmt.Errorf("cannot set activity ID when query is set") + } else if s.RunId != "" { + return nil, nil, fmt.Errorf("cannot set run ID when query is set") + } + + // 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 yesFlag { + promptMessage = fmt.Sprintf("Start batch against standalone activities matching query %q? y/N", s.Query) + } else { + count, err := cl.CountActivities(cctx, client.CountActivitiesOptions{Query: s.Query}) + if err != nil { + return nil, nil, fmt.Errorf("failed counting standalone activities from query: %w", err) + } + promptMessage = fmt.Sprintf("Start batch against approximately %v standalone activities(s)? y/N", count.Count) + } + isYes, err := cctx.promptYes(promptMessage, yesFlag) + if err != nil { + return nil, nil, err + } else if !isYes { + // We consider this a command failure + return nil, nil, fmt.Errorf("user denied confirmation") + } + + return nil, &workflowservice.StartBatchOperationRequest{ + MaxOperationsPerSecond: s.Rps, + Namespace: namespace, + JobId: uuid.NewString(), + VisibilityQuery: s.Query, + }, nil +} + func (c *TemporalActivityCancelCommand) run(cctx *CommandContext, args []string) error { cl, err := dialClient(cctx, &c.Parent.ClientOptions) if err != nil { @@ -569,14 +632,41 @@ func (c *TemporalActivityCancelCommand) run(cctx *CommandContext, args []string) } defer cl.Close() - handle := cl.GetActivityHandle(client.GetActivityHandleOptions{ - ActivityID: c.ActivityId, - RunID: c.RunId, - }) - if err := handle.Cancel(cctx, client.CancelActivityOptions{Reason: c.Reason}); err != nil { - return fmt.Errorf("failed to request activity cancellation: %w", err) + opts := ActivityReferenceOrBatchOptions{ + ActivityId: c.ActivityId, + RunId: c.RunId, + Query: c.Query, + Rps: c.Rps, + } + + activityOptions, batchReq, err := opts.activityExecOrBatch(cctx, c.Parent.Namespace, cl, c.Yes, singleOrBatchOverrides{}) + if err != nil { + return err + } + + if activityOptions != nil { + handle := cl.GetActivityHandle(*activityOptions) + if err := handle.Cancel(cctx, client.CancelActivityOptions{Reason: c.Reason}); err != nil { + return fmt.Errorf("failed to request activity cancellation: %w", err) + } + cctx.Printer.Println("Cancellation requested") + } else { // batchReq != nil + cancelActivitiesOperation := &batch.BatchOperationCancelActivities{ + Identity: c.Parent.Identity, + // do not fallback to defaultReason, to be consistent with single activity cancel + Reason: c.Reason, + } + + // Reason in batch request falls back to defaultReason + batchReq.Reason = cmp.Or(c.Reason, defaultReason()) + batchReq.Operation = &workflowservice.StartBatchOperationRequest_CancelActivitiesOperation{ + CancelActivitiesOperation: cancelActivitiesOperation, + } + + if err := startBatchJob(cctx, cl, batchReq); err != nil { + return err + } } - cctx.Printer.Println("Cancellation requested") return nil } @@ -587,24 +677,117 @@ func (c *TemporalActivityTerminateCommand) run(cctx *CommandContext, args []stri } defer cl.Close() - // The CLI adds a default for terminate but not cancel. - // This matches the behavior for workflows. - reason := c.Reason - if reason == "" { - reason = defaultReason() + opts := ActivityReferenceOrBatchOptions{ + ActivityId: c.ActivityId, + RunId: c.RunId, + Query: c.Query, + Rps: c.Rps, } - handle := cl.GetActivityHandle(client.GetActivityHandleOptions{ - ActivityID: c.ActivityId, - RunID: c.RunId, + + activityOptions, batchReq, err := opts.activityExecOrBatch(cctx, c.Parent.Namespace, cl, c.Yes, singleOrBatchOverrides{}) + if err != nil { + return err + } + + // Reason for single terminate or batch request falls back to defaultReason + reason := cmp.Or(c.Reason, defaultReason()) + + if activityOptions != nil { + // The CLI adds a default for terminate but not cancel. + // This matches the behavior for workflows. + handle := cl.GetActivityHandle(*activityOptions) + // Terminate may fail if the activity doesn't exist or has already completed. + if err := handle.Terminate(cctx, client.TerminateActivityOptions{Reason: reason}); err != nil { + return fmt.Errorf("failed to terminate activity: %w", err) + } + cctx.Printer.Println("Activity terminated") + } else { // batchReq != nil + terminateActivitiesOperation := &batch.BatchOperationTerminateActivities{ + Identity: c.Parent.Identity, + Reason: reason, + } + + batchReq.Reason = reason + batchReq.Operation = &workflowservice.StartBatchOperationRequest_TerminateActivitiesOperation{ + TerminateActivitiesOperation: terminateActivitiesOperation, + } + + if err := startBatchJob(cctx, cl, batchReq); err != nil { + return err + } + } + + return nil +} + +func (c *TemporalActivityDeleteCommand) run(cctx *CommandContext, args []string) error { + cl, err := dialClient(cctx, &c.Parent.ClientOptions) + if err != nil { + return err + } + defer cl.Close() + + // Only warn when the namespace is global, or can't get the namespace info + nsResp, nsErr := cl.WorkflowService().DescribeNamespace(cctx, &workflowservice.DescribeNamespaceRequest{ + Namespace: c.Parent.Namespace, }) - // Terminate may fail if the activity doesn't exist or has already completed. - if err := handle.Terminate(cctx, client.TerminateActivityOptions{Reason: reason}); err != nil { - return fmt.Errorf("failed to terminate activity: %w", err) + if nsErr != nil || nsResp.GetIsGlobalNamespace() { + fmt.Fprintln(cctx.Options.Stderr, activityDeleteWarning) } - cctx.Printer.Println("Activity terminated") + + opts := ActivityReferenceOrBatchOptions{ + ActivityId: c.ActivityId, + RunId: c.RunId, + Query: c.Query, + Rps: c.Rps, + } + + activityOptions, batchReq, err := opts.activityExecOrBatch(cctx, c.Parent.Namespace, cl, c.Yes, singleOrBatchOverrides{ + AllowYesWithActivityID: true, + }) + if err != nil { + return err + } + + if activityOptions != nil { + yes, err := cctx.promptYes(activityDeleteSingleConfirmationMessage(activityOptions), c.Yes) + if err != nil { + return err + } else if !yes { + return fmt.Errorf("user denied confirmation") + } + _, err = cl.WorkflowService().DeleteActivityExecution(cctx, &workflowservice.DeleteActivityExecutionRequest{ + Namespace: c.Parent.Namespace, + ActivityId: c.ActivityId, + RunId: c.RunId, + }) + if err != nil { + return fmt.Errorf("failed to delete standalone activity: %w", err) + } + cctx.Printer.Println("Delete activity succeeded") + } else { // batchReq != nil + deleteActivitiesOperation := &batch.BatchOperationDeleteActivities{} + batchReq.Reason = cmp.Or(c.Reason, defaultReason()) + batchReq.Operation = &workflowservice.StartBatchOperationRequest_DeleteActivitiesOperation{ + DeleteActivitiesOperation: deleteActivitiesOperation, + } + + if err := startBatchJob(cctx, cl, batchReq); err != nil { + return err + } + } + return nil } +func activityDeleteSingleConfirmationMessage(activityOptions *client.GetActivityHandleOptions) string { + action := fmt.Sprintf("Delete Standalone Activity %q", activityOptions.ActivityID) + if activityOptions.RunID != "" { + action += fmt.Sprintf(" with Run ID %q", activityOptions.RunID) + } + return fmt.Sprintf("%s? y/N", action) +} + func (c *TemporalActivityCompleteCommand) run(cctx *CommandContext, args []string) error { cl, err := dialClient(cctx, &c.Parent.ClientOptions) if err != nil { diff --git a/internal/temporalcli/commands.activity_test.go b/internal/temporalcli/commands.activity_test.go index eedd73849..be2bf9c2e 100644 --- a/internal/temporalcli/commands.activity_test.go +++ b/internal/temporalcli/commands.activity_test.go @@ -14,6 +14,7 @@ import ( "go.temporal.io/api/history/v1" "go.temporal.io/api/serviceerror" "go.temporal.io/api/workflowservice/v1" + "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" "go.temporal.io/sdk/converter" "go.temporal.io/sdk/temporal" @@ -1247,6 +1248,38 @@ func (s *SharedServerSuite) TestActivity_Terminate() { s.Contains(err.Error(), "terminated") } +func (s *SharedServerSuite) TestActivity_Delete_Success() { + activityStarted := make(chan struct{}) + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + close(activityStarted) + <-ctx.Done() + return nil, ctx.Err() + }) + + started := s.startActivity("delete-test") + runID := started["runId"].(string) + <-activityStarted + + res := s.Execute( + "activity", "delete", + "--activity-id", "delete-test", + "--run-id", runID, + "--address", s.Address(), + "-y", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Delete activity succeeded") + + s.Eventually(func() bool { + handle := s.Client.GetActivityHandle(client.GetActivityHandleOptions{ + ActivityID: "delete-test", + RunID: runID, + }) + err := handle.Get(s.Context, nil) + return err != nil && strings.Contains(err.Error(), "activity not found") + }, 5*time.Second, 200*time.Millisecond) +} + func (s *SharedServerSuite) TestActivity_SearchAttributes() { s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { return nil, nil @@ -1449,3 +1482,290 @@ func (s *SharedServerSuite) TestActivity_Terminate_DefaultReason_NoUnknownUser() s.Contains(failureMsg, "Requested from CLI by") s.NotContains(failureMsg, "") } + +// batch operators (cancel, terminate, delete) on standalone activities +func (s *SharedServerSuite) TestActivity_CancelTerminateDelete_BatchSuccess() { + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + // don't complete the activity + return nil, activity.ErrResultPending + }) + + for _, operator := range []string{"cancel", "terminate", "delete"} { + uniqueKW := operator + "-" + uuid.NewString()[:8] + activityIds := make([]string, 0, 5) + for i := 0; i < 5; i++ { + activityId := fmt.Sprintf("%s-test-%d", operator, i) + activityIds = append(activityIds, activityId) + s.startActivity(activityId, + "--search-attribute", fmt.Sprintf(`CustomKeywordField="%s"`, uniqueKW), + ) + } + + // Wait for all to be visible + s.Eventually(func() bool { + res := s.Execute( + "activity", "list", + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s" AND ExecutionStatus = "Running"`, uniqueKW), + ) + return res.Err == nil && strings.Count(res.Stdout.String(), operator+"-test-") >= 5 + }, 5*time.Second, 200*time.Millisecond) + + // Send cancel, terminate or delete + reason := "test batch " + operator + res := s.Execute( + "activity", operator, + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + "--reason", reason, + "-y", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Started batch") + + // get job ID for later check. + lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n") + s.Equal(2, len(lines), "expected one success line") + parts := strings.Split(lines[1], ":") + s.Equal(2, len(parts), "expected success line to contain job ID") + jobId := strings.TrimSpace(parts[1]) + + switch operator { + case "cancel": + // Wait for all to be canceled + s.Eventually(func() bool { + count := 0 + for _, activityId := range activityIds { + handle := s.Client.GetActivityHandle(client.GetActivityHandleOptions{ + ActivityID: activityId, + }) + desc, err := handle.Describe(s.Context, client.DescribeActivityOptions{}) + if err == nil && desc.RunState.String() == "CancelRequested" { + count++ + } + } + return count >= 5 + }, 5*time.Second, 200*time.Millisecond) + + case "terminate": + // Wait for all to be terminated + s.Eventually(func() bool { + res := s.Execute( + "activity", "list", + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s" AND ExecutionStatus = "Terminated"`, uniqueKW), + ) + return res.Err == nil && strings.Count(res.Stdout.String(), operator+"-test-") >= 5 + }, 5*time.Second, 200*time.Millisecond) + + case "delete": + // Wait for all to be deleted + s.Eventually(func() bool { + res := s.Execute( + "activity", "list", + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + ) + return res.Err == nil && strings.Count(res.Stdout.String(), operator+"-test-") == 0 + }, 5*time.Second, 200*time.Millisecond) + } + + // check batch job has no failure + res = s.Execute( + "batch", "describe", + "--address", s.Address(), + "--job-id", jobId, + ) + s.NoError(res.Err) + out := res.Stdout.String() + s.ContainsOnSameLine(out, "CompletedCount", "5/5") + s.ContainsOnSameLine(out, "FailureCount", "0/5") + + // check for reason + res = s.Execute( + "batch", "describe", + "--address", s.Address(), + "--job-id", jobId, + "-o", "json", + ) + s.NoError(res.Err) + var jsonOut map[string]any + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.Equal(reason, jsonOut["reason"]) + } +} + +func (s *SharedServerSuite) TestActivity_CancelTerminateDelete_BatchFailed() { + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + // don't complete the activity + return nil, activity.ErrResultPending + }) + + for _, operator := range []string{"cancel", "terminate", "delete"} { + uniqueKW := operator + "-test-fail-" + uuid.NewString()[:8] + activityId := operator + "-test-fail" + runId := operator + "-test-run-id" + + query := fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW) + res := s.Execute( + "activity", operator, + "--address", s.Address(), + "--activity-id", activityId, + "--query", query, + "-y", + ) + s.Error(res.Err) + s.Contains(res.Err.Error(), "cannot set query when activity ID is set") + + res = s.Execute( + "activity", operator, + "--address", s.Address(), + "--run-id", runId, + ) + s.Error(res.Err) + s.Contains(res.Err.Error(), "must set either activity ID or query") + + res = s.Execute( + "activity", operator, + "--address", s.Address(), + "--activity-id", activityId, + "--run-id", runId, + "--query", query, + "-y", + ) + s.Error(res.Err) + s.Contains(res.Err.Error(), "cannot set query when activity ID is set") + + res = s.Execute( + "activity", operator, + "--address", s.Address(), + "--activity-id", activityId, + "--rps", "10", + ) + s.Error(res.Err) + s.Contains(res.Err.Error(), "cannot set rps when activity ID is set") + + res = s.Execute( + "activity", operator, + "--address", s.Address(), + "--run-id", runId, + "--query", query, + "-y", + ) + s.Error(res.Err) + s.Contains(res.Err.Error(), "cannot set run ID when query is set") + + if operator != "delete" { + res = s.Execute( + "activity", operator, + "--address", s.Address(), + "--activity-id", activityId, + "-y", + ) + s.Error(res.Err) + s.Contains(res.Err.Error(), "cannot set 'yes' when activity ID is set") + } + } +} + +func (s *SharedServerSuite) TestActivity_CancelTerminateDelete_BatchDefaultReason() { + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + // don't complete the activity + return nil, activity.ErrResultPending + }) + + for _, operator := range []string{"cancel", "terminate", "delete"} { + // Send cancel, terminate or delete with default reason + res := s.Execute( + "activity", operator, + "--address", s.Address(), + "--query", `CustomKeywordField = "UnknownValue"`, + "-y", + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Started batch") + + // get job ID for later check. + lines := strings.Split(strings.TrimSpace(res.Stdout.String()), "\n") + s.Equal(2, len(lines), "expected two lines, confirmation and success message") + parts := strings.Split(lines[1], ":") + s.Equal(2, len(parts), "expected success line to contain job ID") + jobId := strings.TrimSpace(parts[1]) + + // check for reason + res = s.Execute( + "batch", "describe", + "--address", s.Address(), + "--job-id", jobId, + "-o", "json", + ) + s.NoError(res.Err) + var jsonOut map[string]any + s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) + s.Contains(jsonOut["reason"], "Requested from CLI by") + } +} + +func (s *SharedServerSuite) TestActivity_CancelTerminateDelete_BatchRateLimit() { + s.Worker().OnDevActivity(func(ctx context.Context, a any) (any, error) { + // don't complete the activity + return nil, activity.ErrResultPending + }) + + // following borrowed from testTerminateBatchWorkflow to intercept batch request + var lastRequestLock sync.Mutex + var startBatchRequest *workflowservice.StartBatchOperationRequest + s.CommandHarness.Options.AdditionalClientGRPCDialOptions = append( + s.CommandHarness.Options.AdditionalClientGRPCDialOptions, + grpc.WithChainUnaryInterceptor(func( + ctx context.Context, + method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, + ) error { + lastRequestLock.Lock() + if r, ok := req.(*workflowservice.StartBatchOperationRequest); ok { + startBatchRequest = r + } + lastRequestLock.Unlock() + return invoker(ctx, method, req, reply, cc, opts...) + }), + ) + + for _, operator := range []string{"cancel", "terminate", "delete"} { + uniqueKW := operator + "-rps-" + uuid.NewString()[:8] + iterations := 2 + activityIds := make([]string, 0, iterations) + for i := 0; i < iterations; i++ { + activityId := fmt.Sprintf("%s-test-rps-%d", operator, i) + activityIds = append(activityIds, activityId) + s.startActivity(activityId, + "--search-attribute", fmt.Sprintf(`CustomKeywordField="%s"`, uniqueKW), + ) + } + + // Wait for all to be visible + s.Eventually(func() bool { + res := s.Execute( + "activity", "list", + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s" AND ExecutionStatus = "Running"`, uniqueKW), + ) + return res.Err == nil && strings.Count(res.Stdout.String(), operator+"-test-") >= iterations + }, 5*time.Second, 200*time.Millisecond) + + // Send cancel, terminate or delete + var rps float32 = 1 + s.CommandHarness.Stdin.WriteString("y\n") + res := s.Execute( + "activity", operator, + "--address", s.Address(), + "--query", fmt.Sprintf(`CustomKeywordField = "%s"`, uniqueKW), + "--rps", fmt.Sprint(rps), + ) + s.NoError(res.Err) + s.Contains(res.Stdout.String(), "Started batch") + + s.NotNil(startBatchRequest) + s.Equal(rps, startBatchRequest.MaxOperationsPerSecond) + } +} diff --git a/internal/temporalcli/commands.batch_test.go b/internal/temporalcli/commands.batch_test.go index f09f4398f..3c921798c 100644 --- a/internal/temporalcli/commands.batch_test.go +++ b/internal/temporalcli/commands.batch_test.go @@ -67,7 +67,7 @@ func (s *SharedServerSuite) TestBatchJob_Describe() { var jsonOut map[string]any s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut)) s.Equal(jobId, jsonOut["jobId"]) - s.Equal("BATCH_OPERATION_TYPE_TERMINATE", jsonOut["operationType"]) + s.Equal("BATCH_OPERATION_TYPE_TERMINATE_WORKFLOW", jsonOut["operationType"]) s.Equal("REASON", jsonOut["reason"]) }) }) diff --git a/internal/temporalcli/commands.gen.go b/internal/temporalcli/commands.gen.go index c27b31630..d3f709f93 100644 --- a/internal/temporalcli/commands.gen.go +++ b/internal/temporalcli/commands.gen.go @@ -151,6 +151,22 @@ func (v *DeploymentReferenceOptions) BuildFlags(f *pflag.FlagSet) { _ = cobra.MarkFlagRequired(f, "build-id") } +type ActivityReferenceOrBatchOptions struct { + ActivityId string + RunId string + Query string + Rps float32 + FlagSet *pflag.FlagSet +} + +func (v *ActivityReferenceOrBatchOptions) BuildFlags(f *pflag.FlagSet) { + v.FlagSet = f + f.StringVarP(&v.ActivityId, "activity-id", "a", "", "Activity ID. You must set either --activity-id or --query.") + f.StringVarP(&v.RunId, "run-id", "r", "", "Activity Run ID. If not set, targets the latest run. Only use with --activity-id. Cannot use with --query.") + f.StringVarP(&v.Query, "query", "q", "", "Content for an SQL-like `QUERY` List Filter. You must set either --activity-id or --query. Note: Using --query for batch activity operations is an experimental feature and may change in the future.") + f.Float32Var(&v.Rps, "rps", 0, "Limit batch's requests per second. Only allowed when --query is present.") +} + type SingleActivityOrBatchOptions struct { WorkflowId string Query string @@ -544,6 +560,7 @@ func NewTemporalActivityCommand(cctx *CommandContext, parent *TemporalCommand) * s.Command.AddCommand(&NewTemporalActivityCancelCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalActivityCompleteCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalActivityCountCommand(cctx, &s).Command) + s.Command.AddCommand(&NewTemporalActivityDeleteCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalActivityDescribeCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalActivityExecuteCommand(cctx, &s).Command) s.Command.AddCommand(&NewTemporalActivityFailCommand(cctx, &s).Command) @@ -562,8 +579,9 @@ func NewTemporalActivityCommand(cctx *CommandContext, parent *TemporalCommand) * type TemporalActivityCancelCommand struct { Parent *TemporalActivityCommand Command cobra.Command - ActivityReferenceOptions + ActivityReferenceOrBatchOptions Reason string + Yes bool } func NewTemporalActivityCancelCommand(cctx *CommandContext, parent *TemporalActivityCommand) *TemporalActivityCancelCommand { @@ -573,13 +591,14 @@ func NewTemporalActivityCancelCommand(cctx *CommandContext, parent *TemporalActi s.Command.Use = "cancel [flags]" s.Command.Short = "Request cancellation of a Standalone Activity (Experimental)" if hasHighlighting { - s.Command.Long = "Request cancellation of a Standalone Activity.\n\n\x1b[1mtemporal activity cancel \\\n --activity-id YourActivityId\x1b[0m\n\nRequesting cancellation transitions the Activity's run state\nto CancelRequested. If the Activity is heartbeating, a\ncancellation error will be raised when the next heartbeat\nresponse is received; if the Activity allows this error to\npropagate, the Activity transitions to canceled status." + s.Command.Long = "Request cancellation of a Standalone Activity.\n\n\x1b[1mtemporal activity cancel \\\n --activity-id YourActivityId\x1b[0m\n\nRequesting cancellation transitions the Activity's run state\nto CancelRequested. If the Activity is heartbeating, a\ncancellation error will be raised when the next heartbeat\nresponse is received; if the Activity allows this error to\npropagate, the Activity transitions to canceled status.\n\nA visibility Query lets you send bulk cancellations to Standalone Activity\nExecutions matching the results:\n\n\x1b[1mtemporal activity cancel \\\n --query YourQuery \\\n --reason YourReason\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about Search Attributes\nand Query creation. See \x1b[1mtemporal batch --help\x1b[0m for a quick reference." } else { - s.Command.Long = "Request cancellation of a Standalone Activity.\n\n```\ntemporal activity cancel \\\n --activity-id YourActivityId\n```\n\nRequesting cancellation transitions the Activity's run state\nto CancelRequested. If the Activity is heartbeating, a\ncancellation error will be raised when the next heartbeat\nresponse is received; if the Activity allows this error to\npropagate, the Activity transitions to canceled status." + s.Command.Long = "Request cancellation of a Standalone Activity.\n\n```\ntemporal activity cancel \\\n --activity-id YourActivityId\n```\n\nRequesting cancellation transitions the Activity's run state\nto CancelRequested. If the Activity is heartbeating, a\ncancellation error will be raised when the next heartbeat\nresponse is received; if the Activity allows this error to\npropagate, the Activity transitions to canceled status.\n\nA visibility Query lets you send bulk cancellations to Standalone Activity\nExecutions matching the results:\n\n```\ntemporal activity cancel \\\n --query YourQuery \\\n --reason YourReason\n```\n\nVisit https://docs.temporal.io/visibility to read more about Search Attributes\nand Query creation. See `temporal batch --help` for a quick reference." } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for cancellation.") - s.ActivityReferenceOptions.BuildFlags(s.Command.Flags()) + s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for cancellation. Also used as reason for batch operation with --query, which defaults to a message with the current user's name.") + s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm. Only allowed when --query is present.") + s.ActivityReferenceOrBatchOptions.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) @@ -650,6 +669,37 @@ func NewTemporalActivityCountCommand(cctx *CommandContext, parent *TemporalActiv return &s } +type TemporalActivityDeleteCommand struct { + Parent *TemporalActivityCommand + Command cobra.Command + ActivityReferenceOrBatchOptions + Reason string + Yes bool +} + +func NewTemporalActivityDeleteCommand(cctx *CommandContext, parent *TemporalActivityCommand) *TemporalActivityDeleteCommand { + var s TemporalActivityDeleteCommand + s.Parent = parent + s.Command.DisableFlagsInUseLine = true + s.Command.Use = "delete [flags]" + s.Command.Short = "Delete a Standalone Activity Execution (Experimental)" + if hasHighlighting { + s.Command.Long = "Delete a Standalone Activity Execution and its Event History.\n\n\x1b[1mtemporal activity delete \\\n --activity-id YourActivityId\x1b[0m\n\nThe removal executes asynchronously. If the Execution is Running, the Service\nterminates it before deletion.\n\nA visibility Query lets you send bulk delete to Standalone Activity\nExecutions matching the results:\n\n\x1b[1mtemporal activity delete \\\n --query YourQuery\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about Search Attributes\nand Query creation. See \x1b[1mtemporal batch --help\x1b[0m for a quick reference." + } else { + s.Command.Long = "Delete a Standalone Activity Execution and its Event History.\n\n```\ntemporal activity delete \\\n --activity-id YourActivityId\n```\n\nThe removal executes asynchronously. If the Execution is Running, the Service\nterminates it before deletion.\n\nA visibility Query lets you send bulk delete to Standalone Activity\nExecutions matching the results:\n\n```\ntemporal activity delete \\\n --query YourQuery\n```\n\nVisit https://docs.temporal.io/visibility to read more about Search Attributes\nand Query creation. See `temporal batch --help` for a quick reference." + } + s.Command.Args = cobra.NoArgs + s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for batch operation. Only use with --query. Defaults to a message with the current user's name.") + s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm.") + s.ActivityReferenceOrBatchOptions.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 TemporalActivityDescribeCommand struct { Parent *TemporalActivityCommand Command cobra.Command @@ -907,8 +957,9 @@ func NewTemporalActivityStartCommand(cctx *CommandContext, parent *TemporalActiv type TemporalActivityTerminateCommand struct { Parent *TemporalActivityCommand Command cobra.Command - ActivityReferenceOptions + ActivityReferenceOrBatchOptions Reason string + Yes bool } func NewTemporalActivityTerminateCommand(cctx *CommandContext, parent *TemporalActivityCommand) *TemporalActivityTerminateCommand { @@ -918,13 +969,14 @@ func NewTemporalActivityTerminateCommand(cctx *CommandContext, parent *TemporalA s.Command.Use = "terminate [flags]" s.Command.Short = "Forcefully end a Standalone Activity (Experimental)" if hasHighlighting { - s.Command.Long = "Terminate a Standalone Activity.\n\n\x1b[1mtemporal activity terminate \\\n --activity-id YourActivityId \\\n --reason YourReason\x1b[0m\n\nActivity code cannot see or respond to terminations." + s.Command.Long = "Terminate a Standalone Activity.\n\n\x1b[1mtemporal activity terminate \\\n --activity-id YourActivityId \\\n --reason YourReason\x1b[0m\n\nActivity code cannot see or respond to terminations.\n\nA visibility Query lets you send bulk terminations to Standalone Activity\nExecutions matching the results:\n\n\x1b[1mtemporal activity terminate \\\n --query YourQuery \\\n --reason YourReason\x1b[0m\n\nVisit https://docs.temporal.io/visibility to read more about Search Attributes\nand Query creation. See \x1b[1mtemporal batch --help\x1b[0m for a quick reference." } else { - s.Command.Long = "Terminate a Standalone Activity.\n\n```\ntemporal activity terminate \\\n --activity-id YourActivityId \\\n --reason YourReason\n```\n\nActivity code cannot see or respond to terminations." + s.Command.Long = "Terminate a Standalone Activity.\n\n```\ntemporal activity terminate \\\n --activity-id YourActivityId \\\n --reason YourReason\n```\n\nActivity code cannot see or respond to terminations.\n\nA visibility Query lets you send bulk terminations to Standalone Activity\nExecutions matching the results:\n\n```\ntemporal activity terminate \\\n --query YourQuery \\\n --reason YourReason\n```\n\nVisit https://docs.temporal.io/visibility to read more about Search Attributes\nand Query creation. See `temporal batch --help` for a quick reference." } s.Command.Args = cobra.NoArgs - s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for termination. Defaults to a message with the current user's name.") - s.ActivityReferenceOptions.BuildFlags(s.Command.Flags()) + s.Command.Flags().StringVar(&s.Reason, "reason", "", "Reason for termination. Defaults to a message with the current user's name. Also used as reason for batch operation with --query.") + s.Command.Flags().BoolVarP(&s.Yes, "yes", "y", false, "Don't prompt to confirm. Only allowed when --query is present.") + s.ActivityReferenceOrBatchOptions.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) diff --git a/internal/temporalcli/commands.workflow.go b/internal/temporalcli/commands.workflow.go index b6736c4f3..688bcc082 100644 --- a/internal/temporalcli/commands.workflow.go +++ b/internal/temporalcli/commands.workflow.go @@ -580,6 +580,7 @@ func defaultReason() string { type singleOrBatchOverrides struct { AllowReasonWithWorkflowID bool AllowYesWithWorkflowID bool + AllowYesWithActivityID bool } func (s *SingleWorkflowOrBatchOptions) workflowExecOrBatch( diff --git a/internal/temporalcli/commands.yaml b/internal/temporalcli/commands.yaml index 9700c1547..4a27aa46c 100644 --- a/internal/temporalcli/commands.yaml +++ b/internal/temporalcli/commands.yaml @@ -193,12 +193,33 @@ commands: cancellation error will be raised when the next heartbeat response is received; if the Activity allows this error to propagate, the Activity transitions to canceled status. + + A visibility Query lets you send bulk cancellations to Standalone Activity + Executions matching the results: + + ``` + temporal activity cancel \ + --query YourQuery \ + --reason YourReason + ``` + + Visit https://docs.temporal.io/visibility to read more about Search Attributes + and Query creation. See `temporal batch --help` for a quick reference. option-sets: - - activity-reference + - activity-reference-or-batch options: - name: reason type: string - description: Reason for cancellation. + description: | + Reason for cancellation. + Also used as reason for batch operation with --query, + which defaults to a message with the current user's name. + - name: yes + type: bool + short: y + description: | + Don't prompt to confirm. + Only allowed when --query is present. - name: temporal activity complete summary: Mark an activity as completed successfully with a result @@ -675,14 +696,71 @@ commands: ``` Activity code cannot see or respond to terminations. + + A visibility Query lets you send bulk terminations to Standalone Activity + Executions matching the results: + + ``` + temporal activity terminate \ + --query YourQuery \ + --reason YourReason + ``` + + Visit https://docs.temporal.io/visibility to read more about Search Attributes + and Query creation. See `temporal batch --help` for a quick reference. option-sets: - - activity-reference + - activity-reference-or-batch options: - name: reason type: string description: | Reason for termination. Defaults to a message with the current user's name. + Also used as reason for batch operation with --query. + - name: yes + type: bool + short: y + description: | + Don't prompt to confirm. + Only allowed when --query is present. + + - name: temporal activity delete + summary: Delete a Standalone Activity Execution (Experimental) + description: | + Delete a Standalone Activity Execution and its Event History. + + ``` + temporal activity delete \ + --activity-id YourActivityId + ``` + + The removal executes asynchronously. If the Execution is Running, the Service + terminates it before deletion. + + A visibility Query lets you send bulk delete to Standalone Activity + Executions matching the results: + + ``` + temporal activity delete \ + --query YourQuery + ``` + + Visit https://docs.temporal.io/visibility to read more about Search Attributes + and Query creation. See `temporal batch --help` for a quick reference. + option-sets: + - activity-reference-or-batch + options: + - name: reason + type: string + description: | + Reason for batch operation. + Only use with --query. + Defaults to a message with the current user's name. + - name: yes + type: bool + short: y + description: | + Don't prompt to confirm. - name: temporal batch summary: Manage running batch jobs @@ -5021,6 +5099,36 @@ option-sets: description: Build ID for a Worker Deployment. required: true + # Combines activity-reference and --query, with an experimental note. + # Cannot extend activity-reference, because activity-id is not required. + - name: activity-reference-or-batch + options: + - name: activity-id + type: string + short: a + description: | + Activity ID. + You must set either --activity-id or --query. + - name: run-id + type: string + short: r + description: | + Activity Run ID. + If not set, targets the latest run. + Only use with --activity-id. Cannot use with --query. + - name: query + type: string + short: q + description: | + Content for an SQL-like `QUERY` List Filter. + You must set either --activity-id or --query. + Note: Using --query for batch activity operations is an experimental feature and may change in the future. + - name: rps + type: float + description: | + Limit batch's requests per second. + Only allowed when --query is present. + # Duplicate of single-workflow-or-batch with an experimental note on --query. # Cannot extend the shared option set because workflow commands that also use # it (workflow cancel, workflow count, etc.) are not experimental. diff --git a/internal/temporalcli/commands_test.go b/internal/temporalcli/commands_test.go index 10e46aa91..3146206c4 100644 --- a/internal/temporalcli/commands_test.go +++ b/internal/temporalcli/commands_test.go @@ -249,6 +249,9 @@ func (s *SharedServerSuite) SetupSuite() { // worker heartbeating "frontend.WorkerHeartbeatsEnabled": true, "frontend.ListWorkersEnabled": true, + // Required by TestActivity_CancelTerminateDelete_* + // to enable batch operations on standalone activities. + "frontend.enableBatchOperationsForStandaloneActivities": true, }, }, })