Skip to content

Commit 65627a0

Browse files
feat: improve template behavior (#7)
1 parent 6dd4cd9 commit 65627a0

7 files changed

Lines changed: 273 additions & 108 deletions

File tree

listeners/filters.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from dataclasses import dataclass
2+
from enum import Enum
3+
from typing import List, Optional
4+
5+
6+
class FilterType(Enum):
7+
MULTI_SELECT = "multi_select"
8+
TOGGLE = "toggle"
9+
10+
11+
@dataclass
12+
class FilterOptions:
13+
name: str
14+
value: str
15+
16+
17+
@dataclass
18+
class Filter:
19+
name: str
20+
display_name: str
21+
type: FilterType
22+
display_name_plural: Optional[str] = None
23+
options: Optional[List[FilterOptions]] = None
24+
25+
26+
LANGUAGES_FILTER = Filter(
27+
name="languages",
28+
display_name="Language",
29+
display_name_plural="Languages",
30+
type=FilterType.MULTI_SELECT.value,
31+
options=[
32+
FilterOptions(name="Python", value="python"),
33+
FilterOptions(name="Java", value="java"),
34+
FilterOptions(name="JavaScript", value="javascript"),
35+
FilterOptions(name="TypeScript", value="typescript"),
36+
],
37+
)
38+
39+
TEMPLATES_FILTER = Filter(name="template", display_name="Templates", type=FilterType.TOGGLE.value)
40+
41+
42+
SAMPLES_FILTER = Filter(name="sample", display_name="Samples", type=FilterType.TOGGLE.value)

listeners/functions/filters.py

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,38 @@
11
import logging
2-
from enum import Enum
3-
from typing import List, Optional, TypedDict
2+
from dataclasses import asdict
3+
from typing import Dict
44

55
from slack_bolt import Ack, Complete, Fail
66

7+
from listeners.filters import LANGUAGES_FILTER, SAMPLES_FILTER, TEMPLATES_FILTER
8+
79
FILTER_PROCESSING_ERROR_MSG = (
810
"We encountered an issue processing filter results. Please try again or contact the app owner if the problem persists."
911
)
1012

11-
12-
class FilterType(Enum):
13-
MULTI_SELECT = "multi_select"
14-
TOGGLE = "toggle"
15-
16-
17-
class FilterOptions(TypedDict):
18-
name: str
19-
value: str
20-
21-
22-
class SearchFilter(TypedDict):
23-
name: str
24-
display_name: str
25-
filter_type: FilterType
26-
options: Optional[List[FilterOptions]]
13+
def filter_none(items: Dict):
14+
return {k: v for k, v in items if v is not None}
2715

2816

2917
def filters_step_callback(ack: Ack, inputs: dict, fail: Fail, complete: Complete, logger: logging.Logger):
3018
try:
3119
user_context = inputs.get("user_context", {})
3220
logger.debug(f"User {user_context.get('id')} executing filter request")
3321

34-
filters: List[SearchFilter] = [
35-
{
36-
"name": "languages",
37-
"display_name": "Languages",
38-
"type": FilterType.MULTI_SELECT.value,
39-
"options": [
40-
{"name": "Python", "value": "python"},
41-
{"name": "Java", "value": "java"},
42-
{"name": "JavaScript", "value": "javascript"},
43-
{"name": "TypeScript", "value": "typescript"},
44-
],
45-
},
46-
{
47-
"name": "type",
48-
"display_name": "Type",
49-
"type": FilterType.MULTI_SELECT.value,
50-
"options": [
51-
{"name": "Template", "value": "template"},
52-
{"name": "Sample", "value": "sample"},
53-
],
54-
},
55-
]
56-
57-
complete(outputs={"filters": filters})
22+
complete(
23+
outputs={
24+
"filters": [
25+
asdict(LANGUAGES_FILTER, dict_factory=filter_none),
26+
asdict(TEMPLATES_FILTER, dict_factory=filter_none),
27+
asdict(SAMPLES_FILTER, dict_factory=filter_none),
28+
]
29+
}
30+
)
5831
except Exception as e:
59-
logger.error(f"Unexpected error occurred while processing filter request: {type(e).__name__} - {str(e)}", exc_info=e)
32+
logger.error(
33+
f"Unexpected error occurred while processing filter request: {type(e).__name__} - {str(e)}",
34+
exc_info=e,
35+
)
6036
fail(error=FILTER_PROCESSING_ERROR_MSG)
6137
finally:
6238
ack()

listeners/functions/search.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
2-
from ast import List
3-
from typing import NotRequired, Optional, TypedDict
2+
from typing import List, NotRequired, Optional, TypedDict
43

54
from slack_bolt import Ack, Complete, Fail
65
from slack_sdk import WebClient
@@ -12,8 +11,6 @@
1211
"Please try again or contact the app owner if the problem persists."
1312
)
1413

15-
print(SEARCH_PROCESSING_ERROR_MSG)
16-
1714

1815
class EntityReference(TypedDict):
1916
id: str
@@ -29,21 +26,19 @@ class SearchResult(TypedDict):
2926
content: NotRequired[str]
3027

3128

32-
def search_step_callback(ack: Ack, inputs: dict, fail: Fail, complete: Complete, client: WebClient, logger: logging.Logger):
29+
def search_step_callback(
30+
ack: Ack,
31+
inputs: dict,
32+
fail: Fail,
33+
complete: Complete,
34+
client: WebClient,
35+
logger: logging.Logger,
36+
):
3337
try:
3438
query = inputs.get("query")
35-
filters = inputs.get("filters", {})
36-
languages_filter = filters.get("languages", [])
37-
type_filter = filters.get("type", [])
38-
39-
filters_payload = {}
40-
if languages_filter:
41-
filters_payload["languages"] = languages_filter
42-
if type_filter:
43-
if len(type_filter) == 1:
44-
filters_payload["type"] = type_filter[0]
39+
filters = inputs.get("filters")
4540

46-
response = fetch_sample_data(client=client, query=query, filters=filters_payload, logger=logger)
41+
response = fetch_sample_data(client=client, query=query, filters=filters, logger=logger)
4742

4843
samples = response.get("samples", [])
4944

@@ -66,7 +61,8 @@ def search_step_callback(ack: Ack, inputs: dict, fail: Fail, complete: Complete,
6661
fail(error=SEARCH_PROCESSING_ERROR_MSG)
6762
else:
6863
logger.error(
69-
f"Unexpected error occurred while processing search request: {type(e).__name__} - {str(e)}", exc_info=e
64+
f"Unexpected error occurred while processing search request: {type(e).__name__} - {str(e)}",
65+
exc_info=e,
7066
)
7167
finally:
7268
ack()

listeners/sample_data_service.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import json
21
import logging
32

43
from slack_sdk import WebClient
54

5+
from listeners.filters import LANGUAGES_FILTER, SAMPLES_FILTER, TEMPLATES_FILTER
6+
7+
API_METHOD = "developer.sampleData.get"
8+
69

710
class SlackResponseError(Exception):
811
def __init__(self, message: str):
@@ -11,21 +14,31 @@ def __init__(self, message: str):
1114

1215

1316
def fetch_sample_data(client: WebClient, query: str = None, filters: dict = None, logger: logging.Logger = None):
14-
method = "developer.sampleData.get"
15-
params = {}
16-
if query:
17-
params["query"] = query
17+
params = {"query": query}
18+
1819
if filters:
19-
params["filters"] = json.dumps(filters)
20+
selected_filters = {}
21+
22+
languages = filters.get(LANGUAGES_FILTER.name, [])
23+
templates = filters.get(TEMPLATES_FILTER.name, False)
24+
samples = filters.get(SAMPLES_FILTER.name, False)
2025

21-
response = client.api_call(method, params=params)
26+
if languages:
27+
selected_filters[LANGUAGES_FILTER.name] = languages
28+
29+
if templates ^ samples:
30+
if templates:
31+
selected_filters["type"] = TEMPLATES_FILTER.name
32+
elif samples:
33+
selected_filters["type"] = SAMPLES_FILTER.name
34+
35+
if selected_filters:
36+
params["filters"] = selected_filters
37+
38+
response = client.api_call(API_METHOD, params=params)
2239

2340
if not response.get("ok", False):
2441
logger.error(f"Search API request failed with error: {response.get('error', 'no error found')}")
25-
raise SlackResponseError(f"Failed to fetch sample data from Slack API: ok=false for method={method}")
26-
27-
if "samples" not in response:
28-
logger.error(f"Failed to parse API response as sample data. Received: {json.dumps(response)}")
29-
raise SlackResponseError(f"Invalid response format from Slack API from {method}")
42+
raise SlackResponseError(f"Failed to fetch sample data from Slack API: ok=false for method={API_METHOD}")
3043

3144
return response

tests/listeners/functions/test_filters.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ def setup_method(self):
1515
self.expected_filters = [
1616
{
1717
"name": "languages",
18-
"display_name": "Languages",
18+
"display_name": "Language",
1919
"type": "multi_select",
20+
"display_name_plural": "Languages",
2021
"options": [
2122
{"name": "Python", "value": "python"},
2223
{"name": "Java", "value": "java"},
@@ -25,21 +26,26 @@ def setup_method(self):
2526
],
2627
},
2728
{
28-
"name": "type",
29-
"display_name": "Type",
30-
"type": "multi_select",
31-
"options": [
32-
{"name": "Template", "value": "template"},
33-
{"name": "Sample", "value": "sample"},
34-
],
29+
"name": "template",
30+
"display_name": "Templates",
31+
"type": "toggle",
32+
},
33+
{
34+
"name": "sample",
35+
"display_name": "Samples",
36+
"type": "toggle",
3537
},
3638
]
3739

3840
def test_filters_step_callback_success(self):
3941
inputs = {"user_context": {"id": "U123456"}}
4042

4143
filters_step_callback(
42-
ack=self.mock_ack, inputs=inputs, fail=self.mock_fail, complete=self.mock_complete, logger=self.mock_logger
44+
ack=self.mock_ack,
45+
inputs=inputs,
46+
fail=self.mock_fail,
47+
complete=self.mock_complete,
48+
logger=self.mock_logger,
4349
)
4450

4551
self.mock_complete.assert_called_once()
@@ -52,7 +58,11 @@ def test_filters_step_callback_success(self):
5258

5359
def test_filters_step_callback_empty_user_context(self):
5460
filters_step_callback(
55-
ack=self.mock_ack, inputs={}, fail=self.mock_fail, complete=self.mock_complete, logger=self.mock_logger
61+
ack=self.mock_ack,
62+
inputs={},
63+
fail=self.mock_fail,
64+
complete=self.mock_complete,
65+
logger=self.mock_logger,
5666
)
5767

5868
self.mock_complete.assert_called_once()
@@ -66,12 +76,15 @@ def test_filters_step_callback_unexpected_exception(self):
6676
self.mock_complete.side_effect = Exception("Unexpected error")
6777

6878
filters_step_callback(
69-
ack=self.mock_ack, inputs={}, fail=self.mock_fail, complete=self.mock_complete, logger=self.mock_logger
79+
ack=self.mock_ack,
80+
inputs={},
81+
fail=self.mock_fail,
82+
complete=self.mock_complete,
83+
logger=self.mock_logger,
7084
)
7185

7286
self.mock_fail.assert_called_once()
7387
call_args = self.mock_fail.call_args
74-
print(FILTER_PROCESSING_ERROR_MSG)
7588
assert call_args.kwargs["error"] == FILTER_PROCESSING_ERROR_MSG
7689

7790
self.mock_ack.assert_called_once()

0 commit comments

Comments
 (0)