diff --git a/kairon/api/app/routers/bot/channels.py b/kairon/api/app/routers/bot/channels.py index 9c09d6caa..6d792471d 100644 --- a/kairon/api/app/routers/bot/channels.py +++ b/kairon/api/app/routers/bot/channels.py @@ -106,6 +106,128 @@ async def initiate_platform_onboarding( return Response(message='Channel added', data=channel_endpoint) +@router.post("/whatsapp/flows/{bsp_type}", response_model=Response) +async def add_whatsapp_flow( + request_data: DictData, + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Adds whatsapp flows for configured bsp account. New Flows are created as drafts. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.add_whatsapp_flow(request_data.data, current_user.get_bot(), current_user.get_user()) + return Response(data=response) + + +@router.post("/whatsapp/flows/{bsp_type}/{flow_id}", response_model=Response) +async def edit_whatsapp_flow( + request_data: DictData, + flow_id: str = Path(description="flow id", examples=["594425479261596"]), + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Edits whatsapp flows for configured bsp account. New Flows are created as drafts. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.edit_whatsapp_flow(flow_id, request_data.data.get("flow_json")) + return Response(data=response) + + +@router.get("/whatsapp/flows/{bsp_type}/{flow_id}", response_model=Response) +async def preview_whatsapp_flow( + flow_id: str = Path(description="flow id", examples=["594425479261596"]), + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Flows can be previewed through a public link generated with this endpoint. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.preview_whatsapp_flow(flow_id) + return Response(data=response) + + +@router.get("/whatsapp/flows/{bsp_type}/{flow_id}/assets", response_model=Response) +async def get_whatsapp_flow_assets( + flow_id: str = Path(description="flow id", examples=["594425479261596"]), + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Returns all assets attached to a specified flow with this endpoint. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.get_whatsapp_flow_assets(flow_id) + return Response(data=response) + + +@router.post("/whatsapp/flows/{bsp_type}/{flow_id}/deprecate", response_model=Response) +async def deprecate_whatsapp_flow( + flow_id: str = Path(description="flow id", examples=["594425479261596"]), + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Flow can be deprecated with this endpoint. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.deprecate_whatsapp_flow(flow_id) + return Response(data=response) + + +@router.get("/whatsapp/flows/{bsp_type}", response_model=Response) +async def retrieve_whatsapp_flows( + request: Request, + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Retrieves all whatsapp flows for configured bsp account. + Query parameters passed are used as filters while retrieving these flows. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + flows = provider.list_whatsapp_flows(**request.query_params) + return Response(data={"flows": flows}) + + +@router.delete("/whatsapp/flows/{bsp_type}/{flow_id}", response_model=Response) +async def delete_whatsapp_flow( + flow_id: str = Path(description="flow id", examples=["594425479261596"]), + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Deletes whatsapp flow from configured bsp account. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.delete_flow(flow_id) + return Response(data=response) + + +@router.post("/whatsapp/flows/{bsp_type}/{flow_id}/publish", response_model=Response) +async def publish_whatsapp_flow( + flow_id: str = Path(description="flow id", examples=["594425479261596"]), + bsp_type: str = Path(description="Business service provider type", + examples=[WhatsappBSPTypes.bsp_360dialog.value]), + current_user: User = Security(Authentication.get_current_user_and_bot, scopes=DESIGNER_ACCESS) +): + """ + Publishes whatsapp flow from configured bsp account. + """ + provider = BusinessServiceProviderFactory.get_instance(bsp_type)(current_user.get_bot(), current_user.get_user()) + response = provider.publish_flow(flow_id) + return Response(data=response) + + @router.post("/whatsapp/templates/{bsp_type}", response_model=Response) async def add_message_templates( request_data: DictData, diff --git a/kairon/shared/channels/whatsapp/bsp/dialog360.py b/kairon/shared/channels/whatsapp/bsp/dialog360.py index c523a554d..ea9dc8180 100644 --- a/kairon/shared/channels/whatsapp/bsp/dialog360.py +++ b/kairon/shared/channels/whatsapp/bsp/dialog360.py @@ -1,4 +1,5 @@ import ast +import json from typing import Text, Dict from loguru import logger @@ -82,6 +83,154 @@ def save_channel_config(self, clientId: Text, client: Text, channels: list, part } return ChatDataProcessor.save_channel_config(conf, self.bot, self.user) + def get_flow_endpoint_url(self): + config = ChatDataProcessor.get_channel_config(ChannelTypes.WHATSAPP.value, self.bot, mask_characters=False) + partner_id = Utility.environment["channels"]["360dialog"]["partner_id"] + waba_account_id = config.get("config", {}).get("waba_account_id") + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + flow_endpoint = f'/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows' + return base_url, flow_endpoint + + def add_whatsapp_flow(self, data: Dict, bot: Text, user: Text): + try: + Utility.validate_add_flow_request(data) + template_name = data.pop('template') + flow_json = self.get_flow_json_from_template(template_name) + + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}" + resp = Utility.execute_http_request(request_method="POST", http_url=url, request_body=data, headers=headers, + validate_status=True, err_msg="Failed to add flow: ", + expected_status_code=201) + if not data.get('clone_flow_id'): + flow_id = resp["id"] + self.edit_whatsapp_flow(flow_id, flow_json) + UserActivityLogger.add_log(a_type=UserActivityType.flow_creation.value, email=user, bot=bot, + message=['Flow created!']) + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + @staticmethod + def get_flow_json_from_template(template_name): + with open("metadata/flows/default_meta_flows.json", 'r') as file: + content = json.load(file) + flow_json = {} + for template in content["data"]["xfb_wa_flows_creation_options"]["templates"]: + if template_name == template['id']: + flow_json = template['flow_json'] + break + return flow_json + + @staticmethod + def write_flow_json_into_file(flow_json): + with open("metadata/flows/flow_json.json", 'w') as file: + json.dump(json.loads(flow_json), file, indent=4) + + def edit_whatsapp_flow(self, flow_id, flow_json): + from starlette.datastructures import UploadFile + + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}/{flow_id}/assets" + request_body = { + "asset_type": "FLOW_JSON", + "name": "flow.json" + } + self.write_flow_json_into_file(flow_json) + + file = UploadFile(filename="flow_json.json", file=(open("metadata/flows/flow_json.json", "rb"))) + + resp = Utility.execute_http_request(request_method="POST", http_url=url, request_body=request_body, + headers=headers, validate_status=True, err_msg="Failed to edit flow: ", + expected_status_code=200, + files={'file': (file.filename, file.file, file.content_type)}) + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + except Exception as e: + logger.exception(e) + raise AppException(str(e)) + + def preview_whatsapp_flow(self, flow_id: str): + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}/{flow_id}/preview" + resp = Utility.execute_http_request(request_method="GET", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to get flow: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + def get_whatsapp_flow_assets(self, flow_id: str): + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}/{flow_id}/assets" + resp = Utility.execute_http_request(request_method="GET", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to get flow assets: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + def deprecate_whatsapp_flow(self, flow_id: str): + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}/{flow_id}/deprecate" + resp = Utility.execute_http_request(request_method="POST", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to deprecate flow: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + def list_whatsapp_flows(self, **kwargs): + fields = kwargs.get("fields") + + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}?fields={fields}" if fields else f"{base_url}{flow_endpoint}" + resp = Utility.execute_http_request(request_method="GET", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to get flows: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + def delete_flow(self, flow_id: str): + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}/{flow_id}" + resp = Utility.execute_http_request(request_method="DELETE", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to delete flow: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + + def publish_flow(self, flow_id: str): + try: + base_url, flow_endpoint = self.get_flow_endpoint_url() + headers = {"Authorization": BSP360Dialog.get_partner_auth_token()} + url = f"{base_url}{flow_endpoint}/{flow_id}/publish" + resp = Utility.execute_http_request(request_method="POST", http_url=url, headers=headers, + validate_status=True, err_msg="Failed to publish flow: ") + return resp + except DoesNotExist as e: + logger.exception(e) + raise AppException("Channel not found!") + def add_template(self, data: Dict, bot: Text, user: Text): try: Utility.validate_create_template_request(data) diff --git a/kairon/shared/constants.py b/kairon/shared/constants.py index a0cc5ba14..d68b7eee8 100644 --- a/kairon/shared/constants.py +++ b/kairon/shared/constants.py @@ -62,6 +62,7 @@ class UserActivityType(str, Enum): invalid_login = 'invalid_login' download = "download" template_creation = 'template_creation' + flow_creation = "flow_creation" model_reload = "model_reload" @@ -131,6 +132,27 @@ class WhatsappBSPTypes(str, Enum): bsp_360dialog = "360dialog" +class FlowCategories(str, Enum): + SIGN_UP = "SIGN_UP" + SIGN_IN = "SIGN_IN" + APPOINTMENT_BOOKING = "APPOINTMENT_BOOKING" + LEAD_GENERATION = "LEAD_GENERATION" + CONTACT_US = "CONTACT_US" + CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT" + SURVEY = "SURVEY" + OTHER = "OTHER" + + +class FlowTemplates(str, Enum): + FLOWS_DEFAULT = "FLOWS_DEFAULT" + FLOWS_OFFSITE_CALL_TO_ACTION = "FLOWS_OFFSITE_CALL_TO_ACTION" + FLOWS_CUSTOMER_SATISFACTION = "FLOWS_CUSTOMER_SATISFACTION" + FLOWS_LEAD_RE_ENGAGEMENT = "FLOWS_LEAD_RE_ENGAGEMENT" + FLOWS_CONTENT_ENGAGEMENT = "FLOWS_CONTENT_ENGAGEMENT" + FLOWS_REQUEST_SUPPORT = "FLOWS_REQUEST_SUPPORT" + FLOWS_UPDATE_PREFERENCES = "FLOWS_UPDATE_PREFERENCES" + + class GPT3ResourceTypes(str, Enum): embeddings = "embeddings" chat_completion = "chat/completions" diff --git a/kairon/shared/utils.py b/kairon/shared/utils.py index 24a6eaa58..ee7ddbf23 100644 --- a/kairon/shared/utils.py +++ b/kairon/shared/utils.py @@ -67,7 +67,7 @@ from websockets import connect from .actions.models import ActionParameterType -from .constants import EventClass, UserActivityType +from .constants import EventClass, UserActivityType, FlowCategories, FlowTemplates from .constants import ( MaskingStrategy, SYSTEM_TRIGGERED_UTTERANCES, @@ -1300,6 +1300,22 @@ def reload_model(bot: Text, email: Text): data={"username": email, "exception": exc, "status": status}, ) + @staticmethod + def validate_add_flow_request(data: Dict): + required_keys = ['name', 'categories', 'template'] + missing_keys = [key for key in required_keys if key not in data] + if missing_keys: + raise AppException(f'Missing {", ".join(missing_keys)} in request body!') + categories = data.get('categories') + template = data.get('template') + invalid_categories = [category for category in categories + if category not in [flow_category.value for flow_category in FlowCategories]] + invalid_template = template if template not in [flow_template.value for flow_template in FlowTemplates] else "" + if invalid_categories: + raise AppException(f'Invalid categories {", ".join(invalid_categories)} in request body!') + if invalid_template: + raise AppException(f'Invalid template {template} in request body!') + @staticmethod def validate_create_template_request(data: Dict): required_keys = ["name", "category", "components", "language"] @@ -1695,13 +1711,16 @@ def execute_http_request( timeout=kwargs.get("timeout"), ) elif request_method.lower() in ["post", "put", "patch"]: - response = session.request( - request_method.upper(), - http_url, - json=request_body, - headers=headers, - timeout=kwargs.get("timeout"), - ) + if kwargs.get('files'): + response = session.request( + request_method.upper(), http_url, data=request_body, headers=headers, + timeout=kwargs.get('timeout'), files=kwargs.get('files') + ) + else: + response = session.request( + request_method.upper(), http_url, json=request_body, headers=headers, + timeout=kwargs.get('timeout') + ) else: raise AppException("Invalid request method!") logger.debug("raw response: " + str(response.text)) diff --git a/metadata/flows/default_meta_flows.json b/metadata/flows/default_meta_flows.json new file mode 100644 index 000000000..e84580d76 --- /dev/null +++ b/metadata/flows/default_meta_flows.json @@ -0,0 +1,111 @@ +{ + "data": { + "xfb_wa_flows_creation_options": { + "categories": [ + { + "id": "SIGN_UP", + "name": "Sign up" + }, + { + "id": "SIGN_IN", + "name": "Sign in" + }, + { + "id": "APPOINTMENT_BOOKING", + "name": "Appointment booking" + }, + { + "id": "LEAD_GENERATION", + "name": "Lead generation" + }, + { + "id": "CONTACT_US", + "name": "Contact us" + }, + { + "id": "CUSTOMER_SUPPORT", + "name": "Customer support" + }, + { + "id": "SURVEY", + "name": "Survey" + }, + { + "id": "OTHER", + "name": "Other" + } + ], + "templates": [ + { + "id": "FLOWS_DEFAULT", + "name": "Default", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"WELCOME_SCREEN\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Hello World\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Let's start building things!\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Complete\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {}\n }\n }\n ]\n },\n \"title\": \"Welcome\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {}\n }\n ]\n}", + "is_endpoint_configured": false + }, + { + "id": "FLOWS_SIGN_IN", + "name": "Sign in", + "flow_json": "{\n \"version\": \"3.1\",\n \"data_api_version\": \"3.0\",\n \"routing_model\": {\n \"SIGN_IN\": [\n \"SIGN_UP\",\n \"FORGOT_PASSWORD\"\n ],\n \"SIGN_UP\": [\n \"TERMS_AND_CONDITIONS\"\n ],\n \"FORGOT_PASSWORD\": [],\n \"TERMS_AND_CONDITIONS\": []\n },\n \"screens\": [\n {\n \"id\": \"SIGN_IN\",\n \"title\": \"Sign in\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"sign_in_form\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Email address\",\n \"name\": \"email\",\n \"input-type\": \"email\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Password\",\n \"name\": \"password\",\n \"input-type\": \"password\"\n },\n {\n \"type\": \"EmbeddedLink\",\n \"text\": \"Don't have an account? Sign up\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"SIGN_UP\"\n },\n \"payload\": {}\n }\n },\n {\n \"type\": \"EmbeddedLink\",\n \"text\": \"Forgot password\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"FORGOT_PASSWORD\"\n },\n \"payload\": {\n \"body\": \"Example\"\n }\n }\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Sign in\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"email\": \"${form.email}\",\n \"password\": \"${form.password}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"SIGN_UP\",\n \"title\": \"Sign up\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"sign_up_form\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"First Name\",\n \"name\": \"first_name\",\n \"input-type\": \"text\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Last Name\",\n \"name\": \"last_name\",\n \"input-type\": \"text\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Email address\",\n \"name\": \"email\",\n \"input-type\": \"email\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Set password\",\n \"name\": \"password\",\n \"input-type\": \"password\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Confirm password\",\n \"name\": \"confirm_password\",\n \"input-type\": \"password\"\n },\n {\n \"type\": \"OptIn\",\n \"name\": \"terms_agreement\",\n \"label\": \"I agree with the terms.\",\n \"required\": true,\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"TERMS_AND_CONDITIONS\"\n },\n \"payload\": {}\n }\n },\n {\n \"type\": \"OptIn\",\n \"name\": \"offers_acceptance\",\n \"label\": \"I would like to receive news and offers.\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"first_name\": \"${form.first_name}\",\n \"last_name\": \"${form.last_name}\",\n \"email\": \"${form.email}\",\n \"password\": \"${form.password}\",\n \"confirm_password\": \"${form.confirm_password}\",\n \"terms_agreement\": \"${form.terms_agreement}\",\n \"offers_acceptance\": \"${form.offers_acceptance}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"FORGOT_PASSWORD\",\n \"title\": \"Forgot password\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {\n \"body\": {\n \"type\": \"string\",\n \"__example__\": \"Enter your email address for your account and we'll send a reset link. The single-use link will expire after 24 hours.\"\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"forgot_password_form\",\n \"children\": [\n {\n \"type\": \"TextBody\",\n \"text\": \"${data.body}\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Email address\",\n \"name\": \"email\",\n \"input-type\": \"email\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Sign in\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"email\": \"${form.email}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"TERMS_AND_CONDITIONS\",\n \"title\": \"Terms and conditions\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Our Terms\"\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Data usage\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae odio dui. Praesent ut nulla tincidunt, scelerisque augue malesuada, volutpat lorem. Aliquam iaculis ex at diam posuere mollis. Suspendisse eget purus ac tellus interdum pharetra. In quis dolor turpis. Fusce in porttitor enim, vitae efficitur nunc. Fusce dapibus finibus volutpat. Fusce velit mi, ullamcorper ac gravida vitae, blandit quis ex. Fusce ultrices diam et justo blandit, quis consequat nisl euismod. Vestibulum pretium est sem, vitae convallis justo sollicitudin non. Morbi bibendum purus mattis quam condimentum, a scelerisque erat bibendum. Nullam sit amet bibendum lectus.\"\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Privacy policy\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae odio dui. Praesent ut nulla tincidunt, scelerisque augue malesuada, volutpat lorem. Aliquam iaculis ex at diam posuere mollis. Suspendisse eget purus ac tellus interdum pharetra. In quis dolor turpis. Fusce in porttitor enim, vitae efficitur nunc. Fusce dapibus finibus volutpat. Fusce velit mi, ullamcorper ac gravida vitae, blandit quis ex. Fusce ultrices diam et justo blandit, quis consequat nisl euismod. Vestibulum pretium est sem, vitae convallis justo sollicitudin non. Morbi bibendum purus mattis quam condimentum, a scelerisque erat bibendum. Nullam sit amet bibendum lectus.\"\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": true + }, + { + "id": "FLOWS_REGISTER", + "name": "Register for an account", + "flow_json": "{\n \"version\": \"3.1\",\n \"data_api_version\": \"3.0\",\n \"routing_model\": {\n \"REGISTER\": [\n \"TERMS_AND_CONDITIONS\"\n ],\n \"TERMS_AND_CONDITIONS\": []\n },\n \"screens\": [\n {\n \"id\": \"REGISTER\",\n \"title\": \"Register for an account\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {\n \"error_messages\": {\n \"type\": \"object\",\n \"__example__\": {\n \"confirm_password\": \"Passwords don't match.\"\n }\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"register_form\",\n \"error-messages\": \"${data.error_messages}\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"First name\",\n \"name\": \"first_name\",\n \"input-type\": \"text\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Last name\",\n \"name\": \"last_name\",\n \"input-type\": \"text\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Email address\",\n \"name\": \"email\",\n \"input-type\": \"email\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Set password\",\n \"name\": \"password\",\n \"input-type\": \"password\"\n },\n {\n \"type\": \"TextInput\",\n \"required\": true,\n \"label\": \"Confirm password\",\n \"name\": \"confirm_password\",\n \"input-type\": \"password\"\n },\n {\n \"type\": \"OptIn\",\n \"name\": \"terms_agreement\",\n \"label\": \"I agree with the terms.\",\n \"required\": true,\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"TERMS_AND_CONDITIONS\"\n },\n \"payload\": {}\n }\n },\n {\n \"type\": \"OptIn\",\n \"name\": \"offers_acceptance\",\n \"label\": \"I would like to receive news and offers.\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"first_name\": \"${form.first_name}\",\n \"last_name\": \"${form.last_name}\",\n \"email\": \"${form.email}\",\n \"password\": \"${form.password}\",\n \"confirm_password\": \"${form.confirm_password}\",\n \"terms_agreement\": \"${form.terms_agreement}\",\n \"offers_acceptance\": \"${form.offers_acceptance}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"TERMS_AND_CONDITIONS\",\n \"title\": \"Terms and conditions\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Our Terms\"\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Data usage\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae odio dui. Praesent ut nulla tincidunt, scelerisque augue malesuada, volutpat lorem. Aliquam iaculis ex at diam posuere mollis. Suspendisse eget purus ac tellus interdum pharetra. In quis dolor turpis. Fusce in porttitor enim, vitae efficitur nunc. Fusce dapibus finibus volutpat. Fusce velit mi, ullamcorper ac gravida vitae, blandit quis ex. Fusce ultrices diam et justo blandit, quis consequat nisl euismod. Vestibulum pretium est sem, vitae convallis justo sollicitudin non. Morbi bibendum purus mattis quam condimentum, a scelerisque erat bibendum. Nullam sit amet bibendum lectus.\"\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Privacy policy\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae odio dui. Praesent ut nulla tincidunt, scelerisque augue malesuada, volutpat lorem. Aliquam iaculis ex at diam posuere mollis. Suspendisse eget purus ac tellus interdum pharetra. In quis dolor turpis. Fusce in porttitor enim, vitae efficitur nunc. Fusce dapibus finibus volutpat. Fusce velit mi, ullamcorper ac gravida vitae, blandit quis ex. Fusce ultrices diam et justo blandit, quis consequat nisl euismod. Vestibulum pretium est sem, vitae convallis justo sollicitudin non. Morbi bibendum purus mattis quam condimentum, a scelerisque erat bibendum. Nullam sit amet bibendum lectus.\"\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": true + }, + { + "id": "FLOWS_BOOK_APPOINTMENT", + "name": "Book an appointment", + "flow_json": "{\n \"version\": \"3.1\",\n \"data_api_version\": \"3.0\",\n \"routing_model\": {\n \"APPOINTMENT\": [\n \"DETAILS\"\n ],\n \"DETAILS\": [\n \"SUMMARY\"\n ],\n \"SUMMARY\": [\n \"TERMS\"\n ],\n \"TERMS\": []\n },\n \"screens\": [\n {\n \"id\": \"APPOINTMENT\",\n \"title\": \"Appointment\",\n \"data\": {\n \"department\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"shopping\",\n \"title\": \"Shopping & Groceries\"\n },\n {\n \"id\": \"clothing\",\n \"title\": \"Clothing & Apparel\"\n },\n {\n \"id\": \"home\",\n \"title\": \"Home Goods & Decor\"\n },\n {\n \"id\": \"electronics\",\n \"title\": \"Electronics & Appliances\"\n },\n {\n \"id\": \"beauty\",\n \"title\": \"Beauty & Personal Care\"\n }\n ]\n },\n \"location\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"1\",\n \"title\": \"King\u2019s Cross, London\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Oxford Street, London\"\n },\n {\n \"id\": \"3\",\n \"title\": \"Covent Garden, London\"\n },\n {\n \"id\": \"4\",\n \"title\": \"Piccadilly Circus, London\"\n }\n ]\n },\n \"is_location_enabled\": {\n \"type\": \"boolean\",\n \"__example__\": true\n },\n \"date\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"2024-01-01\",\n \"title\": \"Mon Jan 01 2024\"\n },\n {\n \"id\": \"2024-01-02\",\n \"title\": \"Tue Jan 02 2024\"\n },\n {\n \"id\": \"2024-01-03\",\n \"title\": \"Wed Jan 03 2024\"\n }\n ]\n },\n \"is_date_enabled\": {\n \"type\": \"boolean\",\n \"__example__\": true\n },\n \"time\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"10:30\",\n \"title\": \"10:30\"\n },\n {\n \"id\": \"11:00\",\n \"title\": \"11:00\",\n \"enabled\": false\n },\n {\n \"id\": \"11:30\",\n \"title\": \"11:30\"\n },\n {\n \"id\": \"12:00\",\n \"title\": \"12:00\",\n \"enabled\": false\n },\n {\n \"id\": \"12:30\",\n \"title\": \"12:30\"\n }\n ]\n },\n \"is_time_enabled\": {\n \"type\": \"boolean\",\n \"__example__\": true\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"appointment_form\",\n \"children\": [\n {\n \"type\": \"Dropdown\",\n \"label\": \"Department\",\n \"name\": \"department\",\n \"data-source\": \"${data.department}\",\n \"required\": true,\n \"on-select-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"trigger\": \"department_selected\",\n \"department\": \"${form.department}\"\n }\n }\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"Location\",\n \"name\": \"location\",\n \"data-source\": \"${data.location}\",\n \"required\": \"${data.is_location_enabled}\",\n \"enabled\": \"${data.is_location_enabled}\",\n \"on-select-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"trigger\": \"location_selected\",\n \"department\": \"${form.department}\",\n \"location\": \"${form.location}\"\n }\n }\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"Date\",\n \"name\": \"date\",\n \"data-source\": \"${data.date}\",\n \"required\": \"${data.is_date_enabled}\",\n \"enabled\": \"${data.is_date_enabled}\",\n \"on-select-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"trigger\": \"date_selected\",\n \"department\": \"${form.department}\",\n \"location\": \"${form.location}\",\n \"date\": \"${form.date}\"\n }\n }\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"Time\",\n \"name\": \"time\",\n \"data-source\": \"${data.time}\",\n \"required\": \"${data.is_time_enabled}\",\n \"enabled\": \"${data.is_time_enabled}\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"DETAILS\"\n },\n \"payload\": {\n \"department\": \"${form.department}\",\n \"location\": \"${form.location}\",\n \"date\": \"${form.date}\",\n \"time\": \"${form.time}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"DETAILS\",\n \"title\": \"Details\",\n \"data\": {\n \"department\": {\n \"type\": \"string\",\n \"__example__\": \"beauty\"\n },\n \"location\": {\n \"type\": \"string\",\n \"__example__\": \"1\"\n },\n \"date\": {\n \"type\": \"string\",\n \"__example__\": \"2024-01-01\"\n },\n \"time\": {\n \"type\": \"string\",\n \"__example__\": \"11:30\"\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"details_form\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"label\": \"Name\",\n \"name\": \"name\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Email\",\n \"name\": \"email\",\n \"input-type\": \"email\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Phone\",\n \"name\": \"phone\",\n \"input-type\": \"phone\",\n \"required\": true\n },\n {\n \"type\": \"TextArea\",\n \"label\": \"Further details\",\n \"name\": \"more_details\",\n \"helper-text\": \"More details about your visit\",\n \"required\": false\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"department\": \"${data.department}\",\n \"location\": \"${data.location}\",\n \"date\": \"${data.date}\",\n \"time\": \"${data.time}\",\n \"name\": \"${form.name}\",\n \"email\": \"${form.email}\",\n \"phone\": \"${form.phone}\",\n \"more_details\": \"${form.more_details}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"SUMMARY\",\n \"title\": \"Summary\",\n \"terminal\": true,\n \"data\": {\n \"appointment\": {\n \"type\": \"string\",\n \"__example__\": \"Beauty & Personal Care Department at Kings Cross, London\nMon Jan 01 2024 at 11:30.\"\n },\n \"details\": {\n \"type\": \"string\",\n \"__example__\": \"Name: John Doe\nEmail: john\u0040example.com\nPhone: 123456789\n\nA free skin care consultation, please\"\n },\n \"department\": {\n \"type\": \"string\",\n \"__example__\": \"beauty\"\n },\n \"location\": {\n \"type\": \"string\",\n \"__example__\": \"1\"\n },\n \"date\": {\n \"type\": \"string\",\n \"__example__\": \"2024-01-01\"\n },\n \"time\": {\n \"type\": \"string\",\n \"__example__\": \"11:30\"\n },\n \"name\": {\n \"type\": \"string\",\n \"__example__\": \"John Doe\"\n },\n \"email\": {\n \"type\": \"string\",\n \"__example__\": \"john\u0040example.com\"\n },\n \"phone\": {\n \"type\": \"string\",\n \"__example__\": \"123456789\"\n },\n \"more_details\": {\n \"type\": \"string\",\n \"__example__\": \"A free skin care consultation, please\"\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"confirmation_form\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Appointment\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"${data.appointment}\"\n },\n {\n \"type\": \"TextHeading\",\n \"text\": \"Details\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"${data.details}\"\n },\n {\n \"type\": \"OptIn\",\n \"name\": \"terms\",\n \"label\": \"I agree to the terms\",\n \"required\": true,\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"TERMS\"\n },\n \"payload\": {}\n }\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Confirm Appointment\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"department\": \"${data.department}\",\n \"location\": \"${data.location}\",\n \"date\": \"${data.date}\",\n \"time\": \"${data.time}\",\n \"name\": \"${data.name}\",\n \"email\": \"${data.email}\",\n \"phone\": \"${data.phone}\",\n \"more_details\": \"${data.more_details}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"TERMS\",\n \"title\": \"Terms and Conditions\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Our Terms\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": true + }, + { + "id": "FLOWS_GET_QUOTE", + "name": "Get a quote", + "flow_json": "{\n \"version\": \"3.1\",\n \"data_api_version\": \"3.0\",\n \"routing_model\": {\n \"DETAILS\": [\n \"COVER\"\n ],\n \"COVER\": [\n \"QUOTE\"\n ],\n \"QUOTE\": [\n \"TERMS_AND_CONDITIONS\"\n ],\n \"TERMS_AND_CONDITIONS\": []\n },\n \"screens\": [\n {\n \"id\": \"DETAILS\",\n \"title\": \"Your details\",\n \"data\": {\n \"city\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"1\",\n \"title\": \"Light City, SO\"\n }\n ]\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"details_form\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"label\": \"Your name\",\n \"input-type\": \"text\",\n \"name\": \"name\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Street address\",\n \"input-type\": \"text\",\n \"name\": \"address\",\n \"required\": true\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"City, State\",\n \"name\": \"city\",\n \"data-source\": \"${data.city}\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Zip code\",\n \"input-type\": \"text\",\n \"name\": \"zip_code\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Country/Region\",\n \"input-type\": \"text\",\n \"name\": \"country_region\",\n \"required\": true\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"name\": \"${form.name}\",\n \"address\": \"${form.address}\",\n \"city\": \"${form.city}\",\n \"zip_code\": \"${form.zip_code}\",\n \"country_region\": \"${form.country_region}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"COVER\",\n \"title\": \"Your cover\",\n \"data\": {\n \"options\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"description\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"1\",\n \"title\": \"Fire and theft\",\n \"description\": \"Cover your home against incidents of theft or accidental fires\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Natural disaster\",\n \"description\": \"Protect your home against disasters including earthquakes, floods and storms\"\n },\n {\n \"id\": \"3\",\n \"title\": \"Liability\",\n \"description\": \"Protect yourself from legal liabilities that occur from accidents on your property\"\n }\n ]\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"cover_form\",\n \"children\": [\n {\n \"type\": \"CheckboxGroup\",\n \"name\": \"options\",\n \"data-source\": \"${data.options}\",\n \"label\": \"Options\",\n \"required\": true\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"options\": \"${form.options}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"QUOTE\",\n \"title\": \"Your quote\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {\n \"excess\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n }\n }\n },\n \"__example__\": [\n {\n \"id\": \"1\",\n \"title\": \"$250\"\n }\n ]\n },\n \"total\": {\n \"type\": \"string\",\n \"__example__\": \"$47.98 per month\"\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"quote_form\",\n \"init-values\": {\n \"payment_options\": \"1\"\n },\n \"children\": [\n {\n \"type\": \"Dropdown\",\n \"label\": \"Excess\",\n \"name\": \"excess\",\n \"data-source\": \"${data.excess}\",\n \"on-select-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"excess\": \"${form.excess}\"\n }\n },\n \"required\": true\n },\n {\n \"type\": \"RadioButtonsGroup\",\n \"data-source\": [\n {\n \"id\": \"1\",\n \"title\": \"Monthly\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Annually (Save $115)\"\n }\n ],\n \"name\": \"payment_options\",\n \"label\": \"Payment options\",\n \"on-select-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"payment_options\": \"${form.payment_options}\"\n }\n },\n \"required\": true\n },\n {\n \"type\": \"TextHeading\",\n \"text\": \"${data.total}\"\n },\n {\n \"type\": \"OptIn\",\n \"name\": \"privacy_policy\",\n \"label\": \"Accept our Privacy Policy\",\n \"required\": true,\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"TERMS_AND_CONDITIONS\"\n },\n \"payload\": {}\n }\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Choose quote\",\n \"on-click-action\": {\n \"name\": \"data_exchange\",\n \"payload\": {\n \"privacy_policy\": \"${form.privacy_policy}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"TERMS_AND_CONDITIONS\",\n \"title\": \"Terms and conditions\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Our Terms\"\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Data usage\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae odio dui. Praesent ut nulla tincidunt, scelerisque augue malesuada, volutpat lorem. Aliquam iaculis ex at diam posuere mollis. Suspendisse eget purus ac tellus interdum pharetra. In quis dolor turpis. Fusce in porttitor enim, vitae efficitur nunc. Fusce dapibus finibus volutpat. Fusce velit mi, ullamcorper ac gravida vitae, blandit quis ex. Fusce ultrices diam et justo blandit, quis consequat nisl euismod. Vestibulum pretium est sem, vitae convallis justo sollicitudin non. Morbi bibendum purus mattis quam condimentum, a scelerisque erat bibendum. Nullam sit amet bibendum lectus.\"\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Privacy policy\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae odio dui. Praesent ut nulla tincidunt, scelerisque augue malesuada, volutpat lorem. Aliquam iaculis ex at diam posuere mollis. Suspendisse eget purus ac tellus interdum pharetra. In quis dolor turpis. Fusce in porttitor enim, vitae efficitur nunc. Fusce dapibus finibus volutpat. Fusce velit mi, ullamcorper ac gravida vitae, blandit quis ex. Fusce ultrices diam et justo blandit, quis consequat nisl euismod. Vestibulum pretium est sem, vitae convallis justo sollicitudin non. Morbi bibendum purus mattis quam condimentum, a scelerisque erat bibendum. Nullam sit amet bibendum lectus.\"\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": true + }, + { + "id": "FLOWS_OFFSITE_CALL_TO_ACTION", + "name": "Register for an event", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"SIGN_UP\",\n \"title\": \"Sign Up\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Join our next webinar!\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"First, we'll need a few details from you.\"\n },\n {\n \"type\": \"TextInput\",\n \"name\": \"firstName\",\n \"label\": \"First Name\",\n \"input-type\": \"text\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Last Name\",\n \"name\": \"lastName\",\n \"input-type\": \"text\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Email Address\",\n \"name\": \"email\",\n \"input-type\": \"email\",\n \"required\": true\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"SURVEY\"\n },\n \"payload\": {\n \"firstName\": \"${form.firstName}\",\n \"lastName\": \"${form.lastName}\",\n \"email\": \"${form.email}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"SURVEY\",\n \"title\": \"Thank you\",\n \"data\": {\n \"firstName\": {\n \"type\": \"string\",\n \"__example__\": \"Example\"\n },\n \"lastName\": {\n \"type\": \"string\",\n \"__example__\": \"Example\"\n },\n \"email\": {\n \"type\": \"string\",\n \"__example__\": \"Example\"\n }\n },\n \"terminal\": true,\n \"success\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Before you go\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"How did you hear about us?\"\n },\n {\n \"type\": \"RadioButtonsGroup\",\n \"label\": \"Choose one\",\n \"required\": false,\n \"name\": \"source\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Friend's recommendation\"\n },\n {\n \"id\": \"1\",\n \"title\": \"TV advertisement\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Search engine\"\n },\n {\n \"id\": \"3\",\n \"title\": \"Social media\"\n }\n ]\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Done\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"source\": \"${form.source}\",\n \"firstName\": \"${data.firstName}\",\n \"lastName\": \"${data.lastName}\",\n \"email\": \"${data.email}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": false + }, + { + "id": "FLOWS_CUSTOMER_SATISFACTION", + "name": "Get feedback", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"RECOMMEND\",\n \"title\": \"Feedback 1 of 2\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Would you recommend us to a friend?\"\n },\n {\n \"type\": \"RadioButtonsGroup\",\n \"label\": \"Choose one\",\n \"name\": \"recommend_radio\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Yes\"\n },\n {\n \"id\": \"1\",\n \"title\": \"No\"\n }\n ],\n \"required\": true\n },\n {\n \"type\": \"TextSubheading\",\n \"text\": \"How could we do better?\"\n },\n {\n \"type\": \"TextArea\",\n \"label\": \"Leave a comment\",\n \"required\": false,\n \"name\": \"comment_text\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"RATE\"\n },\n \"payload\": {\n \"recommend_radio\": \"${form.recommend_radio}\",\n \"comment_text\": \"${form.comment_text}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"RATE\",\n \"title\": \"Feedback 2 of 2\",\n \"data\": {\n \"recommend_radio\": {\n \"type\": \"string\",\n \"__example__\": \"Example\"\n },\n \"comment_text\": {\n \"type\": \"string\",\n \"__example__\": \"Example\"\n }\n },\n \"terminal\": true,\n \"success\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextSubheading\",\n \"text\": \"Rate the following: \"\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"Purchase experience\",\n \"required\": true,\n \"name\": \"purchase_rating\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"\u2605\u2605\u2605\u2605\u2605 \u2022 Excellent (5/5)\"\n },\n {\n \"id\": \"1\",\n \"title\": \"\u2605\u2605\u2605\u2605\u2606 \u2022 Good (4/5)\"\n },\n {\n \"id\": \"2\",\n \"title\": \"\u2605\u2605\u2605\u2606\u2606 \u2022 Average (3/5)\"\n },\n {\n \"id\": \"3\",\n \"title\": \"\u2605\u2605\u2606\u2606\u2606 \u2022 Poor (2/5)\"\n },\n {\n \"id\": \"4\",\n \"title\": \"\u2605\u2606\u2606\u2606\u2606 \u2022 Very Poor (1/5)\"\n }\n ]\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"Delivery and setup\",\n \"required\": true,\n \"name\": \"delivery_rating\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"\u2605\u2605\u2605\u2605\u2605 \u2022 Excellent (5/5)\"\n },\n {\n \"id\": \"1\",\n \"title\": \"\u2605\u2605\u2605\u2605\u2606 \u2022 Good (4/5)\"\n },\n {\n \"id\": \"2\",\n \"title\": \"\u2605\u2605\u2605\u2606\u2606 \u2022 Average (3/5)\"\n },\n {\n \"id\": \"3\",\n \"title\": \"\u2605\u2605\u2606\u2606\u2606 \u2022 Poor (2/5)\"\n },\n {\n \"id\": \"4\",\n \"title\": \"\u2605\u2606\u2606\u2606\u2606 \u2022 Very Poor (1/5)\"\n }\n ]\n },\n {\n \"type\": \"Dropdown\",\n \"label\": \"Customer service\",\n \"required\": true,\n \"name\": \"cs_rating\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"\u2605\u2605\u2605\u2605\u2605 \u2022 Excellent (5/5)\"\n },\n {\n \"id\": \"1\",\n \"title\": \"\u2605\u2605\u2605\u2605\u2606 \u2022 Good (4/5)\"\n },\n {\n \"id\": \"2\",\n \"title\": \"\u2605\u2605\u2605\u2606\u2606 \u2022 Average (3/5)\"\n },\n {\n \"id\": \"3\",\n \"title\": \"\u2605\u2605\u2606\u2606\u2606 \u2022 Poor (2/5)\"\n },\n {\n \"id\": \"4\",\n \"title\": \"\u2605\u2606\u2606\u2606\u2606 \u2022 Very Poor (1/5)\"\n }\n ]\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Done\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"purchase_rating\": \"${form.purchase_rating}\",\n \"delivery_rating\": \"${form.delivery_rating}\",\n \"cs_rating\": \"${form.cs_rating}\",\n \"recommend_radio\": \"${data.recommend_radio}\",\n \"comment_text\": \"${data.comment_text}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": false + }, + { + "id": "FLOWS_LEAD_RE_ENGAGEMENT", + "name": "Complete sign up", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"SIGN_UP\",\n \"title\": \"Finish Sign Up\",\n \"data\": {},\n \"terminal\": true,\n \"success\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"name\": \"firstName\",\n \"label\": \"First Name\",\n \"input-type\": \"text\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Last Name\",\n \"name\": \"lastName\",\n \"input-type\": \"text\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Email Address\",\n \"name\": \"email\",\n \"input-type\": \"email\",\n \"required\": true\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Done\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"firstName\": \"${form.firstName}\",\n \"lastName\": \"${form.lastName}\",\n \"email\": \"${form.email}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": false + }, + { + "id": "FLOWS_CONTENT_ENGAGEMENT", + "name": "Complete our quiz", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"QUESTION_ONE\",\n \"title\": \"Question 1 of 3\",\n \"data\": {},\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"You've found the perfect deal, what do you do next?\"\n },\n {\n \"type\": \"CheckboxGroup\",\n \"label\": \"Choose all that apply:\",\n \"required\": true,\n \"name\": \"question1Checkbox\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Buy it right away\"\n },\n {\n \"id\": \"1\",\n \"title\": \"Check reviews before buying\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Share it with friends + family\"\n },\n {\n \"id\": \"3\",\n \"title\": \"Buy multiple, while its cheap\"\n },\n {\n \"id\": \"4\",\n \"title\": \"None of the above\"\n }\n ]\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"QUESTION_TWO\"\n },\n \"payload\": {\n \"question1Checkbox\": \"${form.question1Checkbox}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"QUESTION_TWO\",\n \"title\": \"Question 2 of 3\",\n \"data\": {\n \"question1Checkbox\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"__example__\": []\n }\n },\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Its your birthday in two weeks, how might you prepare?\"\n },\n {\n \"type\": \"RadioButtonsGroup\",\n \"label\": \"Choose all that apply:\",\n \"required\": true,\n \"name\": \"question2RadioButtons\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Buy something new\"\n },\n {\n \"id\": \"1\",\n \"title\": \"Wear the same, as usual\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Look for a deal online\"\n }\n ]\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Continue\",\n \"on-click-action\": {\n \"name\": \"navigate\",\n \"next\": {\n \"type\": \"screen\",\n \"name\": \"QUESTION_THREE\"\n },\n \"payload\": {\n \"question2RadioButtons\": \"${form.question2RadioButtons}\",\n \"question1Checkbox\": \"${data.question1Checkbox}\"\n }\n }\n }\n ]\n }\n ]\n }\n },\n {\n \"id\": \"QUESTION_THREE\",\n \"title\": \"Question 3 of 3\",\n \"data\": {\n \"question2RadioButtons\": {\n \"type\": \"string\",\n \"__example__\": \"Example\"\n },\n \"question1Checkbox\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"__example__\": []\n }\n },\n \"terminal\": true,\n \"success\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"What's the best gift for a friend?\"\n },\n {\n \"type\": \"CheckboxGroup\",\n \"label\": \"Choose all that apply:\",\n \"required\": true,\n \"name\": \"question3Checkbox\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"A gift voucher\"\n },\n {\n \"id\": \"1\",\n \"title\": \"A new outfit \"\n },\n {\n \"id\": \"2\",\n \"title\": \"A bouquet of flowers\"\n },\n {\n \"id\": \"3\",\n \"title\": \"A meal out together\"\n }\n ]\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Done\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"question1Checkbox\": \"${data.question1Checkbox}\",\n \"question2RadioButtons\": \"${data.question2RadioButtons}\",\n \"question3Checkbox\": \"${form.question3Checkbox}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": false + }, + { + "id": "FLOWS_REQUEST_SUPPORT", + "name": "Get support", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"DETAILS\",\n \"title\": \"Get help\",\n \"data\": {},\n \"terminal\": true,\n \"success\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"TextInput\",\n \"name\": \"name\",\n \"label\": \"Name\",\n \"input-type\": \"text\",\n \"required\": true\n },\n {\n \"type\": \"TextInput\",\n \"label\": \"Order number\",\n \"name\": \"orderNumber\",\n \"input-type\": \"number\",\n \"required\": true,\n \"helper-text\": \"\"\n },\n {\n \"type\": \"RadioButtonsGroup\",\n \"label\": \"Choose a topic\",\n \"name\": \"topicRadio\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Orders and payments\"\n },\n {\n \"id\": \"1\",\n \"title\": \"Maintenance\"\n },\n {\n \"id\": \"2\",\n \"title\": \"Delivery\"\n },\n {\n \"id\": \"3\",\n \"title\": \"Returns\"\n },\n {\n \"id\": \"4\",\n \"title\": \"Other\"\n }\n ],\n \"required\": true\n },\n {\n \"type\": \"TextArea\",\n \"label\": \"Description of issue\",\n \"required\": false,\n \"name\": \"description\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Done\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"name\": \"${form.name}\",\n \"orderNumber\": \"${form.orderNumber}\",\n \"topicRadio\": \"${form.topicRadio}\",\n \"description\": \"${form.description}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": false + }, + { + "id": "FLOWS_UPDATE_PREFERENCES", + "name": "Update preferences", + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"PREFERENCES\",\n \"title\": \"Update Preferences\",\n \"data\": {},\n \"terminal\": true,\n \"success\": true,\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"Form\",\n \"name\": \"form\",\n \"children\": [\n {\n \"type\": \"CheckboxGroup\",\n \"label\": \"Communication types\",\n \"required\": true,\n \"name\": \"communicationTypes\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Special offers and promotions\"\n },\n {\n \"id\": \"1\",\n \"title\": \"Changes to my subscription\"\n },\n {\n \"id\": \"2\",\n \"title\": \"News and events\"\n },\n {\n \"id\": \"3\",\n \"title\": \"New products\"\n }\n ]\n },\n {\n \"type\": \"CheckboxGroup\",\n \"label\": \"Contact Preferences\",\n \"required\": false,\n \"name\": \"contactPrefs\",\n \"data-source\": [\n {\n \"id\": \"0\",\n \"title\": \"Whatsapp\"\n },\n {\n \"id\": \"1\",\n \"title\": \"Email\"\n },\n {\n \"id\": \"2\",\n \"title\": \"SMS\"\n }\n ]\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Done\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {\n \"communicationTypes\": \"${form.communicationTypes}\",\n \"contactPrefs\": \"${form.contactPrefs}\"\n }\n }\n }\n ]\n }\n ]\n }\n }\n ]\n}", + "is_endpoint_configured": false + } + ] + } + }, + "extensions": { + "is_final": true + } +} diff --git a/metadata/flows/flow_json.json b/metadata/flows/flow_json.json new file mode 100644 index 000000000..503fe52cd --- /dev/null +++ b/metadata/flows/flow_json.json @@ -0,0 +1,195 @@ +{ + "version": "3.1", + "screens": [ + { + "id": "RECOMMEND", + "title": "Feedback 1 of 2", + "data": {}, + "layout": { + "type": "SingleColumnLayout", + "children": [ + { + "type": "Form", + "name": "form", + "children": [ + { + "type": "TextSubheading", + "text": "Would you recommend us to a friend?" + }, + { + "type": "RadioButtonsGroup", + "label": "Choose one", + "name": "recommend_radio", + "data-source": [ + { + "id": "0", + "title": "Yes" + }, + { + "id": "1", + "title": "No" + } + ], + "required": true + }, + { + "type": "TextSubheading", + "text": "How could we do better?" + }, + { + "type": "TextArea", + "label": "Leave a comment", + "required": false, + "name": "comment_text" + }, + { + "type": "Footer", + "label": "Continue", + "on-click-action": { + "name": "navigate", + "next": { + "type": "screen", + "name": "RATE" + }, + "payload": { + "recommend_radio": "${form.recommend_radio}", + "comment_text": "${form.comment_text}" + } + } + } + ] + } + ] + } + }, + { + "id": "RATE", + "title": "Feedback 2 of 2", + "data": { + "recommend_radio": { + "type": "string", + "__example__": "Example" + }, + "comment_text": { + "type": "string", + "__example__": "Example" + } + }, + "terminal": true, + "success": true, + "layout": { + "type": "SingleColumnLayout", + "children": [ + { + "type": "Form", + "name": "form", + "children": [ + { + "type": "TextSubheading", + "text": "Rate the following: " + }, + { + "type": "Dropdown", + "label": "Purchase experience", + "required": true, + "name": "purchase_rating", + "data-source": [ + { + "id": "0", + "title": "\u2605\u2605\u2605\u2605\u2605 \u2022 Excellent (5/5)" + }, + { + "id": "1", + "title": "\u2605\u2605\u2605\u2605\u2606 \u2022 Good (4/5)" + }, + { + "id": "2", + "title": "\u2605\u2605\u2605\u2606\u2606 \u2022 Average (3/5)" + }, + { + "id": "3", + "title": "\u2605\u2605\u2606\u2606\u2606 \u2022 Poor (2/5)" + }, + { + "id": "4", + "title": "\u2605\u2606\u2606\u2606\u2606 \u2022 Very Poor (1/5)" + } + ] + }, + { + "type": "Dropdown", + "label": "Delivery and setup", + "required": true, + "name": "delivery_rating", + "data-source": [ + { + "id": "0", + "title": "\u2605\u2605\u2605\u2605\u2605 \u2022 Excellent (5/5)" + }, + { + "id": "1", + "title": "\u2605\u2605\u2605\u2605\u2606 \u2022 Good (4/5)" + }, + { + "id": "2", + "title": "\u2605\u2605\u2605\u2606\u2606 \u2022 Average (3/5)" + }, + { + "id": "3", + "title": "\u2605\u2605\u2606\u2606\u2606 \u2022 Poor (2/5)" + }, + { + "id": "4", + "title": "\u2605\u2606\u2606\u2606\u2606 \u2022 Very Poor (1/5)" + } + ] + }, + { + "type": "Dropdown", + "label": "Customer service", + "required": true, + "name": "cs_rating", + "data-source": [ + { + "id": "0", + "title": "\u2605\u2605\u2605\u2605\u2605 \u2022 Excellent (5/5)" + }, + { + "id": "1", + "title": "\u2605\u2605\u2605\u2605\u2606 \u2022 Good (4/5)" + }, + { + "id": "2", + "title": "\u2605\u2605\u2605\u2606\u2606 \u2022 Average (3/5)" + }, + { + "id": "3", + "title": "\u2605\u2605\u2606\u2606\u2606 \u2022 Poor (2/5)" + }, + { + "id": "4", + "title": "\u2605\u2606\u2606\u2606\u2606 \u2022 Very Poor (1/5)" + } + ] + }, + { + "type": "Footer", + "label": "Done", + "on-click-action": { + "name": "complete", + "payload": { + "purchase_rating": "${form.purchase_rating}", + "delivery_rating": "${form.delivery_rating}", + "cs_rating": "${form.cs_rating}", + "recommend_radio": "${data.recommend_radio}", + "comment_text": "${data.comment_text}" + } + } + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/integration_test/services_test.py b/tests/integration_test/services_test.py index d5d66fd5a..e612744d8 100644 --- a/tests/integration_test/services_test.py +++ b/tests/integration_test/services_test.py @@ -19009,6 +19009,128 @@ def test_list_whatsapp_templates_error(): assert actual["message"] == "Channel not found!" +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_add_whatsapp_flow_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"], + "template": "FLOWS_OFFSITE_CALL_TO_ACTION", + } + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog", + json={'data': data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_edit_whatsapp_flow_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + data = { + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"WELCOME_SCREEN\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Hello World\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Let's start building things!\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Complete\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {}\n }\n }\n ]\n },\n \"title\": \"Welcome\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {}\n }\n ]\n}" + } + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id", + json={"data": data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token} + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_preview_whatsapp_flow_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.get( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_retrieve_whatsapp_flows_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.get( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_delete_whatsapp_flow_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.delete( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_publish_whatsapp_flow_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id/publish", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_get_whatsapp_flow_assets_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.get( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id/assets", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_partner_auth_token", autospec=True) +def test_deprecate_whatsapp_flow_error(mock_get_partner_auth_token): + mock_get_partner_auth_token.return_value = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id/deprecate", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert actual["message"] == "Channel not found!" + assert actual["data"] is None + + def test_get_channel_logs(): from kairon.shared.chat.data_objects import ChannelLogs ChannelLogs( @@ -19353,6 +19475,190 @@ def test_list_templates(mock_list_templates): assert actual["data"]["templates"] == api_resp["waba_templates"] +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.add_whatsapp_flow", autospec=True) +def test_add_whatsapp_flow(mock_add_whatsapp_flow): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_resp = { + "id": "594425479261596", + } + mock_add_whatsapp_flow.return_value = api_resp + + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog", + json={'data': data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == {'id': '594425479261596'} + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.edit_whatsapp_flow", autospec=True) +def test_edit_whatsapp_flow(mock_edit_whatsapp_flow): + data = { + "flow_json": "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"WELCOME_SCREEN\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Hello World\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Let's start building things!\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Complete\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {}\n }\n }\n ]\n },\n \"title\": \"Welcome\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {}\n }\n ]\n}" + } + api_resp = { + "success": True, + "validation_errors": [] + } + mock_edit_whatsapp_flow.return_value = api_resp + + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id", + json={"data": data}, + headers={"Authorization": pytest.token_type + " " + pytest.access_token} + ) + actual = response.json() + print(actual) + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_resp + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.preview_whatsapp_flow", autospec=True) +def test_preview_whatsapp_flow(mock_preview_whatsapp_flow): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_response = { + "id": "9070429474112345", + "preview": { + "expires_at": "2024-02-29T06:35:40+0000", + "preview_url": "https://business.facebook.com/wa/manage/flows/9070429474112345/preview/?token=ec58dcaa-dd30-4fee-a8a7-3d7e297ac3c9" + } + } + mock_preview_whatsapp_flow.return_value = api_response + + response = client.get( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_response + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.list_whatsapp_flows", autospec=True) +def test_retrieve_whatsapp_flows(mock_list_whatsapp_flows): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_response = [ + { + "id": "9070429474112345", + "name": "flow with multiple categories" + }, + { + "id": "5432129474112345", + "name": "my first flow" + } + ] + mock_list_whatsapp_flows.return_value = api_response + + response = client.get( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"]['flows'] == api_response + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.delete_flow", autospec=True) +def test_delete_whatsapp_flow(mock_delete_flow): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_response = {"success": True} + mock_delete_flow.return_value = api_response + + response = client.delete( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_response + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.publish_flow", autospec=True) +def test_publish_whatsapp_flow(mock_publish_flow): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_response = {"success": True} + mock_publish_flow.return_value = api_response + + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id/publish", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_response + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.get_whatsapp_flow_assets", autospec=True) +def test_get_whatsapp_flow_assets(mock_get_whatsapp_flow_assets): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_response = { + "assets": [ + { + "asset_type": "FLOW_JSON", + "download_url": "https://mmg.whatsapp.net/m1/v/t24/An8n-Ot0L5sxj8lupzxfUTfHYhsdsdsdsRyqAZRySBcATvtUGgPjP76UJKS0wMopyj6SNTmNmf_F1pLAt04wbP3B9kFCIpvy7oOG6CM3HK4wFY61Z7TiLDxjGvzEgXjdog6A?ccb=10-5&oh=01_AdTq_Njj-foFgD0-KlMXq4AbdhrqLoNm6_CtlsxZxC03rA&oe=65F414CD&_nc_sid=471a72", + "name": "flow.json" + } + ], + "count": 1, + "total": 1 + } + mock_get_whatsapp_flow_assets.return_value = api_response + + response = client.get( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id/assets", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_response + + +@patch("kairon.shared.channels.whatsapp.bsp.dialog360.BSP360Dialog.deprecate_whatsapp_flow", autospec=True) +def test_deprecate_whatsapp_flow(mock_deprecate_whatsapp_flow): + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"] + } + api_response = {"success": True} + mock_deprecate_whatsapp_flow.return_value = api_response + + response = client.post( + f"/api/bot/{pytest.bot}/channels/whatsapp/flows/360dialog/test_flow_id/deprecate", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["data"] == api_response + + def test_get_channel_endpoint(monkeypatch): monkeypatch.setitem( Utility.environment["model"]["agent"], "url", "http://localhost:5056" diff --git a/tests/testing_data/flow/sample_flow.json b/tests/testing_data/flow/sample_flow.json new file mode 100644 index 000000000..12f7bdb32 --- /dev/null +++ b/tests/testing_data/flow/sample_flow.json @@ -0,0 +1,205 @@ +{ + "version": "3.0", + "screens": [ + { + "id": "ADDRESS", + "title": "Address", + "data": {}, + "layout": { + "type": "SingleColumnLayout", + "children": [ + { + "type": "Form", + "name": "form", + "children": [ + { + "type": "TextHeading", + "text": "Please Enter Your Details!" + }, + { + "type": "TextBody", + "text": "First, we'll need a few details from you." + }, + { + "type": "TextInput", + "name": "firstName", + "label": "First Name", + "input-type": "text", + "required": true + }, + { + "type": "TextInput", + "label": "Last Name", + "name": "lastName", + "input-type": "text", + "required": true + }, + { + "type": "TextInput", + "label": "Email Address", + "name": "email", + "input-type": "email", + "required": true + }, + { + "type": "DatePicker", + "label": "Date of Birth", + "name": "dateOfBirth", + "required": true + }, + { + "type": "TextInput", + "label": "House Number", + "name": "houseNumber", + "input-type": "text", + "required": true + }, + { + "type": "TextInput", + "label": "Landmark", + "name": "landmark", + "input-type": "text", + "required": true + }, + { + "type": "TextInput", + "label": "District", + "name": "district", + "input-type": "text", + "required": true + }, + { + "type": "TextInput", + "label": "Pincode", + "name": "pincode", + "input-type": "text", + "required": true + }, + { + "type": "Footer", + "label": "Continue to survey", + "on-click-action": { + "name": "navigate", + "next": { + "type": "screen", + "name": "SURVEY" + }, + "payload": { + "firstName": "${form.firstName}", + "lastName": "${form.lastName}", + "email": "${form.email}", + "dateOfBirth": "${form.dateOfBirth}", + "houseNumber": "${form.houseNumber}", + "landmark": "${form.landmark}", + "district": "${form.district}", + "pincode": "${form.pincode}" + } + } + } + ] + } + ] + } + }, + { + "id": "SURVEY", + "title": "Thank you", + "data": { + "firstName": { + "type": "string", + "__example__": "Example" + }, + "lastName": { + "type": "string", + "__example__": "Example" + }, + "email": { + "type": "string", + "__example__": "Example" + }, + "dateOfBirth": { + "type": "string", + "__example__": "Example" + }, + "houseNumber": { + "type": "string", + "__example__": "Example" + }, + "landmark": { + "type": "string", + "__example__": "Example" + }, + "district": { + "type": "string", + "__example__": "Example" + }, + "pincode": { + "type": "string", + "__example__": "Example" + } + }, + "terminal": true, + "layout": { + "type": "SingleColumnLayout", + "children": [ + { + "type": "Form", + "name": "form", + "children": [ + { + "type": "TextHeading", + "text": "Before you go" + }, + { + "type": "TextBody", + "text": "How did you hear about us?" + }, + { + "type": "RadioButtonsGroup", + "label": "Choose one", + "required": false, + "name": "source", + "data-source": [ + { + "id": "FRIEND_RECOMMENDATION", + "title": "Friend's recommendation" + }, + { + "id": "TV_ADVERTISEMENT", + "title": "TV advertisement" + }, + { + "id": "SEARCH_ENGINE", + "title": "Search engine" + }, + { + "id": "SOCIAL_MEDIA", + "title": "Social media" + } + ] + }, + { + "type": "Footer", + "label": "Next", + "on-click-action": { + "name": "complete", + "payload": { + "source": "${form.source}", + "firstName": "${data.firstName}", + "lastName": "${data.lastName}", + "email": "${data.email}", + "dateOfBirth": "${data.dateOfBirth}", + "houseNumber": "${data.houseNumber}", + "landmark": "${data.landmark}", + "district": "${data.district}", + "pincode": "${data.pincode}" + } + } + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/unit_test/channels/bsp_test.py b/tests/unit_test/channels/bsp_test.py index 37c10ab22..b1da0bb26 100644 --- a/tests/unit_test/channels/bsp_test.py +++ b/tests/unit_test/channels/bsp_test.py @@ -291,6 +291,518 @@ def _mock_get_bot_settings(*args, **kwargs): 'partner_id': partner_id, 'bsp_type': '360dialog', 'api_key': 'kHCwksdsdsMVYVx0doabaDyRLUQJUAK', 'waba_account_id': 'Cyih7GWA'} + @responses.activate + def test_add_whatsapp_flow_with_missing_keys(self): + bot = "62bc24b493a0d6b7a46328ff" + data = { + "name": "flow with multiple categories", + "clone_flow_id": "9070429474112345", + "template": "FLOWS_OFFSITE_CALL_TO_ACTION", + } + with pytest.raises(AppException, match="Missing categories in request body!"): + BSP360Dialog(bot, "test").add_whatsapp_flow(data, bot, "test") + + @responses.activate + def test_add_whatsapp_flow_with_invalid_category(self): + bot = "62bc24b493a0d6b7a46328ff" + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY", "FLOW"], + "template": "FLOWS_OFFSITE_CALL_TO_ACTION", + } + with pytest.raises(AppException, match="Invalid categories FLOW in request body!"): + BSP360Dialog(bot, "test").add_whatsapp_flow(data, bot, "test") + + @responses.activate + def test_add_whatsapp_flow_with_invalid_template(self): + bot = "62bc24b493a0d6b7a46328ff" + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"], + "template": "INVALID_TEMPLATE", + } + with pytest.raises(AppException, match="Invalid template INVALID_TEMPLATE in request body!"): + BSP360Dialog(bot, "test").add_whatsapp_flow(data, bot, "test") + + def test_add_whatsapp_flow_error(self): + bot = "62bc24b493a0d6b7a46328fg" + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"], + "template": "FLOWS_OFFSITE_CALL_TO_ACTION", + } + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").add_whatsapp_flow(data, bot, "user") + + @responses.activate + def test_add_whatsapp_flow_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"], + "template": "FLOWS_OFFSITE_CALL_TO_ACTION", + } + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows" + responses.add("POST", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to add flow: *"): + BSP360Dialog(bot, "user").add_whatsapp_flow(data, bot, "user") + + @responses.activate + def test_add_whatsapp_flow(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + responses.reset() + bot = "62bc24b493a0d6b7a46328ff" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + data = { + "name": "flow with multiple categories", + "categories": ["APPOINTMENT_BOOKING", "OTHER", "SURVEY"], + "template": "FLOWS_OFFSITE_CALL_TO_ACTION", + } + api_resp = { + "id": "9070429474112345" + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"]["hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows" + responses.add("POST", json=api_resp, url=url, status=201) + flow_id = api_resp['id'] + api_response = { + "success": True, + "validation_errors": [] + } + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/assets" + responses.add("POST", json=api_response, url=url) + flow_id = BSP360Dialog(bot, "test").add_whatsapp_flow(data, bot, "test") + assert flow_id == {'id': '9070429474112345'} + count = AuditLogData.objects(attributes=[{"key": "bot", "value": bot}], user="test", action="activity", + entity="flow_creation").count() + assert count == 1 + + @responses.activate + def test_edit_whatsapp_flow_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + flow_json = "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"WELCOME_SCREEN\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Hello World\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Let's start building things!\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Complete\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {}\n }\n }\n ]\n },\n \"title\": \"Welcome\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {}\n }\n ]\n}" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").edit_whatsapp_flow(flow_id=flow_id, flow_json=flow_json) + + @responses.activate + def test_edit_whatsapp_flow_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + flow_json = "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"WELCOME_SCREEN\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Hello World\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Let's start building things!\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Complete\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {}\n }\n }\n ]\n },\n \"title\": \"Welcome\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {}\n }\n ]\n}" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/assets" + responses.add("POST", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to edit flow: *"): + BSP360Dialog(bot, "user").edit_whatsapp_flow(flow_id=flow_id, flow_json=flow_json) + + @responses.activate + def test_edit_whatsapp_flow(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + flow_json = "{\n \"version\": \"3.1\",\n \"screens\": [\n {\n \"id\": \"WELCOME_SCREEN\",\n \"layout\": {\n \"type\": \"SingleColumnLayout\",\n \"children\": [\n {\n \"type\": \"TextHeading\",\n \"text\": \"Hello World\"\n },\n {\n \"type\": \"TextBody\",\n \"text\": \"Let's start building things!\"\n },\n {\n \"type\": \"Footer\",\n \"label\": \"Complete\",\n \"on-click-action\": {\n \"name\": \"complete\",\n \"payload\": {}\n }\n }\n ]\n },\n \"title\": \"Welcome\",\n \"terminal\": true,\n \"success\": true,\n \"data\": {}\n }\n ]\n}" + api_response = { + "success": True, + "validation_errors": [] + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/assets" + responses.add("POST", json=api_response, url=url) + + response = BSP360Dialog(bot, "user").edit_whatsapp_flow(flow_id=flow_id, flow_json=flow_json) + assert response == api_response + + @responses.activate + def test_get_whatsapp_flow_assets_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").get_whatsapp_flow_assets(flow_id) + + @responses.activate + def test_get_whatsapp_flow_assets_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/assets" + responses.add("GET", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to get flow assets: *"): + BSP360Dialog(bot, "user").get_whatsapp_flow_assets(flow_id) + + @responses.activate + def test_get_whatsapp_flow_assets(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + api_response = { + "assets": [ + { + "asset_type": "FLOW_JSON", + "download_url": "https://mmg.whatsapp.net/m1/v/t24/An8n-Ot0L5sxj8lupzxfUTfHYhsdsdsdsRyqAZRySBcATvtUGgPjP76UJKS0wMopyj6SNTmNmf_F1pLAt04wbP3B9kFCIpvy7oOG6CM3HK4wFY61Z7TiLDxjGvzEgXjdog6A?ccb=10-5&oh=01_AdTq_Njj-foFgD0-KlMXq4AbdhrqLoNm6_CtlsxZxC03rA&oe=65F414CD&_nc_sid=471a72", + "name": "flow.json" + } + ], + "count": 1, + "total": 1 + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/assets" + responses.add("GET", json=api_response, url=url) + response = BSP360Dialog(bot, "user").get_whatsapp_flow_assets(flow_id) + assert response == api_response + + @responses.activate + def test_deprecate_whatsapp_flow_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").deprecate_whatsapp_flow(flow_id) + + @responses.activate + def test_deprecate_whatsapp_flow_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/deprecate" + responses.add("POST", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to deprecate flow: *"): + BSP360Dialog(bot, "user").deprecate_whatsapp_flow(flow_id) + + @responses.activate + def test_deprecate_whatsapp_flow(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + api_response = {"success": True} + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/deprecate" + responses.add("POST", json=api_response, url=url) + response = BSP360Dialog(bot, "user").deprecate_whatsapp_flow(flow_id) + assert response == api_response + + @responses.activate + def test_preview_whatsapp_flow_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").preview_whatsapp_flow(flow_id) + + @responses.activate + def test_preview_whatsapp_flow_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/preview" + responses.add("GET", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to get flow: *"): + BSP360Dialog(bot, "user").preview_whatsapp_flow(flow_id) + + @responses.activate + def test_preview_whatsapp_flow(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + api_response = { + "id": "9070429474112345", + "preview": { + "expires_at": "2024-02-29T06:35:40+0000", + "preview_url": "https://business.facebook.com/wa/manage/flows/9070429474112345/preview/?token=ec58dcaa-dd30-4fee-a8a7-3d7e297ac3c9" + } + } + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/preview" + responses.add("GET", json=api_response, url=url) + response = BSP360Dialog(bot, "user").preview_whatsapp_flow(flow_id) + assert response == api_response + + @responses.activate + def test_list_whatsapp_flows_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").list_whatsapp_flows() + + @responses.activate + def test_list_whatsapp_flows_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows" + responses.add("GET", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to get flows: *"): + BSP360Dialog(bot, "user").list_whatsapp_flows() + + @responses.activate + def test_list_whatsapp_flows_with_query_params(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + api_response = [ + { + "id": "9070429474112345", + "name": "flow with multiple categories" + }, + { + "id": "5432129474112345", + "name": "my first flow" + } + ] + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows?fields=id,name" + responses.add("GET", json=api_response, url=url) + response = BSP360Dialog(bot, "user").list_whatsapp_flows(fields='id,name') + assert response == api_response + + @responses.activate + def test_list_whatsapp_flows(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + api_response = [ + { + "categories": [ + "APPOINTMENT_BOOKING", + "OTHER", + "SURVEY" + ], + "id": "9070429474112345", + "name": "flow with multiple categories", + "status": "DRAF", + "validation_errors": [] + }, + { + "categories": [ + "SIGN_UP" + ], + "id": "5432129474112345", + "name": "my first flow", + "status": "PUBLISHED", + "validation_errors": [] + } + ] + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows" + responses.add("GET", json=api_response, url=url) + response = BSP360Dialog(bot, "user").list_whatsapp_flows() + assert response == api_response + + @responses.activate + def test_delete_flow_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").delete_flow(flow_id) + + @responses.activate + def test_delete_flow_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}" + responses.add("DELETE", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to delete flow: *"): + BSP360Dialog(bot, "user").delete_flow(flow_id) + + @responses.activate + def test_delete_flow(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}" + responses.add("DELETE", json={"success": True}, url=url) + + response = BSP360Dialog(bot, "user").delete_flow(flow_id) + assert response == {"success": True} + + @responses.activate + def test_publish_flow_channel_not_found(self): + bot = "62bc24b493a0d6b7a46328fg" + flow_id = "test_id" + + with pytest.raises(AppException, match="Channel not found!"): + BSP360Dialog(bot, "user").publish_flow(flow_id) + + @responses.activate + def test_publish_flow_failure(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/publish" + responses.add("POST", json={}, url=url, status=500) + + with pytest.raises(AppException, match=r"Failed to publish flow: *"): + BSP360Dialog(bot, "user").publish_flow(flow_id) + + @responses.activate + def test_publish_flow(self, monkeypatch): + with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): + bot = "62bc24b493a0d6b7a46328ff" + flow_id = "test_id" + partner_id = "new_partner_id" + waba_account_id = "Cyih7GWA" + + def _get_partners_auth_token(*args, **kwargs): + return "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs.ImtpZCI6Ik1EZEZOVFk1UVVVMU9FSXhPRGN3UVVZME9EUTFRVFJDT1.RSRU9VUTVNVGhDTURWRk9UUTNPQSJ9" + + monkeypatch.setattr(BSP360Dialog, 'get_partner_auth_token', _get_partners_auth_token) + base_url = Utility.system_metadata["channels"]["whatsapp"]["business_providers"]["360dialog"][ + "hub_base_url"] + url = f"{base_url}/api/v2/partners/{partner_id}/waba_accounts/{waba_account_id}/flows/{flow_id}/publish" + responses.add("POST", json={"success": True}, url=url) + + response = BSP360Dialog(bot, "user").publish_flow(flow_id) + assert response == {"success": True} + @responses.activate def test_add_template(self, monkeypatch): with mock.patch.dict(Utility.environment, {'channels': {"360dialog": {"partner_id": "new_partner_id"}}}): @@ -343,7 +855,9 @@ def _get_partners_auth_token(*args, **kwargs): responses.add("POST", json=api_resp, url=url, status=201) template = BSP360Dialog(bot, "test").add_template(data, bot, "test") assert template == {'category': 'MARKETING', 'id': '594425479261596', 'status': 'PENDING'} - count = AuditLogData.objects(attributes=[{"key": "bot", "value": bot}], user="test", action="activity").count() + count = AuditLogData.objects(attributes=[{"key": "bot", "value": bot}], user="test", action="activity", + entity="template_creation").count() + assert count == 1 @responses.activate