Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added webhook callback #86

Merged
merged 2 commits into from
Jun 21, 2024
Merged

Added webhook callback #86

merged 2 commits into from
Jun 21, 2024

Conversation

Hrishabh17
Copy link
Contributor

@Hrishabh17 Hrishabh17 commented Jun 20, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a new webhook callback handler for asynchronous expense processing.
    • Added functionality to create admin subscriptions for existing workspaces.
  • Improvements

    • Enhanced exception handling for Fyle credentials and API interactions.
  • Configuration

    • Added API_URL configuration settings.
  • Tests

    • Updated test cases for new features related to webhook handling and admin subscriptions.
  • Chores

    • Lowered test coverage threshold from 99% to 97% in CI workflows.

Copy link

Tests Skipped Failures Errors Time
54 0 💤 0 ❌ 0 🔥 7.822s ⏱️

Copy link

coderabbitai bot commented Jun 20, 2024

Walkthrough

The update introduces a decorator for handling exceptions, a new function for asynchronously handling webhook callbacks, and a new view for managing these callbacks. It also adds functionality for creating admin subscriptions during workspace serialization and invokes these tasks asynchronously. API URL configurations and test settings have also been updated. Additionally, coverage thresholds in GitHub workflows have been slightly reduced.

Changes

Files Change Summaries
apps/fyle/exceptions.py Introduced handle_view_exceptions decorator for exception handling.
apps/fyle/queue.py Added async_handle_webhook_callback function for asynchronous webhook callback handling.
apps/fyle/urls.py, apps/fyle/views.py Added new WebhookCallbackView class and updated URL patterns to include this view.
apps/workspaces/serializers.py Introduced async_task import and added call to async_create_admin_subscriptions task in create method of WorkspaceSerializer.
apps/workspaces/tasks.py Updated imports and added async_create_admin_subcriptions function to interact with Fyle credentials and platform connector.
quickbooks_desktop_api/settings.py, quickbooks_desktop_api/tests/settings.py Added API_URL variable fetching from environment variables.
scripts/python/001_add_admin_subscriptions.py Introduced functionality to create admin subscriptions for existing workspaces via asynchronous task invocation.
tests/test_workspaces/test_tasks.py Added tests for async_create_admin_subcriptions function, and updated imports from Django modules.
.github/workflows/codecov.yml, .github/workflows/tests.yml Lowered code coverage threshold from 99% to 97%.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebhookCallbackView
    participant FylePlatform
    participant TaskQueue

    User->>+WebhookCallbackView: Sends webhook callback
    WebhookCallbackView->>+TaskQueue: async_handle_webhook_callback(body)
    TaskQueue->>FylePlatform: Import and export expenses
    FylePlatform-->>TaskQueue: Response
    TaskQueue-->>-WebhookCallbackView: Completion status
    WebhookCallbackView-->>User: Callback processed response

    Note right of WebhookCallbackView: New flow with decorators
Loading
sequenceDiagram
    participant Serializer
    participant TaskQueue
    participant FylePlatform

    Serializer->>+TaskQueue: async_create_admin_subscriptions(workspace_id)
    TaskQueue->>+FylePlatform: Create admin subscriptions
    FylePlatform-->>-TaskQueue: Response
    TaskQueue-->>-Serializer: Subscriptions created

    Note right of Serializer: Updated with async task calls
Loading

Poem

🐇
In the boundless code garden, we prance and play,
With webhooks and tasks that now lead the way.
Async calls dance, like whispers of delight,
Handling callbacks throughout the night.
Add subscriptions, watch workspaces shine,
In our code burrow, everything is fine!
🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Outside diff range and nitpick comments (3)
quickbooks_desktop_api/tests/settings.py (1)

Line range hint 29-29: Simplify the assignment of DEBUG to remove unnecessary conditional.

- DEBUG = True if os.environ.get('DEBUG') == 'True' else False
+ DEBUG = os.environ.get('DEBUG') == 'True'
quickbooks_desktop_api/settings.py (2)

Line range hint 33-33: Simplify the assignment of DEBUG to remove unnecessary conditional.

- DEBUG = True if os.environ.get('DEBUG') == 'True' else False
+ DEBUG = os.environ.get('DEBUG') == 'True'

Line range hint 150-150: Update logging format specifiers to use modern Python formatting.

- 'format': '{levelname} %s {asctime} {module} {message} ' % 'QBD_API',
+ 'format': f'{{levelname}} QBD_API {{asctime}} {{module}} {{message}}',

- 'format': 'request {levelname} %s {asctime} {message}' % 'QBD_API',
+ 'format': f'request {{levelname}} QBD_API {{asctime}} {{message}}',

Also applies to: 154-154

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between c01ba56 and 4b1b4cf.

Files selected for processing (10)
  • apps/fyle/exceptions.py (1 hunks)
  • apps/fyle/queue.py (2 hunks)
  • apps/fyle/urls.py (1 hunks)
  • apps/fyle/views.py (2 hunks)
  • apps/workspaces/serializers.py (3 hunks)
  • apps/workspaces/tasks.py (3 hunks)
  • quickbooks_desktop_api/settings.py (1 hunks)
  • quickbooks_desktop_api/tests/settings.py (1 hunks)
  • scripts/python/001_add_admin_subscriptions.py (1 hunks)
  • tests/test_workspaces/test_tasks.py (4 hunks)
Additional context used
Ruff
scripts/python/001_add_admin_subscriptions.py

12-12: Use f-string instead of format call (UP032)

Convert to f-string

apps/workspaces/tasks.py

100-100: Use f-string instead of format call (UP032)

Convert to f-string

tests/test_workspaces/test_tasks.py

95-95: Use f-string instead of format call (UP032)

Convert to f-string


122-122: Use f-string instead of format call (UP032)

Convert to f-string

apps/workspaces/serializers.py

163-164: Use a single if statement instead of nested if statements (SIM102)

quickbooks_desktop_api/tests/settings.py

29-29: Remove unnecessary True if ... else False (SIM210)

Remove unnecessary True if ... else False


132-132: Use format specifiers instead of percent format (UP031)

Replace with format specifiers


136-136: Use format specifiers instead of percent format (UP031)

Replace with format specifiers

quickbooks_desktop_api/settings.py

33-33: Remove unnecessary True if ... else False (SIM210)

Remove unnecessary True if ... else False


150-150: Use format specifiers instead of percent format (UP031)

Replace with format specifiers


154-154: Use format specifiers instead of percent format (UP031)

Replace with format specifiers

Additional comments not posted (6)
apps/fyle/urls.py (1)

3-3: The URL path for WebhookCallbackView is correctly defined and follows Django's URL naming conventions.

Also applies to: 8-8

apps/fyle/views.py (1)

25-36: The implementation of WebhookCallbackView is appropriate for handling webhook callbacks. It uses the correct decorator and function calls for asynchronous processing.

apps/fyle/queue.py (1)

63-73: The implementation of async_handle_webhook_callback is well-designed to handle webhook callbacks based on the action specified. It correctly retrieves and triggers tasks for the appropriate workspace.

apps/workspaces/serializers.py (1)

Line range hint 163-164: In AdvancedSettingSerializer.create, consider refactoring the nested if statements into a single condition to simplify the logic and improve readability.
[REFACTOR_SUGGESTion]

- if not advanced_setting:
-     if 'expense_memo_structure' not in validated_data:
+ if not advanced_setting and 'expense_memo_structure' not in validated_data:
quickbooks_desktop_api/tests/settings.py (1)

244-244: Ensure the new API_URL variable is securely used and properly documented.

quickbooks_desktop_api/settings.py (1)

266-266: The introduction of API_URL and other Fyle-related environment variables is well-handled. Ensure that these variables are used securely across the application.

Comment on lines +14 to +52
def handle_view_exceptions():
def decorator(func):
def new_fn(*args, **kwargs):
try:
return func(*args, **kwargs)
except AccountingExport.DoesNotExist:
return Response(data={'message': 'AccountingExport not found'}, status=status.HTTP_400_BAD_REQUEST)

except FyleCredential.DoesNotExist:
return Response(data={'message': 'Fyle credentials not found in workspace'}, status=status.HTTP_400_BAD_REQUEST)

except FyleInvalidTokenError as exception:
logger.info('Fyle token expired workspace_id - %s %s', kwargs['workspace_id'], {'error': exception.response})
return Response(data={'message': 'Fyle token expired workspace_id'}, status=status.HTTP_400_BAD_REQUEST)

except NoPrivilegeError as exception:
logger.info('Invalid Fyle Credentials / Admin is disabled for workspace_id%s %s', kwargs['workspace_id'], {'error': exception.response})
return Response(data={'message': 'Invalid Fyle Credentials / Admin is disabled'}, status=status.HTTP_400_BAD_REQUEST)

except RetryException:
logger.info('Fyle Retry Exception for workspace_id %s', kwargs['workspace_id'])
return Response(data={'message': 'Fyle API rate limit exceeded'}, status=status.HTTP_400_BAD_REQUEST)

except Workspace.DoesNotExist:
return Response(data={'message': 'Workspace with this id does not exist'}, status=status.HTTP_400_BAD_REQUEST)

except AdvancedSetting.DoesNotExist:
return Response(data={'message': 'Advanced Settings does not exist in workspace'}, status=status.HTTP_400_BAD_REQUEST)

except ExportSettings.DoesNotExist:
return Response({'message': 'Export Settings does not exist in workspace'}, status=status.HTTP_400_BAD_REQUEST)

except Exception as exception:
logger.exception(exception)
return Response(data={'message': 'An unhandled error has occurred, please re-try later'}, status=status.HTTP_400_BAD_REQUEST)

return new_fn

return decorator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handle_view_exceptions decorator function is comprehensive and covers a wide range of exceptions. However, consider adding specific HTTP status codes for different exceptions to differentiate the errors more clearly.

- return Response(data={'message': 'An unhandled error has occurred, please re-try later'}, status=status.HTTP_400_BAD_REQUEST)
+ return Response(data={'message': 'An unhandled error has occurred, please re-try later'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def handle_view_exceptions():
def decorator(func):
def new_fn(*args, **kwargs):
try:
return func(*args, **kwargs)
except AccountingExport.DoesNotExist:
return Response(data={'message': 'AccountingExport not found'}, status=status.HTTP_400_BAD_REQUEST)
except FyleCredential.DoesNotExist:
return Response(data={'message': 'Fyle credentials not found in workspace'}, status=status.HTTP_400_BAD_REQUEST)
except FyleInvalidTokenError as exception:
logger.info('Fyle token expired workspace_id - %s %s', kwargs['workspace_id'], {'error': exception.response})
return Response(data={'message': 'Fyle token expired workspace_id'}, status=status.HTTP_400_BAD_REQUEST)
except NoPrivilegeError as exception:
logger.info('Invalid Fyle Credentials / Admin is disabled for workspace_id%s %s', kwargs['workspace_id'], {'error': exception.response})
return Response(data={'message': 'Invalid Fyle Credentials / Admin is disabled'}, status=status.HTTP_400_BAD_REQUEST)
except RetryException:
logger.info('Fyle Retry Exception for workspace_id %s', kwargs['workspace_id'])
return Response(data={'message': 'Fyle API rate limit exceeded'}, status=status.HTTP_400_BAD_REQUEST)
except Workspace.DoesNotExist:
return Response(data={'message': 'Workspace with this id does not exist'}, status=status.HTTP_400_BAD_REQUEST)
except AdvancedSetting.DoesNotExist:
return Response(data={'message': 'Advanced Settings does not exist in workspace'}, status=status.HTTP_400_BAD_REQUEST)
except ExportSettings.DoesNotExist:
return Response({'message': 'Export Settings does not exist in workspace'}, status=status.HTTP_400_BAD_REQUEST)
except Exception as exception:
logger.exception(exception)
return Response(data={'message': 'An unhandled error has occurred, please re-try later'}, status=status.HTTP_400_BAD_REQUEST)
return new_fn
return decorator
def handle_view_exceptions():
def decorator(func):
def new_fn(*args, **kwargs):
try:
return func(*args, **kwargs)
except AccountingExport.DoesNotExist:
return Response(data={'message': 'AccountingExport not found'}, status=status.HTTP_400_BAD_REQUEST)
except FyleCredential.DoesNotExist:
return Response(data={'message': 'Fyle credentials not found in workspace'}, status=status.HTTP_400_BAD_REQUEST)
except FyleInvalidTokenError as exception:
logger.info('Fyle token expired workspace_id - %s %s', kwargs['workspace_id'], {'error': exception.response})
return Response(data={'message': 'Fyle token expired workspace_id'}, status=status.HTTP_400_BAD_REQUEST)
except NoPrivilegeError as exception:
logger.info('Invalid Fyle Credentials / Admin is disabled for workspace_id%s %s', kwargs['workspace_id'], {'error': exception.response})
return Response(data={'message': 'Invalid Fyle Credentials / Admin is disabled'}, status=status.HTTP_400_BAD_REQUEST)
except RetryException:
logger.info('Fyle Retry Exception for workspace_id %s', kwargs['workspace_id'])
return Response(data={'message': 'Fyle API rate limit exceeded'}, status=status.HTTP_400_BAD_REQUEST)
except Workspace.DoesNotExist:
return Response(data={'message': 'Workspace with this id does not exist'}, status=status.HTTP_400_BAD_REQUEST)
except AdvancedSetting.DoesNotExist:
return Response(data={'message': 'Advanced Settings does not exist in workspace'}, status=status.HTTP_400_BAD_REQUEST)
except ExportSettings.DoesNotExist:
return Response({'message': 'Export Settings does not exist in workspace'}, status=status.HTTP_400_BAD_REQUEST)
except Exception as exception:
logger.exception(exception)
return Response(data={'message': 'An unhandled error has occurred, please re-try later'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return new_fn
return decorator

Comment on lines +90 to +102
def async_create_admin_subcriptions(workspace_id: int) -> None:
"""
Create admin subscriptions
:param workspace_id: workspace id
:return: None
"""
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials)
payload = {
'is_enabled': True,
'webhook_url': '{}/workspaces/{}/fyle/webhook_callback/'.format(settings.API_URL, workspace_id)
}
platform.subscriptions.post(payload)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function async_create_admin_subcriptions (note the typo in 'subscriptions') is well-implemented for creating admin subscriptions. However, consider using f-strings for better performance and readability in line 100.

- 'webhook_url': '{}/workspaces/{}/fyle/webhook_callback/'.format(settings.API_URL, workspace_id)
+ 'webhook_url': f'{settings.API_URL}/workspaces/{workspace_id}/fyle/webhook_callback/'
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def async_create_admin_subcriptions(workspace_id: int) -> None:
"""
Create admin subscriptions
:param workspace_id: workspace id
:return: None
"""
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials)
payload = {
'is_enabled': True,
'webhook_url': '{}/workspaces/{}/fyle/webhook_callback/'.format(settings.API_URL, workspace_id)
}
platform.subscriptions.post(payload)
def async_create_admin_subcriptions(workspace_id: int) -> None:
"""
Create admin subscriptions
:param workspace_id: workspace id
:return: None
"""
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials)
payload = {
'is_enabled': True,
'webhook_url': f'{settings.API_URL}/workspaces/{workspace_id}/fyle/webhook_callback/'
}
platform.subscriptions.post(payload)
Tools
Ruff

100-100: Use f-string instead of format call (UP032)

Convert to f-string


payload = {
'is_enabled': True,
'webhook_url': '{}/workspaces/{}/fyle/webhook_callback/'.format(settings.API_URL, workspace_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the test cases for async_create_admin_subscriptions, replace the format method with f-strings for string formatting to enhance readability and performance.

- 'webhook_url': '{}/workspaces/{}/fyle/webhook_callback/'.format(settings.API_URL, workspace_id)
+ 'webhook_url': f'{settings.API_URL}/workspaces/{workspace_id}/fyle/webhook_callback/'

Also applies to: 122-122

Tools
Ruff

95-95: Use f-string instead of format call (UP032)

Convert to f-string

@@ -184,5 +183,6 @@ def create(self, validated_data):
if workspace.onboarding_state == 'ADVANCED_SETTINGS':
workspace.onboarding_state = 'COMPLETE'
workspace.save()
async_task('apps.workspaces.tasks.async_create_admin_subcriptions', workspace_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to async_create_admin_subcriptions within AdvancedSettingSerializer is correctly placed to trigger after settings update. However, ensure the function name typo is corrected across the codebase.

- async_task('apps.workspaces.tasks.async_create_admin_subcriptions', workspace_id)
+ async_task('apps.workspaces.tasks.async_create_admin_subscriptions', workspace_id)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async_task('apps.workspaces.tasks.async_create_admin_subcriptions', workspace_id)
async_task('apps.workspaces.tasks.async_create_admin_subscriptions', workspace_id)

@@ -0,0 +1,13 @@
# Create admin subscriptions for existing workspaces

from apps.workspaces.tasks import async_create_admin_subcriptions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct the typo in the import statement for async_create_admin_subscriptions.

- from apps.workspaces.tasks import async_create_admin_subcriptions
+ from apps.workspaces.tasks import async_create_admin_subscriptions
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from apps.workspaces.tasks import async_create_admin_subcriptions
from apps.workspaces.tasks import async_create_admin_subscriptions

try:
async_create_admin_subcriptions(workspace.id)
except Exception as e:
print('Error while creating admin subscriptions for workspace - {} with ID - {}'.format(workspace.name, workspace.id))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace format with an f-string for better performance and readability.

- print('Error while creating admin subscriptions for workspace - {} with ID - {}'.format(workspace.name, workspace.id))
+ print(f'Error while creating admin subscriptions for workspace - {workspace.name} with ID - {workspace.id}')
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
print('Error while creating admin subscriptions for workspace - {} with ID - {}'.format(workspace.name, workspace.id))
print(f'Error while creating admin subscriptions for workspace - {workspace.name} with ID - {workspace.id}')
Tools
Ruff

12-12: Use f-string instead of format call (UP032)

Convert to f-string

Copy link

Tests Skipped Failures Errors Time
54 0 💤 0 ❌ 0 🔥 7.965s ⏱️

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 4b1b4cf and 80ea6b7.

Files selected for processing (2)
  • .github/workflows/codecov.yml (1 hunks)
  • .github/workflows/tests.yml (1 hunks)
Files skipped from review due to trivial changes (1)
  • .github/workflows/codecov.yml
Additional comments not posted (1)
.github/workflows/tests.yml (1)

19-19: Lowering the coverage threshold to 97% is noted. Ensure this aligns with the intended quality standards of the project.

@Hrishabh17 Hrishabh17 merged commit 16ba287 into master Jun 21, 2024
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants