Integrate an app into the flow event
INFO
This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. You can find the original version here
2022-10-11 - Integrate an app into the flow event
Context
Currently, apps can not extend the list of available events in the flow builder.
Decision
We update the flow builder so the apps can expand the list of available trigger events in the flow builder UI.
Flow aware
We define flow aware classes to detect which data will be available in the event and a function to get them.
Problems:
- We are unsure which data will the app event provide
Solution:
- We create an interface CustomAppAware that will use as implementation for the custom event from the app.
Example pseudocode
interface CustomAppAware
{
public const APP_DATA = 'customAppData';
public function getCustomAppData(): array;
}
Flow storer
Flow data storer saves the data from the event as the StorableFlow, and we use them in flow actions.
Problems:
- Currently, we keep the event data in the core but do not store any personalized event data from the application.
Solution:
- We create a CustomAppStorer, which is used to store the data from custom app event.
- When the API triggers a custom trigger, the data in the body will be stored in FlowStore by their keys.
Example to define data from the API:
{
"customerId": "d20e4d60e35e4afdb795c767eee08fec",
"salesChannelId": "55cb094fd1794d489c63975a6b4b5b90",
"shopName": "Shopware's Shop",
"url": "https://shopware.com"
}
After that, at actions we can get data thought FlowStorer.
$salesChanelId = $flow->getData(MailAware::SALES_CHANNEL_ID));
$customer = $flow->getData(CustomerAware::CUSTOMER_ID));
Or we can use the data when defining the email template.
<h3>Welcome to {{ shopName }}</h3>
<h1>Visit us at: {{ url }} </h1>
Example pseudocode
class CustomAppStore extends FlowStorer
{
public function store(FlowEventAware $event, array $stored): array
{
//check if $event is an instance of CustomAppAware
foreach ($event->getCustomAppData() as $key => $data) {
$stored[ScalarValuesAware::STORE_VALUES][$key] = $data;
$stored[$key] = $data;
}
}
public function restore(StorableFlow $storable): void
{
return;
}
}
Flow Events
Events must implement FlowEventAware to be able to available in the flow builder triggers.
Problems:
- We do not possess any
FlowEventAware
event instances that app developers can utilize for custom triggers to be dispatched or triggered from an app.
Solution:
- We create a new CustomAppEvent class that can be triggered by the App system.
Example pseudocode
class CustomAppEvent extends Event implements CustomAppAware, FlowEventAware
{
private string $name;
private array $data;
// __construct()
//getters
}
BusinessEventCollector
BusinessEventCollector collects events that implemented FlowEventAware and output to flow builder.
Problems:
- We currently collect events that implemented FlowEventAware. So the collector does not contain the events from the activated app.
Solution:
- We will collect all
CustomAppEvent
events from activated apps.
Example pseudocode
public function collect(Context $context): BusinessEventCollectorResponse
{
//fetch app event
$this->fetchAppEvents(new BusinessEventCollectorResponse)
}
private function fetchAppEvents(BusinessEventCollectorResponse $result): BusinessEventCollectorResponse
{
//check valid app events from the database
return $this->createCustomAppEvent();
}
private function createCustomAppEvent(): CustomAppEvent
{
// return new CustomAppEvent
}
Trigger app custom events API
We will provide an APIs to trigger CustomAppEvent.
Problems:
- Currently, the events are provided and triggered from the core when the user performs specific actions from the storefront or admin, like checkout order or user recovery. 3rd parties can not add custom triggers and trigger them by themself.
Solution:
- We will provide an API. The app calls the API to trigger the custom event and needs to provide the event name and the data. The API will create a CustomAppEvent object and dispatch it with the information provided.
Example pseudocode
/**
* @Since("6.5.2.0")
*/
#[Route(path: '/api/_action/trigger-event/{eventName}', name: 'api.action.trigger_event', methods: ['POST'])]
public function flowCustomTrigger(string $eventName, Request $request, Context $context): JsonResponse
{
$data = $request->request->all();
$criteria = new Criteria([$data['flowAppEventId']])
$criteria->addFilter(new EqualsFilter('appId', $data['flowId']));
$criteria->addFilter(new EqualsFilter('app.active', 1));
$flowEvent = $flowAppEventRepository->search($criteria);
//return http status code 404 if $flowEvent is empty
$this->eventDispatcher->dispatch(new CustomAppEvent($flowEvent->getName(), $data));
//return http status code 200 and success message
}
Defining an App flow event in Xml
The flow events are configured in a <appRoot>/src/Resources/flow.xml
file. We can store the following information for a flow event, Also, we can define more than one event in one app:
<name>
- The technical name - is unique and should be prefixed with the app vendor prefix, used when dispatching CustomAppEvent.php.<aware>
- Use for deciding what flow actions will be allowed to show after the event.The list of aware supported following:
orderAware
customerAware
mailAware
userAware
salesChannelAware
productAware
customerGroupAware
Example:
<aware>orderAware</aware>
We will have a list of actions related to Order that can be selected at the flow below:
- action.add.order.tag,
- action.remove.order.tag,
- action.generate.document,
- action.grant.download.access,
- action.set.order.state,
- action.add.order.affiliate.and.campaign.code,
- action.set.order.custom.field,
- action.stop.flow
<aware>customerAware</aware>
We will have a list of actions related to Customer that can be selected at the flow below:
- action.add.customer.tag
- action.remove.customer.tag
- action.change.customer.group
- action.change.customer.status
- action.set.customer.custom.field
- action.add.customer.affiliate.and.campaign.code
- action.stop.flow
A complete XML structure looks like this:
<flow-extensions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://test-flow.com/flow-1.0.xsd">
<flow-events>
<flow-event>
<name>swag.before.open.the.doors</name>
<aware>customerAware</aware>
<aware>orderAware</aware>
</flow-event>
<flow-event>
...
</flow-event>
</flow-events>
</flow-extensions>
Defining translated
We support defining translation for custom trigger events to show in the trigger tree and the trigger's name in the flow list.
- We will create the snippet file in folder
<appRoot>/src/Resources/app/administration/snippet/
. The structure of the snippet should follow some principles below:sw-flow-custom-event
is a fixed key instance for snippets using at the trigger event.event-tree
is a fixed key. The keys are defined inside this key based on the specified trigger name atname
inflow.xml
used to translate in trigger tree.flow-list
is a fixed key, The keys defined inside the key based on the trigger name defined atname
inflow.xml
used to translate in the trigger tree. Example pseudocodejson{ "sw-flow-custom-event": { "event-tree": { "swag": "Swag", "before": "Before", "openTheDoors": "Open the doors" }, "flow-list": { "swag_before_open_the_doors": "Before open the doors" } }
}
## Database migration
* We will create a new table `app_flow_event` to save defined data from the `<appRoot>/src/Resources/flow.xml` file.
* The table will have columns like bellow:
* `id` BINARY(16) NOT NULL,
* `app_id` BINARY(16) NOT NULL,
* `name` VARCHAR(255) NOT NULL UNIQUE,
* `aware` JSON NOT NULL,
* `created_at` DATETIME(3) NOT NULL,
* `updated_at` DATETIME(3) NULL,