diff --git a/api/redmine.py b/api/redmine.py index bf7fe49..cef1937 100644 --- a/api/redmine.py +++ b/api/redmine.py @@ -93,6 +93,28 @@ class RedmineAPI(RedmineConfig): "email": json["mail"], "username": json["login"] } + + def get_issue(self, issue_id: int) -> dict: + """Получает задачу по её id с сервера Redmine""" + resp = requests.get( + self.base_url + self.issues.format(**{"id": issue_id}), + headers={"X-Redmine-Api-Key": self.token}, + timeout=None + ) + if not resp.ok: + logger.error("Ошибка RedmineAPI") + return {} + issue = resp.json()["issue"] + return { + "id": issue["id"], + "subject": issue["subject"], + "description": issue["description"], + "status": issue["status"]["name"], + "priority": issue["priority"]["name"], + "author": issue["author"]["name"], + "tracker": issue["tracker"]["name"], + "project": issue["project"]["name"] + } def _check_token(self): user = self.get_user() diff --git a/config/RedmineTracker.sql b/config/RedmineTracker.sql index 5e49ab5..3f039d0 100644 --- a/config/RedmineTracker.sql +++ b/config/RedmineTracker.sql @@ -8,9 +8,12 @@ CREATE TABLE IF NOT EXISTS "activity" ( "is_tracked" BOOLEAN NOT NULL DEFAULT false, "vetro_project_id" VARCHAR, "tag_id" VARCHAR NOT NULL, + "task_id" INTEGER, "project_id" VARCHAR NOT NULL, PRIMARY KEY("id"), FOREIGN KEY ("author_id") REFERENCES "user"("id") + ON UPDATE NO ACTION ON DELETE NO ACTION, + FOREIGN KEY ("task_id") REFERENCES "task"("id") ON UPDATE NO ACTION ON DELETE NO ACTION ); @@ -32,6 +35,18 @@ CREATE TABLE IF NOT EXISTS "activity_type" ( PRIMARY KEY("id") ); +CREATE TABLE IF NOT EXISTS "task" ( + "id" INTEGER, + "subject" TEXT, + "description" TEXT, + "status" TEXT, + "priority" TEXT, + "author" TEXT, + "tracker" TEXT, + "project" TEXT, + PRIMARY KEY ("id") +); + CREATE TABLE IF NOT EXISTS "time_entry" ( "id" INTEGER, "author_id" INTEGER NOT NULL, @@ -43,6 +58,9 @@ CREATE TABLE IF NOT EXISTS "time_entry" ( "activity_type_id" INTEGER NOT NULL, PRIMARY KEY("id"), FOREIGN KEY ("author_id") REFERENCES "user"("id") + ON UPDATE NO ACTION ON DELETE NO ACTION, + FOREIGN KEY ("task_id") REFERENCES "task"("id") + ON UPDATE NO ACTION ON DELETE NO ACTION, FOREIGN KEY ("activity_type_id") REFERENCES "activity_type"("id") ON UPDATE NO ACTION ON DELETE NO ACTION ); diff --git a/config/redmine.py b/config/redmine.py index 645e68e..17b46a2 100644 --- a/config/redmine.py +++ b/config/redmine.py @@ -5,3 +5,4 @@ class RedmineConfig: self.my_account_url = "/my/account.json" self.datetime_format = "%Y-%m-%d" self.time_entry_activities = "/enumerations/time_entry_activities.json" + self.issues = "/issues/{id}.json" diff --git a/db/db.py b/db/db.py index 5ae8c81..9d3e522 100644 --- a/db/db.py +++ b/db/db.py @@ -43,7 +43,7 @@ class Database: is_updated = bool(tags) try: self.cursor.execute( - "INSERT INTO activity VALUES(?,?,?,?,?,?,?,?,?,?)", + "INSERT INTO activity VALUES(?,?,?,?,?,?,?,?,?,?,?)", ( act.id, act.description, @@ -54,13 +54,14 @@ class Database: act.is_tracked, act.vetro_project_id, act.tag_id, + act.task_id, act.project_id, ) ) except sq.IntegrityError: self.cursor.execute( """UPDATE activity - SET description = ?, time_spent = ?, date_start = ?, date_end = ?, is_tracked = ?, vetro_project_id = ?, tag_id = ?, project_id = ? + SET description = ?, time_spent = ?, date_start = ?, date_end = ?, is_tracked = ?, vetro_project_id = ?, tag_id = ?, task_id = ?, project_id = ? WHERE id = ?""", ( act.description, @@ -70,6 +71,7 @@ class Database: act.is_tracked, act.vetro_project_id, act.tag_id, + act.task_id, act.project_id, act.id, ) @@ -211,3 +213,46 @@ class Database: ] except Exception as e: logging.error(e) + + def insert_tasks(self, tasks: list[dict]): + """Сохраняет в БД Задачи из Redmine""" + for task in tasks: + try: + self.cursor.execute(""" + INSERT INTO task( + "id", + "subject", + "description", + "status", + "priority", + "author", + "tracker", + "project" + ) + VALUES(?, ?, ?, ?, ?, ?, ?, ?)""", + [v for k, v in task.items()]) + except sq.IntegrityError: + self.cursor.execute(""" + UPDATE task + SET "subject" = ?, + "description" = ?, + "status" = ?, + "priority" = ?, + "author" = ?, + "tracker" = ?, + "project" = ? + WHERE "id" = ?""", + ( + task["subject"], + task["description"], + task["status"], + task["priority"], + task["author"], + task["tracker"], + task["project"], + task["id"] + ) + ) + finally: + self.con.commit() + diff --git a/track.py b/track.py index a4a2f52..7ef94d1 100644 --- a/track.py +++ b/track.py @@ -16,7 +16,7 @@ from colorama import Fore # noqa: E402 from api import ClockifyAPI, RedmineAPI # noqa: E402 from config import arg_parser, config, db # noqa: E402 -from utils import format_date, str_to_date, today_start, today_end # noqa: E402 +from utils import format_date, str_to_date, today_start, today_end, parse_task_id # noqa: E402 WORKSPACE_ID = config.WORKSPACE_ID @@ -80,9 +80,22 @@ def main(): if args.init_db: logger.info(Fore.YELLOW + "Обновление базы данных..." + Fore.RESET) try: - db.insert_tags(clock.get_tags(is_updating=True, **{"page-size": clock.tags_count})) - db.insert_vetro_projects(clock.get_vetro_projects()) - db.insert_activity_types(red.get_time_entry_activities()) + logging.info(f"{Fore.YELLOW}Получение Тегов{Fore.RESET}") + tags = clock.get_tags(is_updating=True, **{"page-size": clock.tags_count}) + logging.info(f"{Fore.YELLOW}Получение Задач с Redmine{Fore.RESET}") + issues = [red.get_issue(parse_task_id(tag.title)) for tag in tags] + logging.info(f"{Fore.YELLOW}Получение Проектов Ветро{Fore.RESET}") + vetro_projects = clock.get_vetro_projects() + logging.info(f"{Fore.YELLOW}Получение Типов деятельности{Fore.RESET}") + activity_types = red.get_time_entry_activities() + logging.info(f"{Fore.YELLOW}Сохранение Тегов{Fore.RESET}") + db.insert_tags(tags) + logging.info(f"{Fore.YELLOW}Сохранение Задач Redmine{Fore.RESET}") + db.insert_tasks(issues) + logging.info(f"{Fore.YELLOW}Сохранение Проектов Ветро{Fore.RESET}") + db.insert_vetro_projects(vetro_projects) + logging.info(f"{Fore.YELLOW}Сохранение Типов деятелности{Fore.RESET}") + db.insert_activity_types(activity_types) except Exception as e: logger.error(Fore.RED + f"Ошибка при обновлении базы данных: {e}" + Fore.RESET) else: