diff --git a/kairon/api/app/routers/user.py b/kairon/api/app/routers/user.py index 38bbcf0a1..37b6b874c 100644 --- a/kairon/api/app/routers/user.py +++ b/kairon/api/app/routers/user.py @@ -26,12 +26,14 @@ async def get_users_details(current_user: User = Depends(Authentication.get_curr return {"data": {"user": user_details}} -@router.post("/details", response_model=Response) -async def update_users_details(current_user: User = Depends(Authentication.get_current_user)): +@router.post("/details/{status}", response_model=Response) +async def update_user_details( + status: str = Path(description="user onboarding status", examples=["Completed", "Skipped"]), + current_user: User = Depends(Authentication.get_current_user)): """ Updates the details of the current logged-in user. """ - AccountProcessor.update_is_onboarded(current_user.email) + AccountProcessor.update_user_details(current_user.email, status) return {"message": "Details updated!"} diff --git a/kairon/shared/account/data_objects.py b/kairon/shared/account/data_objects.py index 39dddea37..caa932c3b 100644 --- a/kairon/shared/account/data_objects.py +++ b/kairon/shared/account/data_objects.py @@ -20,7 +20,7 @@ from kairon.shared.constants import UserActivityType from kairon.shared.data.audit.data_objects import Auditlog from kairon.shared.data.signals import push_notification, auditlogger -from kairon.shared.data.constant import ACCESS_ROLES, ACTIVITY_STATUS +from kairon.shared.data.constant import ACCESS_ROLES, ACTIVITY_STATUS, ONBOARDING_STATUS from kairon.shared.utils import Utility @@ -49,6 +49,9 @@ class User(Auditlog): account = LongField(required=True) user = StringField(required=True) is_onboarded = BooleanField(default=False) + onboarding_status = StringField(default=ONBOARDING_STATUS.NOT_COMPLETED.value, + choices=[a_type.value for a_type in ONBOARDING_STATUS]) + onboarding_timestamp = DateTimeField(default=None) timestamp = DateTimeField(default=datetime.utcnow) status = BooleanField(default=True) meta = {"indexes": [{"fields": ["$email", "$first_name", "$last_name"]}]} @@ -65,6 +68,8 @@ def validate(self, clean=True): ) elif isinstance(email(self.email), ValidationFailure): raise ValidationError("Please enter valid email address") + elif self.onboarding_status not in [status.value for status in ONBOARDING_STATUS]: + raise ValidationError(f"{self.onboarding_status} is not a valid status") class BotMetaData(EmbeddedDocument): diff --git a/kairon/shared/account/processor.py b/kairon/shared/account/processor.py index 540a415b4..3c2df1730 100644 --- a/kairon/shared/account/processor.py +++ b/kairon/shared/account/processor.py @@ -37,7 +37,7 @@ from kairon.shared.admin.processor import Sysadmin from kairon.shared.constants import UserActivityType, PluginTypes from kairon.shared.data.audit.data_objects import AuditLogData -from kairon.shared.data.constant import ACCESS_ROLES, ACTIVITY_STATUS, INTEGRATION_STATUS +from kairon.shared.data.constant import ACCESS_ROLES, ACTIVITY_STATUS, INTEGRATION_STATUS, ONBOARDING_STATUS from kairon.shared.data.data_objects import BotSettings, ChatClientConfig, SlotMapping from kairon.shared.plugins.factory import PluginFactory from kairon.shared.utils import Utility @@ -785,10 +785,13 @@ def get_complete_user_details(email: str): return user @staticmethod - def update_is_onboarded(email: Text): + def update_user_details(email: Text, status: Text): try: user = User.objects(email__iexact=email, status=True).get() - user.is_onboarded = True + if status in [ONBOARDING_STATUS.COMPLETED.value, ONBOARDING_STATUS.SKIPPED.value]: + user.is_onboarded = True + user.onboarding_status = status + user.onboarding_timestamp = datetime.utcnow() user.save() except DoesNotExist as e: logging.error(e) diff --git a/kairon/shared/data/constant.py b/kairon/shared/data/constant.py index 6efc12890..c77fd9141 100644 --- a/kairon/shared/data/constant.py +++ b/kairon/shared/data/constant.py @@ -110,6 +110,13 @@ class EVENT_STATUS(str, Enum): ABORTED = "Aborted" +class ONBOARDING_STATUS(str, Enum): + NOT_COMPLETED = "Not Completed" + SKIPPED = "Skipped" + IN_PROGRESS = "In Progress" + COMPLETED = "Completed" + + class TASK_TYPE(str, Enum): ACTION = "Action" EVENT = "Event" diff --git a/tests/integration_test/services_test.py b/tests/integration_test/services_test.py index 6ba45e100..23ff0cb67 100644 --- a/tests/integration_test/services_test.py +++ b/tests/integration_test/services_test.py @@ -2688,6 +2688,142 @@ def test_get_executor_logs(get_executor_logs): assert actual["data"]["logs"][0]['bot'] == pytest.bot +def test_update_user_details_with_invalid_onboarding_status(): + response = client.post( + "/api/user/details/Invalid", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert not actual["data"] + assert actual["message"] == "Invalid is not a valid status" + + response = client.post( + "/api/user/details/INITIATED", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert not actual["data"] + assert actual["message"] == "INITIATED is not a valid status" + + response = client.post( + "/api/user/details/IN PROGRESS", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert not actual["data"] + assert actual["message"] == "IN PROGRESS is not a valid status" + + response = client.post( + "/api/user/details/Done", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + + actual = response.json() + assert not actual["success"] + assert actual["error_code"] == 422 + assert not actual["data"] + assert actual["message"] == "Done is not a valid status" + + +def test_update_user_details_with_onboarding_status(): + response = client.get( + "/api/user/details", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ).json() + assert response["data"]["user"]["_id"] + assert response["data"]["user"]["email"] == "integration@demo.ai" + assert ( + response["data"]["user"]["bots"]["account_owned"][0]["user"] + == "integration@demo.ai" + ) + assert response["data"]["user"]["bots"]["account_owned"][0]["timestamp"] + assert response["data"]["user"]["bots"]["account_owned"][0]["name"] + assert response["data"]["user"]["bots"]["account_owned"][0]["_id"] + assert not response["data"]["user"]["bots"]["shared"] + assert response["data"]["user"]["timestamp"] + assert response["data"]["user"]["status"] + assert response["data"]["user"]["account_name"] == "integration" + assert response["data"]["user"]["first_name"] == "Demo" + assert response["data"]["user"]["last_name"] == "User" + assert response["data"]["user"]["onboarding_status"] == "Not Completed" + assert response["data"]["user"]["is_onboarded"] is False + + response = client.post( + "/api/user/details/In Progress", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["message"] == "Details updated!" + + response = client.get( + "/api/user/details", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ).json() + assert response["data"]["user"]["_id"] + assert response["data"]["user"]["email"] == "integration@demo.ai" + assert ( + response["data"]["user"]["bots"]["account_owned"][0]["user"] + == "integration@demo.ai" + ) + assert response["data"]["user"]["bots"]["account_owned"][0]["timestamp"] + assert response["data"]["user"]["bots"]["account_owned"][0]["name"] + assert response["data"]["user"]["bots"]["account_owned"][0]["_id"] + assert not response["data"]["user"]["bots"]["shared"] + assert response["data"]["user"]["timestamp"] + assert response["data"]["user"]["status"] + assert response["data"]["user"]["account_name"] == "integration" + assert response["data"]["user"]["first_name"] == "Demo" + assert response["data"]["user"]["last_name"] == "User" + assert response["data"]["user"]["onboarding_status"] == "In Progress" + assert response["data"]["user"]["onboarding_timestamp"] + assert response["data"]["user"]["is_onboarded"] is False + + response = client.post( + "/api/user/details/Completed", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ) + + actual = response.json() + assert actual["success"] + assert actual["error_code"] == 0 + assert actual["message"] == "Details updated!" + + response = client.get( + "/api/user/details", + headers={"Authorization": pytest.token_type + " " + pytest.access_token}, + ).json() + assert response["data"]["user"]["_id"] + assert response["data"]["user"]["email"] == "integration@demo.ai" + assert ( + response["data"]["user"]["bots"]["account_owned"][0]["user"] + == "integration@demo.ai" + ) + assert response["data"]["user"]["bots"]["account_owned"][0]["timestamp"] + assert response["data"]["user"]["bots"]["account_owned"][0]["name"] + assert response["data"]["user"]["bots"]["account_owned"][0]["_id"] + assert not response["data"]["user"]["bots"]["shared"] + assert response["data"]["user"]["timestamp"] + assert response["data"]["user"]["status"] + assert response["data"]["user"]["account_name"] == "integration" + assert response["data"]["user"]["first_name"] == "Demo" + assert response["data"]["user"]["last_name"] == "User" + assert response["data"]["user"]["onboarding_status"] == "Completed" + assert response["data"]["user"]["onboarding_timestamp"] + assert response["data"]["user"]["is_onboarded"] is True + + def test_get_collection_data_with_no_collection_data(): response = client.get( url=f"/api/bot/{pytest.bot}/data/collection", @@ -12628,7 +12764,7 @@ def test_get_user_details(): def test_update_user_details(): response = client.post( - "/api/user/details", + "/api/user/details/Completed", headers={"Authorization": pytest.token_type + " " + pytest.access_token}, ) diff --git a/tests/unit_test/api/api_processor_test.py b/tests/unit_test/api/api_processor_test.py index 676e71710..2008bb16b 100644 --- a/tests/unit_test/api/api_processor_test.py +++ b/tests/unit_test/api/api_processor_test.py @@ -2835,3 +2835,72 @@ def test_get_attributes(self): attributes = AuditDataProcessor.get_attributes(document) assert attributes[0]['key'] == 'email' assert attributes[0]['value'] == 'nk15@digite.com' + + def test_update_user_details_with_invalid_onboarding_status(self): + with pytest.raises(ValidationError, match="Invalid is not a valid status"): + AccountProcessor.update_user_details("fshaikh@digite.com", "Invalid") + + with pytest.raises(ValidationError, match="INITIATED is not a valid status"): + AccountProcessor.update_user_details("fshaikh@digite.com", "INITIATED") + + with pytest.raises(ValidationError, match="IN PROGRESS is not a valid status"): + AccountProcessor.update_user_details("fshaikh@digite.com", "IN PROGRESS") + + with pytest.raises(ValidationError, match="Done is not a valid status"): + AccountProcessor.update_user_details("fshaikh@digite.com", "Done") + + def test_update_user_details_with_onboarding_status(self): + assert len(AccountProcessor.get_accessible_bot_details(pytest.account, "fshaikh@digite.com")['account_owned']) == 1 + user_details = AccountProcessor.get_complete_user_details("fshaikh@digite.com") + assert user_details["_id"] + assert user_details["email"] == "fshaikh@digite.com" + assert user_details["bots"]["account_owned"][0]["user"] == "fshaikh@digite.com" + assert user_details["bots"]["account_owned"][0]["timestamp"] + assert user_details["bots"]["account_owned"][0]["name"] == "test_bot" + assert user_details["bots"]["account_owned"][0]["_id"] + assert not user_details["bots"]["shared"] + assert user_details["timestamp"] + assert user_details["status"] + assert user_details["account_name"] == "paypal" + assert user_details["first_name"] == "Fahad Ali" + assert user_details["last_name"] == "Shaikh" + assert user_details["onboarding_status"] == "Not Completed" + assert user_details["is_onboarded"] is False + + AccountProcessor.update_user_details("fshaikh@digite.com", "In Progress") + + user_details = AccountProcessor.get_complete_user_details("fshaikh@digite.com") + assert user_details["_id"] + assert user_details["email"] == "fshaikh@digite.com" + assert user_details["bots"]["account_owned"][0]["user"] == "fshaikh@digite.com" + assert user_details["bots"]["account_owned"][0]["timestamp"] + assert user_details["bots"]["account_owned"][0]["name"] == "test_bot" + assert user_details["bots"]["account_owned"][0]["_id"] + assert not user_details["bots"]["shared"] + assert user_details["timestamp"] + assert user_details["status"] + assert user_details["account_name"] == "paypal" + assert user_details["first_name"] == "Fahad Ali" + assert user_details["last_name"] == "Shaikh" + assert user_details["onboarding_status"] == "In Progress" + assert user_details["onboarding_timestamp"] + assert user_details["is_onboarded"] is False + + AccountProcessor.update_user_details("fshaikh@digite.com", "Completed") + + user_details = AccountProcessor.get_complete_user_details("fshaikh@digite.com") + assert user_details["_id"] + assert user_details["email"] == "fshaikh@digite.com" + assert user_details["bots"]["account_owned"][0]["user"] == "fshaikh@digite.com" + assert user_details["bots"]["account_owned"][0]["timestamp"] + assert user_details["bots"]["account_owned"][0]["name"] == "test_bot" + assert user_details["bots"]["account_owned"][0]["_id"] + assert not user_details["bots"]["shared"] + assert user_details["timestamp"] + assert user_details["status"] + assert user_details["account_name"] == "paypal" + assert user_details["first_name"] == "Fahad Ali" + assert user_details["last_name"] == "Shaikh" + assert user_details["onboarding_status"] == "Completed" + assert user_details["onboarding_timestamp"] + assert user_details["is_onboarded"] is True