Compare commits

...

10 Commits

16 changed files with 264 additions and 81 deletions

View File

@ -8,6 +8,7 @@ pyside6 = "~=6.3"
requests = "~=2.28" requests = "~=2.28"
requests-futures = "~=1.0" requests-futures = "~=1.0"
packaging = "~=21.3" packaging = "~=21.3"
loguru = "~=0.6"
[dev-packages] [dev-packages]
pyinstaller = "~=5.1" pyinstaller = "~=5.1"

10
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "633aca7e0b43a295ce3429d3e12976a4c16dcf422cee53e37d266f8000ef5feb" "sha256": "b04dfa0eda7fd2df5b6d27d5a1f7d71168c984c74ce363c45a888430bca7b7d8"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -40,6 +40,14 @@
"markers": "python_version >= '3.5'", "markers": "python_version >= '3.5'",
"version": "==3.4" "version": "==3.4"
}, },
"loguru": {
"hashes": [
"sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c",
"sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"
],
"index": "pypi",
"version": "==0.6.0"
},
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",

8
scripts/release.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
last_patch=$(git tag | sort -V | tail -n1 | cut -d "." -f3)
git tag "1.0.$((last_patch + 1))"
git push gitlab main -o ci.skip
git push gitlab --tags
git push origin main
git push origin --tags

View File

@ -22,6 +22,7 @@ install_requires =
requests requests
requests-futures requests-futures
packaging packaging
loguru
[options.packages.find] [options.packages.find]
where = src where = src

View File

@ -1,5 +1,5 @@
try: try:
from fime._version import __version__, __version_tuple__ from fime._version import __version__, __version_tuple__
except ImportError: except ImportError:
__version__ = version = '1.0.0.dev0' __version__ = version = '0+devfallback'
__version_tuple__ = version_tuple = (1, 0, 0, 'dev0') __version_tuple__ = version_tuple = (0, 'devfallback')

View File

@ -1,22 +1,22 @@
import sys import sys
import traceback
from copy import copy from copy import copy
from pathlib import Path
from textwrap import dedent from textwrap import dedent
from threading import Lock from threading import Lock
from typing import Optional from typing import Optional
from loguru import logger
from packaging.version import Version from packaging.version import Version
from requests_futures.sessions import FuturesSession from requests_futures.sessions import FuturesSession
from fime.progressindicator import ProgressIndicator
from fime.util import get_icon
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
import fime import fime
from fime.progressindicator import ProgressIndicator
from fime.util import get_icon
URL = "https://gitlab.com/faerbit/fime/-/releases/permalink/latest" URL = "https://gitlab.com/faerbit/fime/-/releases/permalink/latest"
@ -51,8 +51,7 @@ class UpdateChecker:
else: else:
self.result = f"Newer fime version available: {latest_version}" self.result = f"Newer fime version available: {latest_version}"
except Exception: except Exception:
print("Could not get update info:\n", file=sys.stderr) logger.exception("Could not get update info")
print(traceback.format_exc(), file=sys.stderr)
finally: finally:
with self.lock: with self.lock:
self._done = True self._done = True
@ -63,14 +62,18 @@ class About(QtWidgets.QDialog):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.setWindowTitle("About") self.setWindowTitle("About")
log_dir_path = Path(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation)) / "logs"
text = dedent(f"""\ text = dedent(f"""\
fime fime
Copyright (c) 2020 - 2022 Faerbit Copyright (c) 2020 - 2022 Faerbit
<a href="https://gitlab.com/faerbit/fime/-/blob/main/LICENSE">License</a> <a href="https://fime.faerb.it">Website</a> <a href="https://gitlab.com/faerbit/fime/-/blob/main/LICENSE">License</a>
fime Version {fime.__version__} fime Version {fime.__version__}
Qt Version {QtCore.__version__} Qt Version {QtCore.__version__}
Python Version {sys.version} Python Version {sys.version}
Log directory: <a href="{log_dir_path}">{log_dir_path}</a>
""") """)
text = text.replace("\n", "<br/>") text = text.replace("\n", "<br/>")
version_label = QtWidgets.QLabel(self) version_label = QtWidgets.QLabel(self)

View File

@ -1,6 +1,9 @@
from configparser import ConfigParser from configparser import ConfigParser
from io import StringIO
from pathlib import Path from pathlib import Path
from loguru import logger
try: try:
from PySide6 import QtCore from PySide6 import QtCore
except ImportError: except ImportError:
@ -20,32 +23,63 @@ class Config:
def __init__(self): def __init__(self):
self._configparser = ConfigParser() self._configparser = ConfigParser()
config_dir_path = Path(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppConfigLocation)) config_dir_path = Path(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppConfigLocation))
config_path = config_dir_path / "fime.conf" self.config_path = config_dir_path / "fime.conf"
if config_path.exists(): if self.config_path.exists():
print(f'Reading config file "{config_path}"') logger.info(f'reading config file "{self.config_path}"')
with open(config_path) as f: with self.config_path.open(encoding="utf-8") as f:
config_text = f.read() config_text = f.read()
config_text = "[DEFAULT]\n" + config_text config_text = "[DEFAULT]\n" + config_text
self._configparser.read_string(config_text) self._configparser.read_string(config_text)
# TODO change menu items
if (not self._configparser.has_option("DEFAULT", "jira_url") or if (not self._configparser.has_option("DEFAULT", "jira_url") or
not self._configparser.has_option("DEFAULT", "jira_token")): not self._configparser.has_option("DEFAULT", "jira_token")):
raise FimeException(f'Please add config file {config_path} ' raise FimeException(f'Please add config file {self.config_path} '
f'with config keys "jira_url" and "jira_token" in INI style') f'with config keys "jira_url" and "jira_token" in INI style')
def save(self):
logger.info(f'writing config file "{self.config_path}"')
config_str = StringIO()
self._configparser.write(config_str)
# do not conform to configparser's stupid section requirement
config_str = "\n".join(config_str.getvalue().splitlines()[1:])
with self.config_path.open("w", encoding="utf-8") as f:
f.write(config_str)
@property @property
def jira_url(self): def jira_url(self):
return dequotify(self._configparser["DEFAULT"]["jira_url"]) return dequotify(self._configparser["DEFAULT"]["jira_url"])
@jira_url.setter
def jira_url(self, value):
self._configparser["DEFAULT"]["jira_url"] = f'"{value}"'
@property @property
def jira_token(self): def jira_token(self):
return dequotify(self._configparser["DEFAULT"]["jira_token"]) return dequotify(self._configparser["DEFAULT"]["jira_token"])
@jira_token.setter
def jira_token(self, value):
self._configparser["DEFAULT"]["jira_token"] = f'"{value}"'
@property @property
def tray_theme(self): def tray_theme(self):
val = dequotify(self._configparser.get("DEFAULT", "tray_theme", fallback="dark")).lower() val = dequotify(self._configparser.get("DEFAULT", "tray_theme", fallback="dark")).lower()
return val if val in ["light", "dark"] else "dark" return val if val in ["light", "dark"] else "dark"
@tray_theme.setter
def tray_theme(self, value):
value = value.lower()
if value not in ["light", "dark"]:
raise RuntimeError('config key "tray_theme" can only be set to "light" or "dark"')
self._configparser["DEFAULT"]["tray_theme"] = f'"{value}"'
@property @property
def flip_menu(self): def flip_menu(self):
val = dequotify(self._configparser.get("DEFAULT", "flip_menu", fallback="no")).lower() val = dequotify(self._configparser.get("DEFAULT", "flip_menu", fallback="no")).lower()
return val in ["yes", "true", "1"] return val in ["yes", "true", "1"]
@flip_menu.setter
def flip_menu(self, value):
if type(value) is not bool:
raise RuntimeError('config key "flip_menu" must be a bool')
self._configparser["DEFAULT"]["flip_menu"] = f'"{value}"'

View File

@ -8,6 +8,8 @@ from datetime import datetime, date, timedelta, time
from threading import Thread, Event from threading import Thread, Event
from typing import List, Tuple, Dict, Union from typing import List, Tuple, Dict, Union
from loguru import logger
try: try:
from PySide6 import QtCore from PySide6 import QtCore
except ImportError: except ImportError:
@ -63,7 +65,7 @@ class Data(MutableMapping):
def _save(self): def _save(self):
for key in self._hot_keys: for key in self._hot_keys:
print(f"... saving dict {key} ...") logger.info(f"saving dict {key}")
to_write = self._cache[key] # apparently thread-safe to_write = self._cache[key] # apparently thread-safe
with open(self.data_path.format(key), "w+") as f: with open(self.data_path.format(key), "w+") as f:
f.write(json.dumps(to_write)) f.write(json.dumps(to_write))

View File

@ -14,17 +14,19 @@ class ImportTask(QtWidgets.QDialog):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.setWindowTitle("New Tasks") self.setWindowTitle("New Tasks")
self.config = config
self.line_edit = QtWidgets.QLineEdit(self) self.line_edit = QtWidgets.QLineEdit(self)
completer = TaskCompleter(config) self.completer = TaskCompleter(config)
self.line_edit.setCompleter(completer) self.line_edit.setCompleter(self.completer)
self.line_edit.textChanged.connect(completer.update_picker) self.line_edit.textChanged.connect(self.completer.update_picker)
self.line_edit.setFocus() self.line_edit.setFocus()
self.indicator = ProgressIndicator(self) self.indicator = ProgressIndicator(self)
self.indicator.setAnimationDelay(70) self.indicator.setAnimationDelay(70)
self.indicator.setDisplayedWhenStopped(False) self.indicator.setDisplayedWhenStopped(False)
completer.running.connect(self.spin) self.completer.running.connect(self.spin)
completer.stopped.connect(self.no_spin) self.completer.stopped.connect(self.no_spin)
ok_button = QtWidgets.QPushButton() ok_button = QtWidgets.QPushButton()
ok_button.setText("OK") ok_button.setText("OK")
@ -77,6 +79,8 @@ class ImportTask(QtWidgets.QDialog):
return self.line_edit.text() return self.line_edit.text()
def showEvent(self, _): def showEvent(self, _):
# pick up config changes
self.completer.update_urls()
self.line_edit.setText("") self.line_edit.setText("")
self.raise_() self.raise_()
self.activateWindow() self.activateWindow()

View File

@ -2,10 +2,10 @@
import signal import signal
import sys import sys
from functools import partial from functools import partial
from pathlib import Path
from typing import Optional
from fime.about import About from loguru import logger
from fime.config import Config
from fime.worklog import WorklogDialog
try: try:
from PySide6 import QtCore, QtWidgets from PySide6 import QtCore, QtWidgets
@ -14,10 +14,14 @@ except ImportError:
from PySide2 import QtCore, QtWidgets from PySide2 import QtCore, QtWidgets
PYSIDE_6 = False PYSIDE_6 = False
from fime.about import About
from fime.config import Config
from fime.worklog import WorklogDialog
from fime.data import Tasks, Log, Data, LogCommentsData, Worklog, Report from fime.data import Tasks, Log, Data, LogCommentsData, Worklog, Report
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 ReportDialog from fime.report import ReportDialog
from fime.settings import Settings
from fime.task_edit import TaskEdit from fime.task_edit import TaskEdit
from fime.util import get_screen_height, get_icon from fime.util import get_screen_height, get_icon
@ -33,37 +37,44 @@ class App:
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"
config = Config() self.config = Config()
if config.tray_theme == "light":
icon = get_icon("appointment-new-light")
else:
icon = get_icon("appointment-new")
self.menu_flipped = config.flip_menu
self.menu = QtWidgets.QMenu(None) self.menu = QtWidgets.QMenu(None)
self.import_task = ImportTask(config, None) self.import_task = ImportTask(self.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(self.tasks, None)
self.taskEdit.accepted.connect(self.tasks_edited) self.taskEdit.accepted.connect(self.tasks_edited)
self.reportDialog = ReportDialog(self.tasks, Report(lcd), None) self.reportDialog = ReportDialog(self.tasks, Report(lcd), None)
self.reportDialog.accepted.connect(self.log_edited) self.reportDialog.accepted.connect(self.log_edited)
self.worklogDialog = WorklogDialog(config, Worklog(lcd), None) self.worklogDialog = WorklogDialog(self.config, Worklog(lcd), None)
self.worklogDialog.accepted.connect(self.log_edited) self.worklogDialog.accepted.connect(self.log_edited)
self.settings = Settings(self.config, None)
self.settings.accepted.connect(self.update_icon)
self.settings.accepted.connect(self.update_tray_menu)
self.about = About(None) self.about = About(None)
self.tray = QtWidgets.QSystemTrayIcon() self.tray = QtWidgets.QSystemTrayIcon()
self.tray.setIcon(icon)
self.tray.setContextMenu(self.menu) self.tray.setContextMenu(self.menu)
self.update_icon()
self.tray.show() self.tray.show()
self.tray.setToolTip("fime") self.tray.setToolTip("fime")
self.update_tray_menu() self.update_tray_menu()
self.last_dialog: Optional[QtWidgets.QDialog] = None
def update_icon(self):
if self.config.tray_theme == "light":
icon = get_icon("appointment-new-light")
else:
icon = get_icon("appointment-new")
self.tray.setIcon(icon)
@QtCore.Slot() @QtCore.Slot()
def new_task_imported(self): def new_task_imported(self):
if self.import_task.task_text: if self.import_task.task_text:
@ -89,11 +100,21 @@ class App:
self.tray.setToolTip(f"{task} - fime") self.tray.setToolTip(f"{task} - fime")
self.update_tray_menu() self.update_tray_menu()
def close_open_dialog(self):
if self.last_dialog and self.last_dialog.isVisible():
self.last_dialog.reject()
def change_task(self, task): def change_task(self, task):
self.close_open_dialog()
self.active_task = task self.active_task = task
self.log.log(task) self.log.log(task)
self.tasks.update_jira_task_usage(task) self.tasks.update_jira_task_usage(task)
def open_new_dialog(self, new_dialog: QtWidgets.QDialog):
self.close_open_dialog()
self.last_dialog = new_dialog
new_dialog.show()
def update_tray_menu(self): def update_tray_menu(self):
menu_items = [] menu_items = []
tmp_action = self.menu.addAction("tmp") tmp_action = self.menu.addAction("tmp")
@ -107,7 +128,7 @@ class App:
add_tasks(self.tasks.tasks) add_tasks(self.tasks.tasks)
menu_items.append((1, None)) menu_items.append((1, None))
already_taken = (len(self.tasks.tasks) + 5) * action_height already_taken = (len(self.tasks.tasks) + 6) * action_height
available_space = get_screen_height(self.menu) * 0.8 - already_taken available_space = get_screen_height(self.menu) * 0.8 - already_taken
jira_entry_count = int(available_space // action_height) jira_entry_count = int(available_space // action_height)
add_tasks(self.tasks.jira_tasks[-jira_entry_count:]) add_tasks(self.tasks.jira_tasks[-jira_entry_count:])
@ -118,17 +139,21 @@ class App:
add_tasks(["Nothing"]) add_tasks(["Nothing"])
menu_items.append((1, None)) menu_items.append((1, None))
menu_items.append(("Import Jira task", self.import_task.show)) jira_integration = self.config.jira_url and self.config.jira_token
menu_items.append(("Edit tasks", self.edit_tasks)) if jira_integration:
menu_items.append(("Report", self.reportDialog.show)) menu_items.append(("Import Jira task", partial(self.open_new_dialog, self.import_task)))
menu_items.append(("Worklog", self.worklogDialog.show)) menu_items.append(("Edit tasks", partial(self.open_new_dialog, self.taskEdit)))
menu_items.append(("Report", partial(self.open_new_dialog, self.reportDialog)))
if jira_integration:
menu_items.append(("Worklog", partial(self.open_new_dialog, self.worklogDialog)))
menu_items.append((1, None)) menu_items.append((1, None))
menu_items.append(("About", self.about.show)) menu_items.append(("Settings", partial(self.open_new_dialog, self.settings)))
menu_items.append(("About", partial(self.open_new_dialog, self.about)))
menu_items.append(("Close", self.app.quit)) menu_items.append(("Close", self.app.quit))
if self.menu_flipped: if self.config.flip_menu:
menu_items.reverse() menu_items.reverse()
seps = 0 seps = 0
@ -143,7 +168,7 @@ class App:
action.triggered.connect(item[1]) action.triggered.connect(item[1])
def sigterm_handler(self, signo, _frame): def sigterm_handler(self, signo, _frame):
print(f'handling signal "{signal.strsignal(signo)}"') logger.debug(f'handling signal "{signal.strsignal(signo)}"')
self.app.quit() self.app.quit()
def run(self): def run(self):
@ -158,25 +183,28 @@ class App:
else: else:
self.app.exec_() self.app.exec_()
@QtCore.Slot()
def edit_tasks(self): def init_logging():
self.taskEdit.tasks = self.tasks.tasks log_dir_path = Path(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation)) / "logs"
self.taskEdit.show() logger.add(log_dir_path / "fime_{time:YYYY-MM-DD}.log", rotation="1d", retention="30d", compression="zip", level="DEBUG")
def excepthook(original, e_type, e_value, tb_obj): def excepthook(e_type, e_value, tb_obj):
if e_type is FimeException: if e_type is FimeException:
QtWidgets.QMessageBox.critical(None, "Error", str(e_value), QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.critical(None, "Error", str(e_value), QtWidgets.QMessageBox.Ok)
elif issubclass(e_type, KeyboardInterrupt):
sys.__excepthook__(e_type, e_value, tb_obj)
else: else:
original(e_type, e_value, tb_obj) logger.critical("Unhandled exception", exc_info=(e_type, e_value, tb_obj))
sys.__excepthook__(e_type, e_value, tb_obj)
def main(): def main():
# important for QStandardPath to be correct # important for QStandardPath to be correct
QtCore.QCoreApplication.setApplicationName("fime") QtCore.QCoreApplication.setApplicationName("fime")
init_logging()
# also catches exceptions in other threads # also catches exceptions in other threads
original_excepthook = sys.excepthook sys.excepthook = excepthook
sys.excepthook = partial(excepthook, original_excepthook)
app = App() app = App()
app.run() app.run()

93
src/fime/settings.py Normal file
View File

@ -0,0 +1,93 @@
from fime.util import get_icon
try:
from PySide6 import QtCore, QtGui, QtWidgets
except ImportError:
from PySide2 import QtCore, QtGui, QtWidgets
from fime.config import Config
class Settings(QtWidgets.QDialog):
def __init__(self, config: Config, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.setWindowTitle("Settings")
self.config = config
caption_label = QtWidgets.QLabel()
caption_label.setText("Settings")
caption_label.setAlignment(QtCore.Qt.AlignHCenter)
settings_layout = QtWidgets.QGridLayout()
jira_url_label = QtWidgets.QLabel()
jira_url_label.setText("Jira URL")
settings_layout.addWidget(jira_url_label, 0, 0)
self.jira_url_edit = QtWidgets.QLineEdit()
settings_layout.addWidget(self.jira_url_edit, 0, 1)
jira_token_label = QtWidgets.QLabel()
jira_token_label.setText("Jira Personal Access Token<br/> see <a href='https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html#UsingPersonalAccessTokens-CreatingPATsinapplication'>here</a> for how to get one")
jira_token_label.setTextFormat(QtCore.Qt.RichText)
jira_token_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
jira_token_label.setOpenExternalLinks(True)
settings_layout.addWidget(jira_token_label, 1, 0)
self.jira_token_edit = QtWidgets.QLineEdit()
settings_layout.addWidget(self.jira_token_edit, 1, 1)
tray_theme_label = QtWidgets.QLabel()
tray_theme_label.setText("Tray theme")
settings_layout.addWidget(tray_theme_label, 2, 0)
self.tray_theme_combo_box = QtWidgets.QComboBox()
self.tray_theme_combo_box.addItem("Light")
self.tray_theme_combo_box.addItem("Dark")
settings_layout.addWidget(self.tray_theme_combo_box, 2, 1, QtCore.Qt.AlignRight)
flip_menu_label = QtWidgets.QLabel()
flip_menu_label.setText("Flip menu")
settings_layout.addWidget(flip_menu_label, 3, 0)
self.flip_menu_check_box = QtWidgets.QCheckBox()
settings_layout.addWidget(self.flip_menu_check_box, 3, 1, QtCore.Qt.AlignRight)
self.ok_button = QtWidgets.QPushButton()
self.ok_button.setText("OK")
self.ok_button.setIcon(get_icon("dialog-ok"))
self.ok_button.pressed.connect(self.accept)
self.ok_button.setAutoDefault(True)
button_layout = QtWidgets.QHBoxLayout()
button_layout.addStretch(66)
button_layout.addWidget(self.ok_button, 33)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(caption_label)
layout.addLayout(settings_layout)
layout.addLayout(button_layout)
self.setLayout(layout)
self.resize(500, 0)
self.accepted.connect(self._accepted)
def showEvent(self, _):
self.jira_url_edit.setText(self.config.jira_url)
self.jira_token_edit.setText(self.config.jira_token)
self.tray_theme_combo_box.setCurrentText(self.config.tray_theme.capitalize())
self.flip_menu_check_box.setChecked(self.config.flip_menu)
def _accepted(self):
self.config.jira_url = self.jira_url_edit.text()
self.config.jira_token = self.jira_token_edit.text()
self.config.tray_theme = self.tray_theme_combo_box.currentText()
self.config.flip_menu = self.flip_menu_check_box.isChecked()
self.config.save()
# only for dev/debug
if __name__ == "__main__":
QtCore.QCoreApplication.setApplicationName("fime")
app = QtWidgets.QApplication()
cfg = Config()
settings = Settings(cfg, None)
settings.show()
app.exec()

View File

@ -1,11 +1,11 @@
import os import os
import sys
import threading import threading
import traceback
from enum import Enum, auto from enum import Enum, auto
from functools import reduce, partial from functools import reduce, partial
from queue import Queue, Empty from queue import Queue, Empty
from loguru import logger
try: try:
from PySide6 import QtCore, QtWidgets from PySide6 import QtCore, QtWidgets
except ImportError: except ImportError:
@ -29,8 +29,9 @@ class TaskCompleter(QtWidgets.QCompleter):
self.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.session = FuturesSession() self.session = FuturesSession()
self.config = config self.config = config
self.picker_url = os.path.join(self.config.jira_url, "rest/api/2/issue/picker") self.picker_url = None
self.search_url = os.path.join(self.config.jira_url, "rest/api/2/search") self.search_url = None
self.update_urls()
self.text = "" self.text = ""
self.response_text = "" self.response_text = ""
self.model_data = set() self.model_data = set()
@ -44,6 +45,10 @@ class TaskCompleter(QtWidgets.QCompleter):
self.rif_counter_lock = threading.Lock() self.rif_counter_lock = threading.Lock()
self.last_rif_state = TaskCompleter.RifState.STOPPED self.last_rif_state = TaskCompleter.RifState.STOPPED
def update_urls(self):
self.picker_url = os.path.join(self.config.jira_url, "rest/api/2/issue/picker")
self.search_url = os.path.join(self.config.jira_url, "rest/api/2/search")
@QtCore.Slot() @QtCore.Slot()
def process_response(self): def process_response(self):
with self.rif_counter_lock: with self.rif_counter_lock:
@ -105,12 +110,11 @@ class TaskCompleter(QtWidgets.QCompleter):
}) })
else: else:
if not self.escalate: if not self.escalate:
print("No picker results. Escalating") logger.debug("No picker results. Escalating")
self.escalate = True self.escalate = True
self.update_search() self.update_search()
except Exception: except Exception:
print("Ignoring exception, as it only breaks autocompletion:", file=sys.stderr) logger.exception("Ignoring exception, as it only breaks autocompletion")
print(traceback.format_exc(), file=sys.stderr)
return return
def update_search(self): def update_search(self):
@ -144,6 +148,5 @@ class TaskCompleter(QtWidgets.QCompleter):
"result": extracted, "result": extracted,
}) })
except Exception: except Exception:
print("Ignoring exception, as it only breaks autocompletion:", file=sys.stderr) logger.exception("Ignoring exception, as it only breaks autocompletion")
print(traceback.format_exc(), file=sys.stderr)
return return

View File

@ -3,12 +3,15 @@ try:
except ImportError: except ImportError:
from PySide2 import QtCore, QtGui, QtWidgets from PySide2 import QtCore, QtGui, QtWidgets
from fime.data import Tasks
from fime.util import get_icon, EditStartedDetector from fime.util import get_icon, EditStartedDetector
class TaskEdit(QtWidgets.QDialog): class TaskEdit(QtWidgets.QDialog):
def __init__(self, parent, *args, **kwargs): def __init__(self, tasks: Tasks, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self._task_data = tasks
self.setWindowTitle("Edit Tasks") self.setWindowTitle("Edit Tasks")
self.list = QtCore.QStringListModel() self.list = QtCore.QStringListModel()
@ -82,10 +85,7 @@ class TaskEdit(QtWidgets.QDialog):
ret = self.list.stringList() ret = self.list.stringList()
return list(filter(None, ret)) # filter empty strings return list(filter(None, ret)) # filter empty strings
@tasks.setter
def tasks(self, tasks):
self.list.setStringList(tasks)
def showEvent(self, _): def showEvent(self, _):
self.list.setStringList(self._task_data.tasks)
self.raise_() self.raise_()
self.activateWindow() self.activateWindow()

View File

@ -1,5 +1,7 @@
import enum import enum
from loguru import logger
try: try:
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
except ImportError: except ImportError:
@ -13,7 +15,7 @@ def get_screen_height(qobject):
if hasattr(qobject, "screen"): if hasattr(qobject, "screen"):
return qobject.screen().size().height() return qobject.screen().size().height()
else: else:
print("unable to detect screen height falling back to default value of 1080") logger.info("unable to detect screen height falling back to default value of 1080")
return 1080 return 1080
@ -21,7 +23,7 @@ def get_screen_width(qobject):
if hasattr(qobject, "screen"): if hasattr(qobject, "screen"):
return qobject.screen().size().width() return qobject.screen().size().width()
else: else:
print("unable to detect screen width falling back to default value of 1920") logger.info("unable to detect screen width falling back to default value of 1920")
return 1920 return 1920

View File

@ -99,7 +99,7 @@ class WorklogDialog(QtWidgets.QDialog):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.config = config self.config = config
self.rest = WorklogRest(self.config) self.rest = None
self._changing_items = False self._changing_items = False
self._worklog = worklog self._worklog = worklog
@ -175,7 +175,7 @@ class WorklogDialog(QtWidgets.QDialog):
self.update_timer.timeout.connect(self.update_statuses) self.update_timer.timeout.connect(self.update_statuses)
def showEvent(self, _): def showEvent(self, _):
# reinitialize to purge caches # reinitialize to purge caches and pick up config changes
self.rest = WorklogRest(self.config) self.rest = WorklogRest(self.config)
self._worklog.date = date.today() self._worklog.date = date.today()
self.update_all() self.update_all()

View File

@ -1,6 +1,4 @@
import os import os
import sys
import traceback
from concurrent.futures import Future from concurrent.futures import Future
from datetime import date, datetime, timedelta, time from datetime import date, datetime, timedelta, time
from functools import partial from functools import partial
@ -9,6 +7,7 @@ from threading import Lock
from typing import List, Dict, Tuple, Optional from typing import List, Dict, Tuple, Optional
import requests import requests
from loguru import logger
from requests_futures.sessions import FuturesSession from requests_futures.sessions import FuturesSession
from fime.config import Config from fime.config import Config
@ -42,12 +41,9 @@ class WorklogRest:
future.add_done_callback(self._resp_user) future.add_done_callback(self._resp_user)
return future return future
@logger.catch(message="Could not get user key")
def _resp_user(self, future): def _resp_user(self, future):
try:
self._user = future.result().json()["key"] self._user = future.result().json()["key"]
except Exception:
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], pdate: date) -> List[Tuple[Status, str, Optional[str]]]: def get_issues_state(self, issue_keys: List[str], pdate: date) -> List[Tuple[Status, str, Optional[str]]]:
ret = [] ret = []
@ -114,9 +110,9 @@ class WorklogRest:
worklog_found = True worklog_found = True
break break
if worklog_found: if worklog_found:
print(f"Found existing worklog for issue {issue_key}") logger.debug(f"Found existing worklog for issue {issue_key}")
else: else:
print(f"Did not find existing worklog for issue {issue_key}") logger.debug(f"Did not find existing worklog for issue {issue_key}")
self._issue_state[issue_key] = (Status.OK, issue_title) self._issue_state[issue_key] = (Status.OK, issue_title)
def _upload_sanity_check(self, issue_keys: List[str]): def _upload_sanity_check(self, issue_keys: List[str]):
@ -183,7 +179,7 @@ class WorklogRest:
with self._issues_lock: with self._issues_lock:
if resp.status_code in (200, 201): if resp.status_code in (200, 201):
self._issue_state[issue_key] = (Status.OK, "Successfully uploaded") self._issue_state[issue_key] = (Status.OK, "Successfully uploaded")
print(f"Successfully uploaded issue {issue_key}") logger.info(f"Successfully uploaded issue {issue_key}")
else: else:
msg = dedent(f"""\ msg = dedent(f"""\
Worklog upload failed: Worklog upload failed:
@ -193,4 +189,4 @@ class WorklogRest:
Response: {resp.text} Response: {resp.text}
""") """)
self._issue_state[issue_key] = (Status.ERROR, msg) self._issue_state[issue_key] = (Status.ERROR, msg)
print(msg, file=sys.stderr) logger.error(msg)