Add Report Issue button to tool details panel
- Client: add submit_issue() method for POST /tools/{owner}/{name}/issues
- New IssueDialog with type (bug/compatibility/security), severity, title,
and description fields with character count and validation
- Report Issue button in rating bar beside Rate Tool, visible for registry
tools only. Submits via background SubmitIssueWorker thread.
Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Issue report dialog for registry tools."""
|
||||
|
||||
from typing import Dict
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QLineEdit, QPlainTextEdit, QComboBox,
|
||||
QFormLayout, QMessageBox
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
|
||||
# Maps display labels to API values
|
||||
ISSUE_TYPES = [
|
||||
("Bug", "bug"),
|
||||
("Compatibility Issue", "compatibility"),
|
||||
("Security Issue", "security"),
|
||||
]
|
||||
|
||||
SEVERITY_LEVELS = [
|
||||
("Low", "low"),
|
||||
("Medium", "medium"),
|
||||
("High", "high"),
|
||||
("Critical", "critical"),
|
||||
]
|
||||
|
||||
|
||||
class IssueDialog(QDialog):
|
||||
"""Dialog for reporting an issue on a registry tool."""
|
||||
|
||||
def __init__(self, parent, owner: str, name: str):
|
||||
super().__init__(parent)
|
||||
self._owner = owner
|
||||
self._name = name
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
self.setWindowTitle(f"Report Issue - {self._owner}/{self._name}")
|
||||
self.setMinimumSize(450, 400)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(14)
|
||||
|
||||
# Title
|
||||
title = QLabel(f"Report an issue with {self._owner}/{self._name}")
|
||||
title.setStyleSheet("font-size: 15px; font-weight: 600;")
|
||||
layout.addWidget(title)
|
||||
|
||||
# Form
|
||||
form = QFormLayout()
|
||||
form.setSpacing(10)
|
||||
|
||||
self.type_combo = QComboBox()
|
||||
self.type_combo.setMinimumHeight(28)
|
||||
for label, _ in ISSUE_TYPES:
|
||||
self.type_combo.addItem(label)
|
||||
form.addRow("Issue type:", self.type_combo)
|
||||
|
||||
self.severity_combo = QComboBox()
|
||||
self.severity_combo.setMinimumHeight(28)
|
||||
for label, _ in SEVERITY_LEVELS:
|
||||
self.severity_combo.addItem(label)
|
||||
self.severity_combo.setCurrentIndex(1) # Default: Medium
|
||||
form.addRow("Severity:", self.severity_combo)
|
||||
|
||||
self.title_input = QLineEdit()
|
||||
self.title_input.setMaxLength(200)
|
||||
self.title_input.setMinimumHeight(28)
|
||||
self.title_input.setPlaceholderText("Brief description of the issue")
|
||||
form.addRow("Title:", self.title_input)
|
||||
|
||||
layout.addLayout(form)
|
||||
|
||||
# Description
|
||||
desc_label = QLabel("Description (optional):")
|
||||
desc_label.setStyleSheet("font-weight: 500;")
|
||||
layout.addWidget(desc_label)
|
||||
|
||||
self.desc_input = QPlainTextEdit()
|
||||
self.desc_input.setPlaceholderText("Provide details about the issue, steps to reproduce, etc.")
|
||||
self.desc_input.setMinimumHeight(120)
|
||||
self.desc_input.setMaximumHeight(200)
|
||||
layout.addWidget(self.desc_input)
|
||||
|
||||
# Character count
|
||||
self._char_count = QLabel("0 / 5000")
|
||||
self._char_count.setStyleSheet("color: #718096; font-size: 11px;")
|
||||
self._char_count.setAlignment(Qt.AlignRight)
|
||||
self.desc_input.textChanged.connect(self._update_char_count)
|
||||
layout.addWidget(self._char_count)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
# Buttons
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_layout.addStretch()
|
||||
|
||||
btn_cancel = QPushButton("Cancel")
|
||||
btn_cancel.setObjectName("secondary")
|
||||
btn_cancel.clicked.connect(self.reject)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
self.btn_submit = QPushButton("Submit")
|
||||
self.btn_submit.clicked.connect(self._on_submit)
|
||||
btn_layout.addWidget(self.btn_submit)
|
||||
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
def _update_char_count(self):
|
||||
count = len(self.desc_input.toPlainText())
|
||||
self._char_count.setText(f"{count} / 5000")
|
||||
if count > 5000:
|
||||
self._char_count.setStyleSheet("color: #e53e3e; font-size: 11px;")
|
||||
else:
|
||||
self._char_count.setStyleSheet("color: #718096; font-size: 11px;")
|
||||
|
||||
def _on_submit(self):
|
||||
title = self.title_input.text().strip()
|
||||
if not title:
|
||||
QMessageBox.warning(self, "Validation", "Title is required.")
|
||||
return
|
||||
|
||||
desc = self.desc_input.toPlainText().strip()
|
||||
if len(desc) > 5000:
|
||||
QMessageBox.warning(self, "Validation", "Description must be 5000 characters or less.")
|
||||
return
|
||||
|
||||
self.accept()
|
||||
|
||||
def get_issue_data(self) -> Dict:
|
||||
"""Get the issue data entered by the user."""
|
||||
type_idx = self.type_combo.currentIndex()
|
||||
severity_idx = self.severity_combo.currentIndex()
|
||||
return {
|
||||
"issue_type": ISSUE_TYPES[type_idx][1],
|
||||
"severity": SEVERITY_LEVELS[severity_idx][1],
|
||||
"title": self.title_input.text().strip(),
|
||||
"description": self.desc_input.toPlainText().strip()[:5000],
|
||||
}
|
||||
Reference in New Issue
Block a user