feat(jira-knowledge): add contextual help tooltips and inline validation for Project Mapping
- Add QToolButton help icon (?) next to Project Mapping label - Implement comprehensive tooltip explaining Project ID and Jira Key concepts - Add inline validation to detect common mistake: entering issue keys (ABC-123) instead of project keys (ABC) - Add 13 new i18n keys for help text in English, Vietnamese, and Japanese - Add 15 comprehensive UX tests covering icon presence, tooltip content, validation logic, and accessibility - Update jira-knowledge-guide.md with help icon reference and setup instructions - Fix missing _on_paste method that was causing AttributeError Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+83
-10
@@ -12,9 +12,11 @@ setup dialog; OneDrive/SharePoint: neither, they only toggle).
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox, QDialog, QFormLayout, QFrame, QGroupBox, QHBoxLayout, QLabel,
|
||||
QLineEdit, QMessageBox, QPushButton, QScrollArea, QVBoxLayout, QWidget,
|
||||
QLineEdit, QMessageBox, QPushButton, QScrollArea, QToolButton, QToolTip,
|
||||
QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from ..core.ext_connectors import CATEGORIES as EXT_CATEGORIES
|
||||
@@ -81,19 +83,19 @@ class JiraConnectDialog(QDialog):
|
||||
main_layout.addWidget(conn_group)
|
||||
|
||||
# === Project Knowledge Section ===
|
||||
kb_group = QGroupBox("Project Knowledge")
|
||||
kb_group = QGroupBox(tr("connectors.jira_kb_section"))
|
||||
kb_layout = QVBoxLayout(kb_group)
|
||||
|
||||
self.kb_enabled = QCheckBox("Enable Jira Project Knowledge")
|
||||
self.kb_enabled = QCheckBox(tr("connectors.jira_kb_enable"))
|
||||
self.kb_enabled.setChecked(jira_kb.get("enabled", False))
|
||||
kb_layout.addWidget(self.kb_enabled)
|
||||
|
||||
kb_hint = QLabel("Map Cowork projects to Jira project keys. Format: cowork_project_id:JIRA_KEY")
|
||||
kb_hint = QLabel(tr("connectors.jira_kb_mapping_hint"))
|
||||
kb_hint.setObjectName("hint")
|
||||
kb_hint.setWordWrap(True)
|
||||
kb_layout.addWidget(kb_hint)
|
||||
|
||||
# Project mapping input
|
||||
# Project mapping input with help icons for Project ID and Jira Key
|
||||
mapping_form = QFormLayout()
|
||||
self.project_mapping = QLineEdit()
|
||||
# Load existing mappings
|
||||
@@ -102,17 +104,67 @@ class JiraConnectDialog(QDialog):
|
||||
mapping_str = ", ".join(f"{k}:{v}" for k, v in existing_projects.items())
|
||||
self.project_mapping.setText(mapping_str)
|
||||
self.project_mapping.setPlaceholderText("proj-alpha:ALPHA, proj-beta:BETA")
|
||||
mapping_form.addRow("Project Mapping", self.project_mapping)
|
||||
|
||||
# Label with help icon explaining both Project ID and Jira Key
|
||||
mapping_label = QLabel(tr("connectors.jira_kb_mapping_label"))
|
||||
self.help_icon = QToolButton()
|
||||
self.help_icon.setText("?")
|
||||
self.help_icon.setToolTip(
|
||||
"<b>" + tr("connectors.jira_kb_project_id_title") + "</b><br>"
|
||||
+ tr("connectors.jira_kb_project_id_help")
|
||||
+ "<br><br><b>" + tr("connectors.jira_kb_jira_key_title") + "</b><br>"
|
||||
+ tr("connectors.jira_kb_jira_key_help")
|
||||
)
|
||||
self.help_icon.setStyleSheet("""
|
||||
QToolButton {
|
||||
border: 1px solid #5B9BD5;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: #5B9BD5;
|
||||
font-weight: bold;
|
||||
padding: 2px 6px;
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
}
|
||||
QToolButton:hover {
|
||||
background: #5B9BD5;
|
||||
color: white;
|
||||
}
|
||||
QToolButton:pressed {
|
||||
background: #4A8BC7;
|
||||
color: white;
|
||||
}
|
||||
""")
|
||||
# Native Qt tooltip: hover to show, no click handler needed.
|
||||
# AutoRaise=False keeps the button always visible (not faded out).
|
||||
self.help_icon.setAutoRaise(False)
|
||||
label_row = QHBoxLayout()
|
||||
label_row.setSpacing(4)
|
||||
label_row.addWidget(mapping_label)
|
||||
label_row.addWidget(self.help_icon)
|
||||
label_row.addStretch(1)
|
||||
label_widget = QWidget()
|
||||
label_widget.setLayout(label_row)
|
||||
mapping_form.addRow(label_widget, self.project_mapping)
|
||||
|
||||
# Validation hint for common mistakes (e.g., entering ABC-123 instead of ABC)
|
||||
self.mapping_validation = QLabel()
|
||||
self.mapping_validation.setObjectName("hint")
|
||||
self.mapping_validation.setWordWrap(True)
|
||||
self.mapping_validation.hide()
|
||||
mapping_form.addRow("", self.mapping_validation)
|
||||
self.project_mapping.textChanged.connect(self._validate_mapping)
|
||||
|
||||
kb_layout.addLayout(mapping_form)
|
||||
|
||||
# Sync controls
|
||||
sync_row = QHBoxLayout()
|
||||
self.sync_btn = QPushButton("Sync Now")
|
||||
self.sync_btn = QPushButton(tr("connectors.jira_kb_sync_now"))
|
||||
self.sync_btn.clicked.connect(self._trigger_sync)
|
||||
self.sync_btn.setEnabled(False)
|
||||
sync_row.addWidget(self.sync_btn)
|
||||
|
||||
self.sync_status = QLabel("Not configured")
|
||||
self.sync_status = QLabel(tr("connectors.jira_kb_not_configured"))
|
||||
self.sync_status.setObjectName("hint")
|
||||
sync_row.addWidget(self.sync_status)
|
||||
sync_row.addStretch(1)
|
||||
@@ -136,15 +188,36 @@ class JiraConnectDialog(QDialog):
|
||||
self.kb_enabled.toggled.connect(self._update_sync_state)
|
||||
self._update_sync_state(self.kb_enabled.isChecked())
|
||||
|
||||
def _on_paste(self, text: str) -> None:
|
||||
"""Auto-fill Base URL from a pasted Jira link."""
|
||||
import re
|
||||
# Extract base URL from patterns like https://example.atlassian.net/browse/ABC-123
|
||||
match = re.search(r"(https?://[^/\s]+\.atlassian\.net)", text.strip())
|
||||
if match and not self.url.text().strip():
|
||||
self.url.setText(match.group(1))
|
||||
|
||||
def _update_sync_state(self, enabled: bool) -> None:
|
||||
"""Enable/disable sync controls based on KB checkbox."""
|
||||
self.sync_btn.setEnabled(enabled)
|
||||
if not enabled:
|
||||
self.sync_status.setText("Disabled")
|
||||
self.sync_status.setText(tr("connectors.jira_kb_disabled"))
|
||||
|
||||
def _validate_mapping(self, text: str) -> None:
|
||||
"""Show inline hint if user appears to enter an Issue Key (ABC-123) instead of just Jira Key (ABC)."""
|
||||
import re
|
||||
# Detect pattern like "proj:ABC-123" or "proj:ABC-123, proj2:DEF-456"
|
||||
# A Jira project key should be uppercase letters only (e.g., ABC), not ABC-123
|
||||
issue_key_pattern = re.compile(r':\s*[A-Z]+-\d+')
|
||||
if issue_key_pattern.search(text):
|
||||
self.mapping_validation.setText(tr("connectors.jira_kb_validation_issue_key"))
|
||||
self.mapping_validation.setStyleSheet("color: #D4A017; font-style: italic;")
|
||||
self.mapping_validation.show()
|
||||
else:
|
||||
self.mapping_validation.hide()
|
||||
|
||||
def _trigger_sync(self) -> None:
|
||||
"""Trigger a background sync job using JiraSyncService."""
|
||||
self.sync_status.setText("Syncing...")
|
||||
self.sync_status.setText(tr("connectors.jira_kb_syncing"))
|
||||
self.sync_btn.setEnabled(False)
|
||||
|
||||
def job(_w):
|
||||
|
||||
Reference in New Issue
Block a user