Skip to content

Commit

Permalink
Added pagination logic in the slack bot (#489)
Browse files Browse the repository at this point in the history
* Added pagination logic in the slack bot

* pre-commit

* chhnages after conflit and new chnages

* fixed bug: do not append a blank button

* resolved issues

* Update code

---------

Co-authored-by: Arkadii Yakovets <[email protected]>
Co-authored-by: Arkadii Yakovets <[email protected]>
  • Loading branch information
3 people authored Jan 20, 2025
1 parent 2c570d3 commit 6cca7a9
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 33 deletions.
55 changes: 44 additions & 11 deletions backend/apps/slack/actions/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
from apps.slack.common.presentation import EntityPresentation
from apps.slack.constants import (
VIEW_CHAPTERS_ACTION,
VIEW_CHAPTERS_ACTION_NEXT,
VIEW_CHAPTERS_ACTION_PREV,
VIEW_COMMITTEES_ACTION,
VIEW_COMMITTEES_ACTION_NEXT,
VIEW_COMMITTEES_ACTION_PREV,
VIEW_PROJECTS_ACTION,
VIEW_PROJECTS_ACTION_NEXT,
VIEW_PROJECTS_ACTION_PREV,
)

logger = logging.getLogger(__name__)
Expand All @@ -23,27 +29,44 @@ def handle_home_actions(ack, body, client):

action_id = body["actions"][0]["action_id"]
user_id = body["user"]["id"]
payload = body.get("actions", [])[0]
value = payload.get("value", "1")

try:
home_presentation = EntityPresentation(
include_feedback=False,
include_metadata=True,
include_pagination=True,
include_timestamps=False,
name_truncation=80,
summary_truncation=200,
)

page = int(value) if value.isdigit() else 1

blocks = []

match action_id:
case "view_chapters_action":
blocks = chapters.get_blocks(limit=10, presentation=home_presentation)

case "view_committees_action":
blocks = committees.get_blocks(limit=10, presentation=home_presentation)

case "view_projects_action":
blocks = projects.get_blocks(limit=10, presentation=home_presentation)
case action if action in {
VIEW_CHAPTERS_ACTION,
VIEW_CHAPTERS_ACTION_PREV,
VIEW_CHAPTERS_ACTION_NEXT,
}:
blocks = chapters.get_blocks(page=page, limit=10, presentation=home_presentation)

case action if action in {
VIEW_COMMITTEES_ACTION,
VIEW_COMMITTEES_ACTION_PREV,
VIEW_COMMITTEES_ACTION_NEXT,
}:
blocks = committees.get_blocks(page=page, limit=10, presentation=home_presentation)

case action if action in {
VIEW_PROJECTS_ACTION,
VIEW_PROJECTS_ACTION_PREV,
VIEW_PROJECTS_ACTION_NEXT,
}:
blocks = projects.get_blocks(page=page, limit=10, presentation=home_presentation)
case _:
blocks = [markdown("Invalid action, please try again.")]

Expand All @@ -63,6 +86,16 @@ def handle_home_actions(ack, body, client):

# Register the actions
if SlackConfig.app:
SlackConfig.app.action(VIEW_CHAPTERS_ACTION)(handle_home_actions)
SlackConfig.app.action(VIEW_COMMITTEES_ACTION)(handle_home_actions)
SlackConfig.app.action(VIEW_PROJECTS_ACTION)(handle_home_actions)
actions = (
VIEW_CHAPTERS_ACTION_NEXT,
VIEW_CHAPTERS_ACTION_PREV,
VIEW_CHAPTERS_ACTION,
VIEW_COMMITTEES_ACTION_NEXT,
VIEW_COMMITTEES_ACTION_PREV,
VIEW_COMMITTEES_ACTION,
VIEW_PROJECTS_ACTION_NEXT,
VIEW_PROJECTS_ACTION_PREV,
VIEW_PROJECTS_ACTION,
)
for action in actions:
SlackConfig.app.action(action)(handle_home_actions)
29 changes: 29 additions & 0 deletions backend/apps/slack/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,32 @@ def get_header():
],
},
]


def get_pagination_buttons(entity_type, page, total_pages):
"""Get pagination buttons for the blocks."""
pagination_buttons = []

if page > 1:
pagination_buttons.append(
{
"type": "button",
"text": {"type": "plain_text", "text": "Previous"},
"action_id": f"view_{entity_type}_action_prev",
"value": str(page - 1),
"style": "primary",
}
)

if total_pages > page:
pagination_buttons.append(
{
"type": "button",
"text": {"type": "plain_text", "text": "Next"},
"action_id": f"view_{entity_type}_action_next",
"value": str(page + 1),
"style": "primary",
}
)

return pagination_buttons
1 change: 1 addition & 0 deletions backend/apps/slack/commands/chapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def chapters_handler(ack, command, client):
presentation=EntityPresentation(
include_feedback=True,
include_metadata=True,
include_pagination=False,
include_timestamps=True,
name_truncation=80,
summary_truncation=300,
Expand Down
2 changes: 1 addition & 1 deletion backend/apps/slack/commands/committees.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ def committees_handler(ack, command, client):
presentation=EntityPresentation(
include_feedback=True,
include_metadata=True,
include_pagination=False,
include_timestamps=True,
name_truncation=80,
summary_truncation=300,
),
)

conversation = client.conversations_open(users=command["user_id"])
client.chat_postMessage(channel=conversation["channel"]["id"], blocks=blocks)

Expand Down
1 change: 1 addition & 0 deletions backend/apps/slack/commands/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def projects_handler(ack, command, client):
presentation=EntityPresentation(
include_feedback=True,
include_metadata=True,
include_pagination=False,
include_timestamps=True,
name_truncation=80,
summary_truncation=300,
Expand Down
26 changes: 22 additions & 4 deletions backend/apps/slack/common/handlers/chapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

from apps.common.constants import NL
from apps.common.utils import get_absolute_url
from apps.slack.blocks import markdown
from apps.slack.blocks import get_pagination_buttons, markdown
from apps.slack.common.constants import TRUNCATION_INDICATOR
from apps.slack.common.presentation import EntityPresentation
from apps.slack.constants import FEEDBACK_CHANNEL_MESSAGE
from apps.slack.utils import escape


def get_blocks(
search_query: str = "", limit: int = 10, presentation: EntityPresentation | None = None
page=1, search_query: str = "", limit: int = 10, presentation: EntityPresentation | None = None
):
"""Get chapters blocks."""
from apps.owasp.api.search.chapter import get_chapters
Expand All @@ -35,7 +35,11 @@ def get_blocks(
"idx_url",
]

chapters = get_chapters(search_query, attributes=attributes, limit=limit)["hits"]
offset = (page - 1) * limit
chapters_data = get_chapters(search_query, attributes=attributes, limit=limit, page=page)
chapters = chapters_data["hits"]
total_pages = chapters_data["nbPages"]

if not chapters:
return [
markdown(
Expand Down Expand Up @@ -71,7 +75,7 @@ def get_blocks(

blocks.append(
markdown(
f"{idx + 1}. <{chapter['idx_url']}|*{name}*>{NL}"
f"{offset + idx + 1}. <{chapter['idx_url']}|*{name}*>{NL}"
f"_{location}_{NL}"
f"{leaders_text}"
f"{escape(summary)}{NL}"
Expand All @@ -88,4 +92,18 @@ def get_blocks(
)
)

if presentation.include_pagination and (
pagination_block := get_pagination_buttons(
"chapters",
page,
total_pages - 1,
)
):
blocks.append(
{
"type": "actions",
"elements": pagination_block,
}
)

return blocks
25 changes: 21 additions & 4 deletions backend/apps/slack/common/handlers/committees.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

from apps.common.constants import NL
from apps.common.utils import get_absolute_url
from apps.slack.blocks import markdown
from apps.slack.blocks import get_pagination_buttons, markdown
from apps.slack.common.constants import TRUNCATION_INDICATOR
from apps.slack.common.presentation import EntityPresentation
from apps.slack.constants import FEEDBACK_CHANNEL_MESSAGE
from apps.slack.utils import escape


def get_blocks(
search_query: str = "", limit: int = 10, presentation: EntityPresentation | None = None
page=1, search_query: str = "", limit: int = 10, presentation: EntityPresentation | None = None
):
"""Get committees blocks."""
from apps.owasp.api.search.committee import get_committees
Expand All @@ -31,7 +31,11 @@ def get_blocks(
"idx_url",
]

committees = get_committees(search_query, attributes=attributes, limit=limit)["hits"]
offset = (page - 1) * limit
committees_data = get_committees(search_query, attributes=attributes, limit=limit, page=page)
committees = committees_data["hits"]
total_pages = committees_data["nbPages"]

if not committees:
return [
markdown(
Expand Down Expand Up @@ -66,7 +70,7 @@ def get_blocks(

blocks.append(
markdown(
f"{idx + 1}. <{committee['idx_url']}|*{name}*>{NL}"
f"{offset + idx + 1}. <{committee['idx_url']}|*{name}*>{NL}"
f"{leaders_text}"
f"{escape(summary)}{NL}"
)
Expand All @@ -81,5 +85,18 @@ def get_blocks(
f"{FEEDBACK_CHANNEL_MESSAGE}"
)
)
if presentation.include_pagination and (
pagination_block := get_pagination_buttons(
"committees",
page,
total_pages - 1,
)
):
blocks.append(
{
"type": "actions",
"elements": pagination_block,
}
)

return blocks
26 changes: 22 additions & 4 deletions backend/apps/slack/common/handlers/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@

from apps.common.constants import NL
from apps.common.utils import get_absolute_url, natural_date
from apps.slack.blocks import markdown
from apps.slack.blocks import get_pagination_buttons, markdown
from apps.slack.common.constants import TRUNCATION_INDICATOR
from apps.slack.common.presentation import EntityPresentation
from apps.slack.constants import FEEDBACK_CHANNEL_MESSAGE
from apps.slack.utils import escape


def get_blocks(
search_query: str = "", limit: int = 10, presentation: EntityPresentation | None = None
page=1, search_query: str = "", limit: int = 10, presentation: EntityPresentation | None = None
):
"""Get projects blocks."""
from apps.owasp.api.search.project import get_projects
Expand All @@ -36,7 +36,11 @@ def get_blocks(
"idx_url",
]

projects = get_projects(search_query, attributes=attributes, limit=limit)["hits"]
offset = (page - 1) * limit
projects_data = get_projects(search_query, attributes=attributes, limit=limit, page=page)
projects = projects_data["hits"]
total_pages = projects_data["nbPages"]

if not projects:
return [
markdown(
Expand Down Expand Up @@ -88,7 +92,7 @@ def get_blocks(

blocks.append(
markdown(
f"{idx + 1}. <{project['idx_url']}|*{name}*>{NL}"
f"{offset + idx + 1}. <{project['idx_url']}|*{name}*>{NL}"
f"{updated_text}"
f"{metadata_text}"
f"{leader_text}"
Expand All @@ -106,4 +110,18 @@ def get_blocks(
)
)

if presentation.include_pagination and (
pagination_block := get_pagination_buttons(
"projects",
page,
total_pages - 1,
)
):
blocks.append(
{
"type": "actions",
"elements": pagination_block,
}
)

return blocks
1 change: 1 addition & 0 deletions backend/apps/slack/common/presentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class EntityPresentation:

include_feedback: bool = True
include_metadata: bool = True
include_pagination: bool = True
include_timestamps: bool = True
name_truncation: int = 80
summary_truncation: int = 300
8 changes: 8 additions & 0 deletions backend/apps/slack/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@
OWASP_THREAT_MODELING_CHANNEL_ID = "#C1CS3C6AF"

VIEW_PROJECTS_ACTION = "view_projects_action"
VIEW_PROJECTS_ACTION_NEXT = "view_projects_action_next"
VIEW_PROJECTS_ACTION_PREV = "view_projects_action_prev"

VIEW_COMMITTEES_ACTION = "view_committees_action"
VIEW_COMMITTEES_ACTION_NEXT = "view_committees_action_next"
VIEW_COMMITTEES_ACTION_PREV = "view_committees_action_prev"

VIEW_CHAPTERS_ACTION = "view_chapters_action"
VIEW_CHAPTERS_ACTION_NEXT = "view_chapters_action_next"
VIEW_CHAPTERS_ACTION_PREV = "view_chapters_action_prev"


FEEDBACK_CHANNEL_MESSAGE = (
Expand Down
6 changes: 3 additions & 3 deletions backend/tests/slack/actions/home_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def _mock_external_calls(self):
patch("apps.owasp.api.search.chapter.get_chapters") as mock_chapters,
patch("apps.owasp.api.search.committee.get_committees") as mock_committees,
):
mock_projects.return_value = {"hits": []}
mock_chapters.return_value = {"hits": []}
mock_committees.return_value = {"hits": []}
mock_projects.return_value = {"hits": [], "nbPages": 1}
mock_chapters.return_value = {"hits": [], "nbPages": 1}
mock_committees.return_value = {"hits": [], "nbPages": 1}
yield

@pytest.mark.parametrize(
Expand Down
5 changes: 3 additions & 2 deletions backend/tests/slack/commands/chapters_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def mock_client(self):
@pytest.fixture(autouse=True)
def mock_get_chapters(self):
with patch("apps.owasp.api.search.chapter.get_chapters") as mock:
mock.return_value = {"hits": []}
mock.return_value = {"hits": [], "nbPages": 1}
yield mock

@pytest.mark.parametrize(
Expand Down Expand Up @@ -73,7 +73,8 @@ def test_chapters_handler_with_results(self, mock_get_chapters, mock_client, moc
"idx_region": "Test Region",
"idx_updated_at": "2024-01-01",
}
]
],
"nbPages": 1,
}

chapters_handler(ack=MagicMock(), command=mock_command, client=mock_client)
Expand Down
Loading

0 comments on commit 6cca7a9

Please sign in to comment.