summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kodereviewer/app.py9
-rw-r--r--kodereviewer/data.py51
-rw-r--r--kodereviewer/models/__init__.py2
-rw-r--r--kodereviewer/models/file.py95
-rw-r--r--kodereviewer/models/line_model.py23
-rw-r--r--kodereviewer/network_manager.py11
-rw-r--r--kodereviewer/qml/Editor.qml3
-rw-r--r--kodereviewer/qml/FilesView.qml29
-rw-r--r--kodereviewer/qml/Main.qml2
-rw-r--r--kodereviewer/qml/ProjectListPage.qml57
-rw-r--r--kodereviewer/qml/PullRequestPage.qml52
11 files changed, 256 insertions, 78 deletions
diff --git a/kodereviewer/app.py b/kodereviewer/app.py
index 94a4e55..ad019a4 100644
--- a/kodereviewer/app.py
+++ b/kodereviewer/app.py
@@ -9,8 +9,16 @@ from PySide6.QtGui import QGuiApplication
from PySide6.QtCore import QUrl, QByteArray
from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
+from kodereviewer.models.file import FileModel
from kodereviewer.network_manager import NetworkManager
from kodereviewer.models import CommentModel, LabelModel, LineModel, PullRequestModel, ProjectModel
+import logging
+from rich.logging import RichHandler
+
+FORMAT = "%(message)s"
+logging.basicConfig(
+ level="INFO", format=FORMAT, datefmt="[%X]", handlers=[RichHandler()]
+)
def main():
@@ -35,6 +43,7 @@ def main():
qmlRegisterType(ProjectModel, "org.deprecated.kodereviewer", 1, 0, "ProjectModel")
qmlRegisterType(CommentModel, "org.deprecated.kodereviewer", 1, 0, "CommentModel")
+ qmlRegisterType(FileModel, "org.deprecated.kodereviewer", 1, 0, "FileModel")
qmlRegisterType(LabelModel, "org.deprecated.kodereviewer", 1, 0, "LabelModel")
qmlRegisterType(LineModel, "org.deprecated.kodereviewer", 1, 0, "LineModel")
qmlRegisterType(PullRequestModel, "org.deprecated.kodereviewer", 1, 0, "PullRequestModel")
diff --git a/kodereviewer/data.py b/kodereviewer/data.py
index 82aad94..0d1bef6 100644
--- a/kodereviewer/data.py
+++ b/kodereviewer/data.py
@@ -49,6 +49,44 @@ class Comment(QObject):
self.user = User(data['user'])
+class ChangedFileStatus(Enum):
+ ADDED = 'added'
+ MODIFIED = 'modified'
+ DELETED = 'deleted'
+
+
+class ChangedFile(QObject):
+ """ {
+ "sha": "bbcd538c8e72b8c175046e27cc8f907076331401",
+ "filename": "file1.txt",
+ "status": "added",
+ "additions": 103,
+ "deletions": 21,
+ "changes": 124,
+ "blob_url": "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
+ "raw_url": "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt",
+ "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e",
+ "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"
+ }"""
+
+ sha: str
+ filename: str
+ status: ChangedFileStatus
+ additions: int
+ deletions: int
+ changes: int
+ patch: str
+
+ def __init__(self, data: dict[str, Any]):
+ self.sha = data['sha']
+ self.filename = data['filename']
+ self.status = data['status']
+ self.additions = int(data['additions'])
+ self.deletions = int(data['deletions'])
+ self.changes = int(data['changes'])
+ self.patch = data.get('patch', '')
+
+
class State(Enum):
OPEN = 'open'
CLOSED = 'closed'
@@ -71,10 +109,12 @@ class PullRequest(QObject):
_labels: list[Label]
_comments: list[Comment]
+ _files: list[ChangedFile]
_initial_data: dict[str, Any]
commentsLoaded = Signal()
+ filesLoaded = Signal()
def __init__(self, data: dict[str, Any], *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -96,6 +136,7 @@ class PullRequest(QObject):
# At first this is empty until it's updated with a request
self._comments = []
+ self._files = []
self._initial_data = data
@@ -145,12 +186,20 @@ class PullRequest(QObject):
def labels(self) -> list[Label]:
return self._labels
+ @Property(list, constant=True)
+ def files(self) -> list[ChangedFile]:
+ return self._files
+
def load_comments(self, response: QByteArray) -> None:
- print("loading comments")
data = json.loads(response.toStdString())
self._comments = [Comment(comment) for comment in data]
self.commentsLoaded.emit()
+ def load_files(self, response: QByteArray) -> None:
+ data = json.loads(response.toStdString())
+ self._files = [ChangedFile(file) for file in data]
+ self.filesLoaded.emit()
+
def copy(self):
"""Create a copy of a PullRequest with it's initial parameters."""
return PullRequest(deepcopy(self._initial_data))
diff --git a/kodereviewer/models/__init__.py b/kodereviewer/models/__init__.py
index b566981..b9082d1 100644
--- a/kodereviewer/models/__init__.py
+++ b/kodereviewer/models/__init__.py
@@ -1,4 +1,5 @@
from kodereviewer.models.comments import CommentModel
+from kodereviewer.models.file import FileModel
from kodereviewer.models.line_model import LineModel
from kodereviewer.models.project import ProjectModel
from kodereviewer.models.pull_request import PullRequestModel
@@ -6,6 +7,7 @@ from kodereviewer.models.label import LabelModel
__all__ = [
'CommentModel',
+ 'FileModel',
'LabelModel'
'LineModel',
'ProjectModel',
diff --git a/kodereviewer/models/file.py b/kodereviewer/models/file.py
new file mode 100644
index 0000000..246bd5b
--- /dev/null
+++ b/kodereviewer/models/file.py
@@ -0,0 +1,95 @@
+from copy import copy
+from enum import IntEnum, auto
+from typing import Any, Optional
+
+from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property
+from PySide6.QtQml import QmlElement
+
+from kodereviewer.project import Project
+from kodereviewer.data import ChangedFile, Label, PullRequest
+
+QML_IMPORT_NAME = "org.deprecated.kodereviewer"
+QML_IMPORT_MAJOR_VERSION = 1
+
+
+@QmlElement
+class FileModel(QAbstractListModel):
+
+ _pull_request: Optional[PullRequest]
+
+ class Roles(IntEnum):
+ Sha = Qt.ItemDataRole.UserRole + 1
+ Filename = auto()
+ Status = auto()
+ Additions = auto()
+ Deletions = auto()
+ Changes = auto()
+ Patch = auto()
+
+ def __init__(self):
+ super().__init__()
+ self._pull_request = None
+
+ pullRequestChanged = Signal()
+
+ def get_pull_request(self) -> Optional[PullRequest]:
+ return self._pull_request
+
+ def set_pull_request(self, pull_request: Optional[PullRequest]) -> None:
+ if pull_request is None:
+ return
+
+ if self._pull_request is not None and self._pull_request == pull_request:
+ return
+
+ self.beginResetModel()
+ self._pull_request = pull_request
+ self._pull_request.filesLoaded.connect(self._reset_model)
+ self.endResetModel()
+ self.pullRequestChanged.emit()
+
+ pullRequest = Property(PullRequest, fget=get_pull_request, fset=set_pull_request,
+ notify=pullRequestChanged)
+
+ def _reset_model(self) -> None:
+ self.beginResetModel()
+ self.endResetModel()
+
+ def data(self,
+ index: QModelIndex | QPersistentModelIndex,
+ role: int = Qt.ItemDataRole.DisplayRole) -> object:
+ if self._pull_request is None:
+ return None
+
+ file: ChangedFile = self._pull_request.files[index.row()]
+
+ if role == self.Roles.Sha:
+ return file.sha
+ if role == self.Roles.Filename:
+ return file.filename
+ if role == self.Roles.Additions:
+ return file.additions
+ if role == self.Roles.Deletions:
+ return file.deletions
+ if role == self.Roles.Changes:
+ return file.changes
+ if role == self.Roles.Patch:
+ return file.patch
+ if role == Qt.ItemDataRole.DisplayRole:
+ return file.filename
+ return None
+
+ def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
+ if self._pull_request is None:
+ return 0
+ return len(self._pull_request.files)
+
+ def roleNames(self) -> dict[int, QByteArray]:
+ return {
+ self.Roles.Sha: QByteArray(b'sha'),
+ self.Roles.Filename: QByteArray(b'filename'),
+ self.Roles.Additions: QByteArray(b'additions'),
+ self.Roles.Deletions: QByteArray(b'deletions'),
+ self.Roles.Changes: QByteArray(b'changes'),
+ self.Roles.Patch: QByteArray(b'patch'),
+ }
diff --git a/kodereviewer/models/line_model.py b/kodereviewer/models/line_model.py
index 1814829..03d14c6 100644
--- a/kodereviewer/models/line_model.py
+++ b/kodereviewer/models/line_model.py
@@ -1,7 +1,9 @@
"""Model used for line numbers in Editor.qml"""
+import logging
from enum import auto, IntEnum
from typing import Optional
+import rich
from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property
from PySide6.QtQuick import QQuickTextDocument
from PySide6.QtQml import QmlElement
@@ -9,6 +11,8 @@ from PySide6.QtQml import QmlElement
QML_IMPORT_NAME = "org.deprecated.kodereviewer"
QML_IMPORT_MAJOR_VERSION = 1
+logger = logging.getLogger(__name__)
+
@QmlElement
class LineModel(QAbstractListModel):
@@ -27,15 +31,17 @@ class LineModel(QAbstractListModel):
return self._document
def set_document(self, document):
- print('setting document')
+ logger.debug('setting document')
if document == self._document:
+ logger.debug(f'Document {document} == {self._document}')
return
+ logger.debug(f'Setting document {document}')
self._document = document
self.documentChanged.emit()
self.resetModel()
documentChanged = Signal()
- document = Property(QQuickTextDocument, fget=get_document, fset=set_document,
+ document = Property(QObject, fget=get_document, fset=set_document,
notify=documentChanged)
def data(self,
@@ -43,29 +49,28 @@ class LineModel(QAbstractListModel):
role: int = Qt.ItemDataRole.DisplayRole) -> object:
if not index.isValid():
- print('index not valid')
+ logger.debug('index not valid')
return
if self._document is None:
- print('document none')
+ logger.debug('document none')
return
row = index.row()
if row < 0 or row > self.rowCount():
- print(f'row: {row}')
+ logger.debug(f'row: {row}')
return
if role == self.Roles.LineHeight:
text_doc = self._document.textDocument()
return int(text_doc.documentLayout().blockBoundingRect(text_doc.findBlockByNumber(row)).height())
- print('found role ?')
+ logger.debug('found role ?')
def rowCount(self, parent: QModelIndex | QPersistentModelIndex = QModelIndex()) -> int:
if self._document is None:
- print('document is none: row count 0')
+ logger.debug('rowCount: _document is None')
return 0
- print(f'returning {self._document.textDocument().blockCount()}')
return self._document.textDocument().blockCount();
def roleNames(self) -> dict[int, QByteArray]:
@@ -76,6 +81,6 @@ class LineModel(QAbstractListModel):
@Slot()
def resetModel(self) -> None:
- print('reseting model?')
+ logger.debug('reseting model?')
self.beginResetModel()
self.endResetModel()
diff --git a/kodereviewer/network_manager.py b/kodereviewer/network_manager.py
index 62b6047..ebc2797 100644
--- a/kodereviewer/network_manager.py
+++ b/kodereviewer/network_manager.py
@@ -5,6 +5,7 @@ from PySide6.QtCore import QObject, QSettings, Signal, Slot, Property, qDebug
from PySide6.QtNetwork import QHttpHeaders, QNetworkAccessManager, QNetworkReply, QNetworkRequestFactory
from PySide6.QtQml import QmlElement
+from kodereviewer.data import PullRequest
from kodereviewer.project import Project
QML_IMPORT_NAME = "org.deprecated.kodereviewer"
@@ -12,6 +13,7 @@ QML_IMPORT_MAJOR_VERSION = 1
PULL_REQUEST_LIST_URL = re.compile(r'/pulls$')
COMMENT_LIST_URL = re.compile(r'/issues/(\d+)/comments')
+FILE_LIST_URL = re.compile(r'/pulls/(\d+)/files')
@QmlElement
@@ -69,6 +71,11 @@ class NetworkManager(QObject):
pull_request = self._project.find_pull_request(pull_request_number)
if pull_request is not None:
pull_request.load_comments(response_body)
+ elif (match := FILE_LIST_URL.search(reply.url().toString())):
+ pull_request_number = int(match.groups()[0])
+ pull_request: Optional[PullRequest] = self._project.find_pull_request(pull_request_number)
+ if pull_request is not None:
+ pull_request.load_files(response_body)
else:
print(f"Can't handle {reply.url()}")
@@ -79,3 +86,7 @@ class NetworkManager(QObject):
@Slot(int)
def getPullRequestComments(self, number: int) -> None:
self._manager.get(self._request_factory.createRequest(f'/issues/{number}/comments'))
+
+ @Slot(int)
+ def getFiles(self, pull_request_number) -> None:
+ self._manager.get(self._request_factory.createRequest(f'/pulls/{pull_request_number}/files'))
diff --git a/kodereviewer/qml/Editor.qml b/kodereviewer/qml/Editor.qml
index 9cc6cbf..052933d 100644
--- a/kodereviewer/qml/Editor.qml
+++ b/kodereviewer/qml/Editor.qml
@@ -35,6 +35,7 @@ TextEdit {
LineModel {
id: lineModel
document: root.textDocument
+ onDocumentChanged: print('Document changed!')
}
onWidthChanged: lineModel.resetModel()
@@ -82,7 +83,7 @@ TextEdit {
}
}
- onTextChanged: print(root.textDocument)
+ // onTextChanged: lineModel.document = root.textDocument
onFileChanged: {
repeater.model.resetModel()
diff --git a/kodereviewer/qml/FilesView.qml b/kodereviewer/qml/FilesView.qml
index 299c353..1442860 100644
--- a/kodereviewer/qml/FilesView.qml
+++ b/kodereviewer/qml/FilesView.qml
@@ -14,23 +14,7 @@ QQC2.SplitView {
anchors.fill: parent
padding: 0
spacing: 0
- ListModel {
- id: fileModel
- ListElement {
- filename: "file1"
- status: "added"
- additions: 100
- deletions: 40
- patch: "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"
- }
- ListElement {
- filename: "file2"
- status: "added"
- additions: 100
- deletions: 40
- patch: "@@ -1,9 +1,9 @@ def something @@ -1000,7 +1000,7 @@ module Test"
- }
- }
+ required property FileModel fileModel
QQC2.ScrollView {
@@ -52,10 +36,11 @@ QQC2.SplitView {
}
}
}
-
- Editor {
- id: textArea
- text: ""
- file: ""
+ QQC2.ScrollView {
+ Editor {
+ id: textArea
+ text: ""
+ file: ""
+ }
}
}
diff --git a/kodereviewer/qml/Main.qml b/kodereviewer/qml/Main.qml
index 827cf59..56248d7 100644
--- a/kodereviewer/qml/Main.qml
+++ b/kodereviewer/qml/Main.qml
@@ -15,7 +15,7 @@ Kirigami.ApplicationWindow {
title: qsTr("Kode Reviewer")
minimumWidth: Kirigami.Units.gridUnit * 20
- minimumHeight: Kirigami.Units.gridUnit * 20
+ //minimumHeight: Kirigami.Units.gridUnit * 20
width: minimumWidth
height: minimumHeight
diff --git a/kodereviewer/qml/ProjectListPage.qml b/kodereviewer/qml/ProjectListPage.qml
index 2846db1..f156dc1 100644
--- a/kodereviewer/qml/ProjectListPage.qml
+++ b/kodereviewer/qml/ProjectListPage.qml
@@ -9,7 +9,7 @@ import org.kde.kirigami as Kirigami
import org.deprecated.kodereviewer
-Kirigami.Page {
+Kirigami.ScrollablePage {
id: root
required property NetworkManager connection
@@ -56,43 +56,32 @@ Kirigami.Page {
}
]
- contentItem: QQC2.StackView {
- id: stackView
- anchors.fill: parent
-
- initialItem: pullRequestListView
-
- Component {
- id: pullRequestListView
- QQC2.ScrollView {
- ListView {
- id: view
- model: pullRequestModel
- clip: true
- delegate: Delegates.RoundedItemDelegate {
- required property int number
- required property string title
- required property bool draft
- required property int index
-
- highlighted: ListView.isCurrentItem
-
- text: `${number} - ${title}`
- icon {
- name: "vcs-merge-request"
- color: draft ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.positiveTextColor
- }
-
- onClicked: {
- view.currentIndex = index
- root.pullRequestSelected(number)
- }
- }
- }
+ ListView {
+ id: view
+ model: pullRequestModel
+ clip: true
+ delegate: Delegates.RoundedItemDelegate {
+ required property int number
+ required property string title
+ required property bool draft
+ required property int index
+
+ highlighted: ListView.isCurrentItem
+
+ text: `${number} - ${title}`
+ icon {
+ name: "vcs-merge-request"
+ color: draft ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.positiveTextColor
+ }
+
+ onClicked: {
+ view.currentIndex = index
+ root.pullRequestSelected(number)
}
}
}
+
MouseArea {
anchors.top: parent.top
anchors.bottom: parent.bottom
diff --git a/kodereviewer/qml/PullRequestPage.qml b/kodereviewer/qml/PullRequestPage.qml
index e99d81b..219762a 100644
--- a/kodereviewer/qml/PullRequestPage.qml
+++ b/kodereviewer/qml/PullRequestPage.qml
@@ -56,11 +56,26 @@ Kirigami.ScrollablePage {
}
}
- CommentModel {
- id: commentModel
- pullRequest: root.pullRequest
+ Loader {
+ id: commentModelLoader
+ active: !!root.pullRequest
+ sourceComponent: CommentModel {
+ id: commentModel
+ pullRequest: root.pullRequest
+
+ onPullRequestChanged: root.connection.getPullRequestComments(root.pullRequest.number)
+ }
+ }
- onPullRequestChanged: root.connection.getPullRequestComments(root.pullRequest.number)
+ Loader {
+ id: fileModelLoader
+ active: !!root.pullRequest
+ sourceComponent: FileModel {
+ id: fileModel
+ pullRequest: root.pullRequest
+
+ onPullRequestChanged: root.connection.getFiles(root.pullRequest.number)
+ }
}
Kirigami.PlaceholderMessage {
@@ -91,11 +106,21 @@ Kirigami.ScrollablePage {
RowLayout {
QQC2.Label {
+ text: "Author"
+ elide: Text.ElideRight
+ }
+ QQC2.Label {
+ text: root.pullRequest ? root.pullRequest.username : ""
+ elide: Text.ElideLeft
+ }
+ }
+ RowLayout {
+ QQC2.Label {
text: "State: "
elide: Text.ElideRight
}
QQC2.Label {
- text: root.pullRequest.state
+ text: root.pullRequest ? root.pullRequest.state : ""
elide: Text.ElideLeft
}
}
@@ -105,16 +130,22 @@ Kirigami.ScrollablePage {
elide: Text.ElideRight
}
QQC2.Label {
- text: root.pullRequest.draft ? i18n("Yes") : i18n("No")
+ text: root.pullRequest ? root.pullRequest.draft ? i18n("Yes") : i18n("No") : ""
elide: Text.ElideLeft
}
}
+ Loader {
+ id: labelModelLoader
+ active: !!root.pullRequest
+ sourceComponent: LabelModel {
+ pullRequest: root.pullRequest
+ }
+ }
+
RowLayout {
Repeater {
- model: LabelModel {
- pullRequest: root.pullRequest
- }
+ model: labelModelLoader.item
delegate: Rectangle {
required property string name
required property string labelColor
@@ -150,13 +181,14 @@ Kirigami.ScrollablePage {
ColumnLayout {
visible: !!root.pullRequest && root.currentView == "comments"
Repeater {
- model: commentModel
+ model: commentModelLoader.item
delegate: CommentDelegate {}
}
}
FilesView {
visible: !!root.pullRequest && root.currentView == "files"
+ fileModel: fileModelLoader.item
}
footer: Kirigami.NavigationTabBar {