diff --git a/src/fime/main.py b/src/fime/main.py index 4c28727..bbe400e 100755 --- a/src/fime/main.py +++ b/src/fime/main.py @@ -12,8 +12,6 @@ except ImportError: from PySide2 import QtCore, QtWidgets PYSIDE_6 = False -# noinspection PyUnresolvedReferences -import fime.icons from fime.data import Tasks, Log, Data, LogCommentsData from fime.exceptions import FimeException from fime.import_task import ImportTask diff --git a/src/fime/util.py b/src/fime/util.py index 183ae59..3ffb33f 100644 --- a/src/fime/util.py +++ b/src/fime/util.py @@ -1,8 +1,13 @@ +import enum + try: from PySide6 import QtCore, QtGui, QtWidgets except ImportError: from PySide2 import QtCore, QtGui, QtWidgets +# noinspection PyUnresolvedReferences +import fime.icons + def get_screen_height(qobject): if hasattr(qobject, "screen"): @@ -11,6 +16,7 @@ def get_screen_height(qobject): print("unable to detect screen height falling back to default value of 1080") return 1080 + def get_screen_width(qobject): if hasattr(qobject, "screen"): return qobject.screen().size().width() @@ -37,3 +43,9 @@ class EditStartedDetector(QtWidgets.QStyledItemDelegate): editor.editingFinished.connect(self.editFinished) self.editStarted.emit() return editor + + +class Status(enum.Enum): + PROGRESS = enum.auto() + OK = enum.auto() + ERROR = enum.auto() diff --git a/src/fime/worklog.py b/src/fime/worklog.py index e550983..90565b0 100644 --- a/src/fime/worklog.py +++ b/src/fime/worklog.py @@ -1,12 +1,11 @@ -import enum -from datetime import datetime from functools import reduce, partial from typing import Optional, List, Tuple from fime.data import Worklog from fime.progressindicator import ProgressIndicator from fime.task_completer import TaskCompleter -from fime.util import get_icon, get_screen_height, EditStartedDetector +from fime.util import get_icon, EditStartedDetector, Status +from fime.worklog_rest import WorklogRest try: from PySide6 import QtCore, QtGui, QtWidgets @@ -14,8 +13,6 @@ except ImportError: from PySide2 import QtCore, QtGui, QtWidgets from fime.util import get_screen_width -# noinspection PyUnresolvedReferences -import fime.icons class WorklogDialog(QtWidgets.QDialog): @@ -83,6 +80,7 @@ class WorklogDialog(QtWidgets.QDialog): completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) editor.setCompleter(completer) + editor.textChanged.connect(completer.update_picker) return editor @QtCore.Slot() @@ -94,23 +92,20 @@ class WorklogDialog(QtWidgets.QDialog): if not self.return_: self.edit_finished_row.emit(row) - class Status(enum.Enum): - PROGRESS = enum.auto() - OK = enum.auto() - ERROR = enum.auto() - def __init__(self, config, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) - self.setWindowTitle("Worklog") + self.rest = WorklogRest(config) self._changing_items = False self._worklog: Optional[Worklog] = None self._worklog_data: List[List[str]] = [] - self._status: List[Tuple[WorklogDialog.Status, str]] = [] + self._status: List[Tuple[Status, str]] = [] self.row_height = None self._focussed = False + self.setWindowTitle("Worklog") + self.tableWidget = WorklogDialog.TabTable(self) self.tableWidget.setColumnCount(3) self.tableWidget.setHorizontalHeaderLabels(["Task", "Comment", "Duration"]) @@ -171,18 +166,31 @@ class WorklogDialog(QtWidgets.QDialog): layout.addLayout(alayout) self.setLayout(layout) + self.update_timer = QtCore.QTimer(self) + self.update_timer.setInterval(500) + self.update_timer.timeout.connect(self.update_statuses) + def set_data(self, worklog: Worklog): self._worklog = worklog self.refresh_table() self.update_title() self.update_prev_next() + self.update_statuses() + self.update_timer.start() - def update_status(self, row, new_status, status_text="Could not find specified issue"): - self._status[row] = (new_status, status_text) - self.update_status_view(row) - all_ok = reduce(lambda acc, it: acc and it[0] is WorklogDialog.Status.OK, self._status, True) + def update_statuses(self): + issue_keys = [] + for row in self._worklog_data[:-1]: + issue_keys.append(row[0].split()[0]) + self._status = self.rest.get_issues_state(issue_keys) + for row, _ in enumerate(self._status): + self.update_status_view(row) + all_ok = reduce(lambda acc, it: acc and it[0] is Status.OK, self._status, True) if all_ok: self.upload_button.setEnabled(True) + all_done = reduce(lambda acc, it: acc and it[0] is not Status.PROGRESS, self._status, True) + if all_done: + self.update_timer.stop() def save(self): self._worklog.worklog = self._worklog_data @@ -193,7 +201,7 @@ class WorklogDialog(QtWidgets.QDialog): def refresh_table(self): self._worklog_data = self._worklog.worklog for i, _ in enumerate(self._worklog_data[:-1]): - self._status.append((WorklogDialog.Status.PROGRESS, "Fetching")) + self._status.append((Status.PROGRESS, "Fetching")) self._changing_items = True if not self.row_height: self.tableWidget.setRowCount(1) @@ -239,15 +247,15 @@ class WorklogDialog(QtWidgets.QDialog): self.move(x, y) def update_status_view(self, row): - if self._status[row][0] is WorklogDialog.Status.PROGRESS: + if self._status[row][0] is Status.PROGRESS: icon = ProgressIndicator(self) icon.setMaximumSize(QtCore.QSize(self.row_height * 0.75, self.row_height * 0.75)) icon.setAnimationDelay(70) icon.startAnimation() else: - if self._status[row][0] is WorklogDialog.Status.OK: + if self._status[row][0] is Status.OK: item_name = "dialog-ok" - elif self._status[row][0] is WorklogDialog.Status.ERROR: + elif self._status[row][0] is Status.ERROR: item_name = "edit-delete-remove" icon = QtWidgets.QLabel() icon.setPixmap(get_icon(item_name).pixmap(self.row_height * 0.75, self.row_height * 0.75)) @@ -308,7 +316,8 @@ class WorklogDialog(QtWidgets.QDialog): return self._worklog_data[row][0] = self.tableWidget.item(row, 0).text() self.save() - self.update_status(row, WorklogDialog.Status.PROGRESS, "Fetching") + self.update_statuses() + self.update_timer.start() self.refresh_table() @QtCore.Slot() diff --git a/src/fime/worklog_.py b/src/fime/worklog_.py deleted file mode 100644 index 26182c1..0000000 --- a/src/fime/worklog_.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -from datetime import date, datetime - -import requests - -from fime.config import Config - - -class Worklog: - def __init__(self, config: Config): - self.config = config - self.user_url = os.path.join(config.jira_url, "rest/api/2/myself") - self.issue_url = os.path.join(config.jira_url, "rest/api/2/issue/{}") - self.worklog_url = os.path.join(config.jira_url, "rest/api/2/issue/{}/worklog") - self.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue}/worklog/{worklog}") - self.user = self.get_user() - - def get_user(self): - resp = requests.get(self.user_url, - headers={ - "Authorization": f"Bearer {self.config.jira_token}", - "Accept": "application/json", - }, - ) - user = resp.json()["key"] - print(user) - return user - - def get(self, issue_key: str, date: date): - resp = requests.get(self.issue_url.format(issue_key), - headers={ - "Authorization": f"Bearer {self.config.jira_token}", - "Accept": "application/json", - }, - ) - if resp.status_code != 200: - raise RuntimeError("issue does not exist") - resp = requests.get(self.worklog_url.format(issue_key), - headers={ - "Authorization": f"Bearer {self.config.jira_token}", - "Accept": "application/json", - }, - ) - worklogs = resp.json()["worklogs"] - found = False - for log in worklogs: - if log["author"]["key"] == self.user \ - and datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == date: - print(log["id"]) - print(log["comment"]) - found = True - break - print(found) - - - diff --git a/src/fime/worklog_rest.py b/src/fime/worklog_rest.py new file mode 100644 index 0000000..70333a3 --- /dev/null +++ b/src/fime/worklog_rest.py @@ -0,0 +1,96 @@ +import os +import traceback +from concurrent.futures import Future +from datetime import date, datetime +from functools import partial +from threading import Lock +from typing import List, Dict, Tuple + +import requests +from requests_futures.sessions import FuturesSession + +from fime.config import Config +from fime.exceptions import FimeException +from fime.util import Status + + +class WorklogRest: + def __init__(self, config: Config): + self.config = config + self.user_url = os.path.join(config.jira_url, "rest/api/2/myself") + self.issue_url = os.path.join(config.jira_url, "rest/api/2/issue/{}") + self.worklog_url = os.path.join(config.jira_url, "rest/api/2/issue/{}/worklog") + self.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue}/worklog/{worklog}") + self.session = FuturesSession() + self.user = self._req_user() + self._issue_state: Dict[str, Tuple[Status, str]] = {} + self._issue_state_lock = Lock() + + def _req_user(self): + return self.session.get( + self.user_url, + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + + def _get_user(self): + try: + return self.user.result().json()["key"] + except Exception: + raise FimeException("Could not get user key:\n" + traceback.format_exc()) + + def get_issues_state(self, issue_keys: List[str]): + ret = [] + with self._issue_state_lock: + for issue_key in issue_keys: + if issue_key not in self._issue_state: + self._issue_state[issue_key] = (Status.PROGRESS, "Fetching") + self._req_issue(issue_key) + ret.append(self._issue_state[issue_key]) + return ret + + def _req_issue(self, issue_key: str): + future = self.session.get(self.issue_url.format(issue_key), + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + future.add_done_callback(partial(self._resp_issue, issue_key)) + + def _resp_issue(self, issue_key: str, future: Future): + resp: requests.Response = future.result() + with self._issue_state_lock: + if resp.status_code == 200: + hint = f'{issue_key} {resp.json()["fields"]["summary"]}' + self._issue_state[issue_key] = (Status.OK, hint) + else: + self._issue_state[issue_key] = (Status.ERROR, "Could not find specified issue") + + def get(self, issue_key: str, date: date): + resp = requests.get(self.issue_url.format(issue_key), + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + if resp.status_code != 200: + raise RuntimeError("issue does not exist") + resp = requests.get(self.worklog_url.format(issue_key), + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + worklogs = resp.json()["worklogs"] + found = False + for log in worklogs: + if log["author"]["key"] == self.user \ + and datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == date: + print(log["id"]) + print(log["comment"]) + found = True + break + print(found)