diff --git a/config.json b/config.json
index 2eff887b..d9adf32b 100644
--- a/config.json
+++ b/config.json
@@ -25,7 +25,6 @@
},
"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",
"curators_sheet_key": "do_not_set_here_please_go_to_config_override",
"hr_sheet_key": "do_not_set_here_please_go_to_config_override",
"hr_pt_sheet_key": "do_not_set_here_please_go_to_config_override",
diff --git a/src/app_context.py b/src/app_context.py
index 7dacdc58..fa15d5d3 100644
--- a/src/app_context.py
+++ b/src/app_context.py
@@ -6,14 +6,12 @@
from .db.db_client import DBClient
from .drive.drive_client import GoogleDriveClient
from .facebook.facebook_client import FacebookClient
-from .focalboard.focalboard_client import FocalboardClient
from .instagram.instagram_client import InstagramClient
from .n8n.n8n_client import N8nClient
from .planka.planka_client import PlankaClient
from .sheets.sheets_client import GoogleSheetsClient
from .strings import StringsDBClient
from .tg.tg_client import TgClient
-from .trello.trello_client import TrelloClient
from .utils.singleton import Singleton
logger = logging.getLogger(__name__)
@@ -47,12 +45,6 @@ def __init__(
self.strings_db_client.fetch_strings_sheet(self.sheets_client)
self.db_client.fetch_all(self.sheets_client)
- self.trello_client = TrelloClient(
- trello_config=config_manager.get_trello_config()
- )
- self.focalboard_client = FocalboardClient(
- focalboard_config=config_manager.get_focalboard_config()
- )
self.planka_client = PlankaClient(
planka_config=config_manager.get_planka_config()
)
diff --git a/src/config_manager.py b/src/config_manager.py
index eaa31808..d50ab183 100644
--- a/src/config_manager.py
+++ b/src/config_manager.py
@@ -57,12 +57,6 @@ def get_latest_jobs_config(self):
logger.debug(f"Got config, last updated: {self._latest_jobs_config_ts}")
return self._latest_jobs_config
- 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_planka_config(self):
return self.get_latest_config().get(consts.PLANKA_CONFIG, {})
diff --git a/src/consts.py b/src/consts.py
index 4786992d..f75de4ec 100644
--- a/src/consts.py
+++ b/src/consts.py
@@ -39,8 +39,6 @@ class AppSource(Enum):
# Upper level config keys
TELEGRAM_CONFIG = "telegram"
-TRELLO_CONFIG = "trello"
-FOCALBOARD_CONFIG = "focalboard"
PLANKA_CONFIG = "planka"
SHEETS_CONFIG = "sheets"
DRIVE_CONFIG = "drive"
@@ -59,9 +57,6 @@ class AppSource(Enum):
# Telegram keys
TELEGRAM_MANAGER_IDS = "manager_chat_ids"
-# Trello keys
-TRELLO_BOARD_ID = "board_id"
-
# Vk consts
VK_POST_LINK = "https://vk.com/{group_alias}?w=wall-{group_id}_{post_id}"
@@ -166,7 +161,6 @@ class PlainTextUserAction(Enum):
"""
# /get_tasks_report items
- GET_TASKS_REPORT__ENTER_BOARD_URL = "get_tasks_report__board_url"
GET_TASKS_REPORT__ENTER_BOARD_NUMBER = "get_tasks_report__board_number"
GET_TASKS_REPORT__ENTER_LIST_NUMBER = "get_tasks_report__list_number"
GET_TASKS_REPORT__ENTER_INTRO = "get_tasks_report__introduction"
diff --git a/src/db/db_client.py b/src/db/db_client.py
index 819516f3..e911bc26 100644
--- a/src/db/db_client.py
+++ b/src/db/db_client.py
@@ -8,8 +8,8 @@
from .. import consts
from ..sheets.sheets_client import GoogleSheetsClient
from ..utils.singleton import Singleton
+from ..utils.telegram import normalize_telegram_username
from .db_objects import (
- Author,
Base,
Chat,
Curator,
@@ -67,28 +67,10 @@ def _ensure_team_telegram_id_column(self):
)
def fetch_all(self, sheets_client: GoogleSheetsClient):
- self.fetch_authors_sheet(sheets_client)
self.fetch_curators_sheet(sheets_client)
self.fetch_team_sheet(sheets_client)
self.fetch_rubrics_sheet(sheets_client)
- def fetch_authors_sheet(self, sheets_client: GoogleSheetsClient):
- session = self.Session()
- try:
- # clean this table
- session.query(Author).delete()
- # re-download it
- authors = sheets_client.fetch_authors()
- for item in authors:
- author = Author.from_sheetfu_item(item)
- session.add(author)
- session.commit()
- except Exception as e:
- logger.warning("Failed to update authors table from sheet", exc_info=e)
- session.rollback()
- return 0
- return len(authors)
-
def fetch_curators_sheet(self, sheets_client: GoogleSheetsClient):
session = self.Session()
try:
@@ -201,17 +183,21 @@ def find_focalboard_username_by_telegram_username(self, telegram_username: str):
return member.focalboard
def find_author_telegram_by_trello(self, trello_id: str):
- # TODO: make batch queries
- session = self.Session()
- author = (
- session.query(Author)
- .filter((Author.trello == trello_id) | (Author.focalboard == trello_id))
- .first()
+ normalized = normalize_telegram_username(trello_id)
+ members = self.Session().query(TeamMember).all()
+ member = next(
+ (
+ m
+ for m in members
+ if normalize_telegram_username(m.trello) == normalized
+ or normalize_telegram_username(m.focalboard) == normalized
+ ),
+ None,
)
- if author is None:
- logger.warning(f"Telegram id not found for author {trello_id}")
+ if member is None:
+ logger.warning(f"Telegram id not found for team member {trello_id}")
return None
- return author.telegram
+ return member.telegram
def get_curator_by_telegram(self, telegram: str) -> Curator:
session = self.Session()
diff --git a/src/db/db_objects.py b/src/db/db_objects.py
index e3a7cbf5..dcf51c27 100644
--- a/src/db/db_objects.py
+++ b/src/db/db_objects.py
@@ -43,58 +43,10 @@ def process_result_value(self, value, dialect):
return value
-class Author(Base):
- __tablename__ = "authors"
-
- name = Column(String, primary_key=True)
- curator = Column(String)
- status = Column(String)
- telegram = Column(String)
- trello = Column(String)
- focalboard = Column(String)
-
- def __repr__(self):
- return f"Author {self.name} tg={self.telegram} trello={self.trello} f={self.focalboard}"
-
- @classmethod
- def from_dict(cls, data):
- author = cls()
- author.name = _get_str_data_item(data, "name")
- author.curator = _get_str_data_item(data, "curator")
- author.status = _get_str_data_item(data, "status")
- author.telegram = _get_str_data_item(data, "telegram")
- author.trello = _get_str_data_item(data, "trello")
- author.focalboard = _get_str_data_item(data, "focalboard")
- return author
-
- def to_dict(self):
- return {
- "name": self.name,
- "curator": self.curator,
- "status": self.status,
- "telegram": self.telegram,
- "trello": self.trello,
- "focalboard": self.focalboard,
- }
-
- @classmethod
- def from_sheetfu_item(cls, item):
- author = cls()
- author.name = item.get_field_value(load("sheets__what_is_your_name"))
- author.curator = item.get_field_value(load("sheets__curator_as_author"))
- author.status = item.get_field_value(load("sheets__status"))
- author.telegram = item.get_field_value(load("sheets__telegram"))
- author.trello = item.get_field_value(load("sheets__trello"))
- author.focalboard = item.get_field_value(load("sheets__focalboard"))
- return author
-
-
class Curator(Base):
__tablename__ = "curators"
- role = Column(
- String, ForeignKey("authors.curator"), primary_key=True
- ) # e.g. "Куратор NLP 1"
+ role = Column(String, primary_key=True) # e.g. "Куратор NLP 1"
name = Column(String, primary_key=True)
telegram = Column(String)
team = Column(String) # e.g. "Авторы"
diff --git a/src/drive/drive_client.py b/src/drive/drive_client.py
index d17a43d2..93ad24a2 100644
--- a/src/drive/drive_client.py
+++ b/src/drive/drive_client.py
@@ -11,7 +11,7 @@
from googleapiclient.http import MediaIoBaseDownload
from oauth2client.service_account import ServiceAccountCredentials
-from ..trello.trello_objects import TrelloCard
+from ..planka.board_objects import TrelloCard
from ..utils.singleton import Singleton
logger = logging.getLogger(__name__)
diff --git a/src/focalboard/focalboard_client.py b/src/focalboard/focalboard_client.py
deleted file mode 100644
index 4b4e622a..00000000
--- a/src/focalboard/focalboard_client.py
+++ /dev/null
@@ -1,508 +0,0 @@
-import json
-import logging
-from datetime import datetime
-from typing import List, Optional
-from urllib.parse import urljoin
-from cachetools import cached, TTLCache
-
-import requests
-
-from ..consts import TrelloCustomFieldTypeAlias, TrelloCustomFieldTypes, BoardListAlias
-from ..db import db_client
-from ..strings import load
-from ..trello import trello_objects as objects
-from ..utils.singleton import Singleton
-
-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, telegram_username: str = None, db_client=None):
- focal_username = None
- if telegram_username and db_client:
- raw_focal = db_client.find_focalboard_username_by_telegram_username(
- f"@{telegram_username}"
- )
- if raw_focal:
- focal_username = raw_focal.strip().lstrip("@").lower()
- logger.debug(
- f"Normalized focalboard username from DB = {focal_username!r}"
- )
- else:
- logger.warning(
- f"No focalboard username found for telegram={telegram_username!r}. "
- "Accessible boards can't be determined. Managers should fill the mapping table."
- )
- return []
- else:
- logger.debug(
- "No telegram_username provided, returning all boards (unfiltered)."
- )
-
- try:
- _, data = self._make_request("api/v2/teams/0/boards")
- all_boards = [
- objects.TrelloBoard.from_focalboard_dict(board) for board in data
- ]
- except Exception as e:
- logger.error("Error fetching boards from Focalboard", exc_info=e)
- return []
-
- if not focal_username:
- return all_boards
-
- accessible = []
- for board in all_boards:
- try:
- members = self.get_members(board.id)
- member_usernames = [
- m.username.strip().lstrip("@").lower()
- for m in members
- if m.username
- ]
- if focal_username in member_usernames:
- accessible.append(board)
- except Exception as e:
- logger.error(f"Error fetching members for board {board.id}", exc_info=e)
-
- logger.info(
- f"Telegram user @{telegram_username} has access to {len(accessible)} boards: "
- f"{[b.name for b in accessible]}"
- )
- return accessible
-
- def get_lists(self, board_id=None, sorted=False):
- if board_id is None:
- # default board
- board_id = self.board_id
- # TODO make it more efficient
- # essentially all list information is already passed via boards handler
- _, data = self._make_request("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"] == "Колонка"
- ][0]
- lists_data = list_data["options"]
- lists = [
- objects.TrelloList.from_focalboard_dict(trello_list, board_id)
- for trello_list in lists_data
- ]
- if sorted:
- # we need to get sorting order from the view, which is currently not efficient
- try:
- _, data = self._make_request(
- f"api/v2/boards/{board_id}/blocks?all=true"
- )
- view = [card_dict for card_dict in data if card_dict["type"] == "view"][
- 0
- ]
- order = view["fields"]["visibleOptionIds"]
- sorted_lists = []
- for list_id in order:
- this_list = [lst for lst in lists if lst.id == list_id]
- # this is needed to fix a strange bug
- # sometimes Focalboard returns ID in visibleOptionIds but not in a full list
- if this_list:
- sorted_lists.append(this_list[0])
- lists = sorted_lists
- except Exception as e:
- logger.error("can't sort focalboard lists", exc_info=e)
- logger.debug(f"get_lists: {lists}")
- return lists
-
- def get_list(self, board_id, list_id):
- _, data = self._make_request("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"] == "Колонка"
- ][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_labels(self, board_id=None):
- if board_id is None:
- # default board
- board_id = self.board_id
- _, data = self._make_request("api/v2/teams/0/boards")
- label_data = [
- prop
- for prop in [board for board in data if board["id"] == board_id][0][
- "cardProperties"
- ]
- if prop["name"] == "Рубрика"
- ][0]
- labels_data = label_data["options"]
- labels = [
- objects.TrelloCardLabel.from_focalboard_dict(label) for label in labels_data
- ]
- return labels
-
- def _get_list_property(self, board_id):
- _, data = self._make_request("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"] == "Колонка"
- ][0]["id"]
-
- def _get_label_property(self, board_id=None):
- if board_id is None:
- # default board
- board_id = self.board_id
- _, data = self._make_request("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"] == "Рубрика"
- ][0]["id"]
-
- def _get_due_property(self, board_id=None):
- if board_id is None:
- # default board
- board_id = self.board_id
- _, data = self._make_request("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"] == "Дедлайн"
- ][0]["id"]
-
- def get_card_due(self, card_id: str, board_id: str) -> Optional[datetime]:
- _, data = self._make_request(f"api/v2/cards/{card_id}")
- due_id = self._get_due_property(board_id)
-
- raw = data["properties"].get(due_id)
- if not raw:
- return None
-
- try:
- payload = json.loads(raw)
- except json.JSONDecodeError:
- logger.error(f"Cannot parse due field for card {card_id}: {raw}")
- return None
-
- ts_ms = payload.get("to")
- if not ts_ms:
- ts_ms = payload.get("from")
- if not ts_ms:
- return None
-
- return datetime.fromtimestamp(ts_ms / 1000)
-
- def _get_member_property(self, board_id):
- _, data = self._make_request("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"] == "Ответственный"
- ][0]["id"]
-
- def get_list_id_from_aliases(self, list_aliases):
- list_ids = [
- self.lists_config[alias]
- for alias in list_aliases
- if alias in self.lists_config
- ]
- if len(list_ids) != len(list_aliases):
- logger.error(
- f"list_ids not found for aliases: "
- f"{[alias for alias in list_aliases if alias not in self.lists_config]}"
- )
- return list_ids
-
- def _fill_alias_id_map(self, items, item_enum):
- result = {}
- for alias in item_enum:
- suitable_items = [
- item for item in items if item.name.startswith(load(alias.value))
- ]
- if len(suitable_items) > 1:
- raise ValueError(
- f"Enum {item_enum.__name__} name {alias.value} is ambiguous!"
- )
- if len(suitable_items) > 0:
- result[alias] = suitable_items[0].id
- return result
-
- def _fill_id_type_map(self, items, item_enum):
- result = {}
- for item in items:
- result[item.id] = TrelloCustomFieldTypes(item.type)
- return result
-
- def get_board_custom_field_types(self):
- board_id = self.board_id
- _, data = self._make_request(f"api/v2/boards/{board_id}")
- custom_field_types = [
- objects.TrelloCustomFieldType.from_focalboard_dict(custom_field_type)
- for custom_field_type in data["cardProperties"]
- ]
- logger.debug(f"get_board_custom_field_types: {custom_field_types}")
- return custom_field_types
-
- def get_custom_fields(self, card_id: str) -> objects.CardCustomFields:
- card_fields = objects.CardCustomFields(card_id)
- board_labels = self._get_labels()
- card_fields_dict = {}
- card_labels_ids = []
- card_labels = []
- _, data = self._make_request(f"api/v2/cards/{card_id}")
- fields = data["properties"]
- for alias, type_id in self.custom_fields_config.items():
- if type_id in fields:
- changed_alias = alias.name.split(".")[-1].lower()
- card_fields_dict[changed_alias] = fields[type_id]
-
- board_label_id = self._get_label_property()
-
- for type_id, value in fields.items():
- if type_id == board_label_id:
- card_labels_ids = value
-
- if isinstance(card_labels_ids, str):
- card_labels_ids = [card_labels_ids]
-
- for card_label_id in card_labels_ids:
- for board_label in board_labels:
- if card_label_id == board_label.id:
- card_labels.append(board_label)
-
- card_fields.authors = [
- author.strip() for author in card_fields_dict.get("author", "").split(",")
- ]
- card_fields.editors = [
- editor.strip() for editor in card_fields_dict.get("editor", "").split(",")
- ]
- card_fields.illustrators = [
- illustrator.strip()
- for illustrator in card_fields_dict.get("illustrator", "").split(",")
- ]
- card_fields.cover = (
- card_fields_dict["cover"] if "cover" in card_fields_dict else None
- )
- card_fields.google_doc = (
- card_fields_dict["google_doc"] if "google_doc" in card_fields_dict else None
- )
- card_fields.title = (
- card_fields_dict["title"] if "title" in card_fields_dict else None
- )
-
- card_fields._data = card_labels
- return card_fields
-
- def set_card_custom_field(self, card: objects.TrelloCard, field_alias, value):
- board_id = self.board_id
- field_id = self.custom_fields_config[field_alias]
- data = {"updatedFields": {"properties": card._fields_properties}}
- data["updatedFields"]["properties"][field_id] = value
- code = self._make_patch_request(
- f"api/v2/boards/{board_id}/blocks/{card.id}", payload=data
- )
- logger.debug(f"set_card_custom_field: {code}")
-
- 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._get_member(member["userId"])
- members.append(objects.TrelloMember.from_focalboard_dict(data))
- logger.debug(f"get_members: {members}")
- return members
-
- @cached(cache=TTLCache(maxsize=1000, ttl=60 * 60 * 24 * 30)) # cache for 30d
- def _get_member(self, user_id):
- return self._make_request(f"api/v2/users/{user_id}")
-
- 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")
- cards = []
- # TODO: move this to app state
- members = self.get_members(board_id)
- lists = self.get_lists(board_id=board_id)
- list_prop = self._get_list_property(board_id)
- labels = self._get_labels()
- label_prop = self._get_label_property(board_id)
- due_prop = self._get_due_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"
- ]
- 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}")
- card.labels = []
- raw_labels = card._fields_properties.get(label_prop, [])
- if isinstance(raw_labels, str):
- raw_labels = [raw_labels]
- for label_id in raw_labels:
- for label in labels:
- if label.id == label_id:
- card.labels.append(label)
- try:
- due = card._fields_properties.get(due_prop, "{}")
- due_ts = json.loads(due).get("to")
- if due_ts:
- card.due = datetime.fromtimestamp(due_ts // 1000)
- except Exception as e:
- print(due)
- print(e)
- # TODO: move this to app state
- for trello_list in lists:
- if trello_list.id == card._fields_properties.get(list_prop, ""):
- card.lst = trello_list
- break
- else:
- logger.error(
- f"List name not found for {card}: {card._fields_properties.get(list_prop, '')}"
- )
- # TODO: move this to app state
- raw_members = card._fields_properties.get(member_prop, [])
- if isinstance(raw_members, str):
- raw_members = [raw_members]
- if len(raw_members) > 0:
- for member_id in raw_members:
- # check if member is in board members
- matched_member = next(
- (m for m in members if m.id == member_id), None
- )
- if matched_member:
- card.members.append(matched_member)
- else:
- # fallback: User left board but is still assigned to card. Fetch them explicitly.
- try:
- _, m_data = self._get_member(member_id)
- m_obj = objects.TrelloMember.from_focalboard_dict(m_data)
- card.members.append(m_obj)
- except Exception as e:
- logger.error(
- f"Failed to fetch user {member_id} for {card}: {e}"
- )
-
- 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.board_id = self._focalboard_config["board_id"]
- self.headers = {
- "Accept": "application/json",
- "Content-Type": "application/json",
- "Authorization": f"Bearer {self.token}",
- "X-Requested-With": "XMLHttpRequest",
- }
- try:
- lists = self.get_lists()
- self.lists_config = self._fill_alias_id_map(lists, BoardListAlias)
- custom_field_types = self.get_board_custom_field_types()
- self.custom_fields_type_config = self._fill_id_type_map(
- custom_field_types, TrelloCustomFieldTypes
- )
- self.custom_fields_config = self._fill_alias_id_map(
- custom_field_types, TrelloCustomFieldTypeAlias
- )
- except Exception as e:
- # TODO remove this when main board is migrated
- logger.error(
- "something went wrong when setting up focalboard client", exc_info=e
- )
- pass
-
- 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()
-
- def _make_patch_request(self, uri, payload={}):
- response = requests.patch(
- urljoin(self.url, uri), json=payload, headers=self.headers
- )
- logger.debug(f"{response.url}")
- return response.status_code, response.json()
-
- def get_boards_for_telegram_user(
- self,
- telegram_username: str,
- db_client: db_client.DBClient,
- ) -> List[objects.TrelloBoard]:
- raw_focal = db_client.find_focalboard_username_by_telegram_username(
- f"@{telegram_username}"
- )
- if raw_focal:
- focal_username = raw_focal.strip().lstrip("@").lower()
-
- logger.debug(f"Normalized focalboard username from DB = {focal_username!r}")
-
- else:
- logger.warning(
- f"No focalboard username found for telegram={telegram_username!r}. "
- f"Accessible boards can't be determined. Managers should check and fill the table."
- )
-
- return []
-
- # 2) Fetch all boards
- all_boards = self.get_boards_for_user()
-
- # 3) Filter by membership
- accessible = []
- for board in all_boards:
- try:
- members = self.get_members(board.id)
- if focal_username in [
- m.username.strip().lstrip("@").lower() for m in members
- ]:
- accessible.append(board)
- except Exception as e:
- logger.error(f"Error fetching members for board {board.id}", exc_info=e)
-
- return accessible
diff --git a/src/jobs/__init__.py b/src/jobs/__init__.py
index e701b5bb..61326929 100644
--- a/src/jobs/__init__.py
+++ b/src/jobs/__init__.py
@@ -10,13 +10,10 @@
from .config_updater_job import ConfigUpdaterJob
from .create_folders_for_illustrators_job import CreateFoldersForIllustratorsJob
from .db_fetch_all_team_members_job import DBFetchAllTeamMembersJob
-from .db_fetch_authors_sheet_job import DBFetchAuthorsSheetJob
from .db_fetch_curators_sheet_job import DBFetchCuratorsSheetJob
from .db_fetch_strings_sheet_job import DBFetchStringsSheetJob
from .db_fetch_team_sheet_job import DBFetchTeamSheetJob
from .fb_analytics_report_job import FBAnalyticsReportJob
-from .fill_posts_list_focalboard_job import FillPostsListFocalboardJob
-from .fill_posts_list_job import FillPostsListJob
from .hr_acquisition_job import HRAcquisitionJob
from .hr_acquisition_pt_job import HRAcquisitionPTJob
from .hr_status_job import HRStatusJob
diff --git a/src/jobs/board_my_cards_razvitie_job.py b/src/jobs/board_my_cards_razvitie_job.py
index 9b0c798c..1ff34cae 100644
--- a/src/jobs/board_my_cards_razvitie_job.py
+++ b/src/jobs/board_my_cards_razvitie_job.py
@@ -4,7 +4,7 @@
from ..app_context import AppContext
from ..strings import load
from ..tg.handlers.get_tasks_report_handler import _make_cards_text
-from ..trello.trello_objects import TrelloCard
+from ..planka.board_objects import TrelloCard
from .base_job import BaseJob
logger = logging.getLogger(__name__)
@@ -72,7 +72,7 @@ def _execute(
]
if my_cards:
list_report = BoardMyCardsRazvitieJob._create_paragraphs_from_cards(
- my_cards, f"📜 {board_list.name}", True, app_context
+ my_cards, f"📜 {board_list.name}", True
)
paragraphs += list_report
paragraphs.append("") # hotfix for separating lists
@@ -86,13 +86,12 @@ def _create_paragraphs_from_cards(
cards: Iterable[TrelloCard],
introduction: str,
need_label: bool,
- app_context: AppContext,
):
paragraphs = []
if introduction:
paragraphs.append(introduction)
- paragraphs += _make_cards_text(cards, need_label, app_context)
+ paragraphs += _make_cards_text(cards, need_label)
return paragraphs
@staticmethod
diff --git a/src/jobs/config_updater_job.py b/src/jobs/config_updater_job.py
index 78080d67..62401eb7 100644
--- a/src/jobs/config_updater_job.py
+++ b/src/jobs/config_updater_job.py
@@ -54,14 +54,6 @@ def _execute(
app_context.tg_client.update_config(tg_config)
# update admins and managers
app_context.set_access_rights(tg_config)
- # update config['trello']
- 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()
diff --git a/src/jobs/db_fetch_all_team_members_job.py b/src/jobs/db_fetch_all_team_members_job.py
index 8fe37db6..4a3f20f1 100644
--- a/src/jobs/db_fetch_all_team_members_job.py
+++ b/src/jobs/db_fetch_all_team_members_job.py
@@ -13,12 +13,6 @@ class DBFetchAllTeamMembersJob(BaseJob):
def _execute(
app_context: AppContext, send: Callable[[str], None], called_from_handler=False
):
- num_authors = app_context.db_client.fetch_authors_sheet(
- app_context.sheets_client
- )
- logger.info(f"Fetched {num_authors} authors")
- send(load("db_fetch_authors_sheet_job__success", num_authors=num_authors))
-
num_curators = app_context.db_client.fetch_curators_sheet(
app_context.sheets_client
)
diff --git a/src/jobs/db_fetch_authors_sheet_job.py b/src/jobs/db_fetch_authors_sheet_job.py
deleted file mode 100644
index fcc00385..00000000
--- a/src/jobs/db_fetch_authors_sheet_job.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import logging
-from typing import Callable
-
-from ..app_context import AppContext
-from ..strings import load
-from .base_job import BaseJob
-
-logger = logging.getLogger(__name__)
-
-
-class DBFetchAuthorsSheetJob(BaseJob):
- @staticmethod
- def _execute(
- app_context: AppContext, send: Callable[[str], None], called_from_handler=False
- ):
- num_authors = app_context.db_client.fetch_authors_sheet(
- app_context.sheets_client
- )
- logger.info(f"Fetched {num_authors} authors")
- send(load("db_fetch_authors_sheet_job__success", num_authors=num_authors))
diff --git a/src/jobs/fill_posts_list_focalboard_job.py b/src/jobs/fill_posts_list_focalboard_job.py
deleted file mode 100644
index 72e75f21..00000000
--- a/src/jobs/fill_posts_list_focalboard_job.py
+++ /dev/null
@@ -1,136 +0,0 @@
-import datetime
-import logging
-from typing import Callable, List
-
-from ..app_context import AppContext
-from ..consts import BoardCardColor, TrelloCardColor, BoardListAlias
-from ..focalboard.focalboard_client import FocalboardClient
-from ..sheets.sheets_objects import RegistryPost
-from ..strings import load
-from ..tg.sender import pretty_send
-from .base_job import BaseJob
-from .utils import check_trello_card, format_errors
-
-logger = logging.getLogger(__name__)
-
-
-class FillPostsListFocalboardJob(BaseJob):
- @staticmethod
- def _execute(
- app_context: AppContext,
- send: Callable[[str], None],
- called_from_handler=False,
- ):
- errors = {}
- registry_posts = []
- all_rubrics = app_context.db_client.get_rubrics()
-
- registry_posts += FillPostsListFocalboardJob._retrieve_cards_for_registry(
- focalboard_client=app_context.focalboard_client,
- list_aliases=(
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- ),
- all_rubrics=all_rubrics,
- errors=errors,
- )
-
- if len(errors) == 0:
- posts_added = app_context.sheets_client.update_posts_registry(
- registry_posts
- )
- if len(posts_added) == 0:
- paragraphs = [load("fill_posts_list_job__unchanged")]
- else:
- paragraphs = [load("fill_posts_list_job__success")] + [
- "\n".join(
- f"{index + 1}) {post_name}"
- for index, post_name in enumerate(posts_added)
- )
- ]
- else:
- paragraphs = format_errors(errors)
-
- pretty_send(paragraphs, send)
-
- @staticmethod
- def _retrieve_cards_for_registry(
- focalboard_client: FocalboardClient,
- list_aliases: List[str],
- errors: dict,
- all_rubrics: List,
- show_due=True,
- need_illustrators=True,
- strict_archive_rules=True,
- ) -> List[str]:
- """
- Returns a list of paragraphs that should always go in a single message.
- """
- list_ids = focalboard_client.get_list_id_from_aliases(list_aliases)
- cards = focalboard_client.get_cards(list_ids)
- if show_due:
- cards.sort(key=lambda card: card.due or datetime.datetime.min)
- parse_failure_counter = 0
-
- registry_posts = []
-
- for card in cards:
- if not card:
- parse_failure_counter += 1
- continue
-
- card_fields = focalboard_client.get_custom_fields(card.id)
-
- label_names = [label.name for label in card_fields._data]
- card.labels = card_fields._data
- is_main_post = load("common_trello_label__main_post") in label_names
- is_archive_post = load("common_trello_label__archive") in label_names
-
- card.due = focalboard_client.get_card_due(
- card.id, focalboard_client.board_id
- )
- card_is_ok = check_trello_card(
- card,
- errors,
- is_bad_title=(
- card_fields.title is None
- and card.lst.id
- != focalboard_client.lists_config[BoardListAlias.PENDING_EDITOR_5]
- ),
- is_bad_google_doc=card_fields.google_doc is None,
- is_bad_authors=len(card_fields.authors) == 0,
- is_bad_editors=len(card_fields.editors) == 0,
- is_bad_cover=card_fields.cover is None and not is_archive_post,
- is_bad_illustrators=(
- len(card_fields.illustrators) == 0
- and need_illustrators
- and not is_archive_post
- ),
- is_bad_due_date=card.due is None and show_due,
- is_bad_label_names=len(
- [
- label
- for label in card.labels
- if label.color
- not in [TrelloCardColor.BLACK, BoardCardColor.BLACK]
- ]
- )
- == 0,
- )
-
- if not card_is_ok:
- continue
-
- registry_posts.append(
- RegistryPost(
- card,
- card_fields,
- is_main_post,
- is_archive_post,
- all_rubrics,
- )
- )
-
- if parse_failure_counter > 0:
- logger.error(f"Unparsed cards encountered: {parse_failure_counter}")
- return registry_posts
diff --git a/src/jobs/fill_posts_list_job.py b/src/jobs/fill_posts_list_job.py
deleted file mode 100644
index d89ea2bc..00000000
--- a/src/jobs/fill_posts_list_job.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import datetime
-import logging
-from typing import Callable, List
-
-from ..app_context import AppContext
-from ..consts import BoardCardColor, TrelloCardColor, BoardListAlias
-from ..sheets.sheets_objects import RegistryPost
-from ..strings import load
-from ..tg.sender import pretty_send
-from ..trello.trello_client import TrelloClient
-from .base_job import BaseJob
-from .utils import check_trello_card, format_errors
-
-logger = logging.getLogger(__name__)
-
-
-class FillPostsListJob(BaseJob):
- @staticmethod
- def _execute(
- app_context: AppContext, send: Callable[[str], None], called_from_handler=False
- ):
- errors = {}
- registry_posts = []
- all_rubrics = app_context.db_client.get_rubrics()
-
- registry_posts += FillPostsListJob._retrieve_cards_for_registry(
- trello_client=app_context.trello_client,
- list_aliases=[BoardListAlias.PUBLISH_DONE_11],
- all_rubrics=all_rubrics,
- errors=errors,
- show_due=True,
- strict_archive_rules=True,
- )
-
- if len(errors) == 0:
- posts_added = app_context.sheets_client.update_posts_registry(
- registry_posts
- )
- if len(posts_added) == 0:
- paragraphs = [load("fill_posts_list_job__unchanged")]
- else:
- paragraphs = [load("fill_posts_list_job__success")] + [
- "\n".join(
- f"{index + 1}) {post_name}"
- for index, post_name in enumerate(posts_added)
- )
- ]
- else:
- paragraphs = format_errors(errors)
-
- pretty_send(paragraphs, send)
-
- @staticmethod
- def _retrieve_cards_for_registry(
- trello_client: TrelloClient,
- list_aliases: List[str],
- errors: dict,
- all_rubrics: List,
- show_due=True,
- need_illustrators=True,
- strict_archive_rules=True,
- ) -> List[str]:
- """
- Returns a list of paragraphs that should always go in a single message.
- """
- list_ids = trello_client.get_list_id_from_aliases(list_aliases)
- cards = trello_client.get_cards(list_ids)
- if show_due:
- cards.sort(key=lambda card: card.due or datetime.datetime.min)
- parse_failure_counter = 0
-
- registry_posts = []
-
- for card in cards:
- label_names = [label.name for label in card.labels]
- is_main_post = load("common_trello_label__main_post") in label_names
- is_archive_post = load("common_trello_label__archive") in label_names
-
- if not card:
- parse_failure_counter += 1
- continue
-
- card_fields = trello_client.get_custom_fields(card.id)
-
- card_is_ok = check_trello_card(
- card,
- errors,
- is_bad_title=(
- card_fields.title is None
- and card.lst.id
- != trello_client.lists_config[BoardListAlias.PENDING_EDITOR_5]
- ),
- is_bad_google_doc=card_fields.google_doc is None,
- is_bad_authors=len(card_fields.authors) == 0,
- is_bad_editors=len(card_fields.editors) == 0,
- is_bad_cover=card_fields.cover is None and not is_archive_post,
- is_bad_illustrators=(
- len(card_fields.illustrators) == 0
- and need_illustrators
- and not is_archive_post
- ),
- is_bad_due_date=card.due is None and show_due,
- is_bad_label_names=len(
- [
- label
- for label in card.labels
- if label.color
- not in [TrelloCardColor.BLACK, BoardCardColor.BLACK]
- ]
- )
- == 0,
- )
-
- if not card_is_ok:
- continue
-
- registry_posts.append(
- RegistryPost(
- card,
- card_fields,
- is_main_post,
- is_archive_post,
- all_rubrics,
- )
- )
-
- if parse_failure_counter > 0:
- logger.error(f"Unparsed cards encountered: {parse_failure_counter}")
- return registry_posts
diff --git a/src/jobs/hr_acquisition_job.py b/src/jobs/hr_acquisition_job.py
index eef5b406..73c3305a 100644
--- a/src/jobs/hr_acquisition_job.py
+++ b/src/jobs/hr_acquisition_job.py
@@ -7,6 +7,7 @@
from ..sheets.sheets_objects import HRPersonProcessed, HRPersonRaw
from ..strings import load
from ..tg.sender import pretty_send
+from ..utils.telegram import normalize_telegram_username
from .base_job import BaseJob
logger = logging.getLogger(__name__)
@@ -47,10 +48,6 @@ def _process_new_people(
return new_items
- @staticmethod
- def _normalize_telegram(username: str) -> str:
- return username.strip().lstrip("@").lower()
-
@staticmethod
def _process_raw_forms(
forms_raw: Table, forms_processed: Table
@@ -59,7 +56,7 @@ def _process_raw_forms(
existing_people = [person for person in people if person.status]
new_people = [person for person in people if not person.status]
existing_telegrams = {
- HRAcquisitionJob._normalize_telegram(p.telegram)
+ normalize_telegram_username(p.telegram)
for p in existing_people
if p.telegram
}
@@ -71,9 +68,9 @@ def _process_raw_forms(
person.status = load("sheets__hr__raw__status_rejection")
continue
if person.telegram:
- normalized = HRAcquisitionJob._normalize_telegram(person.telegram)
+ normalized = normalize_telegram_username(person.telegram)
new_telegrams = {
- HRAcquisitionJob._normalize_telegram(p.telegram)
+ normalize_telegram_username(p.telegram)
for p in new_items
if p.telegram
}
diff --git a/src/jobs/hr_acquisition_pt_job.py b/src/jobs/hr_acquisition_pt_job.py
index 1328f215..0353f3ac 100644
--- a/src/jobs/hr_acquisition_pt_job.py
+++ b/src/jobs/hr_acquisition_pt_job.py
@@ -7,6 +7,7 @@
from ..sheets.sheets_objects import HRPersonPTProcessed, HRPersonPTRaw
from ..strings import load
from ..tg.sender import pretty_send
+from ..utils.telegram import normalize_telegram_username
from .base_job import BaseJob
logger = logging.getLogger(__name__)
@@ -47,10 +48,6 @@ def _process_new_people(
return new_items
- @staticmethod
- def _normalize_telegram(username: str) -> str:
- return username.strip().lstrip("@").lower()
-
@staticmethod
def _process_raw_forms(
forms_raw: Table, forms_processed: Table
@@ -59,7 +56,7 @@ def _process_raw_forms(
existing_people = [person for person in people if person.status]
new_people = [person for person in people if not person.status]
existing_telegrams = {
- HRAcquisitionPTJob._normalize_telegram(p.telegram)
+ normalize_telegram_username(p.telegram)
for p in existing_people
if p.telegram
}
@@ -70,11 +67,9 @@ def _process_raw_forms(
if not person.telegram:
person.status = load("sheets__hr__pt__raw__status_rejection")
continue
- normalized = HRAcquisitionPTJob._normalize_telegram(person.telegram)
+ normalized = normalize_telegram_username(person.telegram)
new_telegrams = {
- HRAcquisitionPTJob._normalize_telegram(p.telegram)
- for p in new_items
- if p.telegram
+ normalize_telegram_username(p.telegram) for p in new_items if p.telegram
}
if normalized in existing_telegrams or normalized in new_telegrams:
person.status = load("sheets__hr__pt__raw__status_double")
diff --git a/src/jobs/trello_get_articles_rubric_job.py b/src/jobs/trello_get_articles_rubric_job.py
index f672641a..890990ec 100644
--- a/src/jobs/trello_get_articles_rubric_job.py
+++ b/src/jobs/trello_get_articles_rubric_job.py
@@ -2,10 +2,9 @@
from ..app_context import AppContext
from ..consts import BoardListAlias
+from ..planka.planka_client import PlankaClient
from ..strings import load
from ..tg.sender import pretty_send
-from ..trello.trello_client import TrelloClient
-from ..trello.trello_objects import TrelloCard
from . import utils
from .base_job import BaseJob
@@ -44,8 +43,7 @@ def _execute(
BoardListAlias.PUBLISH_DONE_11,
]:
paragraphs += TrelloGetArticlesRubricJob._get_rubric_paragraphs(
- app_context=app_context,
- trello_client=app_context.trello_client,
+ planka_client=app_context.planka_client,
rubric_title=load(alias.value),
rubric_alias=alias,
rubric_name=rubric_name,
@@ -54,11 +52,8 @@ def _execute(
pretty_send(paragraphs, send)
@staticmethod
- def _format_card(card: TrelloCard, app_context: AppContext) -> str:
- if not app_context.trello_client.deprecated:
- card_fields = app_context.trello_client.get_custom_fields(card.id)
- else:
- card_fields = app_context.focalboard_client.get_custom_fields(card.id)
+ def _format_card(card, planka_client: PlankaClient) -> str:
+ card_fields = planka_client.get_custom_fields(card.id)
return load(
"rubric_report_job__card",
date=card.due.strftime("%d.%m").lower() if card.due else "",
@@ -70,24 +65,18 @@ def _format_card(card: TrelloCard, app_context: AppContext) -> str:
)
def _get_rubric_paragraphs(
- app_context: AppContext,
- trello_client: TrelloClient,
+ planka_client: PlankaClient,
rubric_title: str,
rubric_alias: str,
rubric_name: str,
) -> List[str]:
- if not trello_client.deprecated:
- list_ids = trello_client.get_list_id_from_aliases([rubric_alias])
- cards = trello_client.get_cards(list_ids)
- else:
- list_ids = app_context.focalboard_client.get_list_id_from_aliases(
- [rubric_alias]
- )
- cards = app_context.focalboard_client.get_cards(list_ids)
- cards_filtered = []
- for card in cards:
- if rubric_name in [label.name for label in card.labels]:
- cards_filtered.append(card)
+ list_ids = planka_client.get_list_id_from_aliases([rubric_alias])
+ cards = planka_client.get_cards(list_ids)
+ cards_filtered = [
+ card
+ for card in cards
+ if rubric_name in [label.name for label in card.labels]
+ ]
paragraphs = [
load(
@@ -97,6 +86,7 @@ def _get_rubric_paragraphs(
)
]
for card in cards_filtered:
- formatted_card = TrelloGetArticlesRubricJob._format_card(card, app_context)
- paragraphs.append(formatted_card)
+ paragraphs.append(
+ TrelloGetArticlesRubricJob._format_card(card, planka_client)
+ )
return paragraphs
diff --git a/src/jobs/utils.py b/src/jobs/utils.py
index fab4c89a..be43c65a 100644
--- a/src/jobs/utils.py
+++ b/src/jobs/utils.py
@@ -1,17 +1,14 @@
import datetime
import inspect
import logging
-from collections import defaultdict
from typing import List
from .. import jobs
-from ..app_context import AppContext
from ..consts import BoardCardColor, TrelloCardColor, TrelloCardFieldErrorAlias
from ..db.db_client import DBClient
-from ..db.db_objects import Curator
from ..drive.drive_client import GoogleDriveClient
from ..strings import load
-from ..trello.trello_objects import TrelloMember
+from ..planka.board_objects import TrelloMember
logger = logging.getLogger(__name__)
@@ -44,59 +41,6 @@ def retrieve_usernames(
return [retrieve_username(member, db_client) for member in trello_members]
-def retrieve_curator_names_by_author(
- trello_member: TrelloMember, db_client: DBClient
-) -> List[str]:
- """
- Tries to find a curator for trello member. Returns nothing if user is curator.
- Returns: "John Smith (@jsmith_tg)" where possible, otherwise "John Smith".
- If trello member or curator could not be found in Authors sheet, returns None
- """
- trello_id = "@" + trello_member.username
- try:
- curator = db_client.get_curator_by_trello_id(trello_id)
- if curator:
- curators = [curator]
- else:
- curators = db_client.find_curators_by_author_trello(trello_id)
- except Exception as e:
- logger.error("Could not retrieve curators by author", exc_info=e)
- return
- if not curators:
- return []
- return [_make_curator_string(curator) for curator in curators]
-
-
-def retrieve_curator_names_by_categories(labels: List[str], db_client: DBClient):
- """
- To be used when there is no known authors.
- Category is a trello label (e.g. NLP)
- """
- curators = set()
- try:
- for label in labels:
- curators = curators.union(
- set(db_client.find_curators_by_trello_label(label.name))
- )
- except Exception as e:
- logger.error("Could not retrieve curators by category", exc_info=e)
- return
- if not curators:
- return []
- return [_make_curator_string(curator) for curator in curators]
-
-
-def _make_curator_string(curator: Curator):
- """
- Returns: (pretty_curator_string, tg_login_or_None)
- """
- if curator.name:
- if curator.telegram:
- return f"{curator.name} ({curator.telegram})", curator.telegram
- return curator.name, None
- return curator.telegram, curator.telegram
-
-
def get_job_runnable(job_id: str):
"""
Finds a job class inside a module and returns its execute method.
@@ -271,33 +215,3 @@ def check_trello_card(
errors[card] = this_card_bad_fields
return False
return True
-
-
-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)
- if not curators:
- # TODO: get main curator from spreadsheet
- curators = [("Илья Булгаков (@bulgak0v)", "@bulgak0v")]
- for curator in curators:
- curator_cards[curator].append(card)
-
- return curator_cards
-
-
-def get_curators_by_card(card, db_client):
- curators = set()
- for member in card.members:
- curator_names = retrieve_curator_names_by_author(member, db_client)
- curators.update(curator_names)
- if curators:
- return curators
-
- # e.g. if no members in a card, should tag curators based on label
- curators_by_label = retrieve_curator_names_by_categories(card.labels, db_client)
- return curators_by_label
diff --git a/src/planka/board_objects.py b/src/planka/board_objects.py
new file mode 100644
index 00000000..988d189e
--- /dev/null
+++ b/src/planka/board_objects.py
@@ -0,0 +1,121 @@
+TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
+
+
+class TrelloBoard:
+ def __init__(self):
+ self.id = None
+ self.name = None
+ self.url = None
+ self._ok = True
+
+ def __bool__(self):
+ return self._ok
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return f"Board"
+
+ def to_dict(self):
+ return {"id": self.id, "name": self.name, "url": self.url}
+
+
+class TrelloList:
+ def __init__(self):
+ self.id = None
+ self.name = None
+ self.board_id = None
+ self._ok = True
+
+ def __bool__(self):
+ return self._ok
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return f"List"
+
+ def to_dict(self):
+ return {"id": self.id, "name": self.name, "board_id": self.board_id}
+
+
+class TrelloCardLabel:
+ def __init__(self):
+ self.id = None
+ self.name = None
+ self.color = None
+ self._ok = True
+
+ def __bool__(self):
+ return self._ok
+
+ def __str__(self):
+ return self.name
+
+ def __repr__(self):
+ return f"CardLabel"
+
+
+class TrelloCard:
+ def __init__(self):
+ self.id = None
+ self.name = None
+ self.labels = []
+ self.url = None
+ self.due = None
+ self.lst = None
+ self.members = []
+ self._ok = True
+
+ def __bool__(self):
+ return self._ok
+
+ def __str__(self):
+ return self.url
+
+ def __repr__(self):
+ return f"Card"
+
+ def __eq__(self, other):
+ return str(self) == str(other)
+
+ def __hash__(self):
+ return hash(self.id)
+
+
+class TrelloMember:
+ def __init__(self):
+ self.id = None
+ self.username = None
+ self.full_name = None
+
+ def __str__(self):
+ return self.username
+
+ def __repr__(self):
+ return f"Member"
+
+ def __eq__(self, other):
+ return isinstance(other, TrelloMember) and self.username == other.username
+
+ def __lt__(self, other):
+ return isinstance(other, TrelloMember) and self.username < other.username
+
+ def __hash__(self):
+ return hash(self.username)
+
+
+class CardCustomFields:
+ def __init__(self, card_id):
+ self.card_id = card_id
+ self.authors = None
+ self.editors = None
+ self.illustrators = None
+ self.cover = None
+ self.title = None
+ self.google_doc = None
+
+ def __repr__(self):
+ return f"CardCustomFields"
diff --git a/src/planka/planka_client.py b/src/planka/planka_client.py
index 9f956601..ac949fb0 100644
--- a/src/planka/planka_client.py
+++ b/src/planka/planka_client.py
@@ -11,8 +11,8 @@
from ..consts import TrelloCardColor, TrelloCustomFieldTypeAlias
from ..db import db_client
from ..strings import load
-from ..trello import trello_objects as objects
-from ..trello.trello_objects import TIME_FORMAT
+from . import board_objects as objects
+from .board_objects import TIME_FORMAT
from ..utils.singleton import Singleton
logger = logging.getLogger(__name__)
diff --git a/src/sheets/sheets_client.py b/src/sheets/sheets_client.py
index a2acbd7f..f910881b 100644
--- a/src/sheets/sheets_client.py
+++ b/src/sheets/sheets_client.py
@@ -29,7 +29,6 @@ def update_config(self, new_sheets_config: dict):
def _update_from_config(self):
"""Update attributes according to current self._sheets_config"""
- self.authors_sheet_key = self._sheets_config["authors_sheet_key"]
self.curators_sheet_key = self._sheets_config["curators_sheet_key"]
self.hr_sheet_key = self._sheets_config["hr_sheet_key"]
self.hr_pt_sheet_key = self._sheets_config["hr_pt_sheet_key"]
@@ -46,9 +45,6 @@ def _update_from_config(self):
def _authorize(self):
self.client = SpreadsheetApp(self._sheets_config["api_key_path"])
- def fetch_authors(self) -> Table:
- return self._fetch_table(self.authors_sheet_key, "Кураторы и контакты")
-
def fetch_curators(self) -> Table:
return self._fetch_table(self.curators_sheet_key)
diff --git a/src/sheets/sheets_objects.py b/src/sheets/sheets_objects.py
index fed31af7..97c52902 100644
--- a/src/sheets/sheets_objects.py
+++ b/src/sheets/sheets_objects.py
@@ -6,7 +6,7 @@
from ..consts import BoardCardColor, TrelloCardColor
from ..strings import load
-from ..trello.trello_objects import CardCustomFields, TrelloCard
+from ..planka.board_objects import CardCustomFields, TrelloCard
from .utils import convert_excel_datetime_to_string
logger = logging.getLogger(__name__)
diff --git a/src/tg/handler_registry.py b/src/tg/handler_registry.py
index a4dc3e56..7ce6dfbb 100644
--- a/src/tg/handler_registry.py
+++ b/src/tg/handler_registry.py
@@ -50,24 +50,6 @@ class HandlerConfig:
direct_only=True,
description="получить мои карточки из доски Развитие",
),
- HandlerConfig(
- command="fill_posts_list",
- category=CommandCategories.DEBUG,
- access_level="manager",
- job_name="fill_posts_list_job",
- job_type="manager_reply",
- direct_only=True,
- description="заполнить реестр постов (пока не работает)",
- ),
- HandlerConfig(
- command="fill_posts_list_focalboard",
- category=CommandCategories.DEBUG,
- access_level="manager",
- job_name="fill_posts_list_focalboard_job",
- job_type="manager_reply",
- direct_only=True,
- description="заполнить реестр постов из Focalboard (пока не работает)",
- ),
HandlerConfig(
command="hr_acquisition",
category=CommandCategories.HR,
@@ -108,14 +90,6 @@ class HandlerConfig:
job_type="manager_reply",
description="создать папки для иллюстраторов",
),
- HandlerConfig(
- command="get_tasks_report_focalboard",
- category=CommandCategories.MOST_USED,
- access_level="manager",
- handler_func=handlers.get_tasks_report_focalboard,
- direct_only=True,
- description="получить список задач из Focalboard",
- ),
HandlerConfig(
command="get_tasks_report_planka",
category=CommandCategories.MOST_USED,
@@ -265,13 +239,6 @@ class HandlerConfig:
handler_func=handlers.add_manager,
description="добавить менеджера в список",
),
- HandlerConfig(
- command="change_board",
- category=CommandCategories.CONFIG,
- access_level="admin",
- handler_func=handlers.change_board,
- description="изменить Trello board_id",
- ),
HandlerConfig(
command="send_reminders",
category=CommandCategories.BROADCAST,
@@ -323,12 +290,6 @@ class HandlerConfig:
job_name="sample_job",
job_type="admin_reply",
),
- HandlerConfig(
- command="db_fetch_authors_sheet",
- access_level="hidden",
- job_name="db_fetch_authors_sheet_job",
- job_type="admin_reply",
- ),
HandlerConfig(
command="db_fetch_curators_sheet",
access_level="hidden",
diff --git a/src/tg/handlers/__init__.py b/src/tg/handlers/__init__.py
index 3854c526..b816d6c0 100644
--- a/src/tg/handlers/__init__.py
+++ b/src/tg/handlers/__init__.py
@@ -5,7 +5,6 @@
from .access_config_handler import (
add_manager,
- change_board,
get_config,
get_config_jobs,
reload_config_jobs,
@@ -23,12 +22,7 @@
# Admin (developer) handlers
from .get_rubrics_handler import get_rubrics
-from .get_tasks_report_handler import (
- get_tasks_report,
- get_tasks_report_advanced,
- get_tasks_report_focalboard,
- get_tasks_report_planka,
-)
+from .get_tasks_report_handler import get_tasks_report_planka
from .help_handler import help
from .list_chats_handler import list_chats
from .list_job_handler import list_jobs
diff --git a/src/tg/handlers/access_config_handler.py b/src/tg/handlers/access_config_handler.py
index 03d87202..fdd54913 100644
--- a/src/tg/handlers/access_config_handler.py
+++ b/src/tg/handlers/access_config_handler.py
@@ -147,24 +147,6 @@ def add_manager(update, tg_context):
return
-@admin_only
-def change_board(update, tg_context):
- try:
- tokens = update.message.text.strip().split(maxsplit=2)
- assert len(tokens) == 2
- board_id = json.loads(tokens[1])
- _set_config(
- update,
- f"{consts.TRELLO_CONFIG}.{consts.TRELLO_BOARD_ID}",
- board_id,
- ConfigManager(),
- )
- except Exception as e:
- reply(load("access_config_handler__change_board_usage_example"), update)
- logger.warning("Failed to change boards", exc_info=e)
- return
-
-
def _set_config(update, config_path: str, new_value, config_manager: ConfigManager):
current_config = config_manager.get_latest_config()
for config_item in config_path.split("."):
diff --git a/src/tg/handlers/flow_handlers.py b/src/tg/handlers/flow_handlers.py
index 780f190c..d4bb7e80 100644
--- a/src/tg/handlers/flow_handlers.py
+++ b/src/tg/handlers/flow_handlers.py
@@ -16,11 +16,9 @@
)
from ...db.db_client import DBClient
from ...db.db_objects import Reminder
-from ...focalboard.focalboard_client import FocalboardClient
from ...planka.planka_client import PlankaClient
from ...strings import load
from ...tg.handlers import get_tasks_report_handler
-from ...trello.trello_client import TrelloClient
from .utils import get_sender_id, reply, get_sender_username, get_chat_id, get_chat_name
logger = logging.getLogger(__name__)
@@ -303,19 +301,14 @@ def _handle_task_report_helper(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]
- use_planka = command_data.get(consts.GetTasksReportData.USE_PLANKA, False)
messages = get_tasks_report_handler.generate_report_messages(
board_id,
list_id,
introduction,
add_labels,
- use_focalboard=use_focalboard,
- use_planka=use_planka,
)
for message in messages:
reply(message, update)
- # finished with last action for /trello_client_get_lists
return None
@@ -345,59 +338,16 @@ def handle(self) -> Optional[PlainTextUserAction]:
return None
-class GetTasksReportEnterBoardUrlHandler(BaseUserMessageHandler):
- def handle(self) -> Optional[PlainTextUserAction]:
- trello_client = TrelloClient()
- try:
- board = trello_client.get_board_by_url(self.user_input)
- trello_lists = trello_client.get_lists(board.id)
- except Exception:
- reply(load("get_tasks_report_handler__board_not_found"), self.update)
- return PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_URL
-
- self.command_data[consts.GetTasksReportData.BOARD_ID] = board.id
- self.command_data[consts.GetTasksReportData.LISTS] = [
- lst.to_dict() for lst in trello_lists
- ]
-
- trello_lists_formatted = "\n".join(
- [f"{i + 1}) {lst.name}" for i, lst in enumerate(trello_lists)]
- )
- reply(
- load(
- "get_tasks_report_handler__choose_trello_list",
- lists=trello_lists_formatted,
- ),
- self.update,
- )
- return PlainTextUserAction.GET_TASKS_REPORT__ENTER_LIST_NUMBER
-
-
class GetTasksReportEnterBoardNumberHandler(BaseUserMessageHandler):
def handle(self) -> Optional[PlainTextUserAction]:
- trello_client = TrelloClient()
- focalboard_client = FocalboardClient()
planka_client = PlankaClient()
try:
board_list = self.tg_context.chat_data[consts.GetTasksReportData.LISTS]
- use_focalboard = self.tg_context.chat_data.get(
- consts.GetTasksReportData.USE_FOCALBOARD, False
- )
- use_planka = self.tg_context.chat_data.get(
- consts.GetTasksReportData.USE_PLANKA, False
- )
list_idx = int(self.user_input) - 1
assert 0 <= list_idx < len(board_list)
board_id = board_list[list_idx]["id"]
- if use_planka:
- trello_lists = planka_client.get_lists(board_id, sorted=True)
- trello_lists = trello_lists[::-1]
- elif use_focalboard:
- trello_lists = focalboard_client.get_lists(board_id, sorted=True)
- trello_lists = trello_lists[::-1]
- else:
- trello_lists = trello_client.get_lists(board_id)
- trello_lists = trello_lists[::-1]
+ trello_lists = planka_client.get_lists(board_id, sorted=True)
+ trello_lists = trello_lists[::-1]
except Exception as e:
logger.warning("Failed to parse board number", exc_info=e)
reply(
@@ -410,8 +360,6 @@ def handle(self) -> Optional[PlainTextUserAction]:
return PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_NUMBER
self.command_data[consts.GetTasksReportData.BOARD_ID] = board_id
- self.command_data[consts.GetTasksReportData.USE_FOCALBOARD] = use_focalboard
- self.command_data[consts.GetTasksReportData.USE_PLANKA] = use_planka
self.command_data[consts.GetTasksReportData.LISTS] = [
lst.to_dict() for lst in trello_lists
]
diff --git a/src/tg/handlers/get_tasks_report_handler.py b/src/tg/handlers/get_tasks_report_handler.py
index a44efc30..9fcf534a 100644
--- a/src/tg/handlers/get_tasks_report_handler.py
+++ b/src/tg/handlers/get_tasks_report_handler.py
@@ -10,7 +10,7 @@
from ...jobs.utils import retrieve_username
from ...strings import load
from ...tg.sender import paragraphs_to_messages
-from ...trello.trello_objects import TrelloCard
+from ...planka.board_objects import TrelloCard
from .utils import manager_only, reply
TASK_NAME = "get_tasks_report"
@@ -18,36 +18,11 @@
logger = logging.getLogger(__name__)
-@manager_only
-def get_tasks_report(update: telegram.Update, tg_context: telegram.ext.CallbackContext):
- _get_task_report_base(update, tg_context, advanced=False)
-
- 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_planka(
update: telegram.Update, tg_context: telegram.ext.CallbackContext
):
- _get_task_report_base(update, tg_context, advanced=False, use_planka=True)
-
- return
-
-
-@manager_only
-def get_tasks_report_advanced(
- update: telegram.Update, tg_context: telegram.ext.CallbackContext
-):
- _get_task_report_base(update, tg_context, advanced=True)
+ _get_task_report_base(update, tg_context, advanced=False)
return
@@ -56,27 +31,14 @@ def _get_task_report_base(
update: telegram.Update,
tg_context: telegram.ext.CallbackContext,
advanced: bool,
- use_focalboard: bool = False,
- use_planka: bool = False,
):
app_context = AppContext()
- if use_planka:
- telegram_username = update.effective_user.username
-
- boards_list = app_context.planka_client.get_boards_for_telegram_user(
- telegram_username,
- app_context.db_client,
- )
- elif use_focalboard:
- telegram_username = update.effective_user.username
-
- boards_list = app_context.focalboard_client.get_boards_for_user(
- telegram_username=telegram_username,
- db_client=app_context.db_client,
- )
- else:
- boards_list = app_context.trello_client.get_boards_for_user()
+ telegram_username = update.effective_user.username
+ boards_list = app_context.planka_client.get_boards_for_telegram_user(
+ telegram_username,
+ app_context.db_client,
+ )
boards_list = sorted(boards_list, key=lambda board: board.name)
boards_list_formatted = "\n".join(
[f"{i + 1}) {brd.name}" for i, brd in enumerate(boards_list)]
@@ -86,8 +48,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[consts.GetTasksReportData.USE_PLANKA] = use_planka
+ tg_context.chat_data[consts.GetTasksReportData.USE_PLANKA] = True
tg_context.chat_data[TASK_NAME] = {
consts.NEXT_ACTION: consts.PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_NUMBER.value
}
@@ -105,29 +66,16 @@ def generate_report_messages(
list_id: str,
introduction: str,
add_labels: bool,
- use_focalboard: bool,
- use_planka: bool = False,
) -> List[str]:
app_context = AppContext()
- paragraphs = [] # list of paragraph strings
-
- if use_planka:
- trello_list = app_context.planka_client.get_list(board_id, list_id)
- elif 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 = []
+
+ trello_list = app_context.planka_client.get_list(board_id, list_id)
paragraphs.append(load("common__bold_wrapper", arg=trello_list.name))
- if use_planka:
- list_cards = app_context.planka_client.get_cards(list_id, board_id)
- elif 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)
+ list_cards = app_context.planka_client.get_cards(list_id, board_id)
paragraphs += _create_paragraphs_from_cards(
- list_cards, introduction, add_labels, app_context
+ list_cards, introduction, add_labels, app_context.db_client
)
return paragraphs_to_messages(paragraphs)
@@ -136,7 +84,7 @@ def _create_paragraphs_from_cards(
cards: Iterable[TrelloCard],
introduction: str,
need_label: bool,
- app_context: AppContext,
+ db_client,
):
paragraphs = []
if introduction:
@@ -145,20 +93,17 @@ def _create_paragraphs_from_cards(
members = _get_members(cards)
for member in members:
lines = []
- member_name = _make_member_text(member, app_context.db_client)
+ member_name = _make_member_text(member, db_client)
lines.append(member_name)
member_cards = _get_member_cards(member, cards)
- cards_text = _make_cards_text(member_cards, need_label, app_context)
+ cards_text = _make_cards_text(member_cards, need_label)
lines += cards_text
paragraphs.append("\n".join(lines))
- # cards without members at the end
cards_without_members = _get_cards_without_members(cards)
if cards_without_members:
lines = [load("get_tasks_report_handler__misc")]
- cards_without_members_text = _make_cards_text(
- cards_without_members, need_label, app_context
- )
+ cards_without_members_text = _make_cards_text(cards_without_members, need_label)
lines += cards_without_members_text
paragraphs.append("\n".join(lines))
return paragraphs
@@ -220,9 +165,7 @@ def _make_deadline_text(card: TrelloCard) -> str:
)
-def _make_cards_text(
- cards: Iterable[TrelloCard], need_label: bool, app_context: AppContext
-) -> List[str]:
+def _make_cards_text(cards: Iterable[TrelloCard], need_label: bool) -> List[str]:
# generates the text of the cards, cards come already sorted by date
return [_format_card(card, need_label) for card in cards]
diff --git a/src/tg/handlers/user_message_handler.py b/src/tg/handlers/user_message_handler.py
index d4dd0060..f144b108 100644
--- a/src/tg/handlers/user_message_handler.py
+++ b/src/tg/handlers/user_message_handler.py
@@ -24,7 +24,6 @@ def handle_callback_query(
ACTION_HANDLERS = {
PlainTextUserAction.GET_RUBRICS__CHOOSE_RUBRIC: flow_handlers.GetRubricsChooseRubricHandler,
- PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_URL: flow_handlers.GetTasksReportEnterBoardUrlHandler,
PlainTextUserAction.GET_TASKS_REPORT__ENTER_BOARD_NUMBER: flow_handlers.GetTasksReportEnterBoardNumberHandler,
PlainTextUserAction.GET_TASKS_REPORT__ENTER_LIST_NUMBER: flow_handlers.GetTasksReportEnterListNumberHandler,
PlainTextUserAction.GET_TASKS_REPORT__ENTER_INTRO: flow_handlers.GetTasksReportEnterIntroHandler,
diff --git a/src/trello/trello_client.py b/src/trello/trello_client.py
deleted file mode 100644
index e1ab917d..00000000
--- a/src/trello/trello_client.py
+++ /dev/null
@@ -1,316 +0,0 @@
-import json
-import logging
-from typing import List
-from urllib.parse import quote, urljoin
-
-import requests
-
-from ..consts import TrelloCustomFieldTypeAlias, TrelloCustomFieldTypes, BoardListAlias
-from ..strings import load
-from ..utils.singleton import Singleton
-from . import trello_objects as objects
-
-logger = logging.getLogger(__name__)
-
-BASE_URL = "https://api.trello.com/1/"
-
-
-class TrelloClient(Singleton):
- def __init__(self, trello_config=None):
- if self.was_initialized():
- return
-
- self._trello_config = trello_config
- self._update_from_config()
- logger.info("TrelloClient successfully initialized")
-
- def get_board(self, board_id=None):
- if not board_id:
- board_id = self.board_id
- _, data = self._make_request(f"boards/{board_id}")
- return objects.TrelloBoard.from_dict(data)
-
- def get_board_by_url(self, board_url):
- # Safari may copy unquoted url with cyrillic symbols
- board_url = quote(board_url, safe=":/%")
- _, data = self._make_request("members/me/boards")
- for board in data:
- if board.get("url") == board_url:
- return objects.TrelloBoard.from_dict(board)
- raise ValueError(f"Board {board_url} not found!")
-
- def get_board_labels(self, board_id=None):
- if not board_id:
- board_id = self.board_id
- _, data = self._make_request(f"boards/{board_id}/labels")
- labels = [objects.TrelloBoardLabel.from_dict(label) for label in data]
- logger.debug(f"get_board_labels: {labels}")
- return labels
-
- def get_boards_for_user(self, user_id=None):
- _, data = self._make_request("members/me/boards")
- boards = [objects.TrelloBoard.from_dict(label) for label in data]
- logger.debug(f"get_boards_for_user: {boards}")
- return boards
-
- def get_lists(self, board_id=None):
- if not board_id:
- board_id = self.board_id
- _, data = self._make_request(f"boards/{board_id}/lists")
- lists = [objects.TrelloList.from_dict(trello_list) for trello_list in data]
- logger.debug(f"get_lists: {lists}")
- return lists
-
- def get_list(self, list_id):
- _, data = self._make_request(f"lists/{list_id}")
- lst = objects.TrelloList.from_dict(data)
- logger.debug(f"get_list: {list}")
- return lst
-
- def get_cards(self, list_ids=None, board_id=None):
- if not board_id:
- board_id = self.board_id
- if list_ids is not None and len(list_ids) == 1:
- _, data = self._make_request(f"lists/{list_ids[0]}/cards")
- else:
- _, data = self._make_request(f"boards/{board_id}/cards")
- if list_ids:
- data = [
- card_dict for card_dict in data if card_dict["idList"] in list_ids
- ]
- cards = []
- # TODO: move this to app state
- members = self.get_members(board_id)
- lists = self.get_lists(board_id)
- for card_dict in data:
- card = objects.TrelloCard.from_dict(card_dict)
- # TODO: move this to app state
- for trello_list in lists:
- if trello_list.id == card_dict["idList"]:
- 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["idMembers"]) > 0:
- for member in members:
- if member.id in card_dict["idMembers"]:
- 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 get_board_custom_field_types(self, board_id=None):
- if not board_id:
- board_id = self.board_id
- _, data = self._make_request(f"boards/{board_id}/customFields")
- custom_field_types = [
- objects.TrelloCustomFieldType.from_dict(custom_field_type)
- for custom_field_type in data
- ]
- logger.debug(f"get_board_custom_field_types: {custom_field_types}")
- return custom_field_types
-
- def get_card_custom_fields(self, card_id: str) -> List[objects.TrelloCustomField]:
- _, data = self._make_request(f"cards/{card_id}/customFieldItems")
- custom_fields = [
- objects.TrelloCustomField.from_dict(
- custom_field, self.custom_fields_type_config
- )
- for custom_field in data
- ]
- logger.debug(f"get_card_custom_fields: {custom_fields}")
- return custom_fields
-
- def get_card_custom_fields_dict(self, card_id):
- custom_fields = self.get_card_custom_fields(card_id)
- custom_fields_dict = {}
- for alias, type_id in self.custom_fields_config.items():
- suitable_fields = [fld for fld in custom_fields if fld.type_id == type_id]
- if len(suitable_fields) > 0:
- custom_fields_dict[alias] = suitable_fields[0]
- return custom_fields_dict
-
- def get_custom_fields(self, card_id: str) -> objects.CardCustomFields:
- # TODO: think about better naming
- card_fields_dict = self.get_card_custom_fields_dict(card_id)
- card_fields = objects.CardCustomFields(card_id)
- card_fields._data = card_fields_dict
- card_fields.authors = (
- [
- author.strip()
- for author in card_fields_dict[
- TrelloCustomFieldTypeAlias.AUTHOR
- ].value.split(",")
- ]
- if TrelloCustomFieldTypeAlias.AUTHOR in card_fields_dict
- else []
- )
- card_fields.editors = (
- [
- editor.strip()
- for editor in card_fields_dict[
- TrelloCustomFieldTypeAlias.EDITOR
- ].value.split(",")
- ]
- if TrelloCustomFieldTypeAlias.EDITOR in card_fields_dict
- else []
- )
- card_fields.illustrators = (
- [
- illustrator.strip()
- for illustrator in card_fields_dict[
- TrelloCustomFieldTypeAlias.ILLUSTRATOR
- ].value.split(",")
- ]
- if TrelloCustomFieldTypeAlias.ILLUSTRATOR in card_fields_dict
- else []
- )
- card_fields.cover = (
- card_fields_dict[TrelloCustomFieldTypeAlias.COVER].value
- if TrelloCustomFieldTypeAlias.COVER in card_fields_dict
- else None
- )
- card_fields.google_doc = (
- card_fields_dict[TrelloCustomFieldTypeAlias.GOOGLE_DOC].value
- if TrelloCustomFieldTypeAlias.GOOGLE_DOC in card_fields_dict
- else None
- )
- card_fields.title = (
- card_fields_dict[TrelloCustomFieldTypeAlias.TITLE].value
- if TrelloCustomFieldTypeAlias.TITLE in card_fields_dict
- else None
- )
- return card_fields
-
- def set_card_custom_field(self, card_id, field_alias, value):
- data = {"value": {"text": value}}
- field_id = self.custom_fields_config[field_alias]
- code = self._make_put_request(
- f"cards/{card_id}/customField/{field_id}/item", data=data
- )
- logger.debug(f"set_card_custom_field: {code}")
-
- def get_action_create_card(self, card_id):
- _, data = self._make_request(
- f"cards/{card_id}/actions", payload={"filter": "createCard"}
- )
- card_actions = [
- objects.TrelloActionCreateCard.from_dict(action)
- for action in data
- if action["type"] == "createCard"
- ]
- logger.debug(f"get_action_create_card: {card_actions}")
- return card_actions
-
- def get_action_create_cards(self, card_ids):
- card_actions = {}
- for card_id in card_ids:
- card_actions[card_id] = self.get_action_create_card(card_id)
- return card_actions
-
- def get_action_update_card(self, card_id):
- _, data = self._make_request(
- f"cards/{card_id}/actions", payload={"filter": "updateCard"}
- )
- card_actions = [
- objects.TrelloActionUpdateCard.from_dict(action)
- for action in data
- if action["type"] == "updateCard"
- ]
- logger.debug(f"get_action_update_card: {card_actions}")
- return card_actions
-
- def get_action_update_cards(self, card_ids):
- card_actions = {}
- for card_id in card_ids:
- card_actions[card_id] = self.get_action_update_card(card_id)
- return card_actions
-
- def get_members(self, board_id=None) -> List[objects.TrelloMember]:
- if not board_id:
- board_id = self.board_id
- _, data = self._make_request(f"boards/{board_id}/members")
- members = [objects.TrelloMember.from_dict(member) for member in data]
- logger.debug(f"get_members: {members}")
- return members
-
- def update_config(self, new_trello_config):
- """To be called after config automatic update"""
- self._trello_config = new_trello_config
- self._update_from_config()
-
- def _update_from_config(self):
- """Update attributes according to current self._trello_config"""
- self.api_key = self._trello_config["api_key"]
- self.token = self._trello_config["token"]
- self.board_id = self._trello_config["board_id"]
- self.deprecated = self._trello_config["deprecated"]
- self.default_payload = {
- "key": self.api_key,
- "token": self.token,
- }
- # TODO(alexeyqu): move to DB
- lists = self.get_lists()
- self.lists_config = self._fill_alias_id_map(lists, BoardListAlias)
- custom_field_types = self.get_board_custom_field_types()
- self.custom_fields_type_config = self._fill_id_type_map(
- custom_field_types, TrelloCustomFieldTypes
- )
- self.custom_fields_config = self._fill_alias_id_map(
- custom_field_types, TrelloCustomFieldTypeAlias
- )
-
- def get_list_id_from_aliases(self, list_aliases):
- list_ids = [
- self.lists_config[alias]
- for alias in list_aliases
- if alias in self.lists_config
- ]
- if len(list_ids) != len(list_aliases):
- logger.error(
- f"list_ids not found for aliases: "
- f"{[alias for alias in list_aliases if alias not in self.lists_config]}"
- )
- return list_ids
-
- def _fill_alias_id_map(self, items, item_enum):
- result = {}
- for alias in item_enum:
- suitable_items = [
- item for item in items if item.name.startswith(load(alias.value))
- ]
- if len(suitable_items) > 1:
- raise ValueError(
- f"Enum {item_enum.__name__} name {alias.value} is ambiguous!"
- )
- if len(suitable_items) > 0:
- result[alias] = suitable_items[0].id
- return result
-
- def _fill_id_type_map(self, items, item_enum):
- result = {}
- for item in items:
- result[item.id] = TrelloCustomFieldTypes(item.type)
- return result
-
- def _make_request(self, uri, payload={}):
- payload.update(self.default_payload)
- response = requests.get(
- urljoin(BASE_URL, uri),
- params=payload,
- )
- logger.debug(f"{response.url}")
- return response.status_code, response.json()
-
- def _make_put_request(self, uri, data={}):
- response = requests.put(
- urljoin(BASE_URL, uri),
- params=self.default_payload,
- data=json.dumps(data),
- headers={"Content-Type": "application/json"},
- )
- logger.debug(f"{response.url}")
- return response.status_code
diff --git a/src/trello/trello_objects.py b/src/trello/trello_objects.py
deleted file mode 100644
index 9dc0c079..00000000
--- a/src/trello/trello_objects.py
+++ /dev/null
@@ -1,574 +0,0 @@
-import html
-import logging
-from datetime import datetime
-
-from ..consts import BoardCardColor, TrelloCardColor, TrelloCustomFieldTypes
-
-logger = logging.getLogger(__name__)
-
-TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
-
-
-class TrelloBoard:
- def __init__(self):
- self.id = None
- self.name = None
- self.url = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return f"Board"
-
- @classmethod
- def from_dict(cls, data):
- board = cls()
- try:
- board.id = data["id"]
- board.name = html.escape(data["name"])
- board.url = data["shortUrl"]
- except Exception as e:
- board._ok = False
- logger.error(f"Bad board json {data}", exc_info=e)
- return board
-
- @classmethod
- def from_focalboard_dict(cls, data):
- board = cls()
- try:
- board.id = data["id"]
- board.name = html.escape(data["title"])
- # board.url = data["shortUrl"]
- except Exception as e:
- board._ok = False
- logger.error(f"Bad board json {data}", exc_info=e)
- return board
-
- def to_dict(self):
- return {
- "id": self.id,
- "name": self.name,
- "url": self.url,
- }
-
-
-class TrelloBoardLabel:
- def __init__(self):
- self.id = None
- self.name = None
- self.color = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return f"BoardLabel"
-
- @classmethod
- def from_dict(cls, data):
- label = cls()
- try:
- label.id = data["id"]
- label.name = html.escape(data["name"])
- label.color = data["color"]
- except Exception as e:
- label._ok = False
- logger.error(f"Bad board label json {data}", exc_info=e)
- return label
-
- @classmethod
- def from_focalboard_dict(cls, data):
- label = cls()
- try:
- label.id = data["id"]
- label.name = html.escape(data["value"])
- label.color = data["color"]
- except Exception as e:
- label._ok = False
- logger.error(f"Bad board label json {data}", exc_info=e)
- return label
-
- def to_dict(self):
- return {
- "id": self.id,
- "name": self.name,
- "color": self.color,
- }
-
-
-class TrelloList:
- def __init__(self):
- self.id = None
- self.name = None
- self.board_id = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return f"List"
-
- @classmethod
- def from_dict(cls, data):
- trello_list = cls()
- try:
- trello_list.id = data["id"]
- trello_list.name = html.escape(data["name"])
- trello_list.board_id = data["idBoard"]
- except Exception as e:
- trello_list._ok = False
- logger.error(f"Bad list json {data}", exc_info=e)
- return trello_list
-
- @classmethod
- def from_focalboard_dict(cls, data, board_id):
- trello_list = cls()
- try:
- trello_list.id = data["id"]
- trello_list.name = html.escape(data["value"])
- trello_list.board_id = board_id
- except Exception as e:
- trello_list._ok = False
- logger.error(f"Bad list json {data}", exc_info=e)
- return trello_list
-
- def to_dict(self):
- return {
- "id": self.id,
- "name": self.name,
- "idBoard": self.board_id,
- }
-
-
-class TrelloCardLabel:
- def __init__(self):
- self.id = None
- self.name = None
- self.color = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return f"CardLabel"
-
- @classmethod
- def from_dict(cls, data):
- label = cls()
- try:
- label.id = data["id"]
- label.name = html.escape(data["name"])
- label.color = None
- try:
- label.color = TrelloCardColor(data["color"])
- except Exception:
- label.color = TrelloCardColor(TrelloCardColor.UNKNOWN)
- except Exception as e:
- label._ok = False
- logger.error(f"Bad card label json {data}", exc_info=e)
- return label
-
- @classmethod
- def from_focalboard_dict(cls, data):
- label = cls()
- try:
- label.id = data["id"]
- label.name = html.escape(data["value"])
- label.color = None
- try:
- label.color = BoardCardColor(data["color"])
- except Exception:
- label.color = BoardCardColor(BoardCardColor.UNKNOWN)
- except Exception as e:
- label._ok = False
- logger.error(f"Bad card label json {data}", exc_info=e)
- return label
-
- def to_dict(self):
- return {
- "id": self.id,
- "name": self.name,
- "color": self.color.value,
- }
-
-
-class TrelloCard:
- def __init__(self):
- self.id = None
- self.name = None
- self.labels = []
- self.url = None
- self.due = None
- # TODO: move this to app state
- self.lst = None
- self.members = []
- # focalboard fields
- self._fields_properties = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.url
-
- def __repr__(self):
- return f"Card"
-
- def __eq__(self, other):
- return str(self) == str(other)
-
- def __hash__(self):
- return hash(self.id)
-
- @classmethod
- def from_dict(cls, data):
- card = cls()
- try:
- card.id = data["id"]
- card.name = html.escape(data["name"])
- card.labels = [TrelloCardLabel.from_dict(label) for label in data["labels"]]
- card.url = data["shortUrl"]
- card.due = (
- datetime.strptime(data["due"], TIME_FORMAT) if data["due"] else None
- )
- except Exception as e:
- card._ok = False
- logger.error(f"Bad card json {data}", exc_info=e)
- return card
-
- @classmethod
- def from_focalboard_dict(cls, data):
- card = cls()
- try:
- card.id = data["id"]
- card.name = html.escape(data["title"])
- card._fields_properties = data["fields"]["properties"]
- except Exception as e:
- card._ok = False
- logger.error(f"Bad card json {data}", exc_info=e)
- return card
-
- def to_dict(self):
- return {
- "id": self.id,
- "name": self.name,
- "labels": [label.to_dict() for label in self.labels],
- "url": self.url,
- "due": datetime.strftime(self.due, TIME_FORMAT) if self.due else None,
- "list": self.lst.to_dict() if self.lst is not None else {},
- "members": [member.to_dict() for member in self.members],
- }
-
-
-class TrelloCustomFieldType:
- def __init__(self):
- self.id = None
- self.name = None
- self.type = None
- self.options = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.name
-
- def __repr__(self):
- return f"CustomFieldType"
-
- @classmethod
- def from_dict(cls, data):
- field_type = cls()
- try:
- field_type.id = data["id"]
- field_type.name = html.escape(data["name"])
- field_type.type = TrelloCustomFieldTypes(data["type"])
- if field_type.type == TrelloCustomFieldTypes.LIST:
- field_type.options = {
- option["id"]: option["value"]["text"] for option in data["options"]
- }
- except Exception as e:
- field_type._ok = False
- logger.error(f"Bad field type json {data}", exc_info=e)
- return field_type
-
- @classmethod
- def from_focalboard_dict(cls, data):
- field_type = cls()
- try:
- field_type.id = data["id"]
- field_type.name = html.escape(data["name"])
- field_type.type = TrelloCustomFieldTypes(data["type"])
- if field_type.type == TrelloCustomFieldTypes.LIST:
- field_type.options = {
- option["id"]: option["value"] for option in data["options"]
- }
- except Exception as e:
- field_type._ok = False
- logger.error(f"Bad field type json {data}", exc_info=e)
- return field_type
-
- def to_dict(self):
- dct = {
- "id": self.id,
- "name": self.name,
- "type": self.type.value,
- }
- if self.type == TrelloCustomFieldTypes.LIST:
- dct["options"] = [
- {"id": idValue, "value": {"text": value}}
- for idValue, value in self.options.items()
- ]
- return dct
-
-
-class TrelloCustomField:
- def __init__(self):
- self.id = None
- self.value = None
- self.type_id = None
-
- self._ok = True
- self._custom_fields_type_config = None
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return self.value
-
- def __repr__(self):
- return f"CustomField"
-
- @classmethod
- def from_dict(cls, data, custom_fields_type_config):
- custom_field = cls()
- custom_field._custom_fields_type_config = custom_fields_type_config
- try:
- custom_field.id = data["id"]
- custom_field.type_id = data["idCustomField"]
- # TODO probably support other custom field value types
- if (
- custom_fields_type_config[custom_field.type_id]
- == TrelloCustomFieldTypes.TEXT
- ):
- custom_field.value = html.escape(data["value"]["text"])
- except Exception as e:
- custom_field._ok = False
- logger.error(f"Bad custom field json {data}", exc_info=e)
- return custom_field
-
- def to_dict(self):
- dct = {
- "id": self.id,
- "idCustomField": self.type_id,
- }
- # TODO probably support other custom field value types
- if self._custom_fields_type_config[self.type_id] == TrelloCustomFieldTypes.TEXT:
- dct["value"] = {"text": self.value}
- return dct
-
-
-class TrelloActionCreateCard:
- def __init__(self):
- self.id = None
- self.date = None
- self.card_url = None
- self.list_id = None
- self.list_name = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return f'Card "{self.card_url}" created in {self.list_name}'
-
- def __repr__(self):
- return (
- f"ActionCreateCard"
- )
-
- @classmethod
- def from_dict(cls, data):
- action = cls()
- try:
- assert data["type"] == "createCard"
- action.id = data["id"]
- action.date = datetime.strptime(data["date"], TIME_FORMAT)
- action.card_url = (
- "https://trello.com/c/" + data["data"]["card"]["shortLink"]
- )
- if "list" in data["data"]:
- action.list_id = data["data"]["list"].get("id")
- action.list_name = data["data"]["list"].get("name")
- except Exception as e:
- action._ok = False
- logger.error(f"Bad createCard action json {data}", exc_info=e)
- return action
-
- def to_dict(self):
- return {
- "id": self.id,
- "date": datetime.strftime(self.date, TIME_FORMAT),
- "type": "createCard",
- "data": {
- "card": {"shortLink": self.card_url.split("/")[-1]},
- "list": {
- "id": self.list_id,
- "name": self.list_name,
- },
- },
- }
-
-
-class TrelloActionUpdateCard:
- def __init__(self):
- self.id = None
- self.date = None
- # probably will update fields if need be
- self.card_url = None
- self.list_before_id = None
- self.list_before_name = None
- self.list_after_id = None
- self.list_after_name = None
-
- self._ok = True
-
- def __bool__(self):
- return self._ok
-
- def __str__(self):
- return f'Card "{self.card_url}" moved {self.list_before_name} -> {self.list_after_name}'
-
- def __repr__(self):
- return (
- f"ActionUpdateCard"
- )
-
- @classmethod
- def from_dict(cls, data):
- action = cls()
- try:
- assert data["type"] == "updateCard"
- action.id = data["id"]
- action.date = datetime.strptime(data["date"], TIME_FORMAT)
- action.card_url = (
- "https://trello.com/c/" + data["data"]["card"]["shortLink"]
- )
- if "listBefore" in data["data"]:
- action.list_before_id = data["data"]["listBefore"]["id"]
- action.list_before_name = data["data"]["listBefore"]["name"]
- if "listAfter" in data["data"]:
- action.list_after_id = data["data"]["listAfter"]["id"]
- action.list_after_name = data["data"]["listAfter"]["name"]
- except Exception as e:
- action._ok = False
- logger.error(f"Bad updateCard action json {data}", exc_info=e)
- return action
-
- def to_dict(self):
- return {
- "id": self.id,
- "date": datetime.strftime(self.date, TIME_FORMAT),
- "type": "updateCard",
- "data": {
- "card": {"shortLink": self.card_url.split("/")[-1]},
- "listBefore": {
- "id": self.list_before_id,
- "name": self.list_before_name,
- },
- "listAfter": {
- "id": self.list_after_id,
- "name": self.list_after_name,
- },
- },
- }
-
-
-class TrelloMember:
- def __init__(self):
- self.id = None
- self.username = None
- self.full_name = None
-
- def __str__(self):
- return self.username
-
- def __repr__(self):
- return f"Member"
-
- def __eq__(self, other):
- return isinstance(other, TrelloMember) and self.username == other.username
-
- def __lt__(self, other):
- return isinstance(other, TrelloMember) and self.username < other.username
-
- def __hash__(self):
- return hash(self.username)
-
- @classmethod
- def from_dict(cls, data):
- member = cls()
- member.id = data["id"]
- member.username = data["username"]
- member.full_name = data["fullName"]
- return member
-
- @classmethod
- def from_focalboard_dict(cls, data):
- member = cls()
- member.id = data["id"]
- member.username = data["username"]
- member.full_name = data["username"]
- return member
-
- def to_dict(self):
- return {
- "id": self.id,
- "username": self.username,
- "fullName": self.full_name,
- }
-
-
-class CardCustomFields:
- def __init__(self, card_id):
- self.card_id = card_id
- self.authors = None
- self.editors = None
- self.illustrators = None
- self.cover = None
- self.title = None
- self.google_doc = None
- self._data = None
-
- def __repr__(self):
- return f"CardCustomFields"
diff --git a/src/utils/card_checks.py b/src/utils/card_checks.py
deleted file mode 100644
index 478346e1..00000000
--- a/src/utils/card_checks.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import datetime
-from typing import Tuple
-
-from ..app_context import AppContext
-from ..consts import BoardListAlias
-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.trello_client.get_list_id_from_aliases(
- [BoardListAlias.DRAFT_N_PROGRESS_3]
- )
- 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.trello_client.get_list_id_from_aliases(
- [BoardListAlias.DRAFT_N_PROGRESS_3]
- )
- 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 = (
- BoardListAlias.DRAFT_N_PROGRESS_3,
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- BoardListAlias.PUBLISH_DONE_11,
- )
- list_ids = app_context.trello_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 = (
- BoardListAlias.DRAFT_N_PROGRESS_3,
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- BoardListAlias.PUBLISH_DONE_11,
- )
- list_ids = app_context.trello_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 = (
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- BoardListAlias.PUBLISH_DONE_11,
- )
- list_ids = app_context.trello_client.get_list_id_from_aliases(list_aliases)
- if card.lst.id not in list_ids:
- return False, {}
-
- doc_url = app_context.trello_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 = (
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- BoardListAlias.PUBLISH_DONE_11,
- )
- list_ids = app_context.trello_client.get_list_id_from_aliases(list_aliases)
- if card.lst.id not in list_ids:
- return False, {}
-
- doc_url = app_context.trello_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",
-}
diff --git a/src/utils/card_checks_focalboard.py b/src/utils/card_checks_focalboard.py
deleted file mode 100644
index dfbd7825..00000000
--- a/src/utils/card_checks_focalboard.py
+++ /dev/null
@@ -1,131 +0,0 @@
-import datetime
-from typing import Tuple
-
-from ..app_context import AppContext
-from ..consts import BoardListAlias
-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(
- [BoardListAlias.DRAFT_N_PROGRESS_3]
- )
- 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(
- [BoardListAlias.DRAFT_N_PROGRESS_3]
- )
- 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 = (
- BoardListAlias.DRAFT_N_PROGRESS_3,
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- )
- 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 = (
- BoardListAlias.DRAFT_N_PROGRESS_3,
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- )
- 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 = (
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- )
- 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 = (
- BoardListAlias.DRAFT_COMPLETED_4,
- BoardListAlias.PENDING_EDITOR_5,
- BoardListAlias.PENDING_SEO_EDITOR_6,
- BoardListAlias.APPROVED_EDITOR_7,
- BoardListAlias.PENDING_CHIEF_EDITOR_8,
- BoardListAlias.PUBLISH_BACKLOG_9,
- BoardListAlias.PUBLISH_IN_PROGRESS_10,
- )
- 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",
-}
diff --git a/src/utils/telegram.py b/src/utils/telegram.py
new file mode 100644
index 00000000..2efd2593
--- /dev/null
+++ b/src/utils/telegram.py
@@ -0,0 +1,4 @@
+def normalize_telegram_username(username: object) -> str:
+ if username is None:
+ return ""
+ return str(username).strip().lstrip("@").lower()
diff --git a/tests/unit/static/config_override.json b/tests/unit/static/config_override.json
index 021aacac..3dc2bd54 100644
--- a/tests/unit/static/config_override.json
+++ b/tests/unit/static/config_override.json
@@ -10,7 +10,6 @@
},
"sheets": {
"api_key_path": "stub",
- "authors_sheet_key": "authors_sheet_key",
"curators_sheet_key": "curators_sheet_key",
"hr_sheet_key": "hr_sheet_key",
"hr_pt_sheet_key": "hr_pt_sheet_key",
diff --git a/tests/unit/test_db_client.py b/tests/unit/test_db_client.py
index b8ef58bb..bec3eacd 100644
--- a/tests/unit/test_db_client.py
+++ b/tests/unit/test_db_client.py
@@ -1,6 +1,51 @@
+import pytest
+
+from src.db.db_client import DBClient
+from src.db.db_objects import TeamMember
+
+
+@pytest.fixture(autouse=True)
+def reset_db_client_singleton():
+ DBClient.drop_instance()
+ yield
+ DBClient.drop_instance()
+
+
def test_init(mock_db_client):
pass
def test_fetch_all(mock_db_client, mock_sheets_client):
mock_db_client.fetch_all(mock_sheets_client)
+
+
+def test_find_author_telegram_by_trello_reads_team_members(mock_db_client):
+ session = mock_db_client.Session()
+ session.add_all(
+ [
+ TeamMember(
+ id="1",
+ name="Trello Person",
+ telegram="@TrelloTelegram",
+ trello="@TrelloUser",
+ focalboard="@DifferentFocalboardUser",
+ ),
+ TeamMember(
+ id="2",
+ name="Focalboard Person",
+ telegram="@FocalboardTelegram",
+ trello="@DifferentTrelloUser",
+ focalboard="@FocalboardUser",
+ ),
+ ]
+ )
+ session.commit()
+
+ assert (
+ mock_db_client.find_author_telegram_by_trello(" trellouser ")
+ == "@TrelloTelegram"
+ )
+ assert (
+ mock_db_client.find_author_telegram_by_trello("@focalboarduser")
+ == "@FocalboardTelegram"
+ )
diff --git a/tests/unit/test_hr_acquisition_jobs.py b/tests/unit/test_hr_acquisition_jobs.py
new file mode 100644
index 00000000..105891eb
--- /dev/null
+++ b/tests/unit/test_hr_acquisition_jobs.py
@@ -0,0 +1,15 @@
+import pytest
+
+from src.utils.telegram import normalize_telegram_username
+
+
+@pytest.mark.parametrize(
+ "raw_telegram, expected",
+ (
+ (" @ExampleUser ", "exampleuser"),
+ (123456789, "123456789"),
+ (None, ""),
+ ),
+)
+def test_normalize_telegram_username_handles_sheet_values(raw_telegram, expected):
+ assert normalize_telegram_username(raw_telegram) == expected
diff --git a/tests/unit/test_sheets_client.py b/tests/unit/test_sheets_client.py
index c0451463..2fa1df7a 100644
--- a/tests/unit/test_sheets_client.py
+++ b/tests/unit/test_sheets_client.py
@@ -24,7 +24,6 @@ def _make_sheets_client(monkeypatch, sheets_config):
def _base_sheets_config(team_identity_sheet_key=None):
config = {
"api_key_path": "stub",
- "authors_sheet_key": "authors_sheet_key",
"curators_sheet_key": "curators_sheet_key",
"hr_sheet_key": "hr_sheet_key",
"hr_pt_sheet_key": "hr_pt_sheet_key",
@@ -115,12 +114,6 @@ def _fetch_table(self, sheet_key, sheet_name=None):
assert calls == [("team_identity_sheet_key", "telegram")]
-@pytest.mark.skip(reason="TODO")
-def test_fetch_authors(mock_sheets_client):
- authors = [author.to_dict() for author in mock_sheets_client.fetch_authors()]
- json_loader.assert_equal(authors, "authors.json")
-
-
@pytest.mark.skip(reason="TODO")
def test_fetch_curators(mock_sheets_client):
curators = [curator.to_dict() for curator in mock_sheets_client.fetch_curators()]
diff --git a/tests/unit/test_telegram_jobs.py b/tests/unit/test_telegram_jobs.py
index dfd82b72..63d04d32 100644
--- a/tests/unit/test_telegram_jobs.py
+++ b/tests/unit/test_telegram_jobs.py
@@ -35,10 +35,6 @@
# # jobs.fill_posts_list_job.FillPostsListJob,
# # ),
# (
- # jobs.db_fetch_authors_sheet_job.DBFetchAuthorsSheetJob,
- # ['Fetched 2']
- # ),
- # (
# jobs.db_fetch_curators_sheet_job.DBFetchCuratorsSheetJob,
# ['Fetched 1']
# ),