Finalize worklog implementation
This commit is contained in:
parent
32621bcc87
commit
2e348e68b4
@ -7,7 +7,7 @@ from collections.abc import MutableMapping
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date, timedelta
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
from typing import List, Tuple, Dict
|
from typing import List, Tuple, Dict, Union
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide6 import QtCore
|
from PySide6 import QtCore
|
||||||
@ -330,27 +330,27 @@ def duration_to_str(duration: timedelta) -> str:
|
|||||||
class PrevNextable:
|
class PrevNextable:
|
||||||
def __init__(self, data: LogCommentsData):
|
def __init__(self, data: LogCommentsData):
|
||||||
self._data = data
|
self._data = data
|
||||||
self._date = date.today()
|
self.date = date.today()
|
||||||
self._prev = None
|
self._prev = None
|
||||||
self._next = None
|
self._next = None
|
||||||
self._update_prev_next()
|
self._update_prev_next()
|
||||||
|
|
||||||
def _update_prev_next(self):
|
def _update_prev_next(self):
|
||||||
self._prev, self._next = self._data.get_prev_next_avail(self._date)
|
self._prev, self._next = self._data.get_prev_next_avail(self.date)
|
||||||
|
|
||||||
def prev_next_avail(self) -> Tuple[bool, bool]:
|
def prev_next_avail(self) -> Tuple[bool, bool]:
|
||||||
return self._prev is not None, self._next is not None
|
return self._prev is not None, self._next is not None
|
||||||
|
|
||||||
def previous(self):
|
def previous(self):
|
||||||
self._date = self._prev
|
self.date = self._prev
|
||||||
self._update_prev_next()
|
self._update_prev_next()
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
self._date = self._next
|
self.date = self._next
|
||||||
self._update_prev_next()
|
self._update_prev_next()
|
||||||
|
|
||||||
def date(self) -> str:
|
def date_str(self) -> str:
|
||||||
return self._date.strftime("%Y-%m-%d")
|
return self.date.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
||||||
class Report(PrevNextable):
|
class Report(PrevNextable):
|
||||||
@ -359,8 +359,8 @@ class Report(PrevNextable):
|
|||||||
self._not_log_len = 0
|
self._not_log_len = 0
|
||||||
|
|
||||||
def report(self) -> Tuple[List[List[str]], int]:
|
def report(self) -> Tuple[List[List[str]], int]:
|
||||||
log = self._data.get_log(self._date)
|
log = self._data.get_log(self.date)
|
||||||
if self._date == date.today():
|
if self.date == date.today():
|
||||||
log.append((datetime.now(), "End"))
|
log.append((datetime.now(), "End"))
|
||||||
ret = []
|
ret = []
|
||||||
for i, t in enumerate(log):
|
for i, t in enumerate(log):
|
||||||
@ -375,12 +375,12 @@ class Report(PrevNextable):
|
|||||||
ret.append(["", "", ""])
|
ret.append(["", "", ""])
|
||||||
ret.append(["", "Sums", ""])
|
ret.append(["", "Sums", ""])
|
||||||
|
|
||||||
tasks_summary, total_sum = summary(self._data, self._date)
|
tasks_summary, total_sum = summary(self._data, self.date)
|
||||||
for task, duration in tasks_summary.items():
|
for task, duration in tasks_summary.items():
|
||||||
ret.append([task, "", duration_to_str(duration)])
|
ret.append([task, "", duration_to_str(duration)])
|
||||||
ret.append(["Total sum", "", duration_to_str(total_sum)])
|
ret.append(["Total sum", "", duration_to_str(total_sum)])
|
||||||
self._not_log_len = 3 + len(tasks_summary)
|
self._not_log_len = 3 + len(tasks_summary)
|
||||||
if self._date == date.today():
|
if self.date == date.today():
|
||||||
self._not_log_len += 1
|
self._not_log_len += 1
|
||||||
editable_len = len(ret) - (4 + len(tasks_summary))
|
editable_len = len(ret) - (4 + len(tasks_summary))
|
||||||
|
|
||||||
@ -391,10 +391,10 @@ class Report(PrevNextable):
|
|||||||
if not report:
|
if not report:
|
||||||
return
|
return
|
||||||
report = list(map(
|
report = list(map(
|
||||||
lambda x: (datetime.combine(self._date, datetime.strptime(x[1], "%H:%M").time()), x[0]),
|
lambda x: (datetime.combine(self.date, datetime.strptime(x[1], "%H:%M").time()), x[0]),
|
||||||
report
|
report
|
||||||
))
|
))
|
||||||
self._data.set_log(self._date, report)
|
self._data.set_log(self.date, report)
|
||||||
|
|
||||||
|
|
||||||
class Worklog(PrevNextable):
|
class Worklog(PrevNextable):
|
||||||
@ -403,22 +403,22 @@ class Worklog(PrevNextable):
|
|||||||
self._worklog = []
|
self._worklog = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def worklog(self) -> List[List[str]]:
|
def worklog(self) -> List[List[Union[str, timedelta]]]:
|
||||||
tasks_summary, total_sum = summary(self._data, self._date)
|
tasks_summary, total_sum = summary(self._data, self.date)
|
||||||
comments = self._data.get_comments(self._date)
|
comments = self._data.get_comments(self.date)
|
||||||
self._worklog = []
|
self._worklog = []
|
||||||
for task, duration in tasks_summary.items():
|
for task, duration in tasks_summary.items():
|
||||||
self._worklog.append([task, comments.setdefault(task, ""), duration_to_str(duration)])
|
self._worklog.append([task, comments.setdefault(task, ""), duration])
|
||||||
self._worklog.append(["Total sum", "", duration_to_str(total_sum)])
|
self._worklog.append(["Total sum", "", total_sum])
|
||||||
return deepcopy(self._worklog)
|
return deepcopy(self._worklog)
|
||||||
|
|
||||||
@worklog.setter
|
@worklog.setter
|
||||||
def worklog(self, worklog: List[List[str]]):
|
def worklog(self, worklog: List[List[str]]):
|
||||||
log = self._data.get_log(self._date)
|
log = self._data.get_log(self.date)
|
||||||
set_comments = dict()
|
set_comments = dict()
|
||||||
for i, (task, comment, duration) in enumerate(worklog[:-1]):
|
for i, (task, comment, duration) in enumerate(worklog[:-1]):
|
||||||
set_comments[task] = comment
|
set_comments[task] = comment
|
||||||
if self._worklog[i][0] != task:
|
if self._worklog[i][0] != task:
|
||||||
log = list(map(lambda x: (x[0], x[1].replace(self._worklog[i][0], task)), log))
|
log = list(map(lambda x: (x[0], x[1].replace(self._worklog[i][0], task)), log))
|
||||||
self._data.set_comments(self._date, set_comments)
|
self._data.set_comments(self.date, set_comments)
|
||||||
self._data.set_log(self._date, log)
|
self._data.set_log(self.date, log)
|
||||||
|
@ -4,6 +4,7 @@ import sys
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from fime.config import Config
|
from fime.config import Config
|
||||||
|
from fime.worklog import WorklogDialog
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide6 import QtCore, QtWidgets
|
from PySide6 import QtCore, QtWidgets
|
||||||
@ -12,7 +13,7 @@ except ImportError:
|
|||||||
from PySide2 import QtCore, QtWidgets
|
from PySide2 import QtCore, QtWidgets
|
||||||
PYSIDE_6 = False
|
PYSIDE_6 = False
|
||||||
|
|
||||||
from fime.data import Tasks, Log, Data, LogCommentsData
|
from fime.data import Tasks, Log, Data, LogCommentsData, Worklog
|
||||||
from fime.exceptions import FimeException
|
from fime.exceptions import FimeException
|
||||||
from fime.import_task import ImportTask
|
from fime.import_task import ImportTask
|
||||||
from fime.report import Report
|
from fime.report import Report
|
||||||
@ -28,18 +29,19 @@ class App:
|
|||||||
data = Data()
|
data = Data()
|
||||||
self.tasks = Tasks(data)
|
self.tasks = Tasks(data)
|
||||||
lcd = LogCommentsData(data)
|
lcd = LogCommentsData(data)
|
||||||
|
self.worklogData = Worklog(lcd)
|
||||||
self.log = Log(lcd)
|
self.log = Log(lcd)
|
||||||
self._active_task = self.log.last_log() or "Nothing"
|
self._active_task = self.log.last_log() or "Nothing"
|
||||||
|
|
||||||
self.config = Config()
|
config = Config()
|
||||||
if self.config.tray_theme == "light":
|
if config.tray_theme == "light":
|
||||||
icon = get_icon("appointment-new-light")
|
icon = get_icon("appointment-new-light")
|
||||||
else:
|
else:
|
||||||
icon = get_icon("appointment-new")
|
icon = get_icon("appointment-new")
|
||||||
|
|
||||||
self.menu = QtWidgets.QMenu(None)
|
self.menu = QtWidgets.QMenu(None)
|
||||||
|
|
||||||
self.import_task = ImportTask(self.config, None)
|
self.import_task = ImportTask(config, None)
|
||||||
self.import_task.accepted.connect(self.new_task_imported)
|
self.import_task.accepted.connect(self.new_task_imported)
|
||||||
|
|
||||||
self.taskEdit = TaskEdit(None)
|
self.taskEdit = TaskEdit(None)
|
||||||
@ -48,6 +50,9 @@ class App:
|
|||||||
self.reportDialog = Report(self.tasks, None)
|
self.reportDialog = Report(self.tasks, None)
|
||||||
self.reportDialog.accepted.connect(self.report_done)
|
self.reportDialog.accepted.connect(self.report_done)
|
||||||
|
|
||||||
|
self.worklogDialog = WorklogDialog(config, None)
|
||||||
|
self.worklogDialog.accepted.connect(self.report_done)
|
||||||
|
|
||||||
self.tray = QtWidgets.QSystemTrayIcon()
|
self.tray = QtWidgets.QSystemTrayIcon()
|
||||||
self.tray.setIcon(icon)
|
self.tray.setIcon(icon)
|
||||||
self.tray.setContextMenu(self.menu)
|
self.tray.setContextMenu(self.menu)
|
||||||
@ -120,6 +125,9 @@ class App:
|
|||||||
report_action = self.menu.addAction("Report")
|
report_action = self.menu.addAction("Report")
|
||||||
report_action.triggered.connect(self.report)
|
report_action.triggered.connect(self.report)
|
||||||
|
|
||||||
|
worklog_action = self.menu.addAction("Worklog")
|
||||||
|
worklog_action.triggered.connect(self.worklog)
|
||||||
|
|
||||||
self.menu.addSeparator()
|
self.menu.addSeparator()
|
||||||
|
|
||||||
exit_action = self.menu.addAction("Close")
|
exit_action = self.menu.addAction("Close")
|
||||||
@ -146,6 +154,11 @@ class App:
|
|||||||
self.reportDialog.set_data(self.log.report())
|
self.reportDialog.set_data(self.log.report())
|
||||||
self.reportDialog.show()
|
self.reportDialog.show()
|
||||||
|
|
||||||
|
@QtCore.Slot()
|
||||||
|
def worklog(self):
|
||||||
|
self.worklogDialog.set_data(self.worklogData)
|
||||||
|
self.worklogDialog.show()
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
def new_task_slot(self):
|
def new_task_slot(self):
|
||||||
self.import_task.reset_task_text()
|
self.import_task.reset_task_text()
|
||||||
@ -157,14 +170,21 @@ class App:
|
|||||||
self.taskEdit.show()
|
self.taskEdit.show()
|
||||||
|
|
||||||
|
|
||||||
|
def excepthook(original, e_type, e_value, tb_obj):
|
||||||
|
if e_type is FimeException:
|
||||||
|
QtWidgets.QMessageBox.critical(None, "Error", str(e_value), QtWidgets.QMessageBox.Ok)
|
||||||
|
else:
|
||||||
|
original(e_type, e_value, tb_obj)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
# important for QStandardPath to be correct
|
||||||
# important for QStandardPath to be correct
|
QtCore.QCoreApplication.setApplicationName("fime")
|
||||||
QtCore.QCoreApplication.setApplicationName("fime")
|
# also catches exceptions in other threads
|
||||||
app = App()
|
original_excepthook = sys.excepthook
|
||||||
app.run()
|
sys.excepthook = partial(excepthook, original_excepthook)
|
||||||
except FimeException as e:
|
app = App()
|
||||||
QtWidgets.QMessageBox.critical(None, "Error", str(e), QtWidgets.QMessageBox.Ok)
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -107,7 +107,7 @@ class Report(QtWidgets.QDialog):
|
|||||||
self._report.save(self._report_data)
|
self._report.save(self._report_data)
|
||||||
|
|
||||||
def update_title(self):
|
def update_title(self):
|
||||||
self.setWindowTitle(f"Report {self._report.date()}")
|
self.setWindowTitle(f"Report {self._report.date_str()}")
|
||||||
|
|
||||||
def refresh_table(self):
|
def refresh_table(self):
|
||||||
self._report_data, self._edit_len = self._report.report()
|
self._report_data, self._edit_len = self._report.report()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from functools import reduce, partial
|
from functools import reduce, partial
|
||||||
from typing import Optional, List, Tuple
|
from typing import Optional, List, Tuple
|
||||||
|
|
||||||
from fime.data import Worklog
|
from fime.data import Worklog, duration_to_str
|
||||||
from fime.progressindicator import ProgressIndicator
|
from fime.progressindicator import ProgressIndicator
|
||||||
from fime.task_completer import TaskCompleter
|
from fime.task_completer import TaskCompleter
|
||||||
from fime.util import get_icon, EditStartedDetector, Status
|
from fime.util import get_icon, EditStartedDetector, Status
|
||||||
@ -143,7 +143,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
self.upload_button = QtWidgets.QPushButton()
|
self.upload_button = QtWidgets.QPushButton()
|
||||||
self.upload_button.setText("Upload")
|
self.upload_button.setText("Upload")
|
||||||
self.upload_button.setIcon(get_icon("cloud-upload"))
|
self.upload_button.setIcon(get_icon("cloud-upload"))
|
||||||
#self.upload_button.pressed.connect(self.del_log)
|
self.upload_button.pressed.connect(self.upload)
|
||||||
self.upload_button.setAutoDefault(False)
|
self.upload_button.setAutoDefault(False)
|
||||||
self.upload_button.setMinimumWidth(self.upload_button.minimumSizeHint().width() * 1.33)
|
self.upload_button.setMinimumWidth(self.upload_button.minimumSizeHint().width() * 1.33)
|
||||||
self.upload_button.setEnabled(False)
|
self.upload_button.setEnabled(False)
|
||||||
@ -172,6 +172,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
|
|
||||||
def set_data(self, worklog: Worklog):
|
def set_data(self, worklog: Worklog):
|
||||||
self._worklog = worklog
|
self._worklog = worklog
|
||||||
|
self.rest.purge_cache()
|
||||||
self.update_all()
|
self.update_all()
|
||||||
|
|
||||||
def update_all(self):
|
def update_all(self):
|
||||||
@ -201,7 +202,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
self._worklog.worklog = self._worklog_data
|
self._worklog.worklog = self._worklog_data
|
||||||
|
|
||||||
def update_title(self):
|
def update_title(self):
|
||||||
self.setWindowTitle(f"Worklog {self._worklog.date()}")
|
self.setWindowTitle(f"Worklog {self._worklog.date_str()}")
|
||||||
|
|
||||||
def refresh_table(self):
|
def refresh_table(self):
|
||||||
self._worklog_data = self._worklog.worklog
|
self._worklog_data = self._worklog.worklog
|
||||||
@ -231,7 +232,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
if row == 0 and not self._focussed:
|
if row == 0 and not self._focussed:
|
||||||
text_edit.setFocus()
|
text_edit.setFocus()
|
||||||
self._focussed = True
|
self._focussed = True
|
||||||
item2 = QtWidgets.QTableWidgetItem(self._worklog_data[row][2])
|
item2 = QtWidgets.QTableWidgetItem(duration_to_str(self._worklog_data[row][2]))
|
||||||
self.tableWidget.setItem(row, 2, item2)
|
self.tableWidget.setItem(row, 2, item2)
|
||||||
item2.setFlags(item2.flags() & QtCore.Qt.ItemIsEnabled)
|
item2.setFlags(item2.flags() & QtCore.Qt.ItemIsEnabled)
|
||||||
self._changing_items = False
|
self._changing_items = False
|
||||||
@ -298,6 +299,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
self._worklog.previous()
|
self._worklog.previous()
|
||||||
self._focussed = False
|
self._focussed = False
|
||||||
self.update_all()
|
self.update_all()
|
||||||
|
self.upload_button.setEnabled(False)
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
def next(self):
|
def next(self):
|
||||||
@ -305,6 +307,14 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
self._worklog.next()
|
self._worklog.next()
|
||||||
self._focussed = False
|
self._focussed = False
|
||||||
self.update_all()
|
self.update_all()
|
||||||
|
self.upload_button.setEnabled(False)
|
||||||
|
|
||||||
|
@QtCore.Slot()
|
||||||
|
def upload(self):
|
||||||
|
issues = list(map(lambda x: (x[0].split()[0], x[1], x[2]), self._worklog_data[:-1]))
|
||||||
|
self.rest.upload(issues, self._worklog.date)
|
||||||
|
self.update_statuses()
|
||||||
|
self.update_timer.start()
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
def cell_changed(self, row, _):
|
def cell_changed(self, row, _):
|
||||||
@ -313,6 +323,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
self._worklog_data[row][0] = self.tableWidget.item(row, 0).text()
|
self._worklog_data[row][0] = self.tableWidget.item(row, 0).text()
|
||||||
self.save()
|
self.save()
|
||||||
self.update_all()
|
self.update_all()
|
||||||
|
self.upload_button.setEnabled(False)
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
def text_edit_changed(self, text, row):
|
def text_edit_changed(self, text, row):
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime, timedelta, time
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from textwrap import dedent
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import List, Dict, Tuple
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
@ -20,44 +22,51 @@ class WorklogRest:
|
|||||||
self.user_url = os.path.join(config.jira_url, "rest/api/2/myself")
|
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.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_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.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue_key}/worklog/{worklog_id}")
|
||||||
self.session = FuturesSession()
|
self.session = FuturesSession()
|
||||||
self.user = self._req_user()
|
self._user = None
|
||||||
self._issue_state: Dict[str, Tuple[Status, str]] = {}
|
self._issue_state: Dict[str, Tuple[Status, str]] = dict()
|
||||||
self._issue_state_lock = Lock()
|
self._issue_state_lock = Lock()
|
||||||
|
self._req_user()
|
||||||
|
|
||||||
def _req_user(self):
|
def _req_user(self):
|
||||||
return self.session.get(
|
future = self.session.get(
|
||||||
self.user_url,
|
self.user_url,
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {self.config.jira_token}",
|
"Authorization": f"Bearer {self.config.jira_token}",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
future.add_done_callback(self._resp_user)
|
||||||
|
|
||||||
def _get_user(self):
|
def _resp_user(self, future):
|
||||||
try:
|
try:
|
||||||
return self.user.result().json()["key"]
|
self._user = future.result().json()["key"]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise FimeException("Could not get user key:\n" + traceback.format_exc())
|
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]):
|
||||||
ret = []
|
ret = []
|
||||||
with self._issue_state_lock:
|
with self._issue_state_lock:
|
||||||
for issue_key in issue_keys:
|
for issue_key in issue_keys:
|
||||||
if issue_key not in self._issue_state:
|
if issue_key not in self._issue_state:
|
||||||
self._issue_state[issue_key] = (Status.PROGRESS, "Fetching")
|
self._issue_state[issue_key] = (Status.PROGRESS, "Working")
|
||||||
self._req_issue(issue_key)
|
self._req_issue(issue_key)
|
||||||
ret.append(self._issue_state[issue_key])
|
ret.append(self._issue_state[issue_key])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def purge_cache(self):
|
||||||
|
self._issue_state = dict()
|
||||||
|
|
||||||
def _req_issue(self, issue_key: str):
|
def _req_issue(self, issue_key: str):
|
||||||
future = self.session.get(self.issue_url.format(issue_key),
|
future = self.session.get(
|
||||||
headers={
|
self.issue_url.format(issue_key),
|
||||||
"Authorization": f"Bearer {self.config.jira_token}",
|
headers={
|
||||||
"Accept": "application/json",
|
"Authorization": f"Bearer {self.config.jira_token}",
|
||||||
},
|
"Accept": "application/json",
|
||||||
)
|
},
|
||||||
|
)
|
||||||
future.add_done_callback(partial(self._resp_issue, issue_key))
|
future.add_done_callback(partial(self._resp_issue, issue_key))
|
||||||
|
|
||||||
def _resp_issue(self, issue_key: str, future: Future):
|
def _resp_issue(self, issue_key: str, future: Future):
|
||||||
@ -69,28 +78,95 @@ class WorklogRest:
|
|||||||
else:
|
else:
|
||||||
self._issue_state[issue_key] = (Status.ERROR, "Could not find specified issue")
|
self._issue_state[issue_key] = (Status.ERROR, "Could not find specified issue")
|
||||||
|
|
||||||
def get(self, issue_key: str, date: date):
|
def _upload_sanity_check(self, issue_keys: List[str]):
|
||||||
resp = requests.get(self.issue_url.format(issue_key),
|
if not self._user:
|
||||||
headers={
|
raise FimeException("Could not get user key")
|
||||||
"Authorization": f"Bearer {self.config.jira_token}",
|
for issue_key in issue_keys:
|
||||||
"Accept": "application/json",
|
if issue_key not in self._issue_state or self._issue_state[issue_key] is not Status.OK:
|
||||||
},
|
raise FimeException(f"Issue with key {issue_key} in unexpected state")
|
||||||
)
|
|
||||||
if resp.status_code != 200:
|
def upload(self, issues: List[Tuple[str, str, timedelta]], pdate: date):
|
||||||
raise RuntimeError("issue does not exist")
|
"""@:param issues: [(
|
||||||
resp = requests.get(self.worklog_url.format(issue_key),
|
"ISS-1234",
|
||||||
headers={
|
"I did some stuff",
|
||||||
"Authorization": f"Bearer {self.config.jira_token}",
|
timedelta(seconds=300),
|
||||||
"Accept": "application/json",
|
), ... ]
|
||||||
},
|
"""
|
||||||
)
|
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"]
|
worklogs = resp.json()["worklogs"]
|
||||||
found = False
|
worklog_id = None
|
||||||
for log in worklogs:
|
for log in worklogs:
|
||||||
if log["author"]["key"] == self.user \
|
if log["author"]["key"] == self._user \
|
||||||
and datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == date:
|
and datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == pdate:
|
||||||
print(log["id"])
|
worklog_id = log["id"]
|
||||||
print(log["comment"])
|
|
||||||
found = True
|
|
||||||
break
|
break
|
||||||
print(found)
|
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)
|
||||||
|
|
||||||
|
def _worklog_create(self, issue_key: str, comment: str, time_spent: timedelta, pdate: date):
|
||||||
|
future = self.session.post(
|
||||||
|
self.worklog_url.format(issue_key),
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {self.config.jira_token}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
json={
|
||||||
|
"started": datetime.combine(
|
||||||
|
pdate, time(8, 0), datetime.now().astimezone().tzinfo).strftime("%Y-%m-%dT%H:%M:%S.000%z"),
|
||||||
|
"timeSpentSeconds": time_spent.seconds,
|
||||||
|
"comment": comment,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
future.add_done_callback(partial(self._worklog_resp, issue_key))
|
||||||
|
|
||||||
|
def _worklog_update(self, issue_key: str, worklog_id: str, comment: str, time_spent: timedelta, pdate: date):
|
||||||
|
future = self.session.put(
|
||||||
|
self.worklog_update_url.format(issue_key=issue_key, worklog_id=worklog_id),
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {self.config.jira_token}",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
json={
|
||||||
|
"started": datetime.combine(
|
||||||
|
pdate, time(8, 0), datetime.now().astimezone().tzinfo).strftime("%Y-%m-%dT%H:%M:%S.000%z"),
|
||||||
|
"timeSpentSeconds": time_spent.seconds,
|
||||||
|
"comment": comment,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
future.add_done_callback(partial(self._worklog_resp, issue_key))
|
||||||
|
|
||||||
|
def _worklog_resp(self, issue_key: str, future: Future):
|
||||||
|
resp: requests.Response = future.result()
|
||||||
|
with self._issue_state_lock:
|
||||||
|
if resp.status_code == 200:
|
||||||
|
self._issue_state[issue_key] = (Status.OK, "Successfully uploaded")
|
||||||
|
print(f"Successfully uploaded issue {issue_key}")
|
||||||
|
else:
|
||||||
|
msg = dedent(f"""\
|
||||||
|
Worklog upload failed:
|
||||||
|
Method: {resp.request.method}
|
||||||
|
URL: {resp.request.url}
|
||||||
|
Response code: {resp.status_code}
|
||||||
|
Response: {resp.text}
|
||||||
|
""")
|
||||||
|
self._issue_state[issue_key] = (Status.ERROR, msg)
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
Loading…
Reference in New Issue
Block a user