From abeb483806f658b40365d5a9e068b2f1af5bf7f0 Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Tue, 2 Jan 2024 15:29:42 +0530 Subject: [PATCH 1/8] Update Create Employee on Webhook trigger --- apps/bamboohr/tasks.py | 28 ++++++++++++++++++++++++++++ apps/bamboohr/views.py | 18 ++++++++++++++++++ fyle_employee_imports/bamboo_hr.py | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/apps/bamboohr/tasks.py b/apps/bamboohr/tasks.py index f75ed8d0..27d7da42 100644 --- a/apps/bamboohr/tasks.py +++ b/apps/bamboohr/tasks.py @@ -1,9 +1,37 @@ from apps.bamboohr.models import BambooHr from apps.fyle_hrms_mappings.models import DestinationAttribute from fyle_employee_imports.bamboo_hr import BambooHrEmployeeImport +from apps.users.models import User def refresh_employees(org_id, user): bambooHrImporter = BambooHrEmployeeImport(org_id=org_id, user=user) bambooHrImporter.sync_employees() +def update_employee(org_id: int, user: User, payload: dict): + + """ + Update employee in fyle when employee in Bamboohr is added or updated + """ + bamboohr = BambooHr.objects.get(org_id__in=org_id, is_credentials_expired=False) + bamboohr_importer = BambooHrEmployeeImport(org_id=org_id, user=user) + + employee_payload = {'employees': []} + payload = payload['employees'][0] + employee = {} + for field in payload['changedFields']: + employee['id'] = payload['id'] + employee[field] = payload['fields'][field]['value'] + + employee_payload['employees'].append(employee) + + bamboohr_importer.upsert_employees(employees=employee_payload) + + hrms_employees = DestinationAttribute.objects.filter( + attribute_type='EMPLOYEE', + org_id=org_id, + updated_at__gte=bamboohr.employee_exported_at, + ).order_by('value', 'id') + + bamboohr_importer.import_departments(hrms_employees=hrms_employees) + bamboohr_importer.fyle_employee_import(hrms_employees=hrms_employees) diff --git a/apps/bamboohr/views.py b/apps/bamboohr/views.py index f3d29e39..9c66a1b4 100644 --- a/apps/bamboohr/views.py +++ b/apps/bamboohr/views.py @@ -48,6 +48,24 @@ def get(self, request, *args, **kwargs): status=status.HTTP_404_NOT_FOUND ) + +class WebhookAPIView(generics.CreateAPIView): + + def post(self, request, *args, **kwargs): + + org_id = kwargs['org_id'] + user = self.request.user + payload = request.data + + async_task('apps.bamboohr.tasks.update_employee', org_id, user, payload) + + return Response( + { + 'status': 'success' + }, + status=status.HTTP_201_OK + ) + class BambooHrView(generics.ListAPIView): serializer_class = BambooHrSerializer diff --git a/fyle_employee_imports/bamboo_hr.py b/fyle_employee_imports/bamboo_hr.py index fa5fbac7..e8c7f8d9 100644 --- a/fyle_employee_imports/bamboo_hr.py +++ b/fyle_employee_imports/bamboo_hr.py @@ -20,7 +20,7 @@ def sync_hrms_employees(self): def upsert_employees(self, employees: Dict): attributes = [] for employee in employees['employees']: - supervisor = [employee['supervisorEmail']] + supervisor = [employee['supervisorEmail']] if employee['supervisorEmail'] else None active_status = True if employee['status'] == 'Active' else False detail = { 'email': employee['workEmail'] if employee['workEmail'] else None, From 416c5969ee238d9889d457eb0b78cff1933cf1ce Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Tue, 2 Jan 2024 21:00:14 +0530 Subject: [PATCH 2/8] tested and bug fix --- apps/bamboohr/tasks.py | 7 ++++--- apps/bamboohr/urls.py | 3 ++- apps/bamboohr/views.py | 2 +- fyle_employee_imports/bamboo_hr.py | 17 +++++++++++------ fyle_employee_imports/base.py | 3 +-- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/bamboohr/tasks.py b/apps/bamboohr/tasks.py index 3ea39c9d..77a760a1 100644 --- a/apps/bamboohr/tasks.py +++ b/apps/bamboohr/tasks.py @@ -15,14 +15,16 @@ def update_employee(org_id: int, user: User, payload: dict): """ Update employee in fyle when employee in Bamboohr is added or updated """ - bamboohr = BambooHr.objects.get(org_id__in=org_id, is_credentials_expired=False) + bamboohr = BambooHr.objects.get(org_id__in=[org_id], is_credentials_expired=False) bamboohr_importer = BambooHrEmployeeImport(org_id=org_id, user=user) employee_payload = {'employees': []} payload = payload['employees'][0] employee = {} + employee['id'] = payload['id'] + employee['firstName'] = payload['fields']['firstName']['value'] + employee['lastName'] = payload['fields']['lastName']['value'] for field in payload['changedFields']: - employee['id'] = payload['id'] employee[field] = payload['fields'][field]['value'] employee_payload['employees'].append(employee) @@ -34,6 +36,5 @@ def update_employee(org_id: int, user: User, payload: dict): org_id=org_id, updated_at__gte=bamboohr.employee_exported_at, ).order_by('value', 'id') - bamboohr_importer.import_departments(hrms_employees=hrms_employees) bamboohr_importer.fyle_employee_import(hrms_employees=hrms_employees) diff --git a/apps/bamboohr/urls.py b/apps/bamboohr/urls.py index 5de8f5b4..d165370e 100644 --- a/apps/bamboohr/urls.py +++ b/apps/bamboohr/urls.py @@ -1,11 +1,12 @@ from django.urls import path from .views import PostFolder, PostPackage, BambooHrConnection, BambooHrView, BambooHrConfigurationView, \ - DisconnectView, SyncEmployeesView, HealthCheck + DisconnectView, SyncEmployeesView, HealthCheck, WebhookAPIView app_name = 'bamboohr' urlpatterns = [ + path('webhook_callback/', WebhookAPIView.as_view(), name='webhook-callback'), path('health_check/', HealthCheck.as_view(), name='health-check'), path('', BambooHrView.as_view(), name='bamboohr'), path('packages/', PostPackage.as_view(), name='package'), diff --git a/apps/bamboohr/views.py b/apps/bamboohr/views.py index 60ecc35d..4da0420e 100644 --- a/apps/bamboohr/views.py +++ b/apps/bamboohr/views.py @@ -63,7 +63,7 @@ def post(self, request, *args, **kwargs): { 'status': 'success' }, - status=status.HTTP_201_OK + status=status.HTTP_201_CREATED ) class BambooHrView(generics.ListAPIView): diff --git a/fyle_employee_imports/bamboo_hr.py b/fyle_employee_imports/bamboo_hr.py index 91ffddd2..fbfd8b76 100644 --- a/fyle_employee_imports/bamboo_hr.py +++ b/fyle_employee_imports/bamboo_hr.py @@ -19,18 +19,23 @@ def sync_hrms_employees(self): def upsert_employees(self, employees: Dict): attributes = [] for employee in employees['employees']: - supervisor = [employee['supervisorEmail']] if employee['supervisorEmail'] else None - active_status = True if employee['status'] == 'Active' else False + supervisor = [employee.get('supervisorEmail', None)] + active_status = True if employee.get('status', None) == 'Active' else False + + display_name = employee.get('displayName', None) + if not display_name: + display_name = employee['firstName'] + ' ' + employee['lastName'] + detail = { - 'email': employee['workEmail'] if employee['workEmail'] else None, - 'department_name': employee['department'] if employee['department'] else None, - 'full_name': employee['displayName'] if employee['displayName'] else None, + 'email': employee.get('workEmail', None), + 'department_name': employee.get('department', None), + 'full_name': display_name, 'approver_emails': supervisor, } attributes.append({ 'attribute_type': 'EMPLOYEE', - 'value': employee['displayName'], + 'value': display_name, 'destination_id': employee['id'], 'detail': detail, 'active': active_status diff --git a/fyle_employee_imports/base.py b/fyle_employee_imports/base.py index d83710d0..5211fda0 100644 --- a/fyle_employee_imports/base.py +++ b/fyle_employee_imports/base.py @@ -115,7 +115,6 @@ def get_employee_and_approver_payload(self, hrms_employees): def fyle_employee_import(self, hrms_employees): fyle_employee_payload, employee_approver_payload = self.get_employee_and_approver_payload(hrms_employees) - if fyle_employee_payload: self.platform_connection.bulk_post_employees(employees_payload=fyle_employee_payload) @@ -141,6 +140,6 @@ def sync_employees(self): org_id=self.org_id, updated_at__gte=self.bamboohr.employee_exported_at, ).order_by('value', 'id') - + self.import_departments(hrms_employees) self.fyle_employee_import(hrms_employees) From 11f8bff46096385ec20edecbf3f910f1d71fae82 Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Tue, 2 Jan 2024 21:50:58 +0530 Subject: [PATCH 3/8] create and delete webhook added --- .../migrations/0007_bamboohr_webhook_id.py | 18 +++++++++ apps/bamboohr/models.py | 1 + apps/bamboohr/signals.py | 38 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 apps/bamboohr/migrations/0007_bamboohr_webhook_id.py diff --git a/apps/bamboohr/migrations/0007_bamboohr_webhook_id.py b/apps/bamboohr/migrations/0007_bamboohr_webhook_id.py new file mode 100644 index 00000000..d5025295 --- /dev/null +++ b/apps/bamboohr/migrations/0007_bamboohr_webhook_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2024-01-02 16:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bamboohr', '0006_bamboohr_is_credentials_expired'), + ] + + operations = [ + migrations.AddField( + model_name='bamboohr', + name='webhook_id', + field=models.IntegerField(help_text='ID of the webhook created by BambooHr', null=True), + ), + ] diff --git a/apps/bamboohr/models.py b/apps/bamboohr/models.py index de284aa0..59bbf41e 100644 --- a/apps/bamboohr/models.py +++ b/apps/bamboohr/models.py @@ -19,6 +19,7 @@ class BambooHr(models.Model): updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') employee_exported_at = models.DateTimeField(auto_now_add=True, help_text='Employee exported to Fyle at datetime') is_credentials_expired = models.BooleanField(default=False, help_text='BambooHr Credential Status') + webhook_id = models.IntegerField(null=True, help_text='ID of the webhook created by BambooHr') class Meta: db_table = 'bamboohr' diff --git a/apps/bamboohr/signals.py b/apps/bamboohr/signals.py index 0de2851c..be146005 100644 --- a/apps/bamboohr/signals.py +++ b/apps/bamboohr/signals.py @@ -1,9 +1,12 @@ -from django.db.models.signals import pre_save +from django.db.models.signals import pre_save, post_save, pre_delete from django.dispatch import receiver +from admin_settings.settings import API_URL from workato import Workato -from apps.bamboohr.models import BambooHrConfiguration + +from bamboosdk.bamboohrsdk import BambooHrSDK +from apps.bamboohr.models import BambooHrConfiguration, BambooHr from apps.orgs.models import Org @receiver(pre_save, sender=BambooHrConfiguration) @@ -11,3 +14,34 @@ def run_pre_save_configuration_triggers(sender, instance: BambooHrConfiguration, connector = Workato() org = Org.objects.get(id=instance.org_id) connector.recipes.post(org.managed_user_id, instance.recipe_id, None, 'stop') + + +@receiver(post_save, sender=BambooHr) +def run_post_save_bamboohr_triggers(sender, instance: BambooHr, **kwargs): + bamboohrsdk = BambooHrSDK(api_token=instance.api_token, sub_domain=instance.sub_domain) + + webhook_payload = { + "postFields": { + "firstName": "firstName", + "lastName": "lastName", + "department": "department", + "workEmail": "workEmail", + "supervisorEmail": "supervisorEmail", + "status": "status" + }, + "name": instance.org.name, + "monitorFields": ["firstName", "lastName", "department", "workEmail", "supervisorEmail", "status"], + "url": API_URL+f'/orgs/{instance.org.id}/bamboohr/webhook_callback/', + "format": "json" + } + + response = bamboohrsdk.webhook.post(payload=webhook_payload) + instance.webhook_id = int(response['id']) + instance.save() + +@receiver(pre_delete, sender=BambooHr) +def run_pre_delete_bamboohr_trigger(sender, instance: BambooHr, **kwargs): + webhook_id = instance.webhook_id + + bambamboohrsdk = BambooHrSDK(api_token=instance.api_token, sub_domain=instance.sub_domain) + bambamboohrsdk.webhook.delete(id=webhook_id) From f2c9958c80d7df59ec9b0978a6bfb44eb9e07d13 Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Tue, 2 Jan 2024 22:14:14 +0530 Subject: [PATCH 4/8] comment resolved --- apps/bamboohr/signals.py | 7 ------- apps/bamboohr/views.py | 18 ++++-------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/bamboohr/signals.py b/apps/bamboohr/signals.py index be146005..bd619c33 100644 --- a/apps/bamboohr/signals.py +++ b/apps/bamboohr/signals.py @@ -38,10 +38,3 @@ def run_post_save_bamboohr_triggers(sender, instance: BambooHr, **kwargs): response = bamboohrsdk.webhook.post(payload=webhook_payload) instance.webhook_id = int(response['id']) instance.save() - -@receiver(pre_delete, sender=BambooHr) -def run_pre_delete_bamboohr_trigger(sender, instance: BambooHr, **kwargs): - webhook_id = instance.webhook_id - - bambamboohrsdk = BambooHrSDK(api_token=instance.api_token, sub_domain=instance.sub_domain) - bambamboohrsdk.webhook.delete(id=webhook_id) diff --git a/apps/bamboohr/views.py b/apps/bamboohr/views.py index 4da0420e..0de90f34 100644 --- a/apps/bamboohr/views.py +++ b/apps/bamboohr/views.py @@ -213,17 +213,12 @@ class DisconnectView(generics.CreateAPIView): def post(self, request, *args, **kwargs): try: - configuration = BambooHrConfiguration.objects.get(org__id=kwargs['org_id']) bamboohr = BambooHr.objects.filter(org__id=kwargs['org_id']).first() - - connection = disconnect_bamboohr(kwargs['org_id'], configuration, bamboohr) - - # in case of an error response - if isinstance(connection, Response): - return connection - + webhook_id = request.data['webhook_id'] + bambamboohrsdk = BambooHrSDK(api_token=bamboohr.api_token, sub_domain=bamboohr.sub_domain) + bambamboohrsdk.webhook.delete(id=webhook_id) return Response( - data=connection, + data='Succesfully disconnected Bamboohr', status=status.HTTP_200_OK ) except BambooHr.DoesNotExist: @@ -233,11 +228,6 @@ def post(self, request, *args, **kwargs): }, status = status.HTTP_404_NOT_FOUND ) - except BambooHrConfiguration.DoesNotExist: - return Response( - data={'message': 'BambooHr Configuration does not exist for this Workspace'}, - status=status.HTTP_404_NOT_FOUND - ) class SyncEmployeesView(generics.UpdateAPIView): From 35a5887216106bc2e3a8536640f3cbda419c6d3e Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Wed, 3 Jan 2024 13:44:42 +0530 Subject: [PATCH 5/8] fixed and tested --- apps/bamboohr/signals.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/bamboohr/signals.py b/apps/bamboohr/signals.py index bd619c33..4e0450dc 100644 --- a/apps/bamboohr/signals.py +++ b/apps/bamboohr/signals.py @@ -18,23 +18,29 @@ def run_pre_save_configuration_triggers(sender, instance: BambooHrConfiguration, @receiver(post_save, sender=BambooHr) def run_post_save_bamboohr_triggers(sender, instance: BambooHr, **kwargs): + + # Disconnect the signal to avoid triggering it again + post_save.disconnect(run_post_save_bamboohr_triggers, sender=BambooHr) + bamboohrsdk = BambooHrSDK(api_token=instance.api_token, sub_domain=instance.sub_domain) webhook_payload = { - "postFields": { - "firstName": "firstName", - "lastName": "lastName", - "department": "department", - "workEmail": "workEmail", - "supervisorEmail": "supervisorEmail", - "status": "status" + 'postFields': { + 'firstName': 'firstName', + 'lastName': 'lastName', + 'department': 'department', + 'workEmail': 'workEmail', + 'status': 'status' }, - "name": instance.org.name, - "monitorFields": ["firstName", "lastName", "department", "workEmail", "supervisorEmail", "status"], - "url": API_URL+f'/orgs/{instance.org.id}/bamboohr/webhook_callback/', - "format": "json" + 'name': instance.org.name, + 'monitorFields': ['firstName', 'lastName', 'department', 'workEmail', 'reportingTo', 'status'], + 'url': API_URL + f'/orgs/{instance.org.id}/bamboohr/webhook_callback/', + 'format': 'json' } response = bamboohrsdk.webhook.post(payload=webhook_payload) instance.webhook_id = int(response['id']) instance.save() + + # Reconnect the signal after saving + post_save.connect(run_post_save_bamboohr_triggers, sender=BambooHr) From 536e60ed770f70937063b4169bfb8f4f0c884cee Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Wed, 3 Jan 2024 13:46:05 +0530 Subject: [PATCH 6/8] minor changes --- apps/bamboohr/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bamboohr/signals.py b/apps/bamboohr/signals.py index 4e0450dc..fa9be4b9 100644 --- a/apps/bamboohr/signals.py +++ b/apps/bamboohr/signals.py @@ -33,7 +33,7 @@ def run_post_save_bamboohr_triggers(sender, instance: BambooHr, **kwargs): 'status': 'status' }, 'name': instance.org.name, - 'monitorFields': ['firstName', 'lastName', 'department', 'workEmail', 'reportingTo', 'status'], + 'monitorFields': ['firstName', 'lastName', 'department', 'workEmail', 'status'], 'url': API_URL + f'/orgs/{instance.org.id}/bamboohr/webhook_callback/', 'format': 'json' } From 0112fdfe85236e2a4db0add44c707fe6420805c5 Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Wed, 3 Jan 2024 14:13:00 +0530 Subject: [PATCH 7/8] comment resolved --- apps/bamboohr/urls.py | 4 ++-- apps/bamboohr/views.py | 7 +++---- bamboosdk/api/api_base.py | 3 +-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/bamboohr/urls.py b/apps/bamboohr/urls.py index d165370e..06677144 100644 --- a/apps/bamboohr/urls.py +++ b/apps/bamboohr/urls.py @@ -1,12 +1,12 @@ from django.urls import path from .views import PostFolder, PostPackage, BambooHrConnection, BambooHrView, BambooHrConfigurationView, \ - DisconnectView, SyncEmployeesView, HealthCheck, WebhookAPIView + DisconnectView, SyncEmployeesView, HealthCheck, WebhookCallbackAPIView app_name = 'bamboohr' urlpatterns = [ - path('webhook_callback/', WebhookAPIView.as_view(), name='webhook-callback'), + path('webhook_callback/', WebhookCallbackAPIView.as_view(), name='webhook-callback'), path('health_check/', HealthCheck.as_view(), name='health-check'), path('', BambooHrView.as_view(), name='bamboohr'), path('packages/', PostPackage.as_view(), name='package'), diff --git a/apps/bamboohr/views.py b/apps/bamboohr/views.py index 0de90f34..d9e448fe 100644 --- a/apps/bamboohr/views.py +++ b/apps/bamboohr/views.py @@ -49,7 +49,7 @@ def get(self, request, *args, **kwargs): ) -class WebhookAPIView(generics.CreateAPIView): +class WebhookCallbackAPIView(generics.CreateAPIView): def post(self, request, *args, **kwargs): @@ -214,11 +214,10 @@ class DisconnectView(generics.CreateAPIView): def post(self, request, *args, **kwargs): try: bamboohr = BambooHr.objects.filter(org__id=kwargs['org_id']).first() - webhook_id = request.data['webhook_id'] bambamboohrsdk = BambooHrSDK(api_token=bamboohr.api_token, sub_domain=bamboohr.sub_domain) - bambamboohrsdk.webhook.delete(id=webhook_id) + response = bambamboohrsdk.webhook.delete(id=bamboohr.webhook_id) return Response( - data='Succesfully disconnected Bamboohr', + data=response, status=status.HTTP_200_OK ) except BambooHr.DoesNotExist: diff --git a/bamboosdk/api/api_base.py b/bamboosdk/api/api_base.py index dcba2457..c62c24bd 100644 --- a/bamboosdk/api/api_base.py +++ b/bamboosdk/api/api_base.py @@ -90,8 +90,7 @@ def _delete_request(self, module_api_path): url= self.API_BASE_URL.format(self.__sub_domain) + module_api_path response = requests.delete(url=url, headers=self.headers) if response.status_code == 200: - result = json.loads(response.text) - return result + return {'message':'Web hook has been deleted'} if response.status_code == 403: error_msg = json.loads(response.text) From 42c0b2f147099d68c61fd50769611a025d171918 Mon Sep 17 00:00:00 2001 From: Ashutosh619-sudo Date: Wed, 3 Jan 2024 14:45:51 +0530 Subject: [PATCH 8/8] comment resolved --- apps/bamboohr/signals.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/bamboohr/signals.py b/apps/bamboohr/signals.py index fa9be4b9..80548941 100644 --- a/apps/bamboohr/signals.py +++ b/apps/bamboohr/signals.py @@ -19,9 +19,6 @@ def run_pre_save_configuration_triggers(sender, instance: BambooHrConfiguration, @receiver(post_save, sender=BambooHr) def run_post_save_bamboohr_triggers(sender, instance: BambooHr, **kwargs): - # Disconnect the signal to avoid triggering it again - post_save.disconnect(run_post_save_bamboohr_triggers, sender=BambooHr) - bamboohrsdk = BambooHrSDK(api_token=instance.api_token, sub_domain=instance.sub_domain) webhook_payload = { @@ -39,8 +36,4 @@ def run_post_save_bamboohr_triggers(sender, instance: BambooHr, **kwargs): } response = bamboohrsdk.webhook.post(payload=webhook_payload) - instance.webhook_id = int(response['id']) - instance.save() - - # Reconnect the signal after saving - post_save.connect(run_post_save_bamboohr_triggers, sender=BambooHr) + BambooHr.objects.filter(id=instance.id).update(webhook_id=int(response['id']))