diff --git a/src/backend/base/langflow/components/composio/__init__.py b/src/backend/base/langflow/components/composio/__init__.py index a7d3af130..0b6e6bc3e 100644 --- a/src/backend/base/langflow/components/composio/__init__.py +++ b/src/backend/base/langflow/components/composio/__init__.py @@ -2,6 +2,7 @@ from .composio_api import ComposioAPIComponent from .github_composio import ComposioGitHubAPIComponent from .gmail_composio import ComposioGmailAPIComponent from .googlecalendar_composio import ComposioGoogleCalendarAPIComponent +from .outlook_composio import ComposioOutlookAPIComponent from .slack_composio import ComposioSlackAPIComponent __all__ = [ @@ -9,5 +10,6 @@ __all__ = [ "ComposioGitHubAPIComponent", "ComposioGmailAPIComponent", "ComposioGoogleCalendarAPIComponent", + "ComposioOutlookAPIComponent", "ComposioSlackAPIComponent", ] diff --git a/src/backend/base/langflow/components/composio/outlook_composio.py b/src/backend/base/langflow/components/composio/outlook_composio.py new file mode 100644 index 000000000..7540b9330 --- /dev/null +++ b/src/backend/base/langflow/components/composio/outlook_composio.py @@ -0,0 +1,765 @@ +import json +from typing import Any + +from composio import Action + +from langflow.base.composio.composio_base import ComposioBaseComponent +from langflow.inputs import BoolInput, FileInput, IntInput, MessageTextInput +from langflow.logging import logger + + +class ComposioOutlookAPIComponent(ComposioBaseComponent): + display_name: str = "Outlook" + description: str = "Outlook API" + icon = "Outlook" + documentation: str = "https://docs.composio.dev" + app_name = "outlook" + + _actions_data: dict = { + "OUTLOOK_OUTLOOK_REPLY_EMAIL": { + "display_name": "Reply To Email", + "action_fields": [ + "OUTLOOK_OUTLOOK_REPLY_EMAIL_user_id", + "OUTLOOK_OUTLOOK_REPLY_EMAIL_message_id", + "OUTLOOK_OUTLOOK_REPLY_EMAIL_comment", + "OUTLOOK_OUTLOOK_REPLY_EMAIL_cc_emails", + "OUTLOOK_OUTLOOK_REPLY_EMAIL_bcc_emails", + ], + "get_result_field": False, + }, + "OUTLOOK_OUTLOOK_GET_PROFILE": { + "display_name": "Get Profile", + "action_fields": ["OUTLOOK_OUTLOOK_GET_PROFILE_user_id"], + "get_result_field": True, + "result_field": "response_data", + }, + "OUTLOOK_OUTLOOK_SEND_EMAIL": { + "display_name": "Send Email", + "action_fields": [ + "OUTLOOK_OUTLOOK_SEND_EMAIL_user_id", + "OUTLOOK_OUTLOOK_SEND_EMAIL_subject", + "OUTLOOK_OUTLOOK_SEND_EMAIL_body", + "OUTLOOK_OUTLOOK_SEND_EMAIL_to_email", + "OUTLOOK_OUTLOOK_SEND_EMAIL_to_name", + "OUTLOOK_OUTLOOK_SEND_EMAIL_cc_emails", + "OUTLOOK_OUTLOOK_SEND_EMAIL_bcc_emails", + "OUTLOOK_OUTLOOK_SEND_EMAIL_is_html", + "OUTLOOK_OUTLOOK_SEND_EMAIL_save_to_sent_items", + "OUTLOOK_OUTLOOK_SEND_EMAIL_attachment", + ], + "get_result_field": False, + }, + "OUTLOOK_OUTLOOK_LIST_MESSAGES": { + "display_name": "List Messages", + "action_fields": [ + "OUTLOOK_OUTLOOK_LIST_MESSAGES_user_id", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_folder", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_top", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_skip", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_is_read", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_importance", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_gt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_startswith", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_endswith", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_contains", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_ge", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_lt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_le", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_from_address", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_has_attachments", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_body_preview_contains", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_sent_date_time_gt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_sent_date_time_lt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_categories", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_select", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_orderby", + ], + "get_result_field": True, + "result_field": "value", + }, + "OUTLOOK_OUTLOOK_LIST_EVENTS": { + "display_name": "List Events", + "action_fields": [ + "OUTLOOK_OUTLOOK_LIST_EVENTS_user_id", + "OUTLOOK_OUTLOOK_LIST_EVENTS_top", + "OUTLOOK_OUTLOOK_LIST_EVENTS_skip", + "OUTLOOK_OUTLOOK_LIST_EVENTS_filter", + "OUTLOOK_OUTLOOK_LIST_EVENTS_select", + "OUTLOOK_OUTLOOK_LIST_EVENTS_orderby", + "OUTLOOK_OUTLOOK_LIST_EVENTS_timezone", + ], + "get_result_field": True, + "result_field": "value", + }, + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT": { + "display_name": "Create Calendar Event", + "action_fields": [ + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_user_id", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_subject", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_body", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_is_html", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_start_datetime", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_end_datetime", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_time_zone", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_is_online_meeting", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_online_meeting_provider", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_attendees_info", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_location", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_show_as", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_categories", + ], + "get_result_field": True, + "result_field": "response_data", + }, + "OUTLOOK_OUTLOOK_GET_EVENT": { + "display_name": "Get Calendar Event", + "action_fields": ["OUTLOOK_OUTLOOK_GET_EVENT_user_id", "OUTLOOK_OUTLOOK_GET_EVENT_event_id"], + "get_result_field": True, + "result_field": "response_data", + }, + "OUTLOOK_OUTLOOK_CREATE_DRAFT": { + "display_name": "Create Email Draft", + "action_fields": [ + "OUTLOOK_OUTLOOK_CREATE_DRAFT_subject", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_body", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_to_recipients", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_cc_recipients", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_bcc_recipients", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_is_html", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_attachment", + ], + "get_result_field": True, + "result_field": "response_data", + }, + } + + _all_fields = {field for action_data in _actions_data.values() for field in action_data["action_fields"]} + + _bool_variables = { + "OUTLOOK_OUTLOOK_SEND_EMAIL_is_html", + "OUTLOOK_OUTLOOK_SEND_EMAIL_save_to_sent_items", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_is_html", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_is_html", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_is_online_meeting", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_is_read", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_has_attachments", + } + + _list_variables = { + "OUTLOOK_OUTLOOK_LIST_EVENTS_select", + "OUTLOOK_OUTLOOK_LIST_EVENTS_orderby", + "OUTLOOK_OUTLOOK_SEND_EMAIL_cc_emails", + "OUTLOOK_OUTLOOK_SEND_EMAIL_bcc_emails", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_to_recipients", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_cc_recipients", + "OUTLOOK_OUTLOOK_CREATE_DRAFT_bcc_recipients", + "OUTLOOK_OUTLOOK_REPLY_EMAIL_cc_emails", + "OUTLOOK_OUTLOOK_REPLY_EMAIL_bcc_emails", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_attendees_info", + "OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_categories", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_categories", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_select", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_orderby", + } + + inputs = [ + *ComposioBaseComponent._base_inputs, + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_user_id", + display_name="User Id", + info="The target user's email address or 'me' for the authenticated user.", + show=False, + value="me", + advanced=True, + ), + IntInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_top", + display_name="Max Results", + info="The maximum number of events to return per request.", + show=False, + value=10, + ), + IntInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_skip", + display_name="Skip", + info="The number of events to skip before starting to collect results.", + show=False, + value=0, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_filter", + display_name="Filter", + info="OData query string to filter results. Example: start/dateTime ge '2024-01-01T00:00:00'", + show=False, + value="", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_select", + display_name="Select", + info="List of properties to include in the response comma separated.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_orderby", + display_name="Orderby", + info="Properties to sort results by comma separated.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_EVENTS_timezone", + display_name="Timezone", + info="The timezone for event start and end times in the response.", + show=False, + value="UTC", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_user_id", + display_name="User Id", + info="The user's email address or 'me' for the authenticated user.", + show=False, + value="me", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_subject", + display_name="Subject", + info="Subject of the email", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_body", + display_name="Body", + info="Body content of the email. Can be plain text or HTML based on is_html flag.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_to_email", + display_name="Recipient Email", + info="Recipient email address", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_to_name", + display_name="To Name", + info="Recipient display name", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_cc_emails", + display_name="CC", + info="List of CC recipient email addresses comma separated", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_bcc_emails", + display_name="BCC", + info="List of BCC recipient email addresses comma separated", + show=False, + advanced=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_is_html", + display_name="Is HTML", + info="Set to True if the body content is HTML formatted", + show=False, + value=False, + advanced=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_save_to_sent_items", + display_name="Save To Sent Items", + info="Whether to save the sent email to Sent Items folder.", + show=False, + value=True, + advanced=True, + ), + FileInput( + name="OUTLOOK_OUTLOOK_SEND_EMAIL_attachment", + display_name="Attachment", + file_types=[ + "csv", + "txt", + "doc", + "docx", + "xls", + "xlsx", + "pdf", + "png", + "jpg", + "jpeg", + "gif", + "zip", + "rar", + "ppt", + "pptx", + ], + info="Add an attachment", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_subject", + display_name="Subject", + info="Subject of the email", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_body", + display_name="Body", + info="Body content of the email. Can be plain text or HTML based on is_html flag", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_to_recipients", + display_name="Recipient Email", + info="List of recipient email addresses comma separated", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_cc_recipients", + display_name="Cc Recipients", + info="List of CC recipient email addresses", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_bcc_recipients", + display_name="BCC", + info="List of BCC recipient email addresses comma separated", + show=False, + advanced=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_is_html", + display_name="Is HTML", + info="Set to True if the body content is HTML formatted", + show=False, + value=False, + advanced=True, + ), + FileInput( + name="OUTLOOK_OUTLOOK_CREATE_DRAFT_attachment", + display_name="Attachment", + file_types=[ + "csv", + "txt", + "doc", + "docx", + "xls", + "xlsx", + "pdf", + "png", + "jpg", + "jpeg", + "gif", + "zip", + "rar", + "ppt", + "pptx", + ], + info="Add an attachment", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_GET_PROFILE_user_id", + display_name="User Id", + info="The user's email address or 'me' for the authenticated user.", + show=False, + value="me", + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_REPLY_EMAIL_user_id", + display_name="User Id", + info="The user's email address or 'me' for the authenticated user.", + show=False, + value="me", + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_REPLY_EMAIL_message_id", + display_name="Message Id", + info="The ID of the message to reply to. Can be obtained from OUTLOOK_LIST_MESSAGES action.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_REPLY_EMAIL_comment", + display_name="Comment", + info="Comment to include in the reply. Must be plain text.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_REPLY_EMAIL_cc_emails", + display_name="CC", + info="List of CC recipient email addresses comma separated", + show=False, + value=[], + is_list=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_REPLY_EMAIL_bcc_emails", + display_name="BCC", + info="List of BCC recipient email addresses comma separated", + show=False, + value=[], + is_list=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_user_id", + display_name="User Id", + info="The user's email address or 'me' for the authenticated user.", + show=False, + value="me", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_subject", + display_name="Subject", + info="Subject of the event. Example: 'Team Meeting'.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_body", + display_name="Body", + info="Body content of the event. Can be plain text or HTML.", + show=False, + required=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_is_html", + display_name="Is Html", + info="Set to True if the body content should be interpreted as HTML.", + show=False, + value=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_start_datetime", + display_name="Start Datetime", + info="Start date/time (ISO 8601). Example: '2025-01-03T10:00:00Z'.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_end_datetime", + display_name="End Datetime", + info="End date/time (ISO 8601). Example: '2025-01-03T11:00:00Z'.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_time_zone", + display_name="Time Zone", + info="Time zone (e.g., 'UTC' or 'America/Los_Angeles').", + show=False, + required=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_is_online_meeting", + display_name="Is Online Meeting", + info="Set to True to make this an online meeting and generate a Teams URL.", + show=False, + value=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_online_meeting_provider", + display_name="Online Meeting Provider", + info="The online meeting service provider. Currently only supports 'teamsForBusiness'.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_attendees_info", + display_name="Attendees", + info="A list of attendee information. Only email is required for each attendee., Example: [{ 'email': 'team@example.com', 'name': 'Team', 'type': 'required' }, { 'email': 'other@example.com', 'type': 'optional' }, { 'email': 'other2@example.com' }]", # noqa: E501 + show=False, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_location", + display_name="Location", + info="Location of the event (e.g., 'Conference Room').", + show=False, + value="", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_show_as", + display_name="Show As", + info="Status of the event: 'free', 'tentative', 'busy', or 'oof'.", + show=False, + value="busy", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_CALENDAR_CREATE_EVENT_categories", + display_name="Categories", + info="List of categories associated with the event comma separated.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_GET_EVENT_user_id", + display_name="User Id", + info="The user's email address or 'me' for the authenticated user.", + show=False, + value="me", + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_GET_EVENT_event_id", + display_name="Event Id", + info="The ID of the calendar event to retrieve.", + show=False, + required=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_user_id", + display_name="User Id", + info="The target user's email address or 'me' for the authenticated user. For delegated access scenarios, this should be the email of the shared mailbox or delegated user.", # noqa: E501 + show=False, + value="me", + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_folder", + display_name="Folder", + info="", + show=False, + value="inbox", + advanced=True, + ), + IntInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_top", + display_name="Max Results", + info="The maximum number of messages to return per request. Must be a positive integer between 1 and 1000.", + show=False, + value=10, + ), + IntInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_skip", + display_name="Skip", + info="The number of messages to skip before starting to collect results. Use for paginated responses.", + show=False, + value=0, + advanced=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_is_read", + display_name="Is Read", + info="Filter messages by read status. If set to False, only unread messages will be returned.", + show=False, + value=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_importance", + display_name="Importance", + info="Filter messages by importance. For example, 'high', 'normal', or 'low'.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_subject", + display_name="Subject", + info="Filter messages by subject (exact match).", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_gt", + display_name="Received Date Time Gt", + info="Filter messages with a receivedDateTime greater than the specified value. Example: '2023-01-01T00:00:00Z'.", # noqa: E501 + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_startswith", + display_name="Subject Startswith", + info="Filter messages where the subject starts with the specified string.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_endswith", + display_name="Subject Endswith", + info="Filter messages where the subject ends with the specified string.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_contains", + display_name="Subject Contains", + info="Filter messages where the subject contains the specified substring.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_ge", + display_name="Received Date Time Ge", + info="Filter messages with a receivedDateTime greater than or equal to the specified value.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_lt", + display_name="Received Date Time Lt", + info="Filter messages with a receivedDateTime less than the specified value.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_le", + display_name="Received Date Time Le", + info="Filter messages with a receivedDateTime less than or equal to the specified value.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_from_address", + display_name="From Address", + info="Filter messages by the sender's email address. Uses equality check on from/emailAddress/address.", + show=False, + advanced=True, + ), + BoolInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_has_attachments", + display_name="Has Attachments", + info="Filter messages by whether they have attachments.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_body_preview_contains", + display_name="Body Preview Contains", + info="Filter messages where the bodyPreview contains the specified substring.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_sent_date_time_gt", + display_name="Sent Date Time Gt", + info="Filter messages with a sentDateTime greater than the specified value.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_sent_date_time_lt", + display_name="Sent Date Time Lt", + info="Filter messages with a sentDateTime less than the specified value.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_categories", + display_name="Categories", + info="Filter messages by categories. Matches if the message contains any of the specified categories.", + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_select", + display_name="Select", + info="A list of properties to include in the response comma separated. Common properties: 'subject', 'from', 'toRecipients', 'receivedDateTime'.", # noqa: E501 + show=False, + advanced=True, + ), + MessageTextInput( + name="OUTLOOK_OUTLOOK_LIST_MESSAGES_orderby", + display_name="Orderby", + info="Specify properties to sort results by. For example, 'receivedDateTime desc' for newest messages first.", # noqa: E501 + show=False, + advanced=True, + ), + ] + + def execute_action(self): + """Execute action and return response as Message.""" + toolset = self._build_wrapper() + + try: + self._build_action_maps() + display_name = self.action[0]["name"] if isinstance(self.action, list) and self.action else self.action + action_key = self._display_to_key_map.get(display_name) + if not action_key: + msg = f"Invalid action: {display_name}" + raise ValueError(msg) + + enum_name = getattr(Action, action_key) + params = {} + if action_key in self._actions_data: + for field in self._actions_data[action_key]["action_fields"]: + value = getattr(self, field) + + if value is None or value == "": + continue + + if field in self._list_variables and value: + value = [item.strip() for item in value.split(",")] + + if field in self._bool_variables: + value = bool(value) + + param_name = field.replace(action_key + "_", "") + + params[param_name] = value + + result = toolset.execute_action( + action=enum_name, + params=params, + ) + if not result.get("successful"): + error_data = result.get("data", {}) + error_message = error_data.get("message", str(result.get("error", "Unknown Error"))) + + if isinstance(error_message, str): + try: + error_obj = json.loads(error_message).get("error", {}) + error_obj["status_code"] = error_data.get("status_code", 400) + return error_obj # noqa: TRY300 + except json.JSONDecodeError: + return {"error": error_message, "status_code": error_data.get("status_code", 400)} + + return error_message + + result_data = result.get("data", {}) + actions_data = self._actions_data.get(action_key, {}) + if actions_data.get("get_result_field") and actions_data.get("result_field"): + response_data = result_data.get("response_data", {}) + if response_data and actions_data.get("result_field") in response_data: + result_data = response_data.get(actions_data.get("result_field"), result.get("data", [])) + else: + result_data = result_data.get(actions_data.get("result_field"), result.get("data", [])) + if len(result_data) != 1 and not actions_data.get("result_field") and actions_data.get("get_result_field"): + msg = f"Expected a dict with a single key, got {len(result_data)} keys: {result_data.keys()}" + raise ValueError(msg) + return result_data # noqa: TRY300 + except Exception as e: + logger.error(f"Error executing action: {e}") + display_name = self.action[0]["name"] if isinstance(self.action, list) and self.action else str(self.action) + msg = f"Failed to execute {display_name}: {e!s}" + raise ValueError(msg) from e + + def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None) -> dict: + return super().update_build_config(build_config, field_value, field_name) + + def set_default_tools(self): + self._default_tools = { + self.sanitize_action_name("OUTLOOK_OUTLOOK_SEND_EMAIL").replace(" ", "-"), + self.sanitize_action_name("OUTLOOK_OUTLOOK_LIST_MESSAGES").replace(" ", "-"), + } diff --git a/src/backend/tests/unit/components/bundles/composio/test_outlook.py b/src/backend/tests/unit/components/bundles/composio/test_outlook.py new file mode 100644 index 000000000..fb515b3ad --- /dev/null +++ b/src/backend/tests/unit/components/bundles/composio/test_outlook.py @@ -0,0 +1,184 @@ +from unittest.mock import MagicMock, patch + +import pytest +from composio import Action +from langflow.components.composio.outlook_composio import ComposioOutlookAPIComponent +from langflow.schema.dataframe import DataFrame + +from tests.base import DID_NOT_EXIST, ComponentTestBaseWithoutClient + +from .test_base import MockComposioToolSet + + +class MockAction: + OUTLOOK_OUTLOOK_SEND_EMAIL = "OUTLOOK_OUTLOOK_SEND_EMAIL" + OUTLOOK_OUTLOOK_LIST_MESSAGES = "OUTLOOK_OUTLOOK_LIST_MESSAGES" + + +class TestOutlookComponent(ComponentTestBaseWithoutClient): + @pytest.fixture(autouse=True) + def mock_composio_toolset(self): + with patch("langflow.base.composio.composio_base.ComposioToolSet", MockComposioToolSet): + yield + + @pytest.fixture + def component_class(self): + return ComposioOutlookAPIComponent + + @pytest.fixture + def default_kwargs(self): + return { + "api_key": "", + "entity_id": "default", + "action": None, + } + + @pytest.fixture + def file_names_mapping(self): + return [ + {"version": "1.0.17", "module": "composio", "file_name": DID_NOT_EXIST}, + {"version": "1.0.18", "module": "composio", "file_name": DID_NOT_EXIST}, + {"version": "1.0.19", "module": "composio", "file_name": DID_NOT_EXIST}, + {"version": "1.1.0", "module": "composio", "file_name": DID_NOT_EXIST}, + {"version": "1.1.1", "module": "composio", "file_name": DID_NOT_EXIST}, + ] + + def test_init(self, component_class, default_kwargs): + component = component_class(**default_kwargs) + assert component.display_name == "Outlook" + assert component.app_name == "outlook" + assert "OUTLOOK_OUTLOOK_SEND_EMAIL" in component._actions_data + assert "OUTLOOK_OUTLOOK_LIST_MESSAGES" in component._actions_data + + def test_execute_action_send_email(self, component_class, default_kwargs, monkeypatch): + monkeypatch.setattr(Action, "OUTLOOK_OUTLOOK_SEND_EMAIL", MockAction.OUTLOOK_OUTLOOK_SEND_EMAIL) + + component = component_class(**default_kwargs) + component.api_key = "test_key" + component.action = [{"name": "Send Email"}] + component.OUTLOOK_OUTLOOK_SEND_EMAIL_subject = "Test Subject" + component.OUTLOOK_OUTLOOK_SEND_EMAIL_body = "Test Body" + component.OUTLOOK_OUTLOOK_SEND_EMAIL_to_email = "test@example.com" + + component._actions_data = { + "OUTLOOK_OUTLOOK_SEND_EMAIL": { + "display_name": "Send Email", + "action_fields": [ + "OUTLOOK_OUTLOOK_SEND_EMAIL_user_id", + "OUTLOOK_OUTLOOK_SEND_EMAIL_subject", + "OUTLOOK_OUTLOOK_SEND_EMAIL_body", + "OUTLOOK_OUTLOOK_SEND_EMAIL_to_email", + "OUTLOOK_OUTLOOK_SEND_EMAIL_to_name", + "OUTLOOK_OUTLOOK_SEND_EMAIL_cc_emails", + "OUTLOOK_OUTLOOK_SEND_EMAIL_bcc_emails", + "OUTLOOK_OUTLOOK_SEND_EMAIL_is_html", + "OUTLOOK_OUTLOOK_SEND_EMAIL_save_to_sent_items", + "OUTLOOK_OUTLOOK_SEND_EMAIL_attachment", + ], + }, + } + + result = component.execute_action() + assert result == {"result": "mocked response"} + + def test_execute_action_fetch_emails(self, component_class, default_kwargs, monkeypatch): + monkeypatch.setattr(Action, "OUTLOOK_OUTLOOK_LIST_MESSAGES", MockAction.OUTLOOK_OUTLOOK_LIST_MESSAGES) + + component = component_class(**default_kwargs) + component.api_key = "test_key" + component.action = [{"name": "List Messages"}] + component.OUTLOOK_OUTLOOK_LIST_MESSAGES_folder = "Inbox" + component.OUTLOOK_OUTLOOK_LIST_MESSAGES_top = 10 + + component._actions_data = { + "OUTLOOK_OUTLOOK_LIST_MESSAGES": { + "display_name": "List Messages", + "action_fields": [ + "OUTLOOK_OUTLOOK_LIST_MESSAGES_user_id", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_folder", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_top", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_skip", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_is_read", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_importance", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_gt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_startswith", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_endswith", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_subject_contains", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_ge", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_lt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_received_date_time_le", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_from_address", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_has_attachments", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_body_preview_contains", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_sent_date_time_gt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_sent_date_time_lt", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_categories", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_select", + "OUTLOOK_OUTLOOK_LIST_MESSAGES_orderby", + ], + "get_result_field": True, + "result_field": "value", + }, + } + + mock_toolset = MagicMock() + mock_toolset.execute_action.return_value = { + "successful": True, + "data": {"response_data": {"value": [{"subject": "Test Email", "from": "test@example.com"}]}}, + } + + with patch.object(component, "_build_wrapper", return_value=mock_toolset): + result = component.execute_action() + assert result == [{"subject": "Test Email", "from": "test@example.com"}] + + def test_execute_action_invalid_action(self, component_class, default_kwargs): + component = component_class(**default_kwargs) + component.api_key = "test_key" + component.action = [{"name": "Invalid Action"}] + + with pytest.raises(ValueError, match="Invalid action: Invalid Action"): + component.execute_action() + + def test_as_dataframe(self, component_class, default_kwargs, monkeypatch): + monkeypatch.setattr(Action, "OUTLOOK_OUTLOOK_SEND_EMAIL", MockAction.OUTLOOK_OUTLOOK_SEND_EMAIL) + + component = component_class(**default_kwargs) + component.api_key = "test_key" + component.action = [{"name": "Send Email"}] + component.OUTLOOK_OUTLOOK_SEND_EMAIL_subject = "Test Subject" + component.OUTLOOK_OUTLOOK_SEND_EMAIL_body = "Test Body" + component.OUTLOOK_OUTLOOK_SEND_EMAIL_to_email = "test@example.com" + + mock_emails = [ + { + "message": "Email sent successfully.", + } + ] + with patch.object(component, "execute_action", return_value=mock_emails): + result = component.as_dataframe() + + assert isinstance(result, DataFrame) + assert not result.empty + data_str = str(result) + assert "Email sent successfully." in data_str + + def test_update_build_config(self, component_class, default_kwargs): + component = component_class(**default_kwargs) + build_config = { + "auth_link": {"value": "", "auth_tooltip": ""}, + "action": { + "options": [], + "helper_text": "", + "helper_text_metadata": {}, + }, + } + + result = component.update_build_config(build_config, "", "api_key") + assert result["auth_link"]["value"] == "" + assert "Please provide a valid Composio API Key" in result["auth_link"]["auth_tooltip"] + assert result["action"]["options"] == [] + + component.api_key = "test_key" + result = component.update_build_config(build_config, "test_key", "api_key") + assert len(result["action"]["options"]) > 0 diff --git a/src/frontend/src/icons/lazyIconImports.ts b/src/frontend/src/icons/lazyIconImports.ts index 8ec3bed84..8d0371306 100644 --- a/src/frontend/src/icons/lazyIconImports.ts +++ b/src/frontend/src/icons/lazyIconImports.ts @@ -109,6 +109,8 @@ export const lazyIconsMapping = { })), Gmail: () => import("@/icons/gmail").then((mod) => ({ default: mod.GmailIcon })), + Outlook: () => + import("@/icons/outlook").then((mod) => ({ default: mod.OutlookIcon })), Googlecalendar: () => import("@/icons/googlecalendar").then((mod) => ({ default: mod.GooglecalendarIcon, diff --git a/src/frontend/src/icons/outlook/index.tsx b/src/frontend/src/icons/outlook/index.tsx new file mode 100644 index 000000000..add0e2324 --- /dev/null +++ b/src/frontend/src/icons/outlook/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import OutlookIconSVG from "./outlook"; + +export const OutlookIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); diff --git a/src/frontend/src/icons/outlook/outlook.jsx b/src/frontend/src/icons/outlook/outlook.jsx new file mode 100644 index 000000000..57686b8ba --- /dev/null +++ b/src/frontend/src/icons/outlook/outlook.jsx @@ -0,0 +1,24 @@ +const OutlookIconSVG = (props) => ( + + + + + + +); +export default OutlookIconSVG; diff --git a/src/frontend/src/icons/outlook/outlook.svg b/src/frontend/src/icons/outlook/outlook.svg new file mode 100644 index 000000000..b0333bd6f --- /dev/null +++ b/src/frontend/src/icons/outlook/outlook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 42daa9bb1..b590451d7 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -229,6 +229,7 @@ export const SIDEBAR_CATEGORIES = [ ]; export const SIDEBAR_BUNDLES = [ + { display_name: "Outlook", name: "outlook", icon: "Outlook" }, { display_name: "Language Models", name: "languagemodels", @@ -238,6 +239,7 @@ export const SIDEBAR_BUNDLES = [ { display_name: "Memories", name: "memories", icon: "Cpu" }, { display_name: "Amazon", name: "amazon", icon: "Amazon" }, { display_name: "Gmail", name: "gmail", icon: "Gmail" }, + { display_name: "Outlook", name: "outlook", icon: "Outlook" }, { display_name: "GitHub", name: "github", icon: "Github" }, { display_name: "Googlecalendar", @@ -339,6 +341,7 @@ export const nodeIconToDisplayIconMap: Record = { ChatInput: "MessagesSquare", ChatOutput: "MessagesSquare", //Integration Icons + Outlook: "Outlook", AIML: "AI/ML", AgentQL: "AgentQL", LanguageModels: "BrainCircuit",