From ffcd7a49a1ba6a6c444ef66a1931d22e5e64ec08 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 11 Nov 2025 19:37:02 +0530 Subject: [PATCH 01/10] Sage CRM implementation --- sage_crm_client.py | 145 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 sage_crm_client.py diff --git a/sage_crm_client.py b/sage_crm_client.py new file mode 100644 index 0000000000000..d9bd2d5d5fb52 --- /dev/null +++ b/sage_crm_client.py @@ -0,0 +1,145 @@ +import requests +import base64 +from typing import Dict, List, Optional, Union +from urllib.parse import urljoin + +class SageCRMClient: + """ + A client for interacting with Sage CRM's REST API. + Handles authentication and provides methods for ticket management. + """ + + def __init__(self, base_url: str, username: str, password: str): + """ + Initialize the Sage CRM client. + + Args: + base_url: Base URL of your Sage CRM instance (e.g., 'https://your-company.crm.sage.com') + username: Your Sage CRM username + password: Your Sage CRM password + """ + self.base_url = base_url.rstrip('/') + self.api_base = f"{self.base_url}/api" + self.session = requests.Session() + self._setup_auth(username, password) + + def _setup_auth(self, username: str, password: str) -> None: + """Set up basic authentication.""" + credentials = f"{username}:{password}" + encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8') + self.session.headers.update({ + 'Authorization': f'Basic {encoded_credentials}', + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict: + """Make an API request and handle the response.""" + url = urljoin(f"{self.api_base}/", endpoint.lstrip('/')) + try: + response = self.session.request(method, url, **kwargs) + response.raise_for_status() + return response.json() if response.content else {} + except requests.exceptions.RequestException as e: + print(f"Error making {method} request to {url}: {e}") + raise + + # Ticket Operations + + def get_tickets(self, query: str = None, **filters) -> List[Dict]: + """ + Get a list of tickets, optionally filtered by query or other parameters. + + Args: + query: Search query string + **filters: Additional filter parameters (e.g., status='open') + + Returns: + List of ticket dictionaries + """ + params = {} + if query: + params['query'] = query + params.update(filters) + + return self._make_request('GET', 'tickets', params=params) + + def get_ticket(self, ticket_id: Union[str, int]) -> Dict: + """ + Get a specific ticket by ID. + + Args: + ticket_id: The ID of the ticket to retrieve + + Returns: + Ticket details as a dictionary + """ + return self._make_request('GET', f'tickets/{ticket_id}') + + def create_ticket(self, ticket_data: Dict) -> Dict: + """ + Create a new ticket. + + Args: + ticket_data: Dictionary containing ticket details + + Returns: + The created ticket details + """ + return self._make_request('POST', 'tickets', json=ticket_data) + + def update_ticket(self, ticket_id: Union[str, int], update_data: Dict) -> Dict: + """ + Update an existing ticket. + + Args: + ticket_id: The ID of the ticket to update + update_data: Dictionary containing fields to update + + Returns: + The updated ticket details + """ + return self._make_request('PUT', f'tickets/{ticket_id}', json=update_data) + + def delete_ticket(self, ticket_id: Union[str, int]) -> bool: + """ + Delete a ticket. + + Args: + ticket_id: The ID of the ticket to delete + + Returns: + True if deletion was successful + """ + self._make_request('DELETE', f'tickets/{ticket_id}') + return True + +# Example usage +if __name__ == "__main__": + # Initialize the client with your credentials + client = SageCRMClient( + base_url="https://your-company.crm.sage.com", + username="your_username", + password="your_password" + ) + + # Example: Get all open tickets + open_tickets = client.get_tickets(status="open") + print("Open tickets:", open_tickets) + + # Example: Create a new ticket + new_ticket = client.create_ticket({ + "subject": "API Test Ticket", + "description": "This ticket was created via API", + "priority": "medium", + "status": "open" + }) + print("Created ticket:", new_ticket) + + # Example: Update a ticket + if new_ticket and 'id' in new_ticket: + updated = client.update_ticket(new_ticket['id'], { + "status": "in_progress", + "assigned_to": "support_agent@example.com" + }) + print("Updated ticket:", updated) From f403cec5f3ad359ae37675d8424d0d87c4176f85 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 11 Nov 2025 19:49:53 +0530 Subject: [PATCH 02/10] Improved Sage CRM --- sage_crm_client.py | 320 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 268 insertions(+), 52 deletions(-) diff --git a/sage_crm_client.py b/sage_crm_client.py index d9bd2d5d5fb52..4c8b0769d16c9 100644 --- a/sage_crm_client.py +++ b/sage_crm_client.py @@ -1,15 +1,33 @@ -import requests import base64 -from typing import Dict, List, Optional, Union -from urllib.parse import urljoin +import logging +import os +from datetime import datetime +from typing import Any, Optional +from urllib.parse import urljoin, urlparse + +import requests + +logger = logging.getLogger(__name__) class SageCRMClient: """ A client for interacting with Sage CRM's REST API. Handles authentication and provides methods for ticket management. + + Example: + ```python + from sage_crm_client import SageCRMClient + import os + + client = SageCRMClient( + base_url=os.getenv("SAGE_CRM_URL"), + username=os.getenv("SAGE_CRM_USERNAME"), + password=os.getenv("SAGE_CRM_PASSWORD") + ) + ``` """ - - def __init__(self, base_url: str, username: str, password: str): + + def __init__(self, base_url: str, username: str, password: str) -> None: """ Initialize the Sage CRM client. @@ -18,9 +36,21 @@ def __init__(self, base_url: str, username: str, password: str): username: Your Sage CRM username password: Your Sage CRM password """ + if not base_url: + raise ValueError("base_url cannot be empty") + + parsed_url = urlparse(base_url) + if not all([parsed_url.scheme, parsed_url.netloc]): + raise ValueError(f"Invalid base_url: {base_url}. Must include scheme (http/https) and hostname.") + + if not username or not password: + raise ValueError("Both username and password are required") + self.base_url = base_url.rstrip('/') self.api_base = f"{self.base_url}/api" self.session = requests.Session() + self._last_request_time = 0 + self._min_request_interval = 0.1 # 10 requests per second self._setup_auth(username, password) def _setup_auth(self, username: str, password: str) -> None: @@ -33,113 +63,299 @@ def _setup_auth(self, username: str, password: str) -> None: 'Accept': 'application/json' }) - def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict: - """Make an API request and handle the response.""" + def _make_request(self, method: str, endpoint: str, **kwargs: Any) -> dict | list: + """ + Make an API request and handle the response. + + Args: + method: HTTP method (GET, POST, PUT, DELETE) + endpoint: API endpoint path + **kwargs: Additional arguments to pass to requests.Session.request() + + Returns: + Parsed JSON response as a dictionary or list + + Raises: + requests.exceptions.RequestException: If the request fails + ValueError: If the response cannot be parsed as JSON + """ + # Rate limiting + self._enforce_rate_limit() + url = urljoin(f"{self.api_base}/", endpoint.lstrip('/')) + logger.debug(f"Making {method} request to {url}") + try: response = self.session.request(method, url, **kwargs) + + # Handle specific error status codes + if response.status_code == 401: + raise AuthenticationError("Authentication failed. Please check your credentials.") + elif response.status_code == 403: + raise PermissionError("Insufficient permissions to access this resource.") + elif response.status_code == 404: + raise ResourceNotFoundError(f"Resource not found: {url}") + response.raise_for_status() - return response.json() if response.content else {} + + if not response.content: + return {} + + try: + return response.json() + except ValueError as e: + logger.error(f"Failed to parse JSON response: {e}") + raise ValueError(f"Failed to parse JSON response: {e}") + except requests.exceptions.RequestException as e: - print(f"Error making {method} request to {url}: {e}") + logger.error(f"Error making {method} request to {url}: {str(e)}") raise + + def _enforce_rate_limit(self) -> None: + """Enforce rate limiting by sleeping if needed.""" + current_time = datetime.now().timestamp() + time_since_last = current_time - self._last_request_time + + if time_since_last < self._min_request_interval: + sleep_time = self._min_request_interval - time_since_last + import time + time.sleep(sleep_time) + + self._last_request_time = datetime.now().timestamp() # Ticket Operations - def get_tickets(self, query: str = None, **filters) -> List[Dict]: + def get_tickets(self, query: str | None = None, **filters: Any) -> list[dict]: """ Get a list of tickets, optionally filtered by query or other parameters. - + + Example: + ```python + # Get all open tickets + open_tickets = client.get_tickets(status="open") + + # Search for tickets + tickets = client.get_tickets("urgent", priority="high") + ``` + Args: - query: Search query string - **filters: Additional filter parameters (e.g., status='open') + query: Optional search query string + **filters: Additional filter parameters (e.g., status='open', priority='high') Returns: List of ticket dictionaries + + Raises: + requests.exceptions.RequestException: If the request fails + ValueError: If the response cannot be parsed """ - params = {} + params: dict[str, Any] = {} if query: params['query'] = query params.update(filters) + + response = self._make_request('GET', 'tickets', params=params) - return self._make_request('GET', 'tickets', params=params) + # Handle different possible response formats + if isinstance(response, list): + return response + elif isinstance(response, dict) and 'tickets' in response: + return response['tickets'] + elif isinstance(response, dict): + return [response] + return [] - def get_ticket(self, ticket_id: Union[str, int]) -> Dict: + def get_ticket(self, ticket_id: str | int) -> dict: """ Get a specific ticket by ID. - + + Example: + ```python + ticket = client.get_ticket("TICKET123") + print(ticket['subject']) + ``` + Args: ticket_id: The ID of the ticket to retrieve Returns: Ticket details as a dictionary + + Raises: + ResourceNotFoundError: If the ticket is not found + requests.exceptions.RequestException: If the request fails """ + if not ticket_id: + raise ValueError("ticket_id cannot be empty") + return self._make_request('GET', f'tickets/{ticket_id}') - def create_ticket(self, ticket_data: Dict) -> Dict: + def create_ticket(self, ticket_data: dict) -> dict: """ Create a new ticket. - + + Example: + ```python + new_ticket = client.create_ticket({ + "subject": "API Test Ticket", + "description": "This is a test ticket", + "priority": "medium", + "status": "open" + }) + ``` + Args: - ticket_data: Dictionary containing ticket details + ticket_data: Dictionary containing ticket details. Must include at least 'subject'. Returns: The created ticket details + + Raises: + ValueError: If required fields are missing + requests.exceptions.RequestException: If the request fails """ + if not ticket_data.get('subject'): + raise ValueError("ticket_data must include a 'subject' field") + return self._make_request('POST', 'tickets', json=ticket_data) - def update_ticket(self, ticket_id: Union[str, int], update_data: Dict) -> Dict: + def update_ticket(self, ticket_id: str | int, update_data: dict) -> dict: """ Update an existing ticket. - + + Example: + ```python + updated = client.update_ticket("TICKET123", { + "status": "in_progress", + "priority": "high" + }) + ``` + Args: ticket_id: The ID of the ticket to update update_data: Dictionary containing fields to update Returns: The updated ticket details + + Raises: + ValueError: If ticket_id is empty or update_data is empty + ResourceNotFoundError: If the ticket is not found + requests.exceptions.RequestException: If the request fails """ + if not ticket_id: + raise ValueError("ticket_id cannot be empty") + if not update_data: + raise ValueError("update_data cannot be empty") + return self._make_request('PUT', f'tickets/{ticket_id}', json=update_data) - def delete_ticket(self, ticket_id: Union[str, int]) -> bool: + def delete_ticket(self, ticket_id: str | int) -> None: """ Delete a ticket. - + + Example: + ```python + client.delete_ticket("TICKET123") + ``` + Args: ticket_id: The ID of the ticket to delete Returns: - True if deletion was successful + None if deletion was successful + + Raises: + ValueError: If ticket_id is empty + ResourceNotFoundError: If the ticket is not found + requests.exceptions.RequestException: If the request fails """ + if not ticket_id: + raise ValueError("ticket_id cannot be empty") + self._make_request('DELETE', f'tickets/{ticket_id}') - return True -# Example usage -if __name__ == "__main__": - # Initialize the client with your credentials - client = SageCRMClient( - base_url="https://your-company.crm.sage.com", - username="your_username", - password="your_password" - ) - - # Example: Get all open tickets - open_tickets = client.get_tickets(status="open") - print("Open tickets:", open_tickets) + +class AuthenticationError(Exception): + """Raised when authentication fails.""" + pass + + +class ResourceNotFoundError(Exception): + """Raised when a requested resource is not found.""" + pass + + +def main() -> None: + """Example usage of the SageCRMClient.""" + import logging - # Example: Create a new ticket - new_ticket = client.create_ticket({ - "subject": "API Test Ticket", - "description": "This ticket was created via API", - "priority": "medium", - "status": "open" - }) - print("Created ticket:", new_ticket) + # Set up logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) - # Example: Update a ticket - if new_ticket and 'id' in new_ticket: - updated = client.update_ticket(new_ticket['id'], { - "status": "in_progress", - "assigned_to": "support_agent@example.com" + try: + # Get credentials from environment variables + base_url = os.getenv("SAGE_CRM_URL") + username = os.getenv("SAGE_CRM_USERNAME") + password = os.getenv("SAGE_CRM_PASSWORD") + + if not all([base_url, username, password]): + print("Error: Please set SAGE_CRM_URL, SAGE_CRM_USERNAME, and SAGE_CRM_PASSWORD environment variables") + return + + # Initialize the client + client = SageCRMClient( + base_url=base_url, + username=username, + password=password + ) + + # Example: Get all open tickets + print("Fetching open tickets...") + open_tickets = client.get_tickets(status="open") + print(f"Found {len(open_tickets)} open tickets") + + # Example: Create a new ticket + print("\nCreating a new ticket...") + new_ticket = client.create_ticket({ + "subject": "API Test Ticket", + "description": "This ticket was created via API", + "priority": "medium", + "status": "open" }) - print("Updated ticket:", updated) + print(f"Created ticket: {new_ticket.get('id')} - {new_ticket.get('subject')}") + + # Example: Update the ticket + if new_ticket and 'id' in new_ticket: + print(f"\nUpdating ticket {new_ticket['id']}...") + updated = client.update_ticket(new_ticket['id'], { + "status": "in_progress", + "assigned_to": "support@example.com" + }) + print(f"Updated ticket status to: {updated.get('status')}") + + # Example: Get the updated ticket + print(f"\nFetching updated ticket details...") + ticket = client.get_ticket(new_ticket['id']) + print(f"Ticket details: {ticket}") + + # Example: Delete the ticket (uncomment to enable) + # print(f"\nDeleting ticket {new_ticket['id']}...") + # client.delete_ticket(new_ticket['id']) + # print("Ticket deleted successfully") + + except AuthenticationError as e: + print(f"Authentication failed: {e}") + except ResourceNotFoundError as e: + print(f"Resource not found: {e}") + except requests.exceptions.RequestException as e: + print(f"Request failed: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + + +if __name__ == "__main__": + main() From 2dfcf03648c787670dacd52819eb36448aa3a86d Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 11 Nov 2025 19:54:12 +0530 Subject: [PATCH 03/10] Pr --- PR_DESCRIPTION.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++ update_pr_title.sh | 21 +++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 PR_DESCRIPTION.md create mode 100644 update_pr_title.sh diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md new file mode 100644 index 0000000000000..49ab7f89d18ac --- /dev/null +++ b/PR_DESCRIPTION.md @@ -0,0 +1,65 @@ +# Sage CRM API Client Implementation + +## Why +This PR introduces a modern Python client for interacting with Sage CRM's REST API, providing a more maintainable and type-safe way to manage tickets and other CRM entities. The implementation follows Python best practices and includes comprehensive error handling and rate limiting. + +## What's New +- Modern Python client for Sage CRM API with type hints (Python 3.8+) +- Comprehensive error handling with custom exceptions +- Built-in rate limiting to prevent API abuse +- Input validation and type checking +- Detailed logging for debugging +- Example usage with environment variables + +## Implementation Details + +### Core Features +- **Authentication**: Secure Basic Auth with proper credential handling +- **Rate Limiting**: Configurable request throttling (default: 10 requests/second) +- **Error Handling**: + - Custom exceptions for common error cases + - Detailed error messages with context + - Proper HTTP status code handling + +### API Methods +- `get_tickets()`: List tickets with optional filtering +- `get_ticket()`: Retrieve a single ticket by ID +- `create_ticket()`: Create new tickets +- `update_ticket()`: Modify existing tickets +- `delete_ticket()`: Remove tickets + +### Development Setup +1. Install dependencies: + ```bash + pip install requests python-dotenv + ``` + +2. Set up environment variables (`.env` file): + ``` + SAGE_CRM_URL=https://your-company.crm.sage.com + SAGE_CRM_USERNAME=your_username + SAGE_CRM_PASSWORD=your_password + ``` + +3. Run the example: + ```bash + python sage_crm_client.py + ``` + +## Testing +The implementation includes example usage in the `__main__` block that demonstrates: +- Listing open tickets +- Creating a new ticket +- Updating the ticket status +- Error handling for common scenarios + +## Security +- Credentials are never logged +- HTTPS is enforced for all API requests +- Rate limiting prevents accidental API abuse + +## Future Improvements +- Add support for more Sage CRM entities +- Implement pagination for large result sets +- Add async/await support +- Include unit tests and CI/CD pipeline diff --git a/update_pr_title.sh b/update_pr_title.sh new file mode 100644 index 0000000000000..fec09f034a2d3 --- /dev/null +++ b/update_pr_title.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Update PR title +git config --global user.name "GitHub Actions" +git config --global user.email "actions@github.com" + +git checkout -b update-pr-title +git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git + +git config --global --add safe.directory /github/workspace +git fetch origin + +git commit --allow-empty -m "docs: update PR title and description" + +git push -u origin update-pr-title + +# Create PR with new title +gh pr create \ + --title "feat: Add SageCRMClient for API integration and ticket management" \ + --body "$(cat PR_DESCRIPTION.md)" \ + --base main \ + --head update-pr-title From 4dac4193b48e637beccaa154f1b29cee3fcdf4ae Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 11 Nov 2025 20:00:39 +0530 Subject: [PATCH 04/10] update pr --- update_pr_title.sh | 108 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/update_pr_title.sh b/update_pr_title.sh index fec09f034a2d3..47f6ccea03ee5 100644 --- a/update_pr_title.sh +++ b/update_pr_title.sh @@ -1,21 +1,99 @@ #!/bin/bash -# Update PR title -git config --global user.name "GitHub Actions" -git config --global user.email "actions@github.com" +set -euo pipefail -git checkout -b update-pr-title -git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git +# Error handling +trap 'echo "Error on line $LINENO"; exit 1' ERR -git config --global --add safe.directory /github/workspace -git fetch origin +# Configuration +PR_BRANCH="update-pr-title" +PR_TITLE="feat: Add SageCRMClient for API integration and ticket management" -git commit --allow-empty -m "docs: update PR title and description" +# Setup git +setup_git() { + echo "Setting up git..." + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + git config --global --add safe.directory /github/workspace + + # Get the repository URL from environment or use default + if [ -z "${GITHUB_REPOSITORY:-}" ]; then + echo "Error: GITHUB_REPOSITORY environment variable not set" + exit 1 + fi + + # Set remote URL with token if available + if [ -n "${GITHUB_TOKEN:-}" ]; then + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + fi + + git fetch origin +} -git push -u origin update-pr-title +# Clean up existing branch +cleanup_branch() { + echo "Cleaning up existing branch if it exists..." + # Delete local branch if it exists + if git show-ref --verify --quiet "refs/heads/${PR_BRANCH}"; then + git checkout -q main + git branch -D "${PR_BRANCH}" || true + fi + + # Delete remote branch if it exists + if git ls-remote --exit-code --heads origin "${PR_BRANCH}" >/dev/null 2>&1; then + git push origin --delete "${PR_BRANCH}" 2>/dev/null || true + fi +} -# Create PR with new title -gh pr create \ - --title "feat: Add SageCRMClient for API integration and ticket management" \ - --body "$(cat PR_DESCRIPTION.md)" \ - --base main \ - --head update-pr-title +# Create or update PR +update_pr() { + echo "Creating/updating PR..." + + # Create or update branch + git checkout -b "${PR_BRANCH}" 2>/dev/null || git checkout -B "${PR_BRANCH}" + + # Make an empty commit to trigger PR update + git commit --allow-empty -m "docs: update PR title and description" + + # Push changes + git push -f origin "${PR_BRANCH}" + + # Check if PR already exists + local existing_pr + existing_pr=$(gh pr list --head "${PR_BRANCH}" --json number -q '.[0].number' 2>/dev/null || echo "") + + if [ -n "$existing_pr" ]; then + echo "Updating existing PR #${existing_pr}" + gh pr edit "$existing_pr" --title "${PR_TITLE}" --body "$(cat PR_DESCRIPTION.md)" + else + echo "Creating new PR" + gh pr create \ + --title "${PR_TITLE}" \ + --body "$(cat PR_DESCRIPTION.md)" \ + --base main \ + --head "${PR_BRANCH}" + fi +} + +# Main execution +main() { + # Check if gh CLI is installed + if ! command -v gh &> /dev/null; then + echo "Error: GitHub CLI (gh) is not installed" + exit 1 + fi + + # Check if we're in a git repository + if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Error: Not in a git repository" + exit 1 + fi + + setup_git + cleanup_branch + update_pr + + echo "PR update completed successfully" +} + +# Run main function +main "$@" From 3d8b87190e28052744254d1637fa242eaeea47a0 Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 11 Nov 2025 22:50:19 +0530 Subject: [PATCH 05/10] Gorgias ticket messages functionality --- .../get-ticket-message/get-ticket-message.mjs | 33 ++++++++++++ .../list-ticket-messages.mjs | 54 +++++++++++++++++++ .../gorgias_oauth/gorgias_oauth.app.mjs | 31 +++++++++++ 3 files changed, 118 insertions(+) create mode 100644 components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs create mode 100644 components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs diff --git a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs new file mode 100644 index 0000000000000..80392e7f2e390 --- /dev/null +++ b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs @@ -0,0 +1,33 @@ +import { defineAction } from "@pipedream/types"; +import gorgiasOAuth from "../../gorgias_oauth.app.mjs"; + +export default defineAction({ + name: "Get Ticket Message", + description: "Get a specific message from a ticket [See docs here](https://developers.gorgias.com/reference/get-ticket-message)", + key: "gorgias_oauth-get-ticket-message", + version: "0.0.1", + type: "action", + props: { + gorgiasOAuth, + ticketId: { + propDefinition: [ + gorgiasOAuth, + "ticketId", + ], + }, + messageId: { + type: "integer", + label: "Message ID", + description: "The ID of the message to retrieve", + }, + }, + async run({ $ }) { + const response = await this.gorgiasOAuth.getTicketMessage({ + $, + ticketId: this.ticketId, + messageId: this.messageId, + }); + + return response; + }, +}); diff --git a/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs b/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs new file mode 100644 index 0000000000000..b2f26343abbd5 --- /dev/null +++ b/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs @@ -0,0 +1,54 @@ +import { defineAction } from "@pipedream/types"; +import gorgiasOAuth from "../../gorgias_oauth.app.mjs"; + +export default defineAction({ + name: "List Ticket Messages", + description: "List all messages for a specific ticket [See docs here](https://developers.gorgias.com/reference/list-ticket-messages)", + key: "gorgias_oauth-list-ticket-messages", + version: "0.0.1", + type: "action", + props: { + gorgiasOAuth, + ticketId: { + propDefinition: [ + gorgiasOAuth, + "ticketId", + ], + }, + limit: { + type: "integer", + label: "Limit", + description: "Maximum number of messages to return (1-100)", + min: 1, + max: 100, + default: 50, + }, + cursor: { + type: "string", + label: "Cursor", + description: "Cursor for pagination (get from the meta.next_cursor of the previous response)", + optional: true, + }, + }, + async run({ $ }) { + const params = { + limit: this.limit, + }; + + if (this.cursor) { + params.cursor = this.cursor; + } + + const response = await this.gorgiasOAuth.listTicketMessages({ + $, + ticketId: this.ticketId, + params, + }); + + // Return the data and pagination info + return { + data: response.data, + meta: response.meta, + }; + }, +}); diff --git a/components/gorgias_oauth/gorgias_oauth.app.mjs b/components/gorgias_oauth/gorgias_oauth.app.mjs index d5b36b22a9d32..49d6944a4e626 100644 --- a/components/gorgias_oauth/gorgias_oauth.app.mjs +++ b/components/gorgias_oauth/gorgias_oauth.app.mjs @@ -226,6 +226,37 @@ export default { }, }, methods: { + /** + * List all messages for a specific ticket + * @param {Object} params - Parameters for the request + * @param {number} params.ticketId - The ID of the ticket + * @param {Object} params.params - Optional query parameters (cursor, limit, etc.) + * @returns {Promise} - Returns the list of messages and pagination info + */ + listTicketMessages({ $, ticketId, params = {} }) { + return this._makeRequest({ + $, + path: `/api/tickets/${ticketId}/messages`, + method: 'get', + params, + }); + }, + + /** + * Get a specific message by ID + * @param {Object} params - Parameters for the request + * @param {number} params.ticketId - The ID of the ticket + * @param {number} params.messageId - The ID of the message to retrieve + * @returns {Promise} - Returns the message details + */ + getTicketMessage({ $, ticketId, messageId }) { + return this._makeRequest({ + $, + path: `/api/tickets/${ticketId}/messages/${messageId}`, + method: 'get', + }); + }, + _defaultConfig({ path, method = "get", params = {}, data, }) { From dc7d6bf3fb05f41c8a89f82f84128b7e89796c30 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 12 Nov 2025 22:43:43 +0530 Subject: [PATCH 06/10] Updated Pr --- .../actions/get-ticket-message/get-ticket-message.mjs | 3 +++ .../actions/list-ticket-messages/list-ticket-messages.mjs | 3 +++ components/gorgias_oauth/gorgias_oauth.app.mjs | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs index 80392e7f2e390..6569e81804d64 100644 --- a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs +++ b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs @@ -28,6 +28,9 @@ export default defineAction({ messageId: this.messageId, }); + // Add summary for user feedback + $.export("$summary", `Successfully retrieved message ${this.messageId} from ticket ${this.ticketId}`); + return response; }, }); diff --git a/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs b/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs index b2f26343abbd5..44dec70eed241 100644 --- a/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs +++ b/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs @@ -45,6 +45,9 @@ export default defineAction({ params, }); + // Add summary for user feedback + $.export("$summary", `Successfully retrieved ${response.data.length} message${response.data.length === 1 ? '' : 's'}`); + // Return the data and pagination info return { data: response.data, diff --git a/components/gorgias_oauth/gorgias_oauth.app.mjs b/components/gorgias_oauth/gorgias_oauth.app.mjs index 49d6944a4e626..44aad3a4150d7 100644 --- a/components/gorgias_oauth/gorgias_oauth.app.mjs +++ b/components/gorgias_oauth/gorgias_oauth.app.mjs @@ -236,7 +236,7 @@ export default { listTicketMessages({ $, ticketId, params = {} }) { return this._makeRequest({ $, - path: `/api/tickets/${ticketId}/messages`, + path: `/tickets/${ticketId}/messages`, method: 'get', params, }); @@ -252,7 +252,7 @@ export default { getTicketMessage({ $, ticketId, messageId }) { return this._makeRequest({ $, - path: `/api/tickets/${ticketId}/messages/${messageId}`, + path: `/tickets/${ticketId}/messages/${messageId}`, method: 'get', }); }, From f4135c8ec3132490554e3aa85d7d83048c958982 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 21 Nov 2025 14:02:43 -0500 Subject: [PATCH 07/10] remove unecessary files --- PR_DESCRIPTION.md | 65 -------- sage_crm_client.py | 361 --------------------------------------------- update_pr_title.sh | 99 ------------- 3 files changed, 525 deletions(-) delete mode 100644 PR_DESCRIPTION.md delete mode 100644 sage_crm_client.py delete mode 100644 update_pr_title.sh diff --git a/PR_DESCRIPTION.md b/PR_DESCRIPTION.md deleted file mode 100644 index 49ab7f89d18ac..0000000000000 --- a/PR_DESCRIPTION.md +++ /dev/null @@ -1,65 +0,0 @@ -# Sage CRM API Client Implementation - -## Why -This PR introduces a modern Python client for interacting with Sage CRM's REST API, providing a more maintainable and type-safe way to manage tickets and other CRM entities. The implementation follows Python best practices and includes comprehensive error handling and rate limiting. - -## What's New -- Modern Python client for Sage CRM API with type hints (Python 3.8+) -- Comprehensive error handling with custom exceptions -- Built-in rate limiting to prevent API abuse -- Input validation and type checking -- Detailed logging for debugging -- Example usage with environment variables - -## Implementation Details - -### Core Features -- **Authentication**: Secure Basic Auth with proper credential handling -- **Rate Limiting**: Configurable request throttling (default: 10 requests/second) -- **Error Handling**: - - Custom exceptions for common error cases - - Detailed error messages with context - - Proper HTTP status code handling - -### API Methods -- `get_tickets()`: List tickets with optional filtering -- `get_ticket()`: Retrieve a single ticket by ID -- `create_ticket()`: Create new tickets -- `update_ticket()`: Modify existing tickets -- `delete_ticket()`: Remove tickets - -### Development Setup -1. Install dependencies: - ```bash - pip install requests python-dotenv - ``` - -2. Set up environment variables (`.env` file): - ``` - SAGE_CRM_URL=https://your-company.crm.sage.com - SAGE_CRM_USERNAME=your_username - SAGE_CRM_PASSWORD=your_password - ``` - -3. Run the example: - ```bash - python sage_crm_client.py - ``` - -## Testing -The implementation includes example usage in the `__main__` block that demonstrates: -- Listing open tickets -- Creating a new ticket -- Updating the ticket status -- Error handling for common scenarios - -## Security -- Credentials are never logged -- HTTPS is enforced for all API requests -- Rate limiting prevents accidental API abuse - -## Future Improvements -- Add support for more Sage CRM entities -- Implement pagination for large result sets -- Add async/await support -- Include unit tests and CI/CD pipeline diff --git a/sage_crm_client.py b/sage_crm_client.py deleted file mode 100644 index 4c8b0769d16c9..0000000000000 --- a/sage_crm_client.py +++ /dev/null @@ -1,361 +0,0 @@ -import base64 -import logging -import os -from datetime import datetime -from typing import Any, Optional -from urllib.parse import urljoin, urlparse - -import requests - -logger = logging.getLogger(__name__) - -class SageCRMClient: - """ - A client for interacting with Sage CRM's REST API. - Handles authentication and provides methods for ticket management. - - Example: - ```python - from sage_crm_client import SageCRMClient - import os - - client = SageCRMClient( - base_url=os.getenv("SAGE_CRM_URL"), - username=os.getenv("SAGE_CRM_USERNAME"), - password=os.getenv("SAGE_CRM_PASSWORD") - ) - ``` - """ - - def __init__(self, base_url: str, username: str, password: str) -> None: - """ - Initialize the Sage CRM client. - - Args: - base_url: Base URL of your Sage CRM instance (e.g., 'https://your-company.crm.sage.com') - username: Your Sage CRM username - password: Your Sage CRM password - """ - if not base_url: - raise ValueError("base_url cannot be empty") - - parsed_url = urlparse(base_url) - if not all([parsed_url.scheme, parsed_url.netloc]): - raise ValueError(f"Invalid base_url: {base_url}. Must include scheme (http/https) and hostname.") - - if not username or not password: - raise ValueError("Both username and password are required") - - self.base_url = base_url.rstrip('/') - self.api_base = f"{self.base_url}/api" - self.session = requests.Session() - self._last_request_time = 0 - self._min_request_interval = 0.1 # 10 requests per second - self._setup_auth(username, password) - - def _setup_auth(self, username: str, password: str) -> None: - """Set up basic authentication.""" - credentials = f"{username}:{password}" - encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('utf-8') - self.session.headers.update({ - 'Authorization': f'Basic {encoded_credentials}', - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }) - - def _make_request(self, method: str, endpoint: str, **kwargs: Any) -> dict | list: - """ - Make an API request and handle the response. - - Args: - method: HTTP method (GET, POST, PUT, DELETE) - endpoint: API endpoint path - **kwargs: Additional arguments to pass to requests.Session.request() - - Returns: - Parsed JSON response as a dictionary or list - - Raises: - requests.exceptions.RequestException: If the request fails - ValueError: If the response cannot be parsed as JSON - """ - # Rate limiting - self._enforce_rate_limit() - - url = urljoin(f"{self.api_base}/", endpoint.lstrip('/')) - logger.debug(f"Making {method} request to {url}") - - try: - response = self.session.request(method, url, **kwargs) - - # Handle specific error status codes - if response.status_code == 401: - raise AuthenticationError("Authentication failed. Please check your credentials.") - elif response.status_code == 403: - raise PermissionError("Insufficient permissions to access this resource.") - elif response.status_code == 404: - raise ResourceNotFoundError(f"Resource not found: {url}") - - response.raise_for_status() - - if not response.content: - return {} - - try: - return response.json() - except ValueError as e: - logger.error(f"Failed to parse JSON response: {e}") - raise ValueError(f"Failed to parse JSON response: {e}") - - except requests.exceptions.RequestException as e: - logger.error(f"Error making {method} request to {url}: {str(e)}") - raise - - def _enforce_rate_limit(self) -> None: - """Enforce rate limiting by sleeping if needed.""" - current_time = datetime.now().timestamp() - time_since_last = current_time - self._last_request_time - - if time_since_last < self._min_request_interval: - sleep_time = self._min_request_interval - time_since_last - import time - time.sleep(sleep_time) - - self._last_request_time = datetime.now().timestamp() - - # Ticket Operations - - def get_tickets(self, query: str | None = None, **filters: Any) -> list[dict]: - """ - Get a list of tickets, optionally filtered by query or other parameters. - - Example: - ```python - # Get all open tickets - open_tickets = client.get_tickets(status="open") - - # Search for tickets - tickets = client.get_tickets("urgent", priority="high") - ``` - - Args: - query: Optional search query string - **filters: Additional filter parameters (e.g., status='open', priority='high') - - Returns: - List of ticket dictionaries - - Raises: - requests.exceptions.RequestException: If the request fails - ValueError: If the response cannot be parsed - """ - params: dict[str, Any] = {} - if query: - params['query'] = query - params.update(filters) - - response = self._make_request('GET', 'tickets', params=params) - - # Handle different possible response formats - if isinstance(response, list): - return response - elif isinstance(response, dict) and 'tickets' in response: - return response['tickets'] - elif isinstance(response, dict): - return [response] - return [] - - def get_ticket(self, ticket_id: str | int) -> dict: - """ - Get a specific ticket by ID. - - Example: - ```python - ticket = client.get_ticket("TICKET123") - print(ticket['subject']) - ``` - - Args: - ticket_id: The ID of the ticket to retrieve - - Returns: - Ticket details as a dictionary - - Raises: - ResourceNotFoundError: If the ticket is not found - requests.exceptions.RequestException: If the request fails - """ - if not ticket_id: - raise ValueError("ticket_id cannot be empty") - - return self._make_request('GET', f'tickets/{ticket_id}') - - def create_ticket(self, ticket_data: dict) -> dict: - """ - Create a new ticket. - - Example: - ```python - new_ticket = client.create_ticket({ - "subject": "API Test Ticket", - "description": "This is a test ticket", - "priority": "medium", - "status": "open" - }) - ``` - - Args: - ticket_data: Dictionary containing ticket details. Must include at least 'subject'. - - Returns: - The created ticket details - - Raises: - ValueError: If required fields are missing - requests.exceptions.RequestException: If the request fails - """ - if not ticket_data.get('subject'): - raise ValueError("ticket_data must include a 'subject' field") - - return self._make_request('POST', 'tickets', json=ticket_data) - - def update_ticket(self, ticket_id: str | int, update_data: dict) -> dict: - """ - Update an existing ticket. - - Example: - ```python - updated = client.update_ticket("TICKET123", { - "status": "in_progress", - "priority": "high" - }) - ``` - - Args: - ticket_id: The ID of the ticket to update - update_data: Dictionary containing fields to update - - Returns: - The updated ticket details - - Raises: - ValueError: If ticket_id is empty or update_data is empty - ResourceNotFoundError: If the ticket is not found - requests.exceptions.RequestException: If the request fails - """ - if not ticket_id: - raise ValueError("ticket_id cannot be empty") - if not update_data: - raise ValueError("update_data cannot be empty") - - return self._make_request('PUT', f'tickets/{ticket_id}', json=update_data) - - def delete_ticket(self, ticket_id: str | int) -> None: - """ - Delete a ticket. - - Example: - ```python - client.delete_ticket("TICKET123") - ``` - - Args: - ticket_id: The ID of the ticket to delete - - Returns: - None if deletion was successful - - Raises: - ValueError: If ticket_id is empty - ResourceNotFoundError: If the ticket is not found - requests.exceptions.RequestException: If the request fails - """ - if not ticket_id: - raise ValueError("ticket_id cannot be empty") - - self._make_request('DELETE', f'tickets/{ticket_id}') - - -class AuthenticationError(Exception): - """Raised when authentication fails.""" - pass - - -class ResourceNotFoundError(Exception): - """Raised when a requested resource is not found.""" - pass - - -def main() -> None: - """Example usage of the SageCRMClient.""" - import logging - - # Set up logging - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' - ) - - try: - # Get credentials from environment variables - base_url = os.getenv("SAGE_CRM_URL") - username = os.getenv("SAGE_CRM_USERNAME") - password = os.getenv("SAGE_CRM_PASSWORD") - - if not all([base_url, username, password]): - print("Error: Please set SAGE_CRM_URL, SAGE_CRM_USERNAME, and SAGE_CRM_PASSWORD environment variables") - return - - # Initialize the client - client = SageCRMClient( - base_url=base_url, - username=username, - password=password - ) - - # Example: Get all open tickets - print("Fetching open tickets...") - open_tickets = client.get_tickets(status="open") - print(f"Found {len(open_tickets)} open tickets") - - # Example: Create a new ticket - print("\nCreating a new ticket...") - new_ticket = client.create_ticket({ - "subject": "API Test Ticket", - "description": "This ticket was created via API", - "priority": "medium", - "status": "open" - }) - print(f"Created ticket: {new_ticket.get('id')} - {new_ticket.get('subject')}") - - # Example: Update the ticket - if new_ticket and 'id' in new_ticket: - print(f"\nUpdating ticket {new_ticket['id']}...") - updated = client.update_ticket(new_ticket['id'], { - "status": "in_progress", - "assigned_to": "support@example.com" - }) - print(f"Updated ticket status to: {updated.get('status')}") - - # Example: Get the updated ticket - print(f"\nFetching updated ticket details...") - ticket = client.get_ticket(new_ticket['id']) - print(f"Ticket details: {ticket}") - - # Example: Delete the ticket (uncomment to enable) - # print(f"\nDeleting ticket {new_ticket['id']}...") - # client.delete_ticket(new_ticket['id']) - # print("Ticket deleted successfully") - - except AuthenticationError as e: - print(f"Authentication failed: {e}") - except ResourceNotFoundError as e: - print(f"Resource not found: {e}") - except requests.exceptions.RequestException as e: - print(f"Request failed: {e}") - except Exception as e: - print(f"An unexpected error occurred: {e}") - - -if __name__ == "__main__": - main() diff --git a/update_pr_title.sh b/update_pr_title.sh deleted file mode 100644 index 47f6ccea03ee5..0000000000000 --- a/update_pr_title.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Error handling -trap 'echo "Error on line $LINENO"; exit 1' ERR - -# Configuration -PR_BRANCH="update-pr-title" -PR_TITLE="feat: Add SageCRMClient for API integration and ticket management" - -# Setup git -setup_git() { - echo "Setting up git..." - git config --global user.name "GitHub Actions" - git config --global user.email "actions@github.com" - git config --global --add safe.directory /github/workspace - - # Get the repository URL from environment or use default - if [ -z "${GITHUB_REPOSITORY:-}" ]; then - echo "Error: GITHUB_REPOSITORY environment variable not set" - exit 1 - fi - - # Set remote URL with token if available - if [ -n "${GITHUB_TOKEN:-}" ]; then - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - fi - - git fetch origin -} - -# Clean up existing branch -cleanup_branch() { - echo "Cleaning up existing branch if it exists..." - # Delete local branch if it exists - if git show-ref --verify --quiet "refs/heads/${PR_BRANCH}"; then - git checkout -q main - git branch -D "${PR_BRANCH}" || true - fi - - # Delete remote branch if it exists - if git ls-remote --exit-code --heads origin "${PR_BRANCH}" >/dev/null 2>&1; then - git push origin --delete "${PR_BRANCH}" 2>/dev/null || true - fi -} - -# Create or update PR -update_pr() { - echo "Creating/updating PR..." - - # Create or update branch - git checkout -b "${PR_BRANCH}" 2>/dev/null || git checkout -B "${PR_BRANCH}" - - # Make an empty commit to trigger PR update - git commit --allow-empty -m "docs: update PR title and description" - - # Push changes - git push -f origin "${PR_BRANCH}" - - # Check if PR already exists - local existing_pr - existing_pr=$(gh pr list --head "${PR_BRANCH}" --json number -q '.[0].number' 2>/dev/null || echo "") - - if [ -n "$existing_pr" ]; then - echo "Updating existing PR #${existing_pr}" - gh pr edit "$existing_pr" --title "${PR_TITLE}" --body "$(cat PR_DESCRIPTION.md)" - else - echo "Creating new PR" - gh pr create \ - --title "${PR_TITLE}" \ - --body "$(cat PR_DESCRIPTION.md)" \ - --base main \ - --head "${PR_BRANCH}" - fi -} - -# Main execution -main() { - # Check if gh CLI is installed - if ! command -v gh &> /dev/null; then - echo "Error: GitHub CLI (gh) is not installed" - exit 1 - fi - - # Check if we're in a git repository - if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Error: Not in a git repository" - exit 1 - fi - - setup_git - cleanup_branch - update_pr - - echo "PR update completed successfully" -} - -# Run main function -main "$@" From 6703b0e92fd792b6c7f48b1c258bff682d75bca7 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 21 Nov 2025 14:22:36 -0500 Subject: [PATCH 08/10] updates --- .../get-ticket-message/get-ticket-message.mjs | 30 ++++++++-- .../actions/list-messages/list-messages.mjs | 55 +++++++++++++++++++ .../list-ticket-messages.mjs | 19 ++++--- .../gorgias_oauth/gorgias_oauth.app.mjs | 27 +++++++-- components/gorgias_oauth/package.json | 2 +- 5 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 components/gorgias_oauth/actions/list-messages/list-messages.mjs diff --git a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs index 6569e81804d64..45c1e7764fb28 100644 --- a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs +++ b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs @@ -1,12 +1,16 @@ -import { defineAction } from "@pipedream/types"; import gorgiasOAuth from "../../gorgias_oauth.app.mjs"; -export default defineAction({ +export default { name: "Get Ticket Message", - description: "Get a specific message from a ticket [See docs here](https://developers.gorgias.com/reference/get-ticket-message)", + description: "Get a specific message from a ticket. [See the documentation](https://developers.gorgias.com/reference/get-ticket-message)", key: "gorgias_oauth-get-ticket-message", version: "0.0.1", type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, props: { gorgiasOAuth, ticketId: { @@ -19,6 +23,23 @@ export default defineAction({ type: "integer", label: "Message ID", description: "The ID of the message to retrieve", + async options({ prevContext }) { + const { + data: messages, + meta, + } = await this.listTicketMessages({ + ticketId: this.ticketId, + params: { + cursor: prevContext.nextCursor, + }, + }); + return { + options: messages.map(({ id }) => id), + context: { + nextCursor: meta.next_cursor, + }, + }; + }, }, }, async run({ $ }) { @@ -28,9 +49,8 @@ export default defineAction({ messageId: this.messageId, }); - // Add summary for user feedback $.export("$summary", `Successfully retrieved message ${this.messageId} from ticket ${this.ticketId}`); return response; }, -}); +}; diff --git a/components/gorgias_oauth/actions/list-messages/list-messages.mjs b/components/gorgias_oauth/actions/list-messages/list-messages.mjs new file mode 100644 index 0000000000000..1c1a171211b66 --- /dev/null +++ b/components/gorgias_oauth/actions/list-messages/list-messages.mjs @@ -0,0 +1,55 @@ +import gorgiasOAuth from "../../gorgias_oauth.app.mjs"; + +export default { + name: "List Messages", + description: "List all messages for a specific ticket. [See the documentation](https://developers.gorgias.com/reference/list-messages)", + key: "gorgias_oauth-list-messages", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, + props: { + gorgiasOAuth, + limit: { + type: "integer", + label: "Limit", + description: "Maximum number of messages to return (1-100)", + min: 1, + max: 100, + default: 50, + }, + cursor: { + type: "string", + label: "Cursor", + description: "Cursor for pagination (get from the meta.next_cursor of the previous response)", + optional: true, + }, + }, + async run({ $ }) { + const params = { + limit: this.limit, + }; + + if (this.cursor) { + params.cursor = this.cursor; + } + + const response = await this.gorgiasOAuth.listMessages({ + $, + params, + }); + + $.export("$summary", `Successfully retrieved ${response.data.length} message${response.data.length === 1 + ? "" + : "s"}`); + + // Return the data and pagination info + return { + data: response.data, + meta: response.meta, + }; + }, +}; diff --git a/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs b/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs index 44dec70eed241..39b96349e1900 100644 --- a/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs +++ b/components/gorgias_oauth/actions/list-ticket-messages/list-ticket-messages.mjs @@ -1,12 +1,16 @@ -import { defineAction } from "@pipedream/types"; import gorgiasOAuth from "../../gorgias_oauth.app.mjs"; -export default defineAction({ +export default { name: "List Ticket Messages", - description: "List all messages for a specific ticket [See docs here](https://developers.gorgias.com/reference/list-ticket-messages)", + description: "List all messages for a specific ticket. [See the documentation](https://developers.gorgias.com/reference/list-ticket-messages)", key: "gorgias_oauth-list-ticket-messages", version: "0.0.1", type: "action", + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: true, + }, props: { gorgiasOAuth, ticketId: { @@ -34,7 +38,7 @@ export default defineAction({ const params = { limit: this.limit, }; - + if (this.cursor) { params.cursor = this.cursor; } @@ -45,8 +49,9 @@ export default defineAction({ params, }); - // Add summary for user feedback - $.export("$summary", `Successfully retrieved ${response.data.length} message${response.data.length === 1 ? '' : 's'}`); + $.export("$summary", `Successfully retrieved ${response.data.length} message${response.data.length === 1 + ? "" + : "s"}`); // Return the data and pagination info return { @@ -54,4 +59,4 @@ export default defineAction({ meta: response.meta, }; }, -}); +}; diff --git a/components/gorgias_oauth/gorgias_oauth.app.mjs b/components/gorgias_oauth/gorgias_oauth.app.mjs index 44aad3a4150d7..9f6734ea54cab 100644 --- a/components/gorgias_oauth/gorgias_oauth.app.mjs +++ b/components/gorgias_oauth/gorgias_oauth.app.mjs @@ -233,15 +233,15 @@ export default { * @param {Object} params.params - Optional query parameters (cursor, limit, etc.) * @returns {Promise} - Returns the list of messages and pagination info */ - listTicketMessages({ $, ticketId, params = {} }) { + listTicketMessages({ + $, ticketId, params = {}, + }) { return this._makeRequest({ $, path: `/tickets/${ticketId}/messages`, - method: 'get', params, }); }, - /** * Get a specific message by ID * @param {Object} params - Parameters for the request @@ -249,14 +249,29 @@ export default { * @param {number} params.messageId - The ID of the message to retrieve * @returns {Promise} - Returns the message details */ - getTicketMessage({ $, ticketId, messageId }) { + getTicketMessage({ + $, ticketId, messageId, + }) { return this._makeRequest({ $, path: `/tickets/${ticketId}/messages/${messageId}`, - method: 'get', }); }, - + /** + * List all messages + * @param {Object} params - Parameters for the request + * @param {Object} params.params - Optional query parameters (cursor, limit, etc.) + * @returns {Promise} - Returns the list of messages and pagination info + */ + listMessages({ + $, params = {}, + }) { + return this._makeRequest({ + $, + path: "/messages", + params, + }); + }, _defaultConfig({ path, method = "get", params = {}, data, }) { diff --git a/components/gorgias_oauth/package.json b/components/gorgias_oauth/package.json index 186197f6e74de..3fb26c4b162e0 100644 --- a/components/gorgias_oauth/package.json +++ b/components/gorgias_oauth/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/gorgias_oauth", - "version": "0.6.2", + "version": "0.7.0", "description": "Pipedream Gorgias OAuth Components", "main": "gorgias_oauth.app.mjs", "keywords": [ From c4ef2958290e36f1059a0e42d937814f8623abd8 Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 21 Nov 2025 14:25:03 -0500 Subject: [PATCH 09/10] versions --- .../gorgias_oauth/actions/create-customer/create-customer.mjs | 2 +- components/gorgias_oauth/actions/create-macro/create-macro.mjs | 2 +- .../actions/create-ticket-message/create-ticket-message.mjs | 2 +- .../gorgias_oauth/actions/create-ticket/create-ticket.mjs | 2 +- components/gorgias_oauth/actions/delete-macro/delete-macro.mjs | 2 +- components/gorgias_oauth/actions/get-ticket/get-ticket.mjs | 2 +- components/gorgias_oauth/actions/list-macros/list-macros.mjs | 2 +- components/gorgias_oauth/actions/list-tickets/list-tickets.mjs | 2 +- .../actions/retrieve-customer/retrieve-customer.mjs | 2 +- .../gorgias_oauth/actions/update-customer/update-customer.mjs | 2 +- components/gorgias_oauth/actions/update-macro/update-macro.mjs | 2 +- .../gorgias_oauth/actions/update-ticket/update-ticket.mjs | 2 +- .../sources/internal-note-created/internal-note-created.mjs | 2 +- .../gorgias_oauth/sources/macro-updated/macro-updated.mjs | 2 +- .../sources/new-macro-created/new-macro-created.mjs | 2 +- .../gorgias_oauth/sources/ticket-created/ticket-created.mjs | 2 +- .../sources/ticket-message-created/ticket-message-created.mjs | 2 +- .../gorgias_oauth/sources/ticket-updated/ticket-updated.mjs | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/components/gorgias_oauth/actions/create-customer/create-customer.mjs b/components/gorgias_oauth/actions/create-customer/create-customer.mjs index 15d112a85591d..24eb519c812bf 100644 --- a/components/gorgias_oauth/actions/create-customer/create-customer.mjs +++ b/components/gorgias_oauth/actions/create-customer/create-customer.mjs @@ -5,7 +5,7 @@ export default { key: "gorgias_oauth-create-customer", name: "Create Customer", description: "Create a new customer. [See the docs](https://developers.gorgias.com/reference/post_api-customers)", - version: "0.0.8", + version: "0.0.9", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/create-macro/create-macro.mjs b/components/gorgias_oauth/actions/create-macro/create-macro.mjs index 945703190acb1..2796f9dbbd72f 100644 --- a/components/gorgias_oauth/actions/create-macro/create-macro.mjs +++ b/components/gorgias_oauth/actions/create-macro/create-macro.mjs @@ -6,7 +6,7 @@ export default { key: "gorgias_oauth-create-macro", name: "Create Macro", description: "Create a macro. [See the documentation](https://developers.gorgias.com/reference/create-macro)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/create-ticket-message/create-ticket-message.mjs b/components/gorgias_oauth/actions/create-ticket-message/create-ticket-message.mjs index f735e038fe636..08585a39aa810 100644 --- a/components/gorgias_oauth/actions/create-ticket-message/create-ticket-message.mjs +++ b/components/gorgias_oauth/actions/create-ticket-message/create-ticket-message.mjs @@ -7,7 +7,7 @@ export default { key: "gorgias_oauth-create-ticket-message", name: "Create Ticket Message", description: "Create a message for a ticket in the Gorgias system. [See the documentation](https://developers.gorgias.com/reference/create-ticket-message)", - version: "0.0.5", + version: "0.0.6", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/create-ticket/create-ticket.mjs b/components/gorgias_oauth/actions/create-ticket/create-ticket.mjs index a836d7dc54fe6..e5175c84acb84 100644 --- a/components/gorgias_oauth/actions/create-ticket/create-ticket.mjs +++ b/components/gorgias_oauth/actions/create-ticket/create-ticket.mjs @@ -4,7 +4,7 @@ export default { key: "gorgias_oauth-create-ticket", name: "Create Ticket", description: "Create a new ticket. [See the docs](https://developers.gorgias.com/reference/post_api-tickets)", - version: "0.0.9", + version: "0.0.10", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/delete-macro/delete-macro.mjs b/components/gorgias_oauth/actions/delete-macro/delete-macro.mjs index aafb33b18f650..038d9c4e7799b 100644 --- a/components/gorgias_oauth/actions/delete-macro/delete-macro.mjs +++ b/components/gorgias_oauth/actions/delete-macro/delete-macro.mjs @@ -4,7 +4,7 @@ export default { key: "gorgias_oauth-delete-macro", name: "Delete Macro", description: "Delete a macro. [See the documentation](https://developers.gorgias.com/reference/delete-macro)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/get-ticket/get-ticket.mjs b/components/gorgias_oauth/actions/get-ticket/get-ticket.mjs index f2c2469e816c5..54e97e2dce8d7 100644 --- a/components/gorgias_oauth/actions/get-ticket/get-ticket.mjs +++ b/components/gorgias_oauth/actions/get-ticket/get-ticket.mjs @@ -4,7 +4,7 @@ export default { key: "gorgias_oauth-get-ticket", name: "Get Ticket", description: "Get a ticket. [See the documentation](https://developers.gorgias.com/reference/get-ticket)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/list-macros/list-macros.mjs b/components/gorgias_oauth/actions/list-macros/list-macros.mjs index 75e484c26c450..eda9c5c244654 100644 --- a/components/gorgias_oauth/actions/list-macros/list-macros.mjs +++ b/components/gorgias_oauth/actions/list-macros/list-macros.mjs @@ -4,7 +4,7 @@ export default { key: "gorgias_oauth-list-macros", name: "List Macros", description: "List all macros. [See the documentation](https://developers.gorgias.com/reference/list-macros)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/list-tickets/list-tickets.mjs b/components/gorgias_oauth/actions/list-tickets/list-tickets.mjs index a7dfe2981d9d4..8f5b03ddd561c 100644 --- a/components/gorgias_oauth/actions/list-tickets/list-tickets.mjs +++ b/components/gorgias_oauth/actions/list-tickets/list-tickets.mjs @@ -4,7 +4,7 @@ export default { key: "gorgias_oauth-list-tickets", name: "List Tickets", description: "List all tickets. [See the docs](https://developers.gorgias.com/reference/get_api-tickets)", - version: "0.0.9", + version: "0.0.10", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/retrieve-customer/retrieve-customer.mjs b/components/gorgias_oauth/actions/retrieve-customer/retrieve-customer.mjs index 05353ed8f6521..6df8786eaadbf 100644 --- a/components/gorgias_oauth/actions/retrieve-customer/retrieve-customer.mjs +++ b/components/gorgias_oauth/actions/retrieve-customer/retrieve-customer.mjs @@ -4,7 +4,7 @@ export default { key: "gorgias_oauth-retrieve-customer", name: "Retrieve a Customer", description: "Retrieve a customer. [See the docs](https://developers.gorgias.com/reference/get_api-customers-id-)", - version: "0.0.8", + version: "0.0.9", annotations: { destructiveHint: false, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/update-customer/update-customer.mjs b/components/gorgias_oauth/actions/update-customer/update-customer.mjs index f4458dd3f8c98..18d8deade0a67 100644 --- a/components/gorgias_oauth/actions/update-customer/update-customer.mjs +++ b/components/gorgias_oauth/actions/update-customer/update-customer.mjs @@ -10,7 +10,7 @@ export default { key: "gorgias_oauth-update-customer", name: "Update Customer", description: "Update a customer. [See the docs](https://developers.gorgias.com/reference/put_api-customers-id-)", - version: "0.0.8", + version: "0.0.9", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/update-macro/update-macro.mjs b/components/gorgias_oauth/actions/update-macro/update-macro.mjs index 7214080e3ccfa..39e285526c987 100644 --- a/components/gorgias_oauth/actions/update-macro/update-macro.mjs +++ b/components/gorgias_oauth/actions/update-macro/update-macro.mjs @@ -6,7 +6,7 @@ export default { key: "gorgias_oauth-update-macro", name: "Update Macro", description: "Update a macro. [See the documentation](https://developers.gorgias.com/reference/update-macro)", - version: "0.0.2", + version: "0.0.3", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/gorgias_oauth/actions/update-ticket/update-ticket.mjs b/components/gorgias_oauth/actions/update-ticket/update-ticket.mjs index a480e840f1bab..a1727ac2d8248 100644 --- a/components/gorgias_oauth/actions/update-ticket/update-ticket.mjs +++ b/components/gorgias_oauth/actions/update-ticket/update-ticket.mjs @@ -5,7 +5,7 @@ export default { key: "gorgias_oauth-update-ticket", name: "Update Ticket", description: "Updates a predefined ticket in the Gorgias system. [See the documentation](https://developers.gorgias.com/reference/update-ticket)", - version: "0.0.5", + version: "0.0.6", annotations: { destructiveHint: true, openWorldHint: true, diff --git a/components/gorgias_oauth/sources/internal-note-created/internal-note-created.mjs b/components/gorgias_oauth/sources/internal-note-created/internal-note-created.mjs index 8d7bd7c9e832b..0c0b61cbfcfcb 100644 --- a/components/gorgias_oauth/sources/internal-note-created/internal-note-created.mjs +++ b/components/gorgias_oauth/sources/internal-note-created/internal-note-created.mjs @@ -7,7 +7,7 @@ export default { key: "gorgias_oauth-internal-note-created", name: "New Internal Note", description: "Emit new event when an internal note is created on a ticket. [See the documentation](https://developers.gorgias.com/reference/the-event-object)", - version: "0.0.2", + version: "0.0.3", type: "source", props: { ...base.props, diff --git a/components/gorgias_oauth/sources/macro-updated/macro-updated.mjs b/components/gorgias_oauth/sources/macro-updated/macro-updated.mjs index 1b66c9268ff62..7b60f07e9c25b 100644 --- a/components/gorgias_oauth/sources/macro-updated/macro-updated.mjs +++ b/components/gorgias_oauth/sources/macro-updated/macro-updated.mjs @@ -6,7 +6,7 @@ export default { key: "gorgias_oauth-macro-updated", name: "Macro Updated", description: "Emit new event when a macro is updated. [See the documentation](https://developers.gorgias.com/reference/the-event-object)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/gorgias_oauth/sources/new-macro-created/new-macro-created.mjs b/components/gorgias_oauth/sources/new-macro-created/new-macro-created.mjs index ac8678dc79a8b..9ace61cf08200 100644 --- a/components/gorgias_oauth/sources/new-macro-created/new-macro-created.mjs +++ b/components/gorgias_oauth/sources/new-macro-created/new-macro-created.mjs @@ -6,7 +6,7 @@ export default { key: "gorgias_oauth-new-macro-created", name: "New Macro Created", description: "Emit new event when a macro is created. [See the documentation](https://developers.gorgias.com/reference/the-event-object)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/gorgias_oauth/sources/ticket-created/ticket-created.mjs b/components/gorgias_oauth/sources/ticket-created/ticket-created.mjs index 8cd2bce3ee69c..7cd0c4e8d7cec 100644 --- a/components/gorgias_oauth/sources/ticket-created/ticket-created.mjs +++ b/components/gorgias_oauth/sources/ticket-created/ticket-created.mjs @@ -7,7 +7,7 @@ export default { key: "gorgias_oauth-ticket-created", name: "New Ticket", description: "Emit new event when a ticket is created. [See the documentation](https://developers.gorgias.com/reference/the-event-object)", - version: "0.1.8", + version: "0.1.9", type: "source", props: { ...base.props, diff --git a/components/gorgias_oauth/sources/ticket-message-created/ticket-message-created.mjs b/components/gorgias_oauth/sources/ticket-message-created/ticket-message-created.mjs index 504daa74a67ea..3c44a377bb3fd 100644 --- a/components/gorgias_oauth/sources/ticket-message-created/ticket-message-created.mjs +++ b/components/gorgias_oauth/sources/ticket-message-created/ticket-message-created.mjs @@ -8,7 +8,7 @@ export default { key: "gorgias_oauth-ticket-message-created", name: "New Ticket Message", description: "Emit new event when a ticket message is created. [See the documentation](https://developers.gorgias.com/reference/the-event-object)", - version: "0.1.8", + version: "0.1.9", type: "source", props: { ...base.props, diff --git a/components/gorgias_oauth/sources/ticket-updated/ticket-updated.mjs b/components/gorgias_oauth/sources/ticket-updated/ticket-updated.mjs index 3259ed9e02c37..1e19116db59b9 100644 --- a/components/gorgias_oauth/sources/ticket-updated/ticket-updated.mjs +++ b/components/gorgias_oauth/sources/ticket-updated/ticket-updated.mjs @@ -7,7 +7,7 @@ export default { key: "gorgias_oauth-ticket-updated", name: "New Updated Ticket", description: "Emit new event when a ticket is updated. [See the documentation](https://developers.gorgias.com/reference/the-event-object)", - version: "0.1.8", + version: "0.1.9", type: "source", props: { ...base.props, From 670ef529a5a799cd19172d9be58fa8b62f70c69c Mon Sep 17 00:00:00 2001 From: Michelle Bergeron Date: Fri, 21 Nov 2025 14:30:28 -0500 Subject: [PATCH 10/10] updates --- .../actions/get-ticket-message/get-ticket-message.mjs | 2 +- .../gorgias_oauth/actions/list-messages/list-messages.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs index 45c1e7764fb28..5c4576afc9c1a 100644 --- a/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs +++ b/components/gorgias_oauth/actions/get-ticket-message/get-ticket-message.mjs @@ -27,7 +27,7 @@ export default { const { data: messages, meta, - } = await this.listTicketMessages({ + } = await this.gorgiasOAuth.listTicketMessages({ ticketId: this.ticketId, params: { cursor: prevContext.nextCursor, diff --git a/components/gorgias_oauth/actions/list-messages/list-messages.mjs b/components/gorgias_oauth/actions/list-messages/list-messages.mjs index 1c1a171211b66..44d932d47e5d3 100644 --- a/components/gorgias_oauth/actions/list-messages/list-messages.mjs +++ b/components/gorgias_oauth/actions/list-messages/list-messages.mjs @@ -2,7 +2,7 @@ import gorgiasOAuth from "../../gorgias_oauth.app.mjs"; export default { name: "List Messages", - description: "List all messages for a specific ticket. [See the documentation](https://developers.gorgias.com/reference/list-messages)", + description: "List all messages. [See the documentation](https://developers.gorgias.com/reference/list-messages)", key: "gorgias_oauth-list-messages", version: "0.0.1", type: "action",