Quote
In this tutorial, we'll look at a more realistic scenario for LetsFlow. In this scenario, a (potential) customer fills out a form, requesting a quote.
We'll focus on integration with an application; introducing the concept of services and notifications. We'll also look at custom action schemas and how this ties into building a frontend.
Create file
quote.yamlinscenarios.Create subdirectory
quoteinfeaturesfor the test files.Create a directory
schemaswith subdirectoriesactionsandmessages.
Customer request
The process starts with a customer filling out a form. The form has fields for customer info and project requirements.
Test case
We'll create a test case where the customer fills out the form. For completeness, we also validate the case where the customer did not fill out all the required fields.
Feature: The customer fills out the form to request a quote
Background:
Given the process is created from the "quote" scenario
And "Alice" is the "customer" actor
And "Bob" is the "sales" actor
Scenario: Customer fills out all fields correctly
When "Alice" does "request" with:
| company | Acme Inc |
| contact | Alice |
| email | [email protected] |
| address | 123 Main St |
| requirements | The product should be able to do X, Y and Z |
Then the last event is not skipped
And actor "customer" has "company" is "Acme Inc"
* actor "customer" has "contact" is "Alice"
* actor "customer" has "email" is "[email protected]"
* actor "customer" has "address" is "123 Main St"
And the process is in "requested"
Scenario: Customer does not fill out the email address and requirements
When "Alice" does "request" with:
| company | Acme Inc |
Then the last event is skipped with "Response is invalid: data must have required property 'email'"
Then the last event is skipped with "Response is invalid: data must have required property 'requirements'"
And the process is in "initial"Scenario
The scenario has 2 actors; the sales department of our organisation and the customer. The initial action is for the customer to fill out the form.
The role of the sales actor is set to sales, so anyone on the sales team can respond as this actor.
The frontend will render a form for this action. That's done using react-jsonschema-form for this example, which can render a form based on a JSON schema. It will use the response schema with additional render instructions defined in the ui property.
The update instructions will set the provided properties of the customer actor and will store the requirements as a variable.
Form action
The ui property is not part of the LetsFlow JSON schema for a scenario; it is specific to your application.
To integrate LetsFlow into your backend and frontend, you should define schemas for actions, messages, and states. These schemas serve as identifiers for rendering actions or states in a specific way, enabling reusable components.
In this case, the component will use react-jsonschema-form to render a form based on the response schema and ui object.

Visit the LetsFlow React documentation to learn how to build this component.
Sub schema validation
LetsFlow validation will apply the corresponding sub-schemas, ensuring that actions, states, and messages conform to the expected format of your application.
The schema for requires the action to define a response schema. Optionally it can have a ui property, which should be a map of objects.
Always set additionalProperties to true for sub-schemas, so you don't need to define the standard properties that are already defined by the LetsFlow scenario schema.
Organization response
After the form has been submitted, the sales team should create a quote that can be sent to the customer.
Test case
The requirements are shown as instructions to the sales team. The customer info is used to fill out part of the quote template. We're expecting a PDF as the response to this action.
After the quote is created, the 'email' service will send a message to the customer, with the quote in PDF format as an attachment.
Scenario
The 'create_quote' action asks for the sales department to draft a document based on the 'quote' template. The response should be a URI (a URL or other system-specific identifier) that allows the backend to fetch the file.
To generate an authentication token later on, we create a customer ID using the uuid() function of LetsFlow JMESPath. Note that all functions are deterministic. To create a unique identifier we take the (unique) process ID as the namespace and the actor key as input.
Draft action
The create_quote action expects to draft a new document based on a template. The action provides the template name and default data. This action is less abstract than the form action; it expects the application to know how to handle it.
Do not use the scenario as code
We could have split up the create_draft step into multiple abstract steps:
fill out a form for the quote information
fetch a template from the database and store it in a process variable
use mustache to fill out the template and create the quote document
use a service to create a PDF from the quote document
More abstract action types look attractive since you can create different scenarios without writing code. In reality, you're still writing code but now in YAML.
Solving edge cases and error handling, which is normally handled by your application, now need to be part of the scenario. This will make the workflows large, complex, and hard to maintain.
Additionally, it exposes the inner logic of a process, making it more likely that a modification to a service will break scenarios and running processes.
When in doubt; choose more specific over more abstract action definitions.
Sending an email
When the sales team uploads a quote, it should be emailed to the customer. For this, we trigger an external service, which can be a microservice or part of your backend.
The email service fills out a template with the provided data from the process to create a customized email. The quote PDF is added as an attachement.
In the backend documentation, you'll learn how to create the email service.
Test case
The email service fills out an email template and sends it to the recipient. It expects a specific message when triggered. In this case, it uses the quote template with the customer info and cancellation reason as data.
Scenario
In order to generate an authentication token by the email service, we create a customer ID using the uuid() function of LetsFlow JMESPath. Note that all JMESPath functions are deterministic. To create a unique identifier we take the (unique) process ID as the namespace and the actor key as input.
Email service
The message format is defined as a schema. Similar to a schema of an action, sub-schema validation of notification messages is applied when the scenario is validated.
Acceptance
After the sales team has sent the quote, the customer has the option to accept or reject the quote. The email will contain a link to a page where the customer can select to accept or reject.
Test case
If the customer accepts the quote, the process will end in the (accepted) end state. The sales team will handle it further, which is outside the scope of this scenario.
When rejected, the customer should specify a reason. If the customer doesn't respond within 10 days, the quote is automatically rejected.
Scenario
Instead of on we can use after to automatically trigger an event after a certain time has passed. In this case, after 10 days the proposal is automatically rejected.
The <select> data function can be used to select one of the options based on a boolean, numeric or string value. We use that to select the description of the (rejected) state.
Cancellation
The scenario above describes the golden flow; the optimal path to reach the objective. However, alternative paths can exist; for instance, the sales team or the customer can cancel the request.
The sales team might cancel if the company can't meet the requirements. The customer might cancel if it no longer needs the requested product or service.
We want the actor who cancels the process to specify a reason, which is displayed to all participants.
Test case
In case the sales department cancels, the customer should receive an email with the cancellation reason. If the customer cancels, no email should be sent.
Scenario
For the cancel action, we'll reuse the form action schema. The form contains a single field that allows the participant to specify the reason for cancellation. The reason is set as process variable and is used in the email to the client and shown to all participants.
We explicitly define the end-state cancelled, so we can specify a description and notify instructions. If the sales team cancels the process, the email service is notified to send an email to the customer.
The cancel action is similar to reject. With YAML we can create an anchor reject_action and reference it in the cancel action with a << merge operation.
Conclusion
Here is the full scenario with all of the steps combined.
Congratulations!
You've completed the tutorial. For more examples please check out the Cookbook.
Last updated
Was this helpful?