diff --git a/apps/cic-ussd/cic_ussd/processor/menu.py b/apps/cic-ussd/cic_ussd/processor/menu.py index fd5bc2f..f0bbf59 100644 --- a/apps/cic-ussd/cic_ussd/processor/menu.py +++ b/apps/cic-ussd/cic_ussd/processor/menu.py @@ -540,5 +540,8 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio if menu_name == 'exit_successful_token_selection': return menu_processor.exit_successful_token_selection() - preferred_language = get_cached_preferred_language(account.blockchain_address) + preferred_language = i18n.config.get('fallback') + if account: + preferred_language = get_cached_preferred_language(account.blockchain_address) + return translation_for(display_key, preferred_language) diff --git a/apps/cic-ussd/cic_ussd/processor/ussd.py b/apps/cic-ussd/cic_ussd/processor/ussd.py index a07bf4d..118c2d7 100644 --- a/apps/cic-ussd/cic_ussd/processor/ussd.py +++ b/apps/cic-ussd/cic_ussd/processor/ussd.py @@ -16,6 +16,7 @@ from cic_ussd.processor.menu import response from cic_ussd.processor.util import latest_input, resume_last_ussd_session from cic_ussd.session.ussd_session import create_or_update_session, persist_ussd_session from cic_ussd.state_machine import UssdStateMachine +from cic_ussd.state_machine.logic.manager import States from cic_ussd.validator import is_valid_response @@ -123,7 +124,16 @@ def handle_no_account_menu_operations(account: Optional[Account], :return: :rtype: """ - menu = UssdMenu.find_by_name('initial_language_selection') + initial_language_selection = 'initial_language_selection' + menu = UssdMenu.find_by_name(initial_language_selection) + last_ussd_session: UssdSession = UssdSession.last_ussd_session(phone_number, session) + if last_ussd_session: + menu_name = menu.get('name') + if user_input: + state = next_state(account, session, user_input, last_ussd_session.to_json()) + menu = UssdMenu.find_by_name(state) + elif menu_name not in States.non_resumable_states and menu_name != initial_language_selection: + menu = resume_last_ussd_session(last_ussd_session.state) ussd_session = create_or_update_session( external_session_id=external_session_id, msisdn=phone_number, @@ -132,15 +142,6 @@ def handle_no_account_menu_operations(account: Optional[Account], session=session, user_input=user_input) persist_ussd_session(external_session_id, queue) - last_ussd_session: UssdSession = UssdSession.last_ussd_session(phone_number, session) - if last_ussd_session: - if not user_input: - menu = resume_last_ussd_session(last_ussd_session.state) - else: - session = SessionBase.bind_session(session) - state = next_state(account, session, user_input, last_ussd_session.to_json()) - menu = UssdMenu.find_by_name(state) - return response(account=account, display_key=menu.get('display_key'), menu_name=menu.get('name'), diff --git a/apps/cic-ussd/cic_ussd/processor/util.py b/apps/cic-ussd/cic_ussd/processor/util.py index 37690ba..c488e5a 100644 --- a/apps/cic-ussd/cic_ussd/processor/util.py +++ b/apps/cic-ussd/cic_ussd/processor/util.py @@ -6,13 +6,12 @@ import time from typing import List, Union # external imports -from cic_types.condiments import MetadataPointer from cic_types.models.person import get_contact_data_from_vcard from tinydb.table import Document # local imports -from cic_ussd.cache import cache_data_key, get_cached_data from cic_ussd.menu.ussd_menu import UssdMenu +from cic_ussd.state_machine.logic.manager import States from cic_ussd.translation import translation_for logg = logging.getLogger(__file__) @@ -66,19 +65,8 @@ def resume_last_ussd_session(last_state: str) -> Document: :return: :rtype: """ - # TODO [Philip]: This can be cleaned further - non_reusable_states = [ - 'account_creation_prompt', - 'exit', - 'exit_invalid_pin', - 'exit_invalid_new_pin', - 'exit_invalid_recipient', - 'exit_invalid_request', - 'exit_pin_blocked', - 'exit_pin_mismatch', - 'exit_successful_transaction' - ] - if last_state in non_reusable_states: + + if last_state in States.non_resumable_states: return UssdMenu.find_by_name('start') return UssdMenu.find_by_name(last_state) diff --git a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py index 95ff193..84deb8b 100644 --- a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py +++ b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_tasker.py @@ -20,6 +20,7 @@ from cic_ussd.db import dsn_from_config from cic_ussd.db.models.base import SessionBase from cic_ussd.phone_number import Support from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession +from cic_ussd.state_machine.logic.manager import States from cic_ussd.translation import generate_locale_files from cic_ussd.validator import validate_presence @@ -94,6 +95,9 @@ i18n.set('fallback', config.get('LOCALE_FALLBACK')) chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) +# make non-resumable states accessible globally +States.load_non_resumable_states(config.get("MACHINE_NON_RESUMABLE_STATES")) + Chain.spec = chain_spec Support.phone_number = config.get('OFFICE_SUPPORT_PHONE') diff --git a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py index 6a53efb..6fb493f 100644 --- a/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py +++ b/apps/cic-ussd/cic_ussd/runnable/daemons/cic_user_ussd_server.py @@ -34,6 +34,7 @@ from cic_ussd.processor.ussd import handle_menu_operations from cic_ussd.runnable.server_base import exportable_parser, logg from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession from cic_ussd.state_machine import UssdStateMachine +from cic_ussd.state_machine.logic.manager import States from cic_ussd.translation import generate_locale_files, Languages, translation_for from cic_ussd.validator import check_ip, check_request_content_length, validate_phone_number, validate_presence @@ -94,6 +95,9 @@ celery.Celery(backend=config.get('CELERY_RESULT_URL'), broker=config.get('CELERY states = json_file_parser(filepath=config.get('MACHINE_STATES')) transitions = json_file_parser(filepath=config.get('MACHINE_TRANSITIONS')) +# make non-resumable states accessible globally +States.load_non_resumable_states(config.get("MACHINE_NON_RESUMABLE_STATES")) + chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC')) Chain.spec = chain_spec diff --git a/apps/cic-ussd/cic_ussd/state_machine/logic/manager.py b/apps/cic-ussd/cic_ussd/state_machine/logic/manager.py new file mode 100644 index 0000000..bc2692b --- /dev/null +++ b/apps/cic-ussd/cic_ussd/state_machine/logic/manager.py @@ -0,0 +1,15 @@ +# standard imports +import json + +# external imports + +# local imports + + +class States: + non_resumable_states = None + + @classmethod + def load_non_resumable_states(cls, file_path): + with open(file_path, 'r') as states_file: + cls.non_resumable_states = json.load(states_file) diff --git a/apps/cic-ussd/config/app.ini b/apps/cic-ussd/config/app.ini index 0f31e25..1015eb2 100644 --- a/apps/cic-ussd/config/app.ini +++ b/apps/cic-ussd/config/app.ini @@ -4,6 +4,7 @@ max_body_length=1024 password_pepper= [machine] +non_resumable_states=states/non_resumable_states.json states=states/ transitions=transitions/ diff --git a/apps/cic-ussd/config/test/app.ini b/apps/cic-ussd/config/test/app.ini index 24d6a21..ed8ca78 100644 --- a/apps/cic-ussd/config/test/app.ini +++ b/apps/cic-ussd/config/test/app.ini @@ -4,6 +4,7 @@ max_body_length=1024 password_pepper=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I= [machine] +non_resumable_states=states/non_resumable_states.json states=states/ transitions=transitions/ diff --git a/apps/cic-ussd/states/non_resumable_states.json b/apps/cic-ussd/states/non_resumable_states.json new file mode 100644 index 0000000..c10b74e --- /dev/null +++ b/apps/cic-ussd/states/non_resumable_states.json @@ -0,0 +1,11 @@ +[ + "account_creation_prompt", + "exit", + "exit_invalid_pin", + "exit_invalid_new_pin", + "exit_invalid_recipient", + "exit_invalid_request", + "exit_pin_blocked", + "exit_pin_mismatch", + "exit_successful_transaction" +] \ No newline at end of file diff --git a/apps/cic-ussd/tests/fixtures/config.py b/apps/cic-ussd/tests/fixtures/config.py index 67ac618..588253b 100644 --- a/apps/cic-ussd/tests/fixtures/config.py +++ b/apps/cic-ussd/tests/fixtures/config.py @@ -16,6 +16,7 @@ from cic_ussd.files.local_files import create_local_file_data_stores, json_file_ from cic_ussd.menu.ussd_menu import UssdMenu from cic_ussd.phone_number import E164Format, Support from cic_ussd.state_machine import UssdStateMachine +from cic_ussd.state_machine.logic.manager import States from cic_ussd.translation import generate_locale_files, Languages from cic_ussd.validator import validate_presence @@ -69,6 +70,11 @@ def load_e164_region(load_config): E164Format.region = load_config.get('E164_REGION') +@pytest.fixture(scope='session', autouse=True) +def load_non_resumable_states(load_config): + States.load_non_resumable_states(load_config.get('MACHINE_NON_RESUMABLE_STATES')) + + @pytest.fixture(scope='session') def load_support_phone(load_config): Support.phone_number = load_config.get('OFFICE_SUPPORT_PHONE') diff --git a/apps/cic-ussd/transitions/language_setting_transitions.json b/apps/cic-ussd/transitions/language_setting_transitions.json index bba4f49..80664c3 100644 --- a/apps/cic-ussd/transitions/language_setting_transitions.json +++ b/apps/cic-ussd/transitions/language_setting_transitions.json @@ -10,49 +10,57 @@ "trigger": "scan_data", "source": "initial_language_selection", "dest": "initial_middle_language_set", - "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_language_selection", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless":"cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_middle_language_set", "dest": "initial_language_selection", - "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_middle_language_set", "dest": "initial_last_language_set", - "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_middle_language_set", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_last_language_set", "dest": "initial_middle_language_set", - "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_last_language_set", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "initial_middle_language_set", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", @@ -80,49 +88,57 @@ "trigger": "scan_data", "source": "select_preferred_language", "dest": "middle_language_set", - "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "select_preferred_language", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "middle_language_set", "dest": "select_preferred_language", - "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "middle_language_set", "dest": "last_language_set", - "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_eleven_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "middle_language_set", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "last_language_set", "dest": "middle_language_set", - "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_twenty_two_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "last_language_set", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data", "source": "middle_language_set", "dest": "exit", - "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected" + "conditions": "cic_ussd.state_machine.logic.menu.menu_zero_zero_selected", + "unless": "cic_ussd.state_machine.logic.language.is_valid_language_selection" }, { "trigger": "scan_data",