Skip to content

Commit

Permalink
feat: add get_tasks_report for focalboard (#314)
Browse files Browse the repository at this point in the history
* feat: get_task_report from focalboard

* fix: black
  • Loading branch information
alexeyqu authored Mar 31, 2024
1 parent 4018487 commit c128f74
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 9 deletions.
4 changes: 4 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"token": "do_not_set_here_please_go_to_config_override",
"board_id": "do_not_set_here_please_go_to_config_override"
},
"focalboard": {
"token": "do_not_set_here_please_go_to_config_override",
"url": "do_not_set_here_please_go_to_config_override"
},
"sheets": {
"api_key_path": "do_not_set_here_please_go_to_config_override",
"authors_sheet_key": "do_not_set_here_please_go_to_config_override",
Expand Down
4 changes: 4 additions & 0 deletions src/app_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .tg.sender import TelegramSender
from .tg.tg_client import TgClient
from .trello.trello_client import TrelloClient
from .focalboard.focalboard_client import FocalboardClient
from .utils.singleton import Singleton
from .vk.vk_client import VkClient

Expand Down Expand Up @@ -54,6 +55,9 @@ def __init__(
self.trello_client = TrelloClient(
trello_config=config_manager.get_trello_config()
)
self.focalboard_client = FocalboardClient(
focalboard_config=config_manager.get_focalboard_config()
)
self.facebook_client = FacebookClient(
facebook_config=config_manager.get_facebook_config()
)
Expand Down
6 changes: 6 additions & 0 deletions src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ def init_handlers(self):
direct_message_only(handlers.get_tasks_report_advanced),
"получить список задач из Trello (расширенный)",
)
self.add_manager_handler(
"get_tasks_report_focalboard",
CommandCategories.SUMMARY,
direct_message_only(handlers.get_tasks_report_focalboard),
"получить список задач из Focalboard",
)
self.add_manager_handler(
"get_articles_arts",
CommandCategories.SUMMARY,
Expand Down
3 changes: 3 additions & 0 deletions src/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ def get_latest_jobs_config(self):

def get_trello_config(self):
return self.get_latest_config().get(consts.TRELLO_CONFIG, {})

def get_focalboard_config(self):
return self.get_latest_config().get(consts.FOCALBOARD_CONFIG, {})

def get_telegram_config(self):
return self.get_latest_config().get(consts.TELEGRAM_CONFIG, {})
Expand Down
2 changes: 2 additions & 0 deletions src/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class AppSource(Enum):
# Upper level config keys
TELEGRAM_CONFIG = "telegram"
TRELLO_CONFIG = "trello"
FOCALBOARD_CONFIG = "focalboard"
SHEETS_CONFIG = "sheets"
DRIVE_CONFIG = "drive"
FACEBOOK_CONFIG = "facebook"
Expand Down Expand Up @@ -166,6 +167,7 @@ class GetTasksReportData:
LISTS = "lists"
INTRO_TEXT = "introduction"
INCLUDE_LABELS = "include_labels"
USE_FOCALBOARD = "use_focalboard"


class ManageRemindersData:
Expand Down
161 changes: 161 additions & 0 deletions src/focalboard/focalboard_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import json
import logging
from typing import List
from urllib.parse import quote, urljoin

import requests

from ..strings import load
from ..utils.singleton import Singleton
from ..trello import trello_objects as objects

logger = logging.getLogger(__name__)


class FocalboardClient(Singleton):
def __init__(self, focalboard_config=None):
if self.was_initialized():
return

self._focalboard_config = focalboard_config
self._update_from_config()
logger.info("FocalboardClient successfully initialized")

def get_boards_for_user(self, user_id=None):
_, data = self._make_request("api/v2/teams/0/boards")
boards = [objects.TrelloBoard.from_focalboard_dict(board) for board in data]
logger.debug(f"get_boards_for_user: {boards}")
return boards

def get_lists(self, board_id):
# if not board_id:
# board_id = self.board_id
# TODO make it more efficient
# essentially all list information is already passed via boards handler
_, data = self._make_request(f"api/v2/teams/0/boards")
list_data = [
prop
for prop in [board for board in data if board["id"] == board_id][0][
"cardProperties"
]
if prop["name"] == "List"
][0]
lists_data = list_data["options"]
lists = [
objects.TrelloList.from_focalboard_dict(trello_list, board_id)
for trello_list in lists_data
]
logger.debug(f"get_lists: {lists}")
return lists

def get_list(self, board_id, list_id):
_, data = self._make_request(f"api/v2/teams/0/boards")
lists_data = [
prop
for prop in [board for board in data if board["id"] == board_id][0][
"cardProperties"
]
if prop["name"] == "List"
][0]["options"]
lst = [
objects.TrelloList.from_focalboard_dict(trello_list, board_id)
for trello_list in lists_data
if trello_list["id"] == list_id
][0]
logger.debug(f"get_list: {lst}")
return lst

def _get_list_property(self, board_id):
_, data = self._make_request(f"api/v2/teams/0/boards")
return [
prop
for prop in [board for board in data if board["id"] == board_id][0][
"cardProperties"
]
if prop["name"] == "List"
][0]["id"]

def _get_member_property(self, board_id):
_, data = self._make_request(f"api/v2/teams/0/boards")
return [
prop
for prop in [board for board in data if board["id"] == board_id][0][
"cardProperties"
]
if prop["name"] == "Assignee"
][0]["id"]

def get_members(self, board_id) -> List[objects.TrelloMember]:
_, data = self._make_request(f"api/v2/boards/{board_id}/members")
members = []
for member in data:
_, data = self._make_request(f"api/v2/users/{member['userId']}")
members.append(objects.TrelloMember.from_focalboard_dict(data))
logger.debug(f"get_members: {members}")
return members

def get_cards(self, list_ids, board_id):
_, data = self._make_request(f"api/v2/boards/{board_id}/blocks?all=true")
cards = []
# TODO: move this to app state
members = self.get_members(board_id)
lists = self.get_lists(board_id)
list_prop = self._get_list_property(board_id)
member_prop = self._get_member_property(board_id)
view_id = [card_dict for card_dict in data if card_dict["type"] == "view"][0][
"id"
]
data = [
card_dict
for card_dict in data
if card_dict["type"] == "card"
and card_dict["fields"]["properties"].get(list_prop, "") in list_ids
]
for card_dict in data:
card = objects.TrelloCard.from_focalboard_dict(card_dict)
card.url = urljoin(self.url, f"{board_id}/{view_id}/{card.id}")
print(card.url)
# TODO: move this to app state
for trello_list in lists:
if trello_list.id == card_dict["fields"]["properties"].get(
list_prop, ""
):
card.lst = trello_list
break
else:
logger.error(f"List name not found for {card}")
# TODO: move this to app state
if len(card_dict["fields"]["properties"].get(member_prop, [])) > 0:
for member in members:
if member.id in card_dict["fields"]["properties"].get(
member_prop, []
):
card.members.append(member)
if len(card.members) == 0:
logger.error(f"Member username not found for {card}")
cards.append(card)
logger.debug(f"get_cards: {cards}")
return cards

def update_config(self, new_focalboard_config):
"""To be called after config automatic update"""
self._focalboard_config = new_focalboard_config
self._update_from_config()

def _update_from_config(self):
"""Update attributes according to current self._focalboard_config"""
self.token = self._focalboard_config["token"]
self.url = self._focalboard_config["url"]
self.headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {self.token}",
"X-Requested-With": "XMLHttpRequest",
}

def _make_request(self, uri, payload={}):
response = requests.get(
urljoin(self.url, uri), params=payload, headers=self.headers
)
logger.debug(f"{response.url}")
return response.status_code, response.json()
4 changes: 4 additions & 0 deletions src/jobs/config_updater_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def _execute(
app_context.trello_client.update_config(
job_scheduler.config_manager.get_trello_config()
)
# update config['focalboard']
app_context.focalboard_client.update_config(
job_scheduler.config_manager.get_focalboard_config()
)
# update config['sheets']
app_context.sheets_client.update_config(
job_scheduler.config_manager.get_sheets_config()
Expand Down
2 changes: 1 addition & 1 deletion src/tg/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

# Admin (developer) handlers
from .get_roles_for_member_handler import get_roles_for_member
from .get_tasks_report_handler import get_tasks_report, get_tasks_report_advanced
from .get_tasks_report_handler import get_tasks_report, get_tasks_report_advanced, get_tasks_report_focalboard
from .help_handler import help
from .list_chats_handler import list_chats
from .list_job_handler import list_jobs
Expand Down
28 changes: 23 additions & 5 deletions src/tg/handlers/get_tasks_report_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ def get_tasks_report(update: telegram.Update, tg_context: telegram.ext.CallbackC
return


@manager_only
def get_tasks_report_focalboard(update: telegram.Update, tg_context: telegram.ext.CallbackContext):
_get_task_report_base(update, tg_context, advanced=False, use_focalboard=True)

return


@manager_only
def get_tasks_report_advanced(
update: telegram.Update, tg_context: telegram.ext.CallbackContext
Expand All @@ -34,11 +41,14 @@ def get_tasks_report_advanced(


def _get_task_report_base(
update: telegram.Update, tg_context: telegram.ext.CallbackContext, advanced: bool
update: telegram.Update, tg_context: telegram.ext.CallbackContext, advanced: bool, use_focalboard: bool=False
):
app_context = AppContext()

boards_list = app_context.trello_client.get_boards_for_user()
if use_focalboard:
boards_list = app_context.focalboard_client.get_boards_for_user()
else:
boards_list = app_context.trello_client.get_boards_for_user()
boards_list_formatted = "\n".join(
[f"{i + 1}) {brd.name}" for i, brd in enumerate(boards_list)]
)
Expand All @@ -47,6 +57,7 @@ def _get_task_report_base(
tg_context.chat_data[consts.GetTasksReportData.LISTS] = [
lst.to_dict() for lst in boards_list
]
tg_context.chat_data[consts.GetTasksReportData.USE_FOCALBOARD] = use_focalboard
tg_context.chat_data[TASK_NAME] = {
consts.NEXT_ACTION: consts.PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_NUMBER.value
}
Expand All @@ -60,15 +71,22 @@ def _get_task_report_base(


def generate_report_messages(
board_id: str, list_id: str, introduction: str, add_labels: bool
board_id: str, list_id: str, introduction: str, add_labels: bool, use_focalboard: bool
) -> List[str]:
app_context = AppContext()
paragraphs = [] # list of paragraph strings

trello_list = app_context.trello_client.get_list(list_id)
if use_focalboard:
trello_list = app_context.focalboard_client.get_list(board_id, list_id)
else:
trello_list = app_context.trello_client.get_list(list_id)
paragraphs.append(load("common__bold_wrapper", arg=trello_list.name))

list_cards = app_context.trello_client.get_cards([list_id], board_id)
if use_focalboard:
list_cards = app_context.focalboard_client.get_cards([list_id], board_id)
else:
list_cards = app_context.trello_client.get_cards([list_id], board_id)
print(list_cards)
paragraphs += _create_paragraphs_from_cards(
list_cards, introduction, add_labels, app_context
)
Expand Down
15 changes: 12 additions & 3 deletions src/tg/handlers/user_message_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ...strings import load
from ...tg.handlers import get_tasks_report_handler
from ...trello.trello_client import TrelloClient
from ...focalboard.focalboard_client import FocalboardClient
from .utils import get_chat_id, get_chat_name, get_sender_id, reply

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -84,13 +85,19 @@ def handle_user_message(
return
elif next_action == PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_NUMBER:
trello_client = TrelloClient()
focalboard_client = FocalboardClient()
try:
board_list = tg_context.chat_data[consts.GetTasksReportData.LISTS]
use_focalboard = tg_context.chat_data[consts.GetTasksReportData.USE_FOCALBOARD]
list_idx = int(user_input) - 1
assert 0 <= list_idx < len(board_list)
board_id = board_list[list_idx]["id"]
trello_lists = trello_client.get_lists(board_id)
trello_lists = trello_lists[::-1]
if use_focalboard:
trello_lists = focalboard_client.get_lists(board_id)
trello_lists = trello_lists[::-1]
else:
trello_lists = trello_client.get_lists(board_id)
trello_lists = trello_lists[::-1]
except Exception as e:
logger.warning(e)
reply(
Expand All @@ -103,6 +110,7 @@ def handle_user_message(
return

command_data[consts.GetTasksReportData.BOARD_ID] = board_id
command_data[consts.GetTasksReportData.USE_FOCALBOARD] = use_focalboard
command_data[consts.GetTasksReportData.LISTS] = [
lst.to_dict() for lst in trello_lists
]
Expand Down Expand Up @@ -552,8 +560,9 @@ def handle_task_report(command_data, add_labels, update):
board_id = command_data[consts.GetTasksReportData.BOARD_ID]
list_id = command_data[consts.GetTasksReportData.LIST_ID]
introduction = command_data[consts.GetTasksReportData.INTRO_TEXT]
use_focalboard = command_data[consts.GetTasksReportData.USE_FOCALBOARD]
messages = get_tasks_report_handler.generate_report_messages(
board_id, list_id, introduction, add_labels
board_id, list_id, introduction, add_labels, use_focalboard=use_focalboard
)
for message in messages:
reply(message, update)
Expand Down
Loading

0 comments on commit c128f74

Please sign in to comment.