diff --git a/.gitignore b/.gitignore index 3ad9e1b..1e111bb 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ logs/ .pytype/ .vscode .cursor +.claude/ +AGENTS.md diff --git a/listeners/events/entity_details_requested.py b/listeners/events/entity_details_requested.py index 7147d5c..ab19ba8 100644 --- a/listeners/events/entity_details_requested.py +++ b/listeners/events/entity_details_requested.py @@ -61,9 +61,9 @@ def entity_details_requested_callback(event: dict, client: WebClient, logger: lo json=payload, ) except SlackResponseError as e: - logger.error(f"Failed to fetch or parse sample data. Error details: {str(e)}", exc_info=e) + logger.error(f"Failed to fetch or parse sample data. Error details: {e}", exc_info=e) except Exception as e: logger.error( - f"An unexpected error occurred handling entity_details_requested event: {type(e).__name__} - {str(e)}", + f"An unexpected error occurred handling entity_details_requested event: {type(e).__name__} - {e}", exc_info=e, ) diff --git a/listeners/filters.py b/listeners/filters.py index ae6c186..7dac041 100644 --- a/listeners/filters.py +++ b/listeners/filters.py @@ -1,42 +1,16 @@ -from dataclasses import dataclass -from enum import Enum -from typing import List, Optional - - -class FilterType(Enum): - MULTI_SELECT = "multi_select" - TOGGLE = "toggle" - - -@dataclass -class FilterOptions: - name: str - value: str - - -@dataclass -class Filter: - name: str - display_name: str - type: FilterType - display_name_plural: Optional[str] = None - options: Optional[List[FilterOptions]] = None - - -LANGUAGES_FILTER = Filter( - name="languages", - display_name="Language", - display_name_plural="Languages", - type=FilterType.MULTI_SELECT.value, - options=[ - FilterOptions(name="Python", value="python"), - FilterOptions(name="Java", value="java"), - FilterOptions(name="JavaScript", value="javascript"), - FilterOptions(name="TypeScript", value="typescript"), +LANGUAGES_FILTER = { + "name": "languages", + "display_name": "Language", + "display_name_plural": "Languages", + "type": "multi_select", + "options": [ + {"name": "Python", "value": "python"}, + {"name": "Java", "value": "java"}, + {"name": "JavaScript", "value": "javascript"}, + {"name": "TypeScript", "value": "typescript"}, ], -) - -TEMPLATES_FILTER = Filter(name="template", display_name="Templates", type=FilterType.TOGGLE.value) +} +TEMPLATES_FILTER = {"name": "template", "display_name": "Templates", "type": "toggle"} -SAMPLES_FILTER = Filter(name="sample", display_name="Samples", type=FilterType.TOGGLE.value) +SAMPLES_FILTER = {"name": "sample", "display_name": "Samples", "type": "toggle"} diff --git a/listeners/functions/__init__.py b/listeners/functions/__init__.py index 31aff27..8abbcbc 100644 --- a/listeners/functions/__init__.py +++ b/listeners/functions/__init__.py @@ -5,5 +5,5 @@ def register(app: App): - app.function("search", auto_acknowledge=False)(search_step_callback) - app.function("filters", auto_acknowledge=False)(filters_step_callback) + app.function("search", auto_acknowledge=False, ack_timeout=10)(search_step_callback) + app.function("filters", auto_acknowledge=False, ack_timeout=10)(filters_step_callback) diff --git a/listeners/functions/filters.py b/listeners/functions/filters.py index 1904370..13b4739 100644 --- a/listeners/functions/filters.py +++ b/listeners/functions/filters.py @@ -1,38 +1,24 @@ import logging -from dataclasses import asdict -from typing import Dict from slack_bolt import Ack, Complete, Fail from listeners.filters import LANGUAGES_FILTER, SAMPLES_FILTER, TEMPLATES_FILTER -FILTER_PROCESSING_ERROR_MSG = ( - "We encountered an issue processing filter results. Please try again or contact the app owner if the problem persists." -) - -def filter_none(items: Dict): - return {k: v for k, v in items if v is not None} - def filters_step_callback(ack: Ack, inputs: dict, fail: Fail, complete: Complete, logger: logging.Logger): try: user_context = inputs.get("user_context", {}) logger.debug(f"User {user_context.get('id')} executing filter request") - complete( - outputs={ - "filters": [ - asdict(LANGUAGES_FILTER, dict_factory=filter_none), - asdict(TEMPLATES_FILTER, dict_factory=filter_none), - asdict(SAMPLES_FILTER, dict_factory=filter_none), - ] - } - ) + complete(outputs={"filters": [LANGUAGES_FILTER, TEMPLATES_FILTER, SAMPLES_FILTER]}) except Exception as e: logger.error( - f"Unexpected error occurred while processing filter request: {type(e).__name__} - {str(e)}", + f"Unexpected error occurred while processing filter request: {type(e).__name__} - {e}", exc_info=e, ) - fail(error=FILTER_PROCESSING_ERROR_MSG) + fail( + error="We encountered an issue processing filter results. " + "Please try again or contact the app owner if the problem persists." + ) finally: ack() diff --git a/listeners/functions/search.py b/listeners/functions/search.py index 0e3883d..db60ac7 100644 --- a/listeners/functions/search.py +++ b/listeners/functions/search.py @@ -1,30 +1,10 @@ import logging -from typing import List, NotRequired, Optional, TypedDict from slack_bolt import Ack, Complete, Fail from slack_sdk import WebClient from listeners.sample_data_service import SlackResponseError, fetch_sample_data -SEARCH_PROCESSING_ERROR_MSG = ( - "We encountered an issue processing your search results. " - "Please try again or contact the app owner if the problem persists." -) - - -class EntityReference(TypedDict): - id: str - type: Optional[str] - - -class SearchResult(TypedDict): - title: str - description: str - link: str - date_updated: str - external_ref: EntityReference - content: NotRequired[str] - def search_step_callback( ack: Ack, @@ -42,27 +22,14 @@ def search_step_callback( samples = response.get("samples", []) - results: List[SearchResult] = [ - { - "title": sample["title"], - "description": sample["description"], - "link": sample["link"], - "date_updated": sample["date_updated"], - "external_ref": sample["external_ref"], - **({"content": sample["content"]} if "content" in sample else {}), - } - for sample in samples - ] - - complete(outputs={"search_results": results}) + complete(outputs={"search_results": samples}) + except SlackResponseError as e: + logger.error(f"Failed to fetch or parse sample data. Error details: {e}", exc_info=e) + fail( + error="We encountered an issue processing your search results. " + "Please try again or contact the app owner if the problem persists." + ) except Exception as e: - if isinstance(e, SlackResponseError): - logger.error(f"Failed to fetch or parse sample data. Error details: {str(e)}", exc_info=e) - fail(error=SEARCH_PROCESSING_ERROR_MSG) - else: - logger.error( - f"Unexpected error occurred while processing search request: {type(e).__name__} - {str(e)}", - exc_info=e, - ) + logger.error(f"Unexpected error processing search request: {type(e).__name__} - {e}", exc_info=e) finally: ack() diff --git a/listeners/sample_data_service.py b/listeners/sample_data_service.py index b61bdb3..d1d7c5a 100644 --- a/listeners/sample_data_service.py +++ b/listeners/sample_data_service.py @@ -8,9 +8,7 @@ class SlackResponseError(Exception): - def __init__(self, message: str): - super().__init__(message) - self.name = "SlackResponseError" + pass def fetch_sample_data(client: WebClient, query: str = None, filters: dict = None, logger: logging.Logger = None): @@ -19,18 +17,18 @@ def fetch_sample_data(client: WebClient, query: str = None, filters: dict = None if filters: selected_filters = {} - languages = filters.get(LANGUAGES_FILTER.name, []) - templates = filters.get(TEMPLATES_FILTER.name, False) - samples = filters.get(SAMPLES_FILTER.name, False) + languages = filters.get(LANGUAGES_FILTER["name"], []) + templates = filters.get(TEMPLATES_FILTER["name"], False) + samples = filters.get(SAMPLES_FILTER["name"], False) if languages: - selected_filters[LANGUAGES_FILTER.name] = languages + selected_filters[LANGUAGES_FILTER["name"]] = languages if templates ^ samples: if templates: - selected_filters["type"] = TEMPLATES_FILTER.name + selected_filters["type"] = TEMPLATES_FILTER["name"] elif samples: - selected_filters["type"] = SAMPLES_FILTER.name + selected_filters["type"] = SAMPLES_FILTER["name"] if selected_filters: params["filters"] = selected_filters diff --git a/tests/listeners/functions/test_filters.py b/tests/listeners/functions/test_filters.py index f55b6b5..c6a3687 100644 --- a/tests/listeners/functions/test_filters.py +++ b/tests/listeners/functions/test_filters.py @@ -2,7 +2,7 @@ from slack_bolt import Ack, Complete, Fail -from listeners.functions.filters import FILTER_PROCESSING_ERROR_MSG, filters_step_callback +from listeners.functions.filters import filters_step_callback class TestFilters: @@ -84,7 +84,4 @@ def test_filters_step_callback_unexpected_exception(self): ) self.mock_fail.assert_called_once() - call_args = self.mock_fail.call_args - assert call_args.kwargs["error"] == FILTER_PROCESSING_ERROR_MSG - self.mock_ack.assert_called_once() diff --git a/tests/listeners/functions/test_search.py b/tests/listeners/functions/test_search.py index 97b39c9..fbef560 100644 --- a/tests/listeners/functions/test_search.py +++ b/tests/listeners/functions/test_search.py @@ -4,7 +4,7 @@ from slack_sdk import WebClient from listeners.filters import LANGUAGES_FILTER, SAMPLES_FILTER, TEMPLATES_FILTER -from listeners.functions.search import SEARCH_PROCESSING_ERROR_MSG, search_step_callback +from listeners.functions.search import search_step_callback from listeners.sample_data_service import SlackResponseError @@ -41,7 +41,7 @@ def setup_method(self): def test_search_step_callback_success(self, mock_fetch_sample_data): mock_fetch_sample_data.return_value = self.mock_sample_data - filters = {LANGUAGES_FILTER.name: ["python"]} + filters = {LANGUAGES_FILTER["name"]: ["python"]} inputs = {"query": "test query", "filters": filters} @@ -74,7 +74,11 @@ def test_search_step_callback_success(self, mock_fetch_sample_data): def test_search_step_callback_multiple_filter_types(self, mock_fetch_sample_data): mock_fetch_sample_data.return_value = self.mock_sample_data - filters = {TEMPLATES_FILTER.name: True, SAMPLES_FILTER.name: True, LANGUAGES_FILTER.name: ["python", "javascript"]} + filters = { + TEMPLATES_FILTER["name"]: True, + SAMPLES_FILTER["name"]: True, + LANGUAGES_FILTER["name"]: ["python", "javascript"], + } inputs = {"query": "test query", "filters": filters} @@ -135,9 +139,6 @@ def test_search_step_callback_slack_response_error(self, mock_fetch_sample_data) ) self.mock_fail.assert_called_once() - call_args = self.mock_fail.call_args - assert call_args.kwargs["error"] == SEARCH_PROCESSING_ERROR_MSG - self.mock_complete.assert_not_called() self.mock_ack.assert_called_once() diff --git a/tests/listeners/test_sample_data_service.py b/tests/listeners/test_sample_data_service.py index 06d543e..a69e40e 100644 --- a/tests/listeners/test_sample_data_service.py +++ b/tests/listeners/test_sample_data_service.py @@ -48,7 +48,7 @@ def test_fetch_sample_data_with_query(self): assert result == self.mock_response def test_fetch_sample_data_with_languages_filter(self): - filters = {LANGUAGES_FILTER.name: ["python", "javascript"]} + filters = {LANGUAGES_FILTER["name"]: ["python", "javascript"]} result = fetch_sample_data(client=self.mock_client, query="test query", filters=filters, logger=self.mock_logger) @@ -57,41 +57,44 @@ def test_fetch_sample_data_with_languages_filter(self): assert result == self.mock_response def test_fetch_sample_data_with_templates_filter(self): - filters = {TEMPLATES_FILTER.name: True} + filters = {TEMPLATES_FILTER["name"]: True} result = fetch_sample_data(client=self.mock_client, query="test query", filters=filters, logger=self.mock_logger) self.mock_client.api_call.assert_called_once_with( - API_METHOD, params={"query": "test query", "filters": {"type": TEMPLATES_FILTER.name}} + API_METHOD, params={"query": "test query", "filters": {"type": TEMPLATES_FILTER["name"]}} ) assert result == self.mock_response def test_fetch_sample_data_with_samples_filter(self): - filters = {SAMPLES_FILTER.name: True} + filters = {SAMPLES_FILTER["name"]: True} result = fetch_sample_data(client=self.mock_client, query="test query", filters=filters, logger=self.mock_logger) self.mock_client.api_call.assert_called_once_with( - API_METHOD, params={"query": "test query", "filters": {"type": SAMPLES_FILTER.name}} + API_METHOD, params={"query": "test query", "filters": {"type": SAMPLES_FILTER["name"]}} ) assert result == self.mock_response def test_fetch_sample_data_with_combined_filters(self): - filters = {LANGUAGES_FILTER.name: ["python"], TEMPLATES_FILTER.name: True} + filters = {LANGUAGES_FILTER["name"]: ["python"], TEMPLATES_FILTER["name"]: True} result = fetch_sample_data(client=self.mock_client, query="test query", filters=filters, logger=self.mock_logger) self.mock_client.api_call.assert_called_once_with( API_METHOD, - params={"query": "test query", "filters": {LANGUAGES_FILTER.name: ["python"], "type": TEMPLATES_FILTER.name}}, + params={ + "query": "test query", + "filters": {LANGUAGES_FILTER["name"]: ["python"], "type": TEMPLATES_FILTER["name"]}, + }, ) assert result == self.mock_response def test_fetch_sample_data_with_both_template_and_sample(self): - filters = {TEMPLATES_FILTER.name: True, SAMPLES_FILTER.name: True} + filters = {TEMPLATES_FILTER["name"]: True, SAMPLES_FILTER["name"]: True} result = fetch_sample_data(client=self.mock_client, query="test query", filters=filters, logger=self.mock_logger)