summaryrefslogtreecommitdiff
path: root/kodereviewer
diff options
context:
space:
mode:
Diffstat (limited to 'kodereviewer')
-rw-r--r--kodereviewer/app.py4
-rw-r--r--kodereviewer/data.py27
-rw-r--r--kodereviewer/models/__init__.py4
-rw-r--r--kodereviewer/models/comments.py1
-rw-r--r--kodereviewer/models/pull_request.py25
-rw-r--r--kodereviewer/project.py6
-rw-r--r--kodereviewer/qml/Main.qml6
-rw-r--r--kodereviewer/qml/ProjectListPage.qml21
-rw-r--r--kodereviewer/qml/PullRequestPage.qml104
9 files changed, 167 insertions, 31 deletions
diff --git a/kodereviewer/app.py b/kodereviewer/app.py
index 430b5cf..94a4e55 100644
--- a/kodereviewer/app.py
+++ b/kodereviewer/app.py
@@ -10,7 +10,7 @@ from PySide6.QtCore import QUrl, QByteArray
from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
from kodereviewer.network_manager import NetworkManager
-from kodereviewer.models import CommentModel, PullRequestModel, ProjectModel
+from kodereviewer.models import CommentModel, LabelModel, LineModel, PullRequestModel, ProjectModel
def main():
@@ -35,6 +35,8 @@ def main():
qmlRegisterType(ProjectModel, "org.deprecated.kodereviewer", 1, 0, "ProjectModel")
qmlRegisterType(CommentModel, "org.deprecated.kodereviewer", 1, 0, "CommentModel")
+ qmlRegisterType(LabelModel, "org.deprecated.kodereviewer", 1, 0, "LabelModel")
+ qmlRegisterType(LineModel, "org.deprecated.kodereviewer", 1, 0, "LineModel")
qmlRegisterType(PullRequestModel, "org.deprecated.kodereviewer", 1, 0, "PullRequestModel")
qmlRegisterType(NetworkManager, "org.deprecated.kodereviewer", 1, 0, "NetworkManager")
diff --git a/kodereviewer/data.py b/kodereviewer/data.py
index eb86097..82aad94 100644
--- a/kodereviewer/data.py
+++ b/kodereviewer/data.py
@@ -1,3 +1,4 @@
+from copy import deepcopy
from datetime import datetime
from enum import Enum
import json
@@ -63,6 +64,7 @@ class PullRequest(QObject):
_body: str | None
_created_at: datetime
_updated_at: datetime
+ _draft: bool
_user: User
_assignee: User | None
@@ -70,10 +72,12 @@ class PullRequest(QObject):
_comments: list[Comment]
+ _initial_data: dict[str, Any]
+
commentsLoaded = Signal()
- def __init__(self, data: dict[str, Any]):
- super().__init__()
+ def __init__(self, data: dict[str, Any], *args, **kwargs):
+ super().__init__(*args, **kwargs)
self._number = data['number']
self._state = data['state']
self._title = data['title']
@@ -81,6 +85,7 @@ class PullRequest(QObject):
self._body = data['body']
self._created_at = datetime.fromisoformat(data['created_at'])
self._updated_at = datetime.fromisoformat(data['updated_at'])
+ self._draft = data['draft']
self._user = User(data['user'])
self._assignee = None
@@ -92,6 +97,8 @@ class PullRequest(QObject):
# At first this is empty until it's updated with a request
self._comments = []
+ self._initial_data = data
+
def __eq__(self, other: object) -> bool:
if isinstance(other, PullRequest):
return self._number == other._number
@@ -110,7 +117,7 @@ class PullRequest(QObject):
@Property(str, constant=True)
def state(self) -> str:
- return self._state.value
+ return self._state
@Property(str, constant=True)
def url(self) -> str:
@@ -126,12 +133,24 @@ class PullRequest(QObject):
def created_at(self) -> datetime:
return self._created_at
+ @Property(bool, constant=True)
+ def draft(self) -> bool:
+ return self._draft
+
@Property(str, constant=True)
def username(self) -> str:
return self._user.username
+ @Property(list, constant=True)
+ def labels(self) -> list[Label]:
+ return self._labels
+
def load_comments(self, response: QByteArray) -> None:
+ print("loading comments")
data = json.loads(response.toStdString())
self._comments = [Comment(comment) for comment in data]
- print(f'emiting new comments {len(self._comments)}')
self.commentsLoaded.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 853b618..b566981 100644
--- a/kodereviewer/models/__init__.py
+++ b/kodereviewer/models/__init__.py
@@ -1,9 +1,13 @@
from kodereviewer.models.comments import CommentModel
+from kodereviewer.models.line_model import LineModel
from kodereviewer.models.project import ProjectModel
from kodereviewer.models.pull_request import PullRequestModel
+from kodereviewer.models.label import LabelModel
__all__ = [
'CommentModel',
+ 'LabelModel'
+ 'LineModel',
'ProjectModel',
'PullRequestModel',
]
diff --git a/kodereviewer/models/comments.py b/kodereviewer/models/comments.py
index 06de3d7..0d92e23 100644
--- a/kodereviewer/models/comments.py
+++ b/kodereviewer/models/comments.py
@@ -37,7 +37,6 @@ class CommentModel(QAbstractListModel):
if self._pull_request is not None and self._pull_request == pull_request:
return
- print(f"Setting up pull request to {pull_request}")
self.beginResetModel()
self._pull_request = pull_request
self._pull_request.commentsLoaded.connect(self._reset_model)
diff --git a/kodereviewer/models/pull_request.py b/kodereviewer/models/pull_request.py
index 7d08efa..99e3495 100644
--- a/kodereviewer/models/pull_request.py
+++ b/kodereviewer/models/pull_request.py
@@ -1,5 +1,4 @@
-import json
-from os import path
+from copy import copy
from typing import Any, Optional
from PySide6.QtCore import QAbstractListModel, QByteArray, QModelIndex, QObject, QPersistentModelIndex, QStandardPaths, QUrl, Qt, Signal, Slot, Property
@@ -15,9 +14,17 @@ QML_IMPORT_MAJOR_VERSION = 1
class PullRequestModel(QAbstractListModel):
_project: Optional[Project]
+ _pull_requests: list[PullRequest]
+
+ # This attribute holds a reference to a reference to a newly
+ # copied PullRequest so Qt don't frees the memory of the pull
+ # request on _pull_requests
+ _current_pull_request: PullRequest
NumberRole = Qt.ItemDataRole.UserRole + 1
TitleRole = NumberRole + 1
+ StateRole = TitleRole + 1
+ DraftRole = StateRole + 1
def __init__(self):
super().__init__()
@@ -33,6 +40,10 @@ class PullRequestModel(QAbstractListModel):
return pull_request.number
if role == self.TitleRole:
return pull_request.title
+ if role == self.StateRole:
+ return pull_request.state
+ if role == self.DraftRole:
+ return pull_request.draft
if role == Qt.ItemDataRole.DisplayRole:
return f'{pull_request.number} - {pull_request.title}'
return None
@@ -46,6 +57,8 @@ class PullRequestModel(QAbstractListModel):
return {
self.NumberRole: QByteArray(b"number"),
self.TitleRole: QByteArray(b"title"),
+ self.StateRole: QByteArray(b"state"),
+ self.DraftRole: QByteArray(b"draft"),
}
def get_project(self) -> Optional[Project]:
@@ -64,12 +77,14 @@ class PullRequestModel(QAbstractListModel):
@Slot(int, result=PullRequest)
def get(self, index: int) -> Optional[PullRequest]:
- if self._project is not None:
- return self._project.pullRequests[index]
- return None
+ self._current_pull_request = self._pull_requests[index].copy()
+
+ return self._current_pull_request
def _reset_model(self) -> None:
print("Reseting pull request model")
self.beginResetModel()
+ if self._project is not None:
+ self._pull_requests = self._project.pullRequests
self.endResetModel()
diff --git a/kodereviewer/project.py b/kodereviewer/project.py
index d96d47b..e880352 100644
--- a/kodereviewer/project.py
+++ b/kodereviewer/project.py
@@ -43,10 +43,14 @@ class Project(QObject):
def pullRequests(self) -> list[PullRequest]:
return self._pull_requests
+ @Slot(int, result=PullRequest)
+ def pullRequest(self, number: int) -> Optional[PullRequest]:
+ return self.find_pull_request(number)
+
def load_pull_requests(self, response: QByteArray) -> None:
data = json.loads(response.toStdString())
self._pull_requests = [
- PullRequest(pr) for pr in data
+ PullRequest(pr, parent=self) for pr in data
]
self.pullRequestsChanged.emit()
diff --git a/kodereviewer/qml/Main.qml b/kodereviewer/qml/Main.qml
index e7496a6..827cf59 100644
--- a/kodereviewer/qml/Main.qml
+++ b/kodereviewer/qml/Main.qml
@@ -54,10 +54,8 @@ Kirigami.ApplicationWindow {
connection: root.connection
project: root.project
- onPullRequestSelected: pr => {
- print(pr)
- pullRequestPageLoader.item.pullRequest = pr
-
+ onPullRequestSelected: number => {
+ pullRequestPageLoader.item.pullRequest = project.pullRequest(number)
}
}
}
diff --git a/kodereviewer/qml/ProjectListPage.qml b/kodereviewer/qml/ProjectListPage.qml
index 7ffa8b9..2846db1 100644
--- a/kodereviewer/qml/ProjectListPage.qml
+++ b/kodereviewer/qml/ProjectListPage.qml
@@ -14,7 +14,7 @@ Kirigami.Page {
required property NetworkManager connection
required property Project project
- signal pullRequestSelected(var pullRequest)
+ signal pullRequestSelected(int pullRequest)
readonly property int currentWidth: _private.currentWidth + 1
@@ -67,18 +67,26 @@ Kirigami.Page {
QQC2.ScrollView {
ListView {
id: view
- model: pullRequestFilterModel
+ 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"
-
- onClicked: root.pullRequestSelected(pullRequestModel.get(index))
+ icon {
+ name: "vcs-merge-request"
+ color: draft ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.positiveTextColor
+ }
+
+ onClicked: {
+ view.currentIndex = index
+ root.pullRequestSelected(number)
+ }
}
}
}
@@ -108,7 +116,7 @@ Kirigami.Page {
}
if (mouse.x > _lastX) {
// _private.currentWidth = _private.currentWidth + (_lastX + mouse.x);
- _private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX))
+ _private.currentWidth = Math.min(_private.maxWidth, _private.currentWidth + (mouse.x - _lastX))
} else if (mouse.x < _lastX) {
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
if (tmpWidth > _private.minWidth)
@@ -127,6 +135,7 @@ Kirigami.Page {
property int currentWidth: defaultWidth
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
readonly property int minWidth: Kirigami.Units.gridUnit * 2
+ readonly property int maxWidth: Kirigami.Units.gridUnit * 25
}
}
diff --git a/kodereviewer/qml/PullRequestPage.qml b/kodereviewer/qml/PullRequestPage.qml
index 5a687e9..e99d81b 100644
--- a/kodereviewer/qml/PullRequestPage.qml
+++ b/kodereviewer/qml/PullRequestPage.qml
@@ -20,6 +20,41 @@ Kirigami.ScrollablePage {
property string currentView: "info"
+ actions: [
+ Kirigami.Action {
+ id: reviewChangesAction
+ text: "Review changes"
+ icon.name: "preview-symbolic"
+ enabled: !!root.pullRequest
+ onTriggered: reviewChangesDialog.open()
+ }
+ ]
+
+ Kirigami.Dialog {
+ id: reviewChangesDialog
+ standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
+ title: i18nc("@title:window", "Review changes")
+ padding: Kirigami.Units.largeSpacing
+ preferredWidth: Kirigami.Units.gridUnit * 20
+ ColumnLayout {
+ MarkdownTextArea {
+ Layout.fillWidth: true
+ }
+ Kirigami.Separator {
+ Kirigami.FormData.isSection: true
+ Layout.fillWidth: true
+ }
+ QQC2.RadioButton {
+ text: "Approve"
+ }
+ QQC2.RadioButton {
+ text: "Comment"
+ }
+ QQC2.RadioButton {
+ text: "Request changes"
+ }
+ }
+ }
CommentModel {
id: commentModel
@@ -28,7 +63,6 @@ Kirigami.ScrollablePage {
onPullRequestChanged: root.connection.getPullRequestComments(root.pullRequest.number)
}
-
Kirigami.PlaceholderMessage {
visible: !root.pullRequest
anchors.centerIn: parent
@@ -36,11 +70,10 @@ Kirigami.ScrollablePage {
text: "Select a pull request"
}
- ColumnLayout {
- id: mainLayout
+ Kirigami.FormLayout {
+ id: descriptionLayout
visible: !!root.pullRequest && root.currentView == "info"
-
- spacing: Kirigami.Units.largeSpacing * 2
+ anchors.fill: parent
Kirigami.Heading {
Layout.fillWidth: true
@@ -52,9 +85,57 @@ Kirigami.ScrollablePage {
wrapMode: Text.WordWrap
}
- Kirigami.ListSectionHeader {
- Layout.fillWidth: true
- text: "description"
+ Kirigami.Separator {
+ Kirigami.FormData.isSection: true
+ }
+
+ RowLayout {
+ QQC2.Label {
+ text: "State: "
+ elide: Text.ElideRight
+ }
+ QQC2.Label {
+ text: root.pullRequest.state
+ elide: Text.ElideLeft
+ }
+ }
+ RowLayout {
+ QQC2.Label {
+ text: "Draft?: "
+ elide: Text.ElideRight
+ }
+ QQC2.Label {
+ text: root.pullRequest.draft ? i18n("Yes") : i18n("No")
+ elide: Text.ElideLeft
+ }
+ }
+
+ RowLayout {
+ Repeater {
+ model: LabelModel {
+ pullRequest: root.pullRequest
+ }
+ delegate: Rectangle {
+ required property string name
+ required property string labelColor
+ required property string textColor
+ color: labelColor
+ width: thelabel.implicitWidth
+ height: thelabel.implicitHeight
+ radius: 5
+ QQC2.Label {
+ id: thelabel
+ padding: Kirigami.Units.smallSpacing
+ text: name
+ color: textColor
+ }
+ }
+ }
+ }
+
+ Kirigami.Separator {
+ Kirigami.FormData.isSection: true
+ Kirigami.FormData.label: "Description"
}
MarkdownLabel {
@@ -62,7 +143,8 @@ Kirigami.ScrollablePage {
Layout.fillHeight: false
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
- text: root.pullRequest ? root.pullRequest.body : ""
+ text: root.pullRequest ?
+ (root.pullRequest.body != "" ? root.pullRequest.body : "*No description provided.*") : ""
}
}
ColumnLayout {
@@ -73,6 +155,10 @@ Kirigami.ScrollablePage {
}
}
+ FilesView {
+ visible: !!root.pullRequest && root.currentView == "files"
+ }
+
footer: Kirigami.NavigationTabBar {
actions: [
Kirigami.Action {