diff --git a/README.md b/README.md index db889cf..f7f4800 100644 --- a/README.md +++ b/README.md @@ -149,13 +149,14 @@ if err != nil { ## Live Activities -There are five types of Live Activities: +There are six types of Live Activities: - `stats`: best for showing business numbers side by side, such as revenue, sales, new users, conversion, refunds, or any other value you want visible at a glance - `metrics`: best for live percentage values that change often, like server CPU, memory usage, disk usage, or error rate - `segmented_progress`: best for anything that moves through clear stages, like deployments, onboarding flows, backups, ETL pipelines, migrations, and AI agent runs - `progress`: best for tracking real-time progress with percentage, like tasks, backups, migrations, syncs, or uploads - `alert`: best for status updates, such as feature adoption, reactivation, onboarding blockers, incidents, escalations, and other operational states +- `timer`: best for countdowns and elapsed runtime, like benchmark runs, uploads, backups, transcodes, and long-running jobs ### Start & Update Live Activity @@ -283,6 +284,33 @@ activitysmith.LiveActivities.Stream( ) ``` +#### Timer + +
+
+
0 ||
in.numberOfStepsSet ||
@@ -282,9 +288,20 @@ func (in LiveActivityContentStateInput) isSet() bool {
in.percentageSet ||
in.valueSet ||
in.upperLimitSet ||
+ in.durationSecondsSet ||
+ in.countsDownSet ||
in.autoDismissMinutesSet
}
+func (in LiveActivityContentStateInput) applyTimerFields(properties *map[string]interface{}) {
+ if in.DurationSeconds != 0 || in.durationSecondsSet {
+ setAdditionalProperty(properties, "duration_seconds", in.DurationSeconds)
+ }
+ if in.countsDownSet {
+ setAdditionalProperty(properties, "counts_down", in.CountsDown)
+ }
+}
+
func (in LiveActivityContentStateInput) applyAlertFields(properties *map[string]interface{}) {
if in.Message != "" {
setAdditionalProperty(properties, "message", in.Message)
@@ -321,6 +338,7 @@ func (in LiveActivityContentStateInput) applyStart(state *generated.ContentState
if len(in.Metrics) > 0 {
state.SetMetrics(append([]generated.ActivityMetric{}, in.Metrics...))
}
+ in.applyTimerFields(&state.AdditionalProperties)
in.applyAlertFields(&state.AdditionalProperties)
}
@@ -355,6 +373,7 @@ func (in LiveActivityContentStateInput) applyUpdate(state *generated.ContentStat
if len(in.Metrics) > 0 {
state.SetMetrics(append([]generated.ActivityMetric{}, in.Metrics...))
}
+ in.applyTimerFields(&state.AdditionalProperties)
in.applyAlertFields(&state.AdditionalProperties)
}
@@ -396,6 +415,7 @@ func (in LiveActivityContentStateInput) applyEndBase(state *generated.ContentSta
if len(in.Metrics) > 0 {
state.SetMetrics(append([]generated.ActivityMetric{}, in.Metrics...))
}
+ in.applyTimerFields(&state.AdditionalProperties)
in.applyAlertFields(&state.AdditionalProperties)
}
@@ -433,6 +453,7 @@ func (in LiveActivityContentStateInput) applyStream(state *generated.StreamConte
if in.AutoDismissMinutes != 0 || in.autoDismissMinutesSet {
state.SetAutoDismissMinutes(in.AutoDismissMinutes)
}
+ in.applyTimerFields(&state.AdditionalProperties)
in.applyAlertFields(&state.AdditionalProperties)
}
@@ -466,6 +487,18 @@ func (in LiveActivityContentStateInput) WithUpperLimit(v float32) LiveActivityCo
return in
}
+func (in LiveActivityContentStateInput) WithDurationSeconds(v float32) LiveActivityContentStateInput {
+ in.DurationSeconds = v
+ in.durationSecondsSet = true
+ return in
+}
+
+func (in LiveActivityContentStateInput) WithCountsDown(v bool) LiveActivityContentStateInput {
+ in.CountsDown = v
+ in.countsDownSet = true
+ return in
+}
+
func (in LiveActivityContentStateInput) WithAutoDismissMinutes(v int32) LiveActivityContentStateInput {
in.AutoDismissMinutes = v
in.autoDismissMinutesSet = true
@@ -474,29 +507,33 @@ func (in LiveActivityContentStateInput) WithAutoDismissMinutes(v int32) LiveActi
// LiveActivityStartInput is a handwritten DX input with plain optional values.
type LiveActivityStartInput struct {
- ContentState LiveActivityContentStateInput
- Title string
- NumberOfSteps int32
- CurrentStep int32
- Percentage float32
- Value float32
- UpperLimit float32
- Type string
- Subtitle string
- Message string
- Icon LiveActivityAlertIconInput
- Badge LiveActivityAlertBadgeInput
- Color string
- StepColor string
- Metrics []generated.ActivityMetric
- Action *LiveActivityActionInput
- Channels []string
-
- numberOfStepsSet bool
- currentStepSet bool
- percentageSet bool
- valueSet bool
- upperLimitSet bool
+ ContentState LiveActivityContentStateInput
+ Title string
+ NumberOfSteps int32
+ CurrentStep int32
+ Percentage float32
+ Value float32
+ UpperLimit float32
+ DurationSeconds float32
+ CountsDown bool
+ Type string
+ Subtitle string
+ Message string
+ Icon LiveActivityAlertIconInput
+ Badge LiveActivityAlertBadgeInput
+ Color string
+ StepColor string
+ Metrics []generated.ActivityMetric
+ Action *LiveActivityActionInput
+ Channels []string
+
+ numberOfStepsSet bool
+ currentStepSet bool
+ percentageSet bool
+ valueSet bool
+ upperLimitSet bool
+ durationSecondsSet bool
+ countsDownSet bool
}
func (in LiveActivityStartInput) toGenerated() generated.LiveActivityStartRequest {
@@ -529,6 +566,12 @@ func (in LiveActivityStartInput) toGenerated() generated.LiveActivityStartReques
if in.UpperLimit != 0 || in.upperLimitSet {
req.ContentState.SetUpperLimit(in.UpperLimit)
}
+ if in.DurationSeconds != 0 || in.durationSecondsSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "duration_seconds", in.DurationSeconds)
+ }
+ if in.countsDownSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "counts_down", in.CountsDown)
+ }
if in.Subtitle != "" {
req.ContentState.SetSubtitle(in.Subtitle)
}
@@ -589,6 +632,18 @@ func (in LiveActivityStartInput) WithUpperLimit(v float32) LiveActivityStartInpu
return in
}
+func (in LiveActivityStartInput) WithDurationSeconds(v float32) LiveActivityStartInput {
+ in.DurationSeconds = v
+ in.durationSecondsSet = true
+ return in
+}
+
+func (in LiveActivityStartInput) WithCountsDown(v bool) LiveActivityStartInput {
+ in.CountsDown = v
+ in.countsDownSet = true
+ return in
+}
+
func (in LiveActivityStartInput) WithAction(v LiveActivityActionInput) LiveActivityStartInput {
in.Action = &v
return in
@@ -596,29 +651,33 @@ func (in LiveActivityStartInput) WithAction(v LiveActivityActionInput) LiveActiv
// LiveActivityUpdateInput is a handwritten DX input with plain optional values.
type LiveActivityUpdateInput struct {
- ActivityID string
- ContentState LiveActivityContentStateInput
- Title string
- CurrentStep int32
- Percentage float32
- Value float32
- UpperLimit float32
- Type string
- Subtitle string
- Message string
- Icon LiveActivityAlertIconInput
- Badge LiveActivityAlertBadgeInput
- Color string
- StepColor string
- NumberOfSteps int32
- Metrics []generated.ActivityMetric
- Action *LiveActivityActionInput
-
- numberOfStepsSet bool
- currentStepSet bool
- percentageSet bool
- valueSet bool
- upperLimitSet bool
+ ActivityID string
+ ContentState LiveActivityContentStateInput
+ Title string
+ CurrentStep int32
+ Percentage float32
+ Value float32
+ UpperLimit float32
+ DurationSeconds float32
+ CountsDown bool
+ Type string
+ Subtitle string
+ Message string
+ Icon LiveActivityAlertIconInput
+ Badge LiveActivityAlertBadgeInput
+ Color string
+ StepColor string
+ NumberOfSteps int32
+ Metrics []generated.ActivityMetric
+ Action *LiveActivityActionInput
+
+ numberOfStepsSet bool
+ currentStepSet bool
+ percentageSet bool
+ valueSet bool
+ upperLimitSet bool
+ durationSecondsSet bool
+ countsDownSet bool
}
func (in LiveActivityUpdateInput) toGenerated() generated.LiveActivityUpdateRequest {
@@ -649,6 +708,12 @@ func (in LiveActivityUpdateInput) toGenerated() generated.LiveActivityUpdateRequ
if in.UpperLimit != 0 || in.upperLimitSet {
req.ContentState.SetUpperLimit(in.UpperLimit)
}
+ if in.DurationSeconds != 0 || in.durationSecondsSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "duration_seconds", in.DurationSeconds)
+ }
+ if in.countsDownSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "counts_down", in.CountsDown)
+ }
if in.Type != "" {
req.ContentState.SetType(in.Type)
}
@@ -712,6 +777,18 @@ func (in LiveActivityUpdateInput) WithUpperLimit(v float32) LiveActivityUpdateIn
return in
}
+func (in LiveActivityUpdateInput) WithDurationSeconds(v float32) LiveActivityUpdateInput {
+ in.DurationSeconds = v
+ in.durationSecondsSet = true
+ return in
+}
+
+func (in LiveActivityUpdateInput) WithCountsDown(v bool) LiveActivityUpdateInput {
+ in.CountsDown = v
+ in.countsDownSet = true
+ return in
+}
+
func (in LiveActivityUpdateInput) WithAction(v LiveActivityActionInput) LiveActivityUpdateInput {
in.Action = &v
return in
@@ -726,6 +803,8 @@ type LiveActivityEndInput struct {
Percentage float32
Value float32
UpperLimit float32
+ DurationSeconds float32
+ CountsDown bool
Type string
Subtitle string
Message string
@@ -743,6 +822,8 @@ type LiveActivityEndInput struct {
percentageSet bool
valueSet bool
upperLimitSet bool
+ durationSecondsSet bool
+ countsDownSet bool
autoDismissMinutesSet bool
}
@@ -774,6 +855,12 @@ func (in LiveActivityEndInput) toGenerated() generated.LiveActivityEndRequest {
if in.UpperLimit != 0 || in.upperLimitSet {
req.ContentState.SetUpperLimit(in.UpperLimit)
}
+ if in.DurationSeconds != 0 || in.durationSecondsSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "duration_seconds", in.DurationSeconds)
+ }
+ if in.countsDownSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "counts_down", in.CountsDown)
+ }
if in.Type != "" {
req.ContentState.SetType(in.Type)
}
@@ -840,6 +927,18 @@ func (in LiveActivityEndInput) WithUpperLimit(v float32) LiveActivityEndInput {
return in
}
+func (in LiveActivityEndInput) WithDurationSeconds(v float32) LiveActivityEndInput {
+ in.DurationSeconds = v
+ in.durationSecondsSet = true
+ return in
+}
+
+func (in LiveActivityEndInput) WithCountsDown(v bool) LiveActivityEndInput {
+ in.CountsDown = v
+ in.countsDownSet = true
+ return in
+}
+
// WithAutoDismissMinutes forces inclusion of auto_dismiss_minutes, including explicit zero.
func (in LiveActivityEndInput) WithAutoDismissMinutes(v int32) LiveActivityEndInput {
in.AutoDismissMinutes = v
@@ -854,30 +953,34 @@ func (in LiveActivityEndInput) WithAction(v LiveActivityActionInput) LiveActivit
// LiveActivityStreamInput is a handwritten DX input with plain optional values.
type LiveActivityStreamInput struct {
- ContentState LiveActivityContentStateInput
- Title string
- NumberOfSteps int32
- CurrentStep int32
- Percentage float32
- Value float32
- UpperLimit float32
- Type string
- Subtitle string
- Message string
- Icon LiveActivityAlertIconInput
- Badge LiveActivityAlertBadgeInput
- Color string
- StepColor string
- Metrics []generated.ActivityMetric
- Action *LiveActivityActionInput
- Alert *generated.AlertPayload
- Channels []string
-
- numberOfStepsSet bool
- currentStepSet bool
- percentageSet bool
- valueSet bool
- upperLimitSet bool
+ ContentState LiveActivityContentStateInput
+ Title string
+ NumberOfSteps int32
+ CurrentStep int32
+ Percentage float32
+ Value float32
+ UpperLimit float32
+ DurationSeconds float32
+ CountsDown bool
+ Type string
+ Subtitle string
+ Message string
+ Icon LiveActivityAlertIconInput
+ Badge LiveActivityAlertBadgeInput
+ Color string
+ StepColor string
+ Metrics []generated.ActivityMetric
+ Action *LiveActivityActionInput
+ Alert *generated.AlertPayload
+ Channels []string
+
+ numberOfStepsSet bool
+ currentStepSet bool
+ percentageSet bool
+ valueSet bool
+ upperLimitSet bool
+ durationSecondsSet bool
+ countsDownSet bool
}
func (in LiveActivityStreamInput) toGenerated() generated.LiveActivityStreamRequest {
@@ -910,6 +1013,12 @@ func (in LiveActivityStreamInput) toGenerated() generated.LiveActivityStreamRequ
if in.UpperLimit != 0 || in.upperLimitSet {
req.ContentState.SetUpperLimit(in.UpperLimit)
}
+ if in.DurationSeconds != 0 || in.durationSecondsSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "duration_seconds", in.DurationSeconds)
+ }
+ if in.countsDownSet {
+ setAdditionalProperty(&req.ContentState.AdditionalProperties, "counts_down", in.CountsDown)
+ }
if in.Type != "" {
req.ContentState.SetType(in.Type)
}
@@ -972,6 +1081,18 @@ func (in LiveActivityStreamInput) WithUpperLimit(v float32) LiveActivityStreamIn
return in
}
+func (in LiveActivityStreamInput) WithDurationSeconds(v float32) LiveActivityStreamInput {
+ in.DurationSeconds = v
+ in.durationSecondsSet = true
+ return in
+}
+
+func (in LiveActivityStreamInput) WithCountsDown(v bool) LiveActivityStreamInput {
+ in.CountsDown = v
+ in.countsDownSet = true
+ return in
+}
+
func (in LiveActivityStreamInput) WithAction(v LiveActivityActionInput) LiveActivityStreamInput {
in.Action = &v
return in
@@ -979,29 +1100,33 @@ func (in LiveActivityStreamInput) WithAction(v LiveActivityActionInput) LiveActi
// LiveActivityStreamEndInput is an optional payload for ending a managed stream.
type LiveActivityStreamEndInput struct {
- ContentState LiveActivityContentStateInput
- Title string
- NumberOfSteps int32
- CurrentStep int32
- Percentage float32
- Value float32
- UpperLimit float32
- Type string
- Subtitle string
- Message string
- Icon LiveActivityAlertIconInput
- Badge LiveActivityAlertBadgeInput
- Color string
- StepColor string
- Metrics []generated.ActivityMetric
- Action *LiveActivityActionInput
- Alert *generated.AlertPayload
-
- numberOfStepsSet bool
- currentStepSet bool
- percentageSet bool
- valueSet bool
- upperLimitSet bool
+ ContentState LiveActivityContentStateInput
+ Title string
+ NumberOfSteps int32
+ CurrentStep int32
+ Percentage float32
+ Value float32
+ UpperLimit float32
+ DurationSeconds float32
+ CountsDown bool
+ Type string
+ Subtitle string
+ Message string
+ Icon LiveActivityAlertIconInput
+ Badge LiveActivityAlertBadgeInput
+ Color string
+ StepColor string
+ Metrics []generated.ActivityMetric
+ Action *LiveActivityActionInput
+ Alert *generated.AlertPayload
+
+ numberOfStepsSet bool
+ currentStepSet bool
+ percentageSet bool
+ valueSet bool
+ upperLimitSet bool
+ durationSecondsSet bool
+ countsDownSet bool
}
func (in LiveActivityStreamEndInput) toGenerated() generated.LiveActivityStreamDeleteRequest {
@@ -1034,6 +1159,12 @@ func (in LiveActivityStreamEndInput) toGenerated() generated.LiveActivityStreamD
if in.UpperLimit != 0 || in.upperLimitSet {
contentState.SetUpperLimit(in.UpperLimit)
}
+ if in.DurationSeconds != 0 || in.durationSecondsSet {
+ setAdditionalProperty(&contentState.AdditionalProperties, "duration_seconds", in.DurationSeconds)
+ }
+ if in.countsDownSet {
+ setAdditionalProperty(&contentState.AdditionalProperties, "counts_down", in.CountsDown)
+ }
if in.Type != "" {
contentState.SetType(in.Type)
}
@@ -1095,6 +1226,18 @@ func (in LiveActivityStreamEndInput) WithUpperLimit(v float32) LiveActivityStrea
return in
}
+func (in LiveActivityStreamEndInput) WithDurationSeconds(v float32) LiveActivityStreamEndInput {
+ in.DurationSeconds = v
+ in.durationSecondsSet = true
+ return in
+}
+
+func (in LiveActivityStreamEndInput) WithCountsDown(v bool) LiveActivityStreamEndInput {
+ in.CountsDown = v
+ in.countsDownSet = true
+ return in
+}
+
func (in LiveActivityStreamEndInput) WithAction(v LiveActivityActionInput) LiveActivityStreamEndInput {
in.Action = &v
return in
diff --git a/services_test.go b/services_test.go
index c2d1116..765545a 100644
--- a/services_test.go
+++ b/services_test.go
@@ -706,6 +706,68 @@ func TestProgressInputsSerializeProgressFieldsWithoutSegmentedFields(t *testing.
}
}
+func TestTimerInputsSerializeTimerFields(t *testing.T) {
+ server, requests := newAPITestServer(t)
+ defer server.Close()
+
+ client, err := New("test-api-key")
+ if err != nil {
+ t.Fatalf("expected no error, got %v", err)
+ }
+ overrideHostForTests(client, server.URL)
+
+ startInput := LiveActivityStartInput{
+ Title: "Benchmark Run",
+ Subtitle: "sampling performance",
+ Type: LiveActivityTypeTimer,
+ Color: "cyan",
+ }.WithDurationSeconds(300).WithCountsDown(true)
+
+ updateInput := LiveActivityUpdateInput{
+ ActivityID: "act-1",
+ Title: "Benchmark Run",
+ Type: LiveActivityTypeTimer,
+ Subtitle: "complete",
+ Color: "cyan",
+ }
+
+ if _, err := client.LiveActivities.Start(startInput); err != nil {
+ t.Fatalf("Start returned error: %v", err)
+ }
+ if _, err := client.LiveActivities.Update(updateInput); err != nil {
+ t.Fatalf("Update returned error: %v", err)
+ }
+
+ if len(*requests) != 2 {
+ t.Fatalf("expected 2 requests, got %d", len(*requests))
+ }
+
+ startBody := (*requests)[0].Body
+ if !strings.Contains(startBody, `"type":"timer"`) {
+ t.Fatalf("start body missing timer type: %s", startBody)
+ }
+ if !strings.Contains(startBody, `"duration_seconds":300`) {
+ t.Fatalf("start body missing duration_seconds: %s", startBody)
+ }
+ if !strings.Contains(startBody, `"counts_down":true`) {
+ t.Fatalf("start body missing counts_down: %s", startBody)
+ }
+
+ updateBody := (*requests)[1].Body
+ if !strings.Contains(updateBody, `"type":"timer"`) {
+ t.Fatalf("update body missing timer type: %s", updateBody)
+ }
+ if !strings.Contains(updateBody, `"subtitle":"complete"`) {
+ t.Fatalf("update body missing subtitle: %s", updateBody)
+ }
+ if !strings.Contains(updateBody, `"color":"cyan"`) {
+ t.Fatalf("update body missing color: %s", updateBody)
+ }
+ if strings.Contains(updateBody, `"duration_seconds"`) {
+ t.Fatalf("update body should preserve timer window when duration_seconds is omitted: %s", updateBody)
+ }
+}
+
func TestStatsInputsSerializeMetricValuesAndColors(t *testing.T) {
server, requests := newAPITestServer(t)
defer server.Close()
diff --git a/version.go b/version.go
index d046de4..d4d044d 100644
--- a/version.go
+++ b/version.go
@@ -1,3 +1,3 @@
package activitysmith
-const Version = "1.5.0"
+const Version = "1.7.0"