diff --git a/src/fime/worklog.py b/src/fime/worklog.py index 976b9bc..4b215bd 100644 --- a/src/fime/worklog.py +++ b/src/fime/worklog.py @@ -96,7 +96,8 @@ class WorklogDialog(QtWidgets.QDialog): def __init__(self, config: Config, worklog: Worklog, parent, *args, **kwargs): super().__init__(parent, *args, **kwargs) - self.rest = WorklogRest(config) + self.config = config + self.rest = WorklogRest(self.config) self._changing_items = False self._worklog = worklog @@ -172,8 +173,10 @@ class WorklogDialog(QtWidgets.QDialog): self.update_timer.timeout.connect(self.update_statuses) def showEvent(self, _): - self.rest.purge_cache() + # reinitialize to purge caches + self.rest = WorklogRest(self.config) self.update_all() + self.upload_button.setEnabled(False) def update_all(self): self.refresh_table() @@ -187,10 +190,13 @@ class WorklogDialog(QtWidgets.QDialog): for row in self._worklog_data[:-1]: issue_keys.append(row[0].split()[0]) old_statuses = self._statuses - self._statuses = self.rest.get_issues_state(issue_keys) + self._statuses = self.rest.get_issues_state(issue_keys, self._worklog.date) for row, status in enumerate(self._statuses): if len(old_statuses) != len(self._statuses) or old_statuses[row][0] != status[0]: self.update_status_view(row) + if not self._worklog_data[row][1] and status[2]: + self._worklog_data[row][1] = status[2] + self.tableWidget.cellWidget(row, 1).setPlainText(status[2]) all_ok = reduce(lambda acc, it: acc and it[0] is Status.OK, self._statuses, True) if all_ok: self.upload_button.setEnabled(True) diff --git a/src/fime/worklog_rest.py b/src/fime/worklog_rest.py index a3cc0be..22275d3 100644 --- a/src/fime/worklog_rest.py +++ b/src/fime/worklog_rest.py @@ -6,7 +6,7 @@ from datetime import date, datetime, timedelta, time from functools import partial from textwrap import dedent from threading import Lock -from typing import List, Dict, Tuple +from typing import List, Dict, Tuple, Optional import requests from requests_futures.sessions import FuturesSession @@ -25,9 +25,11 @@ class WorklogRest: self.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue_key}/worklog/{worklog_id}") self.session = FuturesSession() self._user = None + self._user_future = self._req_user() self._issue_state: Dict[str, Tuple[Status, str]] = dict() - self._issue_state_lock = Lock() - self._req_user() + self._issue_previous_comments: Dict[str, str] = dict() + self._issue_worklog_id: Dict[str, str] = dict() + self._issues_lock = Lock() def _req_user(self): future = self.session.get( @@ -38,6 +40,7 @@ class WorklogRest: }, ) future.add_done_callback(self._resp_user) + return future def _resp_user(self, future): try: @@ -46,20 +49,21 @@ class WorklogRest: print("Could not get user key:\n", file=sys.stderr) print(traceback.format_exc(), file=sys.stderr) - def get_issues_state(self, issue_keys: List[str]): + def get_issues_state(self, issue_keys: List[str], pdate: date) -> List[Tuple[Status, str, Optional[str]]]: ret = [] - with self._issue_state_lock: + with self._issues_lock: for issue_key in issue_keys: if issue_key not in self._issue_state: self._issue_state[issue_key] = (Status.PROGRESS, "Working") - self._req_issue(issue_key) - ret.append(self._issue_state[issue_key]) + self._req_issue(issue_key, pdate) + if issue_key in self._issue_previous_comments: + prev_comment = self._issue_previous_comments[issue_key] + else: + prev_comment = None + ret.append((*self._issue_state[issue_key], prev_comment)) return ret - def purge_cache(self): - self._issue_state = dict() - - def _req_issue(self, issue_key: str): + def _req_issue(self, issue_key: str, pdate :date): future = self.session.get( self.issue_url.format(issue_key), headers={ @@ -67,22 +71,60 @@ class WorklogRest: "Accept": "application/json", }, ) - future.add_done_callback(partial(self._resp_issue, issue_key)) + future.add_done_callback(partial(self._resp_issue, issue_key, pdate)) - def _resp_issue(self, issue_key: str, future: Future): + def _resp_issue(self, issue_key: str, pdate: date, future: Future): resp: requests.Response = future.result() - with self._issue_state_lock: + with self._issues_lock: if resp.status_code == 200: - hint = f'{issue_key} {resp.json()["fields"]["summary"]}' - self._issue_state[issue_key] = (Status.OK, hint) + issue_title = f'{issue_key} {resp.json()["fields"]["summary"]}' + self._req_worklog_check(issue_key, issue_title, pdate) else: self._issue_state[issue_key] = (Status.ERROR, "Could not find specified issue") + def _req_worklog_check(self, issue_key: str, issue_title: str, pdate: date): + future = self.session.get( + self.worklog_url.format(issue_key), + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + future.add_done_callback(partial(self._resp_worklog_check, issue_key, issue_title, pdate)) + + def _resp_worklog_check(self, issue_key: str, issue_title: str, pdate: date, future: Future): + resp = future.result() + worklogs = resp.json()["worklogs"] + worklogs = sorted( + worklogs, key=lambda x: datetime.strptime(x["started"], "%Y-%m-%dT%H:%M:%S.%f%z"), reverse=True) + self._user_future.result() + if not self._user: + with self._issues_lock: + self._issue_state[issue_key] = (Status.OK, issue_title) + return + worklog_found = False + with self._issues_lock: + for log in worklogs: + if log["author"]["key"] == self._user: + if datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == pdate: + self._issue_worklog_id[issue_key] = log["id"] + worklog_found = True + else: + self._issue_previous_comments[issue_key] = log["comment"].strip() + worklog_found = True + break + if worklog_found: + print(f"Found existing worklog for issue {issue_key}") + else: + print(f"Did not find existing worklog for issue {issue_key}") + self._issue_state[issue_key] = (Status.OK, issue_title) + def _upload_sanity_check(self, issue_keys: List[str]): if not self._user: raise FimeException("Could not get user key") + # lock is held by caller for issue_key in issue_keys: - if issue_key not in self._issue_state or self._issue_state[issue_key] is not Status.OK: + if issue_key not in self._issue_state or self._issue_state[issue_key][0] is not Status.OK: raise FimeException(f"Issue with key {issue_key} in unexpected state") def upload(self, issues: List[Tuple[str, str, timedelta]], pdate: date): @@ -92,33 +134,15 @@ class WorklogRest: timedelta(seconds=300), ), ... ] """ - self._upload_sanity_check(list(map(lambda x: x[0], issues))) - for issue in issues: - future = self.session.get( - self.worklog_url.format(issue[0]), - headers={ - "Authorization": f"Bearer {self.config.jira_token}", - "Accept": "application/json", - } - ) - future.add_done_callback(partial(self._worklog_check_resp, issue[0], issue[1], issue[2], pdate)) - self._issue_state[issue[0]] = (Status.PROGRESS, "Working") - - def _worklog_check_resp(self, issue_key: str, comment: str, time_spent: timedelta, pdate: date, future: Future): - resp = future.result() - worklogs = resp.json()["worklogs"] - worklog_id = None - for log in worklogs: - if log["author"]["key"] == self._user \ - and datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == pdate: - worklog_id = log["id"] - break - if worklog_id: - print(f"Found existing worklog for issue {issue_key} with id {worklog_id}") - self._worklog_update(issue_key, worklog_id, comment, time_spent, pdate) - else: - print(f"Did not find existing worklog for issue {issue_key}") - self._worklog_create(issue_key, comment, time_spent, pdate) + with self._issues_lock: + self._upload_sanity_check(list(map(lambda x: x[0], issues))) + for issue in issues: + issue_key, comment, time_spent = issue + if issue_key in self._issue_worklog_id: + self._worklog_update(issue_key, self._issue_worklog_id[issue_key], comment, time_spent, pdate) + else: + self._worklog_create(issue_key, comment, time_spent, pdate) + self._issue_state[issue[0]] = (Status.PROGRESS, "Working") def _worklog_create(self, issue_key: str, comment: str, time_spent: timedelta, pdate: date): future = self.session.post( @@ -156,8 +180,8 @@ class WorklogRest: def _worklog_resp(self, issue_key: str, future: Future): resp: requests.Response = future.result() - with self._issue_state_lock: - if resp.status_code == 200: + with self._issues_lock: + if resp.status_code in (200, 201): self._issue_state[issue_key] = (Status.OK, "Successfully uploaded") print(f"Successfully uploaded issue {issue_key}") else: