Skip to content

Commit

Permalink
feat: add cmd get_board_state for focalboard state
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyqu committed May 20, 2024
1 parent 7587f3e commit 3adb76e
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 12 deletions.
6 changes: 6 additions & 0 deletions src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ def init_handlers(self):
self.manager_reply_handler("trello_board_state_job"),
"получить сводку о состоянии доски",
)
self.add_manager_handler(
"get_board_state",
CommandCategories.SUMMARY,
self.manager_reply_handler("board_state_job"),
"получить сводку о состоянии доски (focalboard)",
)
self.add_manager_handler(
"get_editorial_board_stats",
CommandCategories.STATS,
Expand Down
27 changes: 17 additions & 10 deletions src/focalboard/focalboard_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,15 @@ def get_custom_fields(self, card_id: str) -> objects.CardCustomFields:
card_labels.append(board_label)

card_fields.authors = [
self.get_username(author.strip())
author.strip()
for author in card_fields_dict.get("author", [])
]
card_fields.editors = [
self.get_username(editor.strip())
editor.strip()
for editor in card_fields_dict.get("editor", [])
]
card_fields.illustrators = [
self.get_username(illustrator.strip())
illustrator.strip()
for illustrator in card_fields_dict.get("illustrator", [])
]
card_fields.cover = (
Expand All @@ -268,7 +268,7 @@ def get_members(self, board_id) -> List[objects.TrelloMember]:
logger.debug(f"get_members: {members}")
return members

def get_cards(self, list_ids, board_id=None):
def get_cards(self, list_ids=None, board_id=None):
if board_id is None:
board_id = self.board_id
_, data = self._make_request(f"api/v2/boards/{board_id}/blocks?all=true")
Expand All @@ -281,12 +281,19 @@ def get_cards(self, list_ids, board_id=None):
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
]
if list_ids:
data = [
card_dict
for card_dict in data
if card_dict["type"] == "card"
and card_dict["fields"]["properties"].get(list_prop, "") in list_ids
]
else:
data = [
card_dict
for card_dict in data
if card_dict["type"] == "card"
]
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}")
Expand Down
1 change: 1 addition & 0 deletions src/jobs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .shrug_job import ShrugJob
from .site_health_check_job import SiteHealthCheckJob
from .tg_analytics_report_job import TgAnalyticsReportJob
from .board_state_job import BoardStateJob
from .trello_board_state_job import TrelloBoardStateJob
from .trello_board_state_notifications_job import TrelloBoardStateNotificationsJob
from .trello_get_articles_arts_job import TrelloGetArticlesArtsJob
Expand Down
89 changes: 89 additions & 0 deletions src/jobs/board_state_job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import datetime
import logging
from typing import Callable, List

from ..app_context import AppContext
from ..consts import TrelloCardColor
from ..strings import load
from ..tg.sender import pretty_send
from ..trello.trello_objects import TrelloCard
from ..utils import card_checks_focalboard
from .base_job import BaseJob
from .utils import get_cards_by_curator, retrieve_usernames

logger = logging.getLogger(__name__)


class BoardStateJob(BaseJob):
@staticmethod
def _execute(
app_context: AppContext, send: Callable[[str], None], called_from_handler=False
):
paragraphs = [
load("trello_board_state_job__intro")
] # list of paragraph strings
curator_cards = get_cards_by_curator(app_context, focalboard=True)
for curator, curator_cards in curator_cards.items():
curator_name, _ = curator
card_paragraphs = []
curator_cards.sort(key=lambda c: c.due if c.due else datetime.datetime.min)
for card in curator_cards:
card_paragraph = BoardStateJob._format_card(
card,
card_checks_focalboard.make_card_failure_reasons(card, app_context),
app_context,
)
if card_paragraph:
card_paragraphs.append(card_paragraph)
if card_paragraphs:
paragraphs.append(f"⭐️ <b>Куратор</b>: {curator_name}")
paragraphs += card_paragraphs
pretty_send(paragraphs, send)

@staticmethod
def _format_card(
card: TrelloCard, failure_reasons: List[str], app_context: AppContext
) -> str:
if not failure_reasons:
return None

failure_reasons_formatted = ", ".join(failure_reasons)
labels = (
load(
"trello_board_state_job__card_labels",
names=", ".join(
# We filter BLACK cards as this is an auxiliary label
label.name
for label in card.labels
if label.color != TrelloCardColor.BLACK
),
)
if card.labels
else ""
)

# Avoiding message overflow, strip explanations in ()
list_name = card.lst.name + "("
list_name = list_name[: list_name.find("(")].strip()

members = (
load(
"trello_board_state_job__card_members",
members=", ".join(
retrieve_usernames(card.members, app_context.db_client)
),
curators="",
)
if card.members
else ""
)

return load(
"trello_board_state_job__card_2",
failure_reasons=failure_reasons_formatted,
url=card.url,
name=card.name,
labels=labels,
list_name=list_name,
members=members,
)
7 changes: 5 additions & 2 deletions src/jobs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,11 @@ def check_trello_card(
return True


def get_cards_by_curator(app_context: AppContext):
cards = app_context.trello_client.get_cards()
def get_cards_by_curator(app_context: AppContext, focalboard=False):
if focalboard:
cards = app_context.focalboard_client.get_cards()
else:
cards = app_context.trello_client.get_cards()
curator_cards = defaultdict(list)
for card in cards:
curators = get_curators_by_card(card, app_context.db_client)
Expand Down
131 changes: 131 additions & 0 deletions src/utils/card_checks_focalboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import datetime
from typing import Tuple

from ..app_context import AppContext
from ..consts import TrelloCardColor, TrelloListAlias
from ..strings import load
from ..trello.trello_objects import TrelloCard


def make_card_failure_reasons(card: TrelloCard, app_context: AppContext):
"""
Returns card description with failure reasons, if any.
If card does not match any of FILTER_TO_FAILURE_REASON, returns None.
"""
failure_reasons = []
for filter_func, reason_alias in FILTER_TO_FAILURE_REASON.items():
is_failed, kwargs = filter_func(card, app_context)
if is_failed:
reason = load(reason_alias, **kwargs)
if reason and len(failure_reasons) > 0:
reason = reason[0].lower() + reason[1:]
failure_reasons.append(reason)
return failure_reasons


def is_deadline_missed(card: TrelloCard, app_context: AppContext) -> Tuple[bool, dict]:
list_ids = app_context.focalboard_client.get_list_id_from_aliases(
[TrelloListAlias.IN_PROGRESS]
)
is_missed = (
card.lst.id in list_ids
and card.due is not None
and card.due.date() < datetime.datetime.now().date()
)
return is_missed, {"date": card.due.strftime("%d.%m")} if is_missed else {}


def is_due_date_missing(card: TrelloCard, app_context: AppContext) -> Tuple[bool, dict]:
if card.due:
return False, {}
list_ids = app_context.focalboard_client.get_list_id_from_aliases(
[TrelloListAlias.IN_PROGRESS]
)
return card.lst.id in list_ids, {}


def is_author_missing(card: TrelloCard, app_context: AppContext) -> Tuple[bool, dict]:
if card.members:
return False, {}

list_aliases = (
TrelloListAlias.IN_PROGRESS,
TrelloListAlias.TO_EDITOR,
TrelloListAlias.EDITED_NEXT_WEEK,
TrelloListAlias.TO_SEO_EDITOR,
TrelloListAlias.EDITED_SOMETIMES,
TrelloListAlias.TO_CHIEF_EDITOR,
TrelloListAlias.PROOFREADING,
TrelloListAlias.DONE,
)
list_ids = app_context.focalboard_client.get_list_id_from_aliases(list_aliases)
return card.lst.id in list_ids, {}


def is_tag_missing(card: TrelloCard, app_context: AppContext) -> Tuple[bool, dict]:
if card.labels:
return False, {}

list_aliases = (
TrelloListAlias.IN_PROGRESS,
TrelloListAlias.TO_EDITOR,
TrelloListAlias.EDITED_NEXT_WEEK,
TrelloListAlias.TO_SEO_EDITOR,
TrelloListAlias.EDITED_SOMETIMES,
TrelloListAlias.TO_CHIEF_EDITOR,
TrelloListAlias.PROOFREADING,
TrelloListAlias.DONE,
)
list_ids = app_context.focalboard_client.get_list_id_from_aliases(list_aliases)
return card.lst.id in list_ids, {}


def is_doc_missing(card: TrelloCard, app_context: AppContext) -> Tuple[bool, dict]:
list_aliases = (
TrelloListAlias.TO_EDITOR,
TrelloListAlias.EDITED_NEXT_WEEK,
TrelloListAlias.TO_SEO_EDITOR,
TrelloListAlias.EDITED_SOMETIMES,
TrelloListAlias.TO_CHIEF_EDITOR,
TrelloListAlias.PROOFREADING,
TrelloListAlias.DONE,
)
list_ids = app_context.focalboard_client.get_list_id_from_aliases(list_aliases)
if card.lst.id not in list_ids:
return False, {}

doc_url = app_context.focalboard_client.get_custom_fields(card.id).google_doc
return not doc_url, {}


def has_no_doc_access(card: TrelloCard, app_context: AppContext) -> Tuple[bool, dict]:
list_aliases = (
TrelloListAlias.TO_EDITOR,
TrelloListAlias.EDITED_NEXT_WEEK,
TrelloListAlias.TO_SEO_EDITOR,
TrelloListAlias.EDITED_SOMETIMES,
TrelloListAlias.TO_CHIEF_EDITOR,
TrelloListAlias.PROOFREADING,
TrelloListAlias.DONE,
)
list_ids = app_context.focalboard_client.get_list_id_from_aliases(list_aliases)
if card.lst.id not in list_ids:
return False, {}

doc_url = app_context.focalboard_client.get_custom_fields(card.id).google_doc
if not doc_url:
# should be handled by is_doc_missing
return False, {}

is_open_for_edit = app_context.drive_client.is_open_for_edit(doc_url)
return not is_open_for_edit, {}


FILTER_TO_FAILURE_REASON = {
is_author_missing: "trello_board_state_job__title_author_missing",
is_due_date_missing: "trello_board_state_job__title_due_date_missing",
is_deadline_missed: "trello_board_state_job__title_due_date_expired",
is_tag_missing: "trello_board_state_job__title_tag_missing",
is_doc_missing: "trello_board_state_job__title_no_doc",
has_no_doc_access: "trello_board_state_job__title_no_doc_access",
}

0 comments on commit 3adb76e

Please sign in to comment.