Implemented issue verification
This commit is contained in:
parent
169d9749af
commit
5a24c0c5eb
@ -12,8 +12,6 @@ except ImportError:
|
|||||||
from PySide2 import QtCore, QtWidgets
|
from PySide2 import QtCore, QtWidgets
|
||||||
PYSIDE_6 = False
|
PYSIDE_6 = False
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
import fime.icons
|
|
||||||
from fime.data import Tasks, Log, Data, LogCommentsData
|
from fime.data import Tasks, Log, Data, LogCommentsData
|
||||||
from fime.exceptions import FimeException
|
from fime.exceptions import FimeException
|
||||||
from fime.import_task import ImportTask
|
from fime.import_task import ImportTask
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide6 import QtCore, QtGui, QtWidgets
|
from PySide6 import QtCore, QtGui, QtWidgets
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
import fime.icons
|
||||||
|
|
||||||
|
|
||||||
def get_screen_height(qobject):
|
def get_screen_height(qobject):
|
||||||
if hasattr(qobject, "screen"):
|
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")
|
print("unable to detect screen height falling back to default value of 1080")
|
||||||
return 1080
|
return 1080
|
||||||
|
|
||||||
|
|
||||||
def get_screen_width(qobject):
|
def get_screen_width(qobject):
|
||||||
if hasattr(qobject, "screen"):
|
if hasattr(qobject, "screen"):
|
||||||
return qobject.screen().size().width()
|
return qobject.screen().size().width()
|
||||||
@ -37,3 +43,9 @@ class EditStartedDetector(QtWidgets.QStyledItemDelegate):
|
|||||||
editor.editingFinished.connect(self.editFinished)
|
editor.editingFinished.connect(self.editFinished)
|
||||||
self.editStarted.emit()
|
self.editStarted.emit()
|
||||||
return editor
|
return editor
|
||||||
|
|
||||||
|
|
||||||
|
class Status(enum.Enum):
|
||||||
|
PROGRESS = enum.auto()
|
||||||
|
OK = enum.auto()
|
||||||
|
ERROR = enum.auto()
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import enum
|
|
||||||
from datetime import datetime
|
|
||||||
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
|
||||||
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, get_screen_height, EditStartedDetector
|
from fime.util import get_icon, EditStartedDetector, Status
|
||||||
|
from fime.worklog_rest import WorklogRest
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PySide6 import QtCore, QtGui, QtWidgets
|
from PySide6 import QtCore, QtGui, QtWidgets
|
||||||
@ -14,8 +13,6 @@ except ImportError:
|
|||||||
from PySide2 import QtCore, QtGui, QtWidgets
|
from PySide2 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from fime.util import get_screen_width
|
from fime.util import get_screen_width
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
import fime.icons
|
|
||||||
|
|
||||||
|
|
||||||
class WorklogDialog(QtWidgets.QDialog):
|
class WorklogDialog(QtWidgets.QDialog):
|
||||||
@ -83,6 +80,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
|
completer.setFilterMode(QtCore.Qt.MatchFlag.MatchContains)
|
||||||
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||||
editor.setCompleter(completer)
|
editor.setCompleter(completer)
|
||||||
|
editor.textChanged.connect(completer.update_picker)
|
||||||
return editor
|
return editor
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
@ -94,23 +92,20 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
if not self.return_:
|
if not self.return_:
|
||||||
self.edit_finished_row.emit(row)
|
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):
|
def __init__(self, config, parent, *args, **kwargs):
|
||||||
super().__init__(parent, *args, **kwargs)
|
super().__init__(parent, *args, **kwargs)
|
||||||
|
|
||||||
self.setWindowTitle("Worklog")
|
self.rest = WorklogRest(config)
|
||||||
|
|
||||||
self._changing_items = False
|
self._changing_items = False
|
||||||
self._worklog: Optional[Worklog] = None
|
self._worklog: Optional[Worklog] = None
|
||||||
self._worklog_data: List[List[str]] = []
|
self._worklog_data: List[List[str]] = []
|
||||||
self._status: List[Tuple[WorklogDialog.Status, str]] = []
|
self._status: List[Tuple[Status, str]] = []
|
||||||
self.row_height = None
|
self.row_height = None
|
||||||
self._focussed = False
|
self._focussed = False
|
||||||
|
|
||||||
|
self.setWindowTitle("Worklog")
|
||||||
|
|
||||||
self.tableWidget = WorklogDialog.TabTable(self)
|
self.tableWidget = WorklogDialog.TabTable(self)
|
||||||
self.tableWidget.setColumnCount(3)
|
self.tableWidget.setColumnCount(3)
|
||||||
self.tableWidget.setHorizontalHeaderLabels(["Task", "Comment", "Duration"])
|
self.tableWidget.setHorizontalHeaderLabels(["Task", "Comment", "Duration"])
|
||||||
@ -171,18 +166,31 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
layout.addLayout(alayout)
|
layout.addLayout(alayout)
|
||||||
self.setLayout(layout)
|
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):
|
def set_data(self, worklog: Worklog):
|
||||||
self._worklog = worklog
|
self._worklog = worklog
|
||||||
self.refresh_table()
|
self.refresh_table()
|
||||||
self.update_title()
|
self.update_title()
|
||||||
self.update_prev_next()
|
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"):
|
def update_statuses(self):
|
||||||
self._status[row] = (new_status, status_text)
|
issue_keys = []
|
||||||
self.update_status_view(row)
|
for row in self._worklog_data[:-1]:
|
||||||
all_ok = reduce(lambda acc, it: acc and it[0] is WorklogDialog.Status.OK, self._status, True)
|
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:
|
if all_ok:
|
||||||
self.upload_button.setEnabled(True)
|
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):
|
def save(self):
|
||||||
self._worklog.worklog = self._worklog_data
|
self._worklog.worklog = self._worklog_data
|
||||||
@ -193,7 +201,7 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
def refresh_table(self):
|
def refresh_table(self):
|
||||||
self._worklog_data = self._worklog.worklog
|
self._worklog_data = self._worklog.worklog
|
||||||
for i, _ in enumerate(self._worklog_data[:-1]):
|
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
|
self._changing_items = True
|
||||||
if not self.row_height:
|
if not self.row_height:
|
||||||
self.tableWidget.setRowCount(1)
|
self.tableWidget.setRowCount(1)
|
||||||
@ -239,15 +247,15 @@ class WorklogDialog(QtWidgets.QDialog):
|
|||||||
self.move(x, y)
|
self.move(x, y)
|
||||||
|
|
||||||
def update_status_view(self, row):
|
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 = ProgressIndicator(self)
|
||||||
icon.setMaximumSize(QtCore.QSize(self.row_height * 0.75, self.row_height * 0.75))
|
icon.setMaximumSize(QtCore.QSize(self.row_height * 0.75, self.row_height * 0.75))
|
||||||
icon.setAnimationDelay(70)
|
icon.setAnimationDelay(70)
|
||||||
icon.startAnimation()
|
icon.startAnimation()
|
||||||
else:
|
else:
|
||||||
if self._status[row][0] is WorklogDialog.Status.OK:
|
if self._status[row][0] is Status.OK:
|
||||||
item_name = "dialog-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"
|
item_name = "edit-delete-remove"
|
||||||
icon = QtWidgets.QLabel()
|
icon = QtWidgets.QLabel()
|
||||||
icon.setPixmap(get_icon(item_name).pixmap(self.row_height * 0.75, self.row_height * 0.75))
|
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
|
return
|
||||||
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_status(row, WorklogDialog.Status.PROGRESS, "Fetching")
|
self.update_statuses()
|
||||||
|
self.update_timer.start()
|
||||||
self.refresh_table()
|
self.refresh_table()
|
||||||
|
|
||||||
@QtCore.Slot()
|
@QtCore.Slot()
|
||||||
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
96
src/fime/worklog_rest.py
Normal file
96
src/fime/worklog_rest.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user