From 5c046f12c280b712e0cc14b93ed18f6fb2ea0d24 Mon Sep 17 00:00:00 2001 From: activitysmith-bot Date: Thu, 19 Mar 2026 09:03:22 +0000 Subject: [PATCH 1/3] chore: regenerate SDK --- generated/Configuration.php | 4 +- generated/Model/LiveActivityAction.php | 564 ++++++++++++++++++ generated/Model/LiveActivityActionType.php | 62 ++ generated/Model/LiveActivityEndRequest.php | 46 +- generated/Model/LiveActivityStartRequest.php | 34 ++ generated/Model/LiveActivityUpdateRequest.php | 46 +- generated/Model/LiveActivityWebhookMethod.php | 62 ++ 7 files changed, 804 insertions(+), 14 deletions(-) create mode 100644 generated/Model/LiveActivityAction.php create mode 100644 generated/Model/LiveActivityActionType.php create mode 100644 generated/Model/LiveActivityWebhookMethod.php diff --git a/generated/Configuration.php b/generated/Configuration.php index c49b238..f223e0d 100644 --- a/generated/Configuration.php +++ b/generated/Configuration.php @@ -100,7 +100,7 @@ class Configuration * * @var string */ - protected $userAgent = 'OpenAPI-Generator/0.1.5/PHP'; + protected $userAgent = 'OpenAPI-Generator/0.1.6/PHP'; /** * Debug switch (default set to false) @@ -433,7 +433,7 @@ public static function toDebugReport() $report .= ' OS: ' . php_uname() . PHP_EOL; $report .= ' PHP Version: ' . PHP_VERSION . PHP_EOL; $report .= ' The version of the OpenAPI document: 1.0.0' . PHP_EOL; - $report .= ' SDK Package Version: 0.1.5' . PHP_EOL; + $report .= ' SDK Package Version: 0.1.6' . PHP_EOL; $report .= ' Temp Folder Path: ' . self::getDefaultConfiguration()->getTempFolderPath() . PHP_EOL; return $report; diff --git a/generated/Model/LiveActivityAction.php b/generated/Model/LiveActivityAction.php new file mode 100644 index 0000000..f1030bc --- /dev/null +++ b/generated/Model/LiveActivityAction.php @@ -0,0 +1,564 @@ + + */ +class LiveActivityAction implements ModelInterface, ArrayAccess, \JsonSerializable +{ + public const DISCRIMINATOR = null; + + /** + * The original name of the model. + * + * @var string + */ + protected static $openAPIModelName = 'LiveActivityAction'; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @var string[] + */ + protected static $openAPITypes = [ + 'title' => 'string', + 'type' => '\ActivitySmith\Generated\Model\LiveActivityActionType', + 'url' => 'string', + 'method' => '\ActivitySmith\Generated\Model\LiveActivityWebhookMethod', + 'body' => 'array' + ]; + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @var string[] + * @phpstan-var array + * @psalm-var array + */ + protected static $openAPIFormats = [ + 'title' => null, + 'type' => null, + 'url' => 'uri', + 'method' => null, + 'body' => null + ]; + + /** + * Array of nullable properties. Used for (de)serialization + * + * @var boolean[] + */ + protected static array $openAPINullables = [ + 'title' => false, + 'type' => false, + 'url' => false, + 'method' => false, + 'body' => false + ]; + + /** + * If a nullable field gets set to null, insert it here + * + * @var boolean[] + */ + protected array $openAPINullablesSetToNull = []; + + /** + * Array of property to type mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPITypes() + { + return self::$openAPITypes; + } + + /** + * Array of property to format mappings. Used for (de)serialization + * + * @return array + */ + public static function openAPIFormats() + { + return self::$openAPIFormats; + } + + /** + * Array of nullable properties + * + * @return array + */ + protected static function openAPINullables(): array + { + return self::$openAPINullables; + } + + /** + * Array of nullable field names deliberately set to null + * + * @return boolean[] + */ + private function getOpenAPINullablesSetToNull(): array + { + return $this->openAPINullablesSetToNull; + } + + /** + * Setter - Array of nullable field names deliberately set to null + * + * @param boolean[] $openAPINullablesSetToNull + */ + private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void + { + $this->openAPINullablesSetToNull = $openAPINullablesSetToNull; + } + + /** + * Checks if a property is nullable + * + * @param string $property + * @return bool + */ + public static function isNullable(string $property): bool + { + return self::openAPINullables()[$property] ?? false; + } + + /** + * Checks if a nullable property is set to null. + * + * @param string $property + * @return bool + */ + public function isNullableSetToNull(string $property): bool + { + return in_array($property, $this->getOpenAPINullablesSetToNull(), true); + } + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @var string[] + */ + protected static $attributeMap = [ + 'title' => 'title', + 'type' => 'type', + 'url' => 'url', + 'method' => 'method', + 'body' => 'body' + ]; + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @var string[] + */ + protected static $setters = [ + 'title' => 'setTitle', + 'type' => 'setType', + 'url' => 'setUrl', + 'method' => 'setMethod', + 'body' => 'setBody' + ]; + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @var string[] + */ + protected static $getters = [ + 'title' => 'getTitle', + 'type' => 'getType', + 'url' => 'getUrl', + 'method' => 'getMethod', + 'body' => 'getBody' + ]; + + /** + * Array of attributes where the key is the local name, + * and the value is the original name + * + * @return array + */ + public static function attributeMap() + { + return self::$attributeMap; + } + + /** + * Array of attributes to setter functions (for deserialization of responses) + * + * @return array + */ + public static function setters() + { + return self::$setters; + } + + /** + * Array of attributes to getter functions (for serialization of requests) + * + * @return array + */ + public static function getters() + { + return self::$getters; + } + + /** + * The original name of the model. + * + * @return string + */ + public function getModelName() + { + return self::$openAPIModelName; + } + + + /** + * Associative array for storing property values + * + * @var mixed[] + */ + protected $container = []; + + /** + * Constructor + * + * @param mixed[] $data Associated array of property values + * initializing the model + */ + public function __construct(array $data = null) + { + $this->setIfExists('title', $data ?? [], null); + $this->setIfExists('type', $data ?? [], null); + $this->setIfExists('url', $data ?? [], null); + $this->setIfExists('method', $data ?? [], null); + $this->setIfExists('body', $data ?? [], null); + } + + /** + * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName + * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the + * $this->openAPINullablesSetToNull array + * + * @param string $variableName + * @param array $fields + * @param mixed $defaultValue + */ + private function setIfExists(string $variableName, array $fields, $defaultValue): void + { + if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) { + $this->openAPINullablesSetToNull[] = $variableName; + } + + $this->container[$variableName] = $fields[$variableName] ?? $defaultValue; + } + + /** + * Show all the invalid properties with reasons. + * + * @return array invalid properties with reasons + */ + public function listInvalidProperties() + { + $invalidProperties = []; + + if ($this->container['title'] === null) { + $invalidProperties[] = "'title' can't be null"; + } + if ($this->container['type'] === null) { + $invalidProperties[] = "'type' can't be null"; + } + if ($this->container['url'] === null) { + $invalidProperties[] = "'url' can't be null"; + } + if (!preg_match("/^https:\/\//", $this->container['url'])) { + $invalidProperties[] = "invalid value for 'url', must be conform to the pattern /^https:\/\//."; + } + + return $invalidProperties; + } + + /** + * Validate all the properties in the model + * return true if all passed + * + * @return bool True if all properties are valid + */ + public function valid() + { + return count($this->listInvalidProperties()) === 0; + } + + + /** + * Gets title + * + * @return string + */ + public function getTitle() + { + return $this->container['title']; + } + + /** + * Sets title + * + * @param string $title Button title displayed in the Live Activity UI. + * + * @return self + */ + public function setTitle($title) + { + if (is_null($title)) { + throw new \InvalidArgumentException('non-nullable title cannot be null'); + } + $this->container['title'] = $title; + + return $this; + } + + /** + * Gets type + * + * @return \ActivitySmith\Generated\Model\LiveActivityActionType + */ + public function getType() + { + return $this->container['type']; + } + + /** + * Sets type + * + * @param \ActivitySmith\Generated\Model\LiveActivityActionType $type type + * + * @return self + */ + public function setType($type) + { + if (is_null($type)) { + throw new \InvalidArgumentException('non-nullable type cannot be null'); + } + $this->container['type'] = $type; + + return $this; + } + + /** + * Gets url + * + * @return string + */ + public function getUrl() + { + return $this->container['url']; + } + + /** + * Sets url + * + * @param string $url HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. + * + * @return self + */ + public function setUrl($url) + { + if (is_null($url)) { + throw new \InvalidArgumentException('non-nullable url cannot be null'); + } + + if ((!preg_match("/^https:\/\//", ObjectSerializer::toString($url)))) { + throw new \InvalidArgumentException("invalid value for \$url when calling LiveActivityAction., must conform to the pattern /^https:\/\//."); + } + + $this->container['url'] = $url; + + return $this; + } + + /** + * Gets method + * + * @return \ActivitySmith\Generated\Model\LiveActivityWebhookMethod|null + */ + public function getMethod() + { + return $this->container['method']; + } + + /** + * Sets method + * + * @param \ActivitySmith\Generated\Model\LiveActivityWebhookMethod|null $method Webhook HTTP method. Used only when type=webhook. + * + * @return self + */ + public function setMethod($method) + { + if (is_null($method)) { + throw new \InvalidArgumentException('non-nullable method cannot be null'); + } + $this->container['method'] = $method; + + return $this; + } + + /** + * Gets body + * + * @return array|null + */ + public function getBody() + { + return $this->container['body']; + } + + /** + * Sets body + * + * @param array|null $body Optional webhook payload body. Used only when type=webhook. + * + * @return self + */ + public function setBody($body) + { + if (is_null($body)) { + throw new \InvalidArgumentException('non-nullable body cannot be null'); + } + $this->container['body'] = $body; + + return $this; + } + /** + * Returns true if offset exists. False otherwise. + * + * @param integer $offset Offset + * + * @return boolean + */ + public function offsetExists($offset): bool + { + return isset($this->container[$offset]); + } + + /** + * Gets offset. + * + * @param integer $offset Offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return $this->container[$offset] ?? null; + } + + /** + * Sets value based on offset. + * + * @param int|null $offset Offset + * @param mixed $value Value to be set + * + * @return void + */ + public function offsetSet($offset, $value): void + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + /** + * Unsets offset. + * + * @param integer $offset Offset + * + * @return void + */ + public function offsetUnset($offset): void + { + unset($this->container[$offset]); + } + + /** + * Serializes the object to a value that can be serialized natively by json_encode(). + * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php + * + * @return mixed Returns data which can be serialized by json_encode(), which is a value + * of any type other than a resource. + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return ObjectSerializer::sanitizeForSerialization($this); + } + + /** + * Gets the string presentation of the object + * + * @return string + */ + public function __toString() + { + return json_encode( + ObjectSerializer::sanitizeForSerialization($this), + JSON_PRETTY_PRINT + ); + } + + /** + * Gets a header-safe presentation of the object + * + * @return string + */ + public function toHeaderValue() + { + return json_encode(ObjectSerializer::sanitizeForSerialization($this)); + } +} + + diff --git a/generated/Model/LiveActivityActionType.php b/generated/Model/LiveActivityActionType.php new file mode 100644 index 0000000..64f837c --- /dev/null +++ b/generated/Model/LiveActivityActionType.php @@ -0,0 +1,62 @@ + 'string', - 'contentState' => '\ActivitySmith\Generated\Model\ContentStateEnd' + 'contentState' => '\ActivitySmith\Generated\Model\ContentStateEnd', + 'action' => '\ActivitySmith\Generated\Model\LiveActivityAction' ]; /** @@ -71,7 +72,8 @@ class LiveActivityEndRequest implements ModelInterface, ArrayAccess, \JsonSerial */ protected static $openAPIFormats = [ 'activityId' => null, - 'contentState' => null + 'contentState' => null, + 'action' => null ]; /** @@ -81,7 +83,8 @@ class LiveActivityEndRequest implements ModelInterface, ArrayAccess, \JsonSerial */ protected static array $openAPINullables = [ 'activityId' => false, - 'contentState' => false + 'contentState' => false, + 'action' => false ]; /** @@ -171,7 +174,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'activityId' => 'activity_id', - 'contentState' => 'content_state' + 'contentState' => 'content_state', + 'action' => 'action' ]; /** @@ -181,7 +185,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'activityId' => 'setActivityId', - 'contentState' => 'setContentState' + 'contentState' => 'setContentState', + 'action' => 'setAction' ]; /** @@ -191,7 +196,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'activityId' => 'getActivityId', - 'contentState' => 'getContentState' + 'contentState' => 'getContentState', + 'action' => 'getAction' ]; /** @@ -253,6 +259,7 @@ public function __construct(array $data = null) { $this->setIfExists('activityId', $data ?? [], null); $this->setIfExists('contentState', $data ?? [], null); + $this->setIfExists('action', $data ?? [], null); } /** @@ -356,6 +363,33 @@ public function setContentState($contentState) return $this; } + + /** + * Gets action + * + * @return \ActivitySmith\Generated\Model\LiveActivityAction|null + */ + public function getAction() + { + return $this->container['action']; + } + + /** + * Sets action + * + * @param \ActivitySmith\Generated\Model\LiveActivityAction|null $action action + * + * @return self + */ + public function setAction($action) + { + if (is_null($action)) { + throw new \InvalidArgumentException('non-nullable action cannot be null'); + } + $this->container['action'] = $action; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/Model/LiveActivityStartRequest.php b/generated/Model/LiveActivityStartRequest.php index 049d386..96ce673 100644 --- a/generated/Model/LiveActivityStartRequest.php +++ b/generated/Model/LiveActivityStartRequest.php @@ -59,6 +59,7 @@ class LiveActivityStartRequest implements ModelInterface, ArrayAccess, \JsonSeri */ protected static $openAPITypes = [ 'contentState' => '\ActivitySmith\Generated\Model\ContentStateStart', + 'action' => '\ActivitySmith\Generated\Model\LiveActivityAction', 'alert' => '\ActivitySmith\Generated\Model\AlertPayload', 'target' => '\ActivitySmith\Generated\Model\ChannelTarget' ]; @@ -72,6 +73,7 @@ class LiveActivityStartRequest implements ModelInterface, ArrayAccess, \JsonSeri */ protected static $openAPIFormats = [ 'contentState' => null, + 'action' => null, 'alert' => null, 'target' => null ]; @@ -83,6 +85,7 @@ class LiveActivityStartRequest implements ModelInterface, ArrayAccess, \JsonSeri */ protected static array $openAPINullables = [ 'contentState' => false, + 'action' => false, 'alert' => false, 'target' => false ]; @@ -174,6 +177,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'contentState' => 'content_state', + 'action' => 'action', 'alert' => 'alert', 'target' => 'target' ]; @@ -185,6 +189,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'contentState' => 'setContentState', + 'action' => 'setAction', 'alert' => 'setAlert', 'target' => 'setTarget' ]; @@ -196,6 +201,7 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'contentState' => 'getContentState', + 'action' => 'getAction', 'alert' => 'getAlert', 'target' => 'getTarget' ]; @@ -258,6 +264,7 @@ public function getModelName() public function __construct(array $data = null) { $this->setIfExists('contentState', $data ?? [], null); + $this->setIfExists('action', $data ?? [], null); $this->setIfExists('alert', $data ?? [], null); $this->setIfExists('target', $data ?? [], null); } @@ -334,6 +341,33 @@ public function setContentState($contentState) return $this; } + /** + * Gets action + * + * @return \ActivitySmith\Generated\Model\LiveActivityAction|null + */ + public function getAction() + { + return $this->container['action']; + } + + /** + * Sets action + * + * @param \ActivitySmith\Generated\Model\LiveActivityAction|null $action action + * + * @return self + */ + public function setAction($action) + { + if (is_null($action)) { + throw new \InvalidArgumentException('non-nullable action cannot be null'); + } + $this->container['action'] = $action; + + return $this; + } + /** * Gets alert * diff --git a/generated/Model/LiveActivityUpdateRequest.php b/generated/Model/LiveActivityUpdateRequest.php index 5c3e280..4ba916f 100644 --- a/generated/Model/LiveActivityUpdateRequest.php +++ b/generated/Model/LiveActivityUpdateRequest.php @@ -59,7 +59,8 @@ class LiveActivityUpdateRequest implements ModelInterface, ArrayAccess, \JsonSer */ protected static $openAPITypes = [ 'activityId' => 'string', - 'contentState' => '\ActivitySmith\Generated\Model\ContentStateUpdate' + 'contentState' => '\ActivitySmith\Generated\Model\ContentStateUpdate', + 'action' => '\ActivitySmith\Generated\Model\LiveActivityAction' ]; /** @@ -71,7 +72,8 @@ class LiveActivityUpdateRequest implements ModelInterface, ArrayAccess, \JsonSer */ protected static $openAPIFormats = [ 'activityId' => null, - 'contentState' => null + 'contentState' => null, + 'action' => null ]; /** @@ -81,7 +83,8 @@ class LiveActivityUpdateRequest implements ModelInterface, ArrayAccess, \JsonSer */ protected static array $openAPINullables = [ 'activityId' => false, - 'contentState' => false + 'contentState' => false, + 'action' => false ]; /** @@ -171,7 +174,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $attributeMap = [ 'activityId' => 'activity_id', - 'contentState' => 'content_state' + 'contentState' => 'content_state', + 'action' => 'action' ]; /** @@ -181,7 +185,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $setters = [ 'activityId' => 'setActivityId', - 'contentState' => 'setContentState' + 'contentState' => 'setContentState', + 'action' => 'setAction' ]; /** @@ -191,7 +196,8 @@ public function isNullableSetToNull(string $property): bool */ protected static $getters = [ 'activityId' => 'getActivityId', - 'contentState' => 'getContentState' + 'contentState' => 'getContentState', + 'action' => 'getAction' ]; /** @@ -253,6 +259,7 @@ public function __construct(array $data = null) { $this->setIfExists('activityId', $data ?? [], null); $this->setIfExists('contentState', $data ?? [], null); + $this->setIfExists('action', $data ?? [], null); } /** @@ -356,6 +363,33 @@ public function setContentState($contentState) return $this; } + + /** + * Gets action + * + * @return \ActivitySmith\Generated\Model\LiveActivityAction|null + */ + public function getAction() + { + return $this->container['action']; + } + + /** + * Sets action + * + * @param \ActivitySmith\Generated\Model\LiveActivityAction|null $action action + * + * @return self + */ + public function setAction($action) + { + if (is_null($action)) { + throw new \InvalidArgumentException('non-nullable action cannot be null'); + } + $this->container['action'] = $action; + + return $this; + } /** * Returns true if offset exists. False otherwise. * diff --git a/generated/Model/LiveActivityWebhookMethod.php b/generated/Model/LiveActivityWebhookMethod.php new file mode 100644 index 0000000..cca53a8 --- /dev/null +++ b/generated/Model/LiveActivityWebhookMethod.php @@ -0,0 +1,62 @@ + Date: Thu, 19 Mar 2026 10:14:11 +0100 Subject: [PATCH 2/3] Add live activity action docs and bump 1.0.0 --- README.md | 52 ++++++++++++++++++ generated/Configuration.php | 4 +- src/Version.php | 2 +- tests/ResourcesTest.php | 104 ++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99b3147..c86227d 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,58 @@ then end it when the work is done. 3. Call `$activitysmith->liveActivities->update(...)` as progress changes. 4. Call `$activitysmith->liveActivities->end(...)` when the work is finished. +### Live Activity Actions + +Live Activities can show one optional button. Use `open_url` when you want a +mobile shortcut into GitHub, a runbook, or an internal dashboard. Use +`webhook` when you want ActivitySmith backend to trigger an operational control +such as pausing a reindex. + +#### Open URL action + +```php +$start = $activitysmith->liveActivities->start([ + 'content_state' => [ + 'title' => 'Deploying payments-api', + 'subtitle' => 'Running database migrations', + 'number_of_steps' => 5, + 'current_step' => 3, + 'type' => 'segmented_progress', + ], + 'action' => [ + 'title' => 'Open Workflow', + 'type' => 'open_url', + 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', + ], +]); + +$activityId = $start->getActivityId(); +``` + +#### Webhook action + +```php +$activitysmith->liveActivities->update([ + 'activity_id' => $activityId, + 'content_state' => [ + 'title' => 'Reindexing product search', + 'subtitle' => 'Shard 7 of 12', + 'number_of_steps' => 12, + 'current_step' => 7, + ], + 'action' => [ + 'title' => 'Pause Reindex', + 'type' => 'webhook', + 'url' => 'https://ops.example.com/hooks/search/reindex/pause', + 'method' => 'POST', + 'body' => [ + 'job_id' => 'reindex-2026-03-19', + 'requested_by' => 'activitysmith-php', + ], + ], +]); +``` + ### Segmented Progress Type Use `segmented_progress` when progress is easier to follow as steps instead of a diff --git a/generated/Configuration.php b/generated/Configuration.php index f223e0d..ef29e0f 100644 --- a/generated/Configuration.php +++ b/generated/Configuration.php @@ -100,7 +100,7 @@ class Configuration * * @var string */ - protected $userAgent = 'OpenAPI-Generator/0.1.6/PHP'; + protected $userAgent = 'OpenAPI-Generator/1.0.0/PHP'; /** * Debug switch (default set to false) @@ -433,7 +433,7 @@ public static function toDebugReport() $report .= ' OS: ' . php_uname() . PHP_EOL; $report .= ' PHP Version: ' . PHP_VERSION . PHP_EOL; $report .= ' The version of the OpenAPI document: 1.0.0' . PHP_EOL; - $report .= ' SDK Package Version: 0.1.6' . PHP_EOL; + $report .= ' SDK Package Version: 1.0.0' . PHP_EOL; $report .= ' Temp Folder Path: ' . self::getDefaultConfiguration()->getTempFolderPath() . PHP_EOL; return $report; diff --git a/src/Version.php b/src/Version.php index a42bf09..376be89 100644 --- a/src/Version.php +++ b/src/Version.php @@ -6,7 +6,7 @@ final class Version { - public const VERSION = '0.1.6'; + public const VERSION = '1.0.0'; private function __construct() { diff --git a/tests/ResourcesTest.php b/tests/ResourcesTest.php index 882121a..84249b3 100644 --- a/tests/ResourcesTest.php +++ b/tests/ResourcesTest.php @@ -252,6 +252,110 @@ public function testLiveActivitiesStartMapsChannelsToTarget(): void ); } + public function testLiveActivitiesPassActionPayloadsThrough(): void + { + $response = (object) ['success' => true]; + $captured = [ + 'start' => [], + 'update' => [], + 'end' => [], + ]; + + $api = $this->getMockBuilder(LiveActivitiesApi::class) + ->disableOriginalConstructor() + ->onlyMethods(['startLiveActivity', 'updateLiveActivity', 'endLiveActivity']) + ->getMock(); + + $api->expects($this->once()) + ->method('startLiveActivity') + ->willReturnCallback(function (...$args) use (&$captured, $response) { + $captured['start'][] = $args; + return $response; + }); + + $api->expects($this->once()) + ->method('updateLiveActivity') + ->willReturnCallback(function (...$args) use (&$captured, $response) { + $captured['update'][] = $args; + return $response; + }); + + $api->expects($this->once()) + ->method('endLiveActivity') + ->willReturnCallback(function (...$args) use (&$captured, $response) { + $captured['end'][] = $args; + return $response; + }); + + $resource = new LiveActivities($api); + + $startPayload = [ + 'content_state' => [ + 'title' => 'Deploying payments-api', + 'subtitle' => 'Running database migrations', + 'number_of_steps' => 5, + 'current_step' => 3, + 'type' => 'segmented_progress', + ], + 'action' => [ + 'title' => 'Open Workflow', + 'type' => 'open_url', + 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', + ], + ]; + + $updatePayload = [ + 'activity_id' => 'act-1', + 'content_state' => [ + 'title' => 'Reindexing product search', + 'subtitle' => 'Shard 7 of 12', + 'number_of_steps' => 12, + 'current_step' => 7, + ], + 'action' => [ + 'title' => 'Pause Reindex', + 'type' => 'webhook', + 'url' => 'https://ops.example.com/hooks/search/reindex/pause', + 'method' => 'POST', + 'body' => [ + 'job_id' => 'reindex-2026-03-19', + ], + ], + ]; + + $endPayload = [ + 'activity_id' => 'act-1', + 'content_state' => [ + 'title' => 'Deploying payments-api', + 'subtitle' => 'Production rollout complete', + 'number_of_steps' => 5, + 'current_step' => 5, + ], + 'action' => [ + 'title' => 'Open Workflow', + 'type' => 'open_url', + 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', + ], + ]; + + $resource->start($startPayload); + $resource->update($updatePayload); + $resource->end($endPayload); + + $this->assertSame( + [[$startPayload, LiveActivitiesApi::contentTypes['startLiveActivity'][0]]], + $captured['start'] + ); + $this->assertSame( + [[$updatePayload, LiveActivitiesApi::contentTypes['updateLiveActivity'][0]]], + $captured['update'] + ); + $this->assertSame( + [[$endPayload, LiveActivitiesApi::contentTypes['endLiveActivity'][0]]], + $captured['end'] + ); + } + public function testLiveActivitiesSupportProgressPayloads(): void { $captured = []; From 449d79da77bbf7ce9249f5a7d41345300348ed89 Mon Sep 17 00:00:00 2001 From: bardonadam Date: Thu, 19 Mar 2026 13:02:39 +0100 Subject: [PATCH 3/3] Polish live activity action README --- README.md | 105 +++++++++++++++++++++++++++--------------------------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index c86227d..b217411 100644 --- a/README.md +++ b/README.md @@ -117,58 +117,6 @@ then end it when the work is done. 3. Call `$activitysmith->liveActivities->update(...)` as progress changes. 4. Call `$activitysmith->liveActivities->end(...)` when the work is finished. -### Live Activity Actions - -Live Activities can show one optional button. Use `open_url` when you want a -mobile shortcut into GitHub, a runbook, or an internal dashboard. Use -`webhook` when you want ActivitySmith backend to trigger an operational control -such as pausing a reindex. - -#### Open URL action - -```php -$start = $activitysmith->liveActivities->start([ - 'content_state' => [ - 'title' => 'Deploying payments-api', - 'subtitle' => 'Running database migrations', - 'number_of_steps' => 5, - 'current_step' => 3, - 'type' => 'segmented_progress', - ], - 'action' => [ - 'title' => 'Open Workflow', - 'type' => 'open_url', - 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', - ], -]); - -$activityId = $start->getActivityId(); -``` - -#### Webhook action - -```php -$activitysmith->liveActivities->update([ - 'activity_id' => $activityId, - 'content_state' => [ - 'title' => 'Reindexing product search', - 'subtitle' => 'Shard 7 of 12', - 'number_of_steps' => 12, - 'current_step' => 7, - ], - 'action' => [ - 'title' => 'Pause Reindex', - 'type' => 'webhook', - 'url' => 'https://ops.example.com/hooks/search/reindex/pause', - 'method' => 'POST', - 'body' => [ - 'job_id' => 'reindex-2026-03-19', - 'requested_by' => 'activitysmith-php', - ], - ], -]); -``` - ### Segmented Progress Type Use `segmented_progress` when progress is easier to follow as steps instead of a @@ -297,6 +245,59 @@ $activitysmith->liveActivities->end([ ]); ``` +### Live Activity Action + +Just like Actionable Push Notifications, Live Activities can have a button that opens provided URL in a browser or triggers a webhook. Webhooks are executed by the ActivitySmith backend. + +

+ Live Activity with action +

+ +#### Open URL action + +```php +$start = $activitysmith->liveActivities->start([ + 'content_state' => [ + 'title' => 'Deploying payments-api', + 'subtitle' => 'Running database migrations', + 'number_of_steps' => 5, + 'current_step' => 3, + 'type' => 'segmented_progress', + ], + 'action' => [ + 'title' => 'Open Workflow', + 'type' => 'open_url', + 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890', + ], +]); + +$activityId = $start->getActivityId(); +``` + +#### Webhook action + +```php +$activitysmith->liveActivities->update([ + 'activity_id' => $activityId, + 'content_state' => [ + 'title' => 'Reindexing product search', + 'subtitle' => 'Shard 7 of 12', + 'number_of_steps' => 12, + 'current_step' => 7, + ], + 'action' => [ + 'title' => 'Pause Reindex', + 'type' => 'webhook', + 'url' => 'https://ops.example.com/hooks/search/reindex/pause', + 'method' => 'POST', + 'body' => [ + 'job_id' => 'reindex-2026-03-19', + 'requested_by' => 'activitysmith-php', + ], + ], +]); +``` + ## Channels Channels are used to target specific team members or devices. Can be used for both push notifications and live activities.