[Design Discussion] Flow Management and Translations #812
Replies: 6 comments 1 reply
-
|
Before going ahead on how we are going to do this, I would like to understand our stance on,
The
My take is, its neutral.. As a intermediate approach, we can reduce the contrast/opacity of these non-ui/logical nodes and give less prominence for those additional nodes. And if we can have a way to switch between views like,
|
Beta Was this translation helpful? Give feedback.
-
|
Additionally,
|
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
|
Hey @ThaminduDilshan & @darshanasbg, I’d like to propose a different approach for representing the flow. ScenarioA user initiates the sign-in process.
If the user selects username/password, the system triggers a Basic Authentication task to validate the credentials. If the user selects SMS OTP, the system triggers a Send SMS task, which delivers an OTP to the user’s mobile number.
After the user submits the OTP, a Verify OTP task is executed. This scenario is modeled using:
Graph Representation (JSON Format)Below is a sample of how the above scenario can be expressed in both verbose and non-verbose modes. Note Verbose mode includes UI metadata, while non-verbose mode strips all UI details and keeps only the logic. Verbose Mode{
"nodes": [
{
"id": "node_001",
"type": "PROMPT",
"meta": {
"components": [
{
"type": "TEXT",
"id": "text_001",
"label": "signin.heading.label",
"variant": "HEADING_01"
},
{
"type": "BLOCK",
"id": "block_001",
"components": [
{
"type": "TEXT_INPUT",
"id": "input_001",
"label": "signin.fields.username.label",
"required": true,
"placeholder": "signin.fields.username.placeholder"
},
{
"type": "PASSWORD_INPUT",
"id": "input_002",
"label": "signin.fields.password.label",
"required": true,
"placeholder": "signin.fields.password.placeholder"
},
{
"type": "ACTION",
"id": "action_001",
"label": "signin.buttons.submit"
}
]
},
{
"type": "DIVIDER",
"id": "divider_001",
"label": "signin.divider"
},
{
"type": "ACTION",
"id": "action_002",
"label": "signin.buttons.continue.with.sms.otp"
}
]
},
"inputs": [
{
"ref": "input_001",
"type": "TEXT_INPUT",
"required": true
},
{
"ref": "input_002",
"type": "PASSWORD_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_001",
"nextNode": "node_004"
},
{
"ref": "action_002",
"nextNode": "node_002"
}
]
},
{
"id": "node_002",
"type": "TASK_EXECUTION",
"executor": {
"name": "SendSMSExecutor",
"properties": {
"senderId": "<SENDER_ID>"
}
},
"onSuccess": "node_003",
"onFailure": "node_007"
},
{
"id": "node_003",
"type": "PROMPT",
"meta": {
"components": [
{
"type": "HEADING",
"id": "heading_003",
"label": "sms.otp.verify.heading"
},
{
"type": "TEXT",
"id": "text_003",
"label": "sms.otp.verify.heading.label",
"variant": "HEADING_01"
},
{
"type": "OTP_INPUT",
"id": "otp_001",
"label": "sms.otp.verify.otp.input",
"length": 6,
"required": true
},
{
"type": "ACTION",
"id": "action_003",
"label": "sms.otp.verify.buttons.submit"
},
{
"type": "ACTION",
"id": "action_004",
"label": "sms.otp.verify.buttons.resend"
}
]
},
"inputs": [
{
"ref": "otp_001",
"type": "OTP_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_003",
"nextNode": "node_005"
},
{
"ref": "action_004",
"nextNode": "node_002"
}
]
},
{
"id": "node_004",
"type": "TASK_EXECUTION",
"executor": {
"name": "BasicAuthExecutor",
"properties": {}
}
},
{
"id": "node_005",
"type": "TASK_EXECUTION",
"executor": {
"name": "VerifyOTPExecutor",
"properties": {}
}
},
{
"id": "node_006",
"type": "END"
},
{
"id": "node_007",
"type": "PROMPT",
"meta": {
"components": [
{
"type": "HEADING",
"id": "heading_007",
"label": "sms.otp.error.heading"
},
{
"type": "TEXT_INPUT",
"id": "text_007",
"label": "sms.otp.error.message"
},
{
"type": "ACTION",
"id": "action_007",
"label": "sms.otp.error.buttons.retry"
}
]
},
"actions": [
{
"ref": "action_007",
"nextNode": "node_002"
}
]
}
]
}Non-Verbose ModeNote There are no UI metadata in the non-verbose mode {
"nodes": [
{
"id": "node_001",
"type": "PROMPT",
"inputs": [
{
"ref": "input_001",
"type": "TEXT_INPUT",
"required": true
},
{
"ref": "input_002",
"type": "PASSWORD_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_001",
"nextNode": "node_004"
},
{
"ref": "action_002",
"nextNode": "node_002"
}
]
},
{
"id": "node_002",
"type": "TASK_EXECUTION",
"executor": {
"name": "SendSMSExecutor",
"properties": {
"senderId": "<SENDER_ID>"
}
},
"onSuccess": "node_003",
"onFailure": "node_007"
},
{
"id": "node_003",
"type": "PROMPT",
"inputs": [
{
"ref": "otp_001",
"type": "OTP_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_003",
"nextNode": "node_005"
},
{
"ref": "action_004",
"nextNode": "node_002"
}
]
},
{
"id": "node_004",
"type": "TASK_EXECUTION",
"executor": {
"name": "BasicAuthExecutor",
"properties": {}
}
},
{
"id": "node_005",
"type": "TASK_EXECUTION",
"executor": {
"name": "VerifyOTPExecutor",
"properties": {}
}
},
{
"id": "node_006",
"type": "END"
},
{
"id": "node_007",
"type": "PROMPT",
"actions": [
{
"ref": "action_007",
"nextNode": "node_002"
}
]
}
]
} |
Beta Was this translation helpful? Give feedback.
-
|
@ThaminduDilshan is there any specific reason for using the Reason When resolving strings, we can easily detect i18n keys by checking whether a value starts with Using WDYT? Also, if do we need a space between |
Beta Was this translation helpful? Give feedback.
-
|
I suggest a small addition to the component metadata of Actions. {
"type": "ACTION",
"id": "action_001",
"label": "{{ t(signin:buttons.submit.label) }}",
+ "variant": "PRIMARY",
+ "eventType": "SUBMIT"
}Why
Complete `auth_flow_config_basic_with_prompt.json` {
"id": "auth_flow_config_basic_with_prompt",
"type": "AUTHENTICATION",
"nodes": [
{
"id": "start",
"type": "START",
"onSuccess": "prompt_credentials"
},
{
"id": "prompt_credentials",
"type": "PROMPT",
"meta": {
"components": [
{
"type": "TEXT",
"id": "text_001",
"label": "{{ t(signin:heading.label) }}",
"variant": "HEADING_1"
},
{
"type": "BLOCK",
"id": "block_001",
"components": [
{
"type": "TEXT_INPUT",
"id": "input_001",
"label": "{{ t(signin:fields.username.label) }}",
"required": true,
"placeholder": "{{ t(signin:fields.username.placeholder) }}"
},
{
"type": "TEXT_INPUT",
"id": "input_002",
"label": "{{ t(signin:fields.password.label) }}",
"required": true,
"placeholder": "{{ t(signin:fields.password.placeholder) }}"
},
{
"type": "ACTION",
"id": "action_001",
"label": "{{ t(signin:buttons.submit.label) }}",
"variant": "PRIMARY",
"eventType": "SUBMIT"
}
]
}
]
},
"inputs": [
{
"ref": "input_001",
"identifier": "username",
"type": "TEXT_INPUT",
"required": true
},
{
"ref": "input_002",
"identifier": "password",
"type": "PASSWORD_INPUT",
"required": true
}
],
"actions": [
{
"ref": "action_001",
"nextNode": "basic_auth"
}
]
},
{
"id": "basic_auth",
"type": "TASK_EXECUTION",
"executor": {
"name": "BasicAuthExecutor"
},
"onSuccess": "authenticated"
},
{
"id": "authenticated",
"type": "AUTHENTICATION_SUCCESS"
}
]
} |
Beta Was this translation helpful? Give feedback.















Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Related Feature Issue
#458
Problem Summary
Currently thunder only supports a pure backend based flow definition where an authentication/ registration flow can be defined in a graph representation in a json file. The flow execution engine takes the graph as the input and traverse through the nodes based on flow inputs.
However this representation is not user friendly and we need to have a step based drag and drop UI composer to define/ edit flows. This requires the introduction of a flow management API along with the management service.
Proposed Approach
The idea is to keep a single flow representation for both backend and frontend that will support both the backend graph execution as well as the frontend step view representation. This requires the introduction of a new UI node that will be responsible for storing and rendering UI steps during flow creation and execution.
With this;
New Node Type: UI_INTERACTION
There'll be a new dedicated UI node for handling UI representations/ layouts. The suggestion is to define a new special node type as
UI_INTERACTIONwhich contains auiDefinitionfor end-user UI. This can store the step view/ representation.UI_INTERACTION Node Structure
{ "id": "login_view", "type": "UI_INTERACTION", "uiDefinition": { "components": [ { "id": "title_1", "category": "DISPLAY", "type": "TYPOGRAPHY", "variant": "H3", "config": { "text": "auth.login.title" } }, { "id": "form_credentials", "category": "BLOCK", "type": "FORM", "config": {}, "components": [ { "id": "input_username", "category": "FIELD", "type": "INPUT", "variant": "TEXT", "config": { "type": "text", "label": "auth.username.label", "placeholder": "auth.username.placeholder", "required": true, "identifier": "username" } }, { "id": "input_password", "category": "FIELD", "type": "INPUT", "variant": "PASSWORD", "config": { "type": "password", "label": "auth.password.label", "placeholder": "auth.password.placeholder", "required": true, "identifier": "password" } }, { "id": "btn_continue", "category": "ACTION", "type": "BUTTON", "variant": "PRIMARY", "config": { "type": "submit", "text": "auth.continue.button" }, "action": { "type": "NEXT", "next": "basic_auth" } } ] }, { "id": "btn_google", "category": "ACTION", "type": "BUTTON", "variant": "SOCIAL", "config": { "type": "button", "text": "auth.google.button", "image": "assets/images/icons/google.svg" }, "action": { "type": "NEXT", "next": "google_auth" } } ] }, "next": ["choose_method"] }Example
Consider a multi step authentication scenario where you can either login with Username/Password or Google.
Now the flow composer will have the following UI.
The related graph definition is given below. This will be the same graph representation that gets submitted from the UI flow composer as well as the one being processed by the flow execution engine.
{ "id": "auth_flow_basic_google", "type": "AUTHENTICATION", "nodes": [ { "id": "login_view", "type": "UI_INTERACTION", "uiDefinition": { "components": [ { "id": "title_1", "category": "DISPLAY", "type": "TYPOGRAPHY", "variant": "H3", "config": { "text": "auth.login.title" } }, { "id": "form_credentials", "category": "BLOCK", "type": "FORM", "config": {}, "components": [ { "id": "input_username", "category": "FIELD", "type": "INPUT", "variant": "TEXT", "config": { "type": "text", "label": "auth.username.label", "placeholder": "auth.username.placeholder", "required": true, "identifier": "username" } }, { "id": "input_password", "category": "FIELD", "type": "INPUT", "variant": "PASSWORD", "config": { "type": "password", "label": "auth.password.label", "placeholder": "auth.password.placeholder", "required": true, "identifier": "password" } }, { "id": "btn_continue", "category": "ACTION", "type": "BUTTON", "variant": "PRIMARY", "config": { "type": "submit", "text": "auth.continue.button" }, "action": { "type": "NEXT", "next": "basic_auth" } } ] }, { "id": "btn_google", "category": "ACTION", "type": "BUTTON", "variant": "SOCIAL", "config": { "type": "button", "text": "auth.google.button", "image": "assets/images/icons/google.svg" }, "action": { "type": "NEXT", "next": "google_auth" } } ] }, "next": ["choose_method"] }, { "id": "choose_method", "type": "DECISION", "next": ["basic_auth", "google_auth"] }, { "id": "basic_auth", "type": "TASK_EXECUTION", "executor": { "name": "BasicAuthExecutor" }, "next": ["authenticated"] }, { "id": "google_auth", "type": "TASK_EXECUTION", "executor": { "name": "GoogleOIDCAuthExecutor" }, "next": ["authenticated"] }, { "id": "authenticated", "type": "AUTHENTICATION_SUCCESS" } ] }Technical Questions for Discussion
uiDefinitionfirst level element.flow/execute)?Alternative Approaches
We discussed an alternative idea where we need to keep the graph view for the backend execution engine and define a separate step based flow definition to be used in the frontends. The flows are defined using a UI-friendly step-based format and internally compiled into a graph-based execution model.
This UI based step representation also needs to preserve the location and size of the components to construct the same view during next visit in the flow composer UI. Additionally the engine needs to store step mappings for the applicable nodes and should be returned along with the flow execution response. However this UI representation needs to be treated as an optional verbose output that can be enabled/ disabled.
Consider a multi step authentication scenario where you can either login with Username/Password or Google.
The flow composer will have a UI similar to the following.
This will be equivalent to the following step representation.
{ "steps": [ { "id": "view_v0h6", "type": "VIEW", "size": { "width": 350, "height": 632 }, "position": { "x": 0, "y": 330 }, "data": { "components": [ { "category": "DISPLAY", "type": "TYPOGRAPHY", "id": "typography_2zvn", "variant": "H3", "config": { "text": "Login to Thunder" } }, { "category": "BLOCK", "type": "FORM", "config": {}, "id": "form_18eu", "components": [ { "category": "FIELD", "type": "INPUT", "variant": "TEXT", "config": { "type": "text", "hint": "", "label": "Username", "required": true, "placeholder": "Enter your username", "identifier": "username" }, "id": "input_h31x" }, { "category": "FIELD", "type": "INPUT", "variant": "PASSWORD", "config": { "identifier": "password", "type": "password", "hint": "", "label": "Password", "required": true, "placeholder": "Enter your password", "identifier": "password" }, "id": "input_qij9" }, { "category": "ACTION", "type": "BUTTON", "id": "button_rf4v", "variant": "PRIMARY", "config": { "type": "submit", "text": "Login" }, "action": { "type": "EXECUTOR", "executor": { "name": "BasicAuthExecutor" }, "next": "END" } } ] }, { "category": "ACTION", "type": "BUTTON", "id": "button_ydw5", "variant": "SOCIAL", "config": { "type": "button", "text": "Continue with Google", "image": "assets/images/icons/google.svg" }, "action": { "type": "NEXT", "next": "ID_k28d" } } ] } }, { "id": "ID_k28d", "type": "EXECUTION", "size": { "width": 200, "height": 99 }, "position": { "x": 580, "y": 730 }, "data": { "action": { "type": "EXECUTOR", "executor": { "name": "GoogleOIDCAuthExecutor", "meta": { "idpName": "Google" } }, "next": "END" } } }, { "id": "END", "type": "END", "size": { "width": 350, "height": 253 }, "position": { "x": 900, "y": 408 }, "data": { "action": { "type": "EXECUTOR", "executor": { "name": "AuthAssertExecutor" } } } } ], "flowType": "AUTHENTICATION" }Above step representation needs to be transformed to the below graph model with UI elements. The flow management component needs to perform this. The engine should be able to fetch and return the related UI elements during node executions.
{ "id": "custom_auth_flow", "type": "AUTHENTICATION", "nodes": [ { "id": "choose_auth", "type": "DECISION", "next": [ "basic_auth", "google_auth" ] }, { "id": "basic_auth", "type": "TASK_EXECUTION", "executor": { "name": "BasicAuthExecutor" }, "next": [ "authenticated" ] }, { "id": "google_auth", "type": "TASK_EXECUTION", "inputData": [ { "name": "nonce", "type": "string", "required": false } ], "properties": { "idpId": "<google-idp-id>" }, "executor": { "name": "GoogleOIDCAuthExecutor" }, "next": [ "authenticated" ] }, { "id": "authenticated", "type": "AUTHENTICATION_SUCCESS" } ] }This approach has a dual-representation model where:
This has some major drawbacks with the complex translation logic and requirement for storing two flow representations.
Architecture and Components
Security Considerations
N/A
Implementation Complexity
Medium (3-6 weeks)
Areas of Thunder that will be impacted
Questions for Community Input
No response
Beta Was this translation helpful? Give feedback.
All reactions