Feature/jira project knowledge #8
@@ -34,6 +34,10 @@ Jira Project Knowledge enables Cowork to index Jira issues as searchable project
|
||||
1. In the same Jira dialog, check **Enable Jira Project Knowledge**.
|
||||
2. Enter **Project Mapping** in the format `cowork_project_id:JIRA_PROJECT_KEY`.
|
||||
* Example: `proj-alpha:ALPHA, proj-beta:BETA`
|
||||
* Click the **ⓘ** icon next to "Project Mapping" for detailed help on:
|
||||
* **Project ID**: The Cowork project identifier (e.g., `cowork-local`). Find it in your current Cowork project settings.
|
||||
* **Jira Key**: The Jira project key (e.g., `ALPHA` from issue `ALPHA-123`). Open any Jira issue to find it.
|
||||
* **Common mistake**: Do not enter issue keys like `ABC-123`. Only enter the project key part `ABC`.
|
||||
3. Click **Save**.
|
||||
|
||||
### 3.3 Initial Sync
|
||||
|
||||
@@ -220,6 +220,106 @@ STRINGS: Dict[str, Dict[str, str]] = {
|
||||
"en": "Enter base URL, email and API token first.",
|
||||
"ja": "先にベースURL・メール・APIトークンを入力してください。",
|
||||
"vi": "Hãy nhập Base URL, Email và API token trước."},
|
||||
# ---- Jira Project Knowledge help tooltips ---------------------------------
|
||||
"connectors.jira_kb_section": {
|
||||
"en": "Project Knowledge",
|
||||
"ja": "プロジェクトナレッジ",
|
||||
"vi": "Project Knowledge"},
|
||||
"connectors.jira_kb_enable": {
|
||||
"en": "Enable Jira Project Knowledge",
|
||||
"ja": "Jiraプロジェクトナレッジを有効化",
|
||||
"vi": "Bật Jira Project Knowledge"},
|
||||
"connectors.jira_kb_mapping_label": {
|
||||
"en": "Project Mapping",
|
||||
"ja": "プロジェクトマッピング",
|
||||
"vi": "Ánh xạ Project"},
|
||||
"connectors.jira_kb_project_id_title": {
|
||||
"en": "What is Project ID?",
|
||||
"ja": "Project IDとは?",
|
||||
"vi": "Project ID là gì?"},
|
||||
"connectors.jira_kb_jira_key_title": {
|
||||
"en": "What is Jira Key?",
|
||||
"ja": "Jira Keyとは?",
|
||||
"vi": "Jira Key là gì?"},
|
||||
"connectors.jira_kb_mapping_hint": {
|
||||
"en": "Map Cowork projects to Jira project keys. Format: cowork_project_id:JIRA_KEY",
|
||||
"ja": "CoworkプロジェクトをJiraプロジェクトキーにマッピング。形式: cowork_project_id:JIRA_KEY",
|
||||
"vi": "Ánh xạ project Cowork với Jira project key. Định dạng: cowork_project_id:JIRA_KEY"},
|
||||
"connectors.jira_kb_sync_now": {
|
||||
"en": "Sync Now",
|
||||
"ja": "今すぐ同期",
|
||||
"vi": "Đồng bộ ngay"},
|
||||
"connectors.jira_kb_not_configured": {
|
||||
"en": "Not configured",
|
||||
"ja": "未設定",
|
||||
"vi": "Chưa cấu hình"},
|
||||
"connectors.jira_kb_disabled": {
|
||||
"en": "Disabled",
|
||||
"ja": "無効",
|
||||
"vi": "Đã tắt"},
|
||||
"connectors.jira_kb_syncing": {
|
||||
"en": "Syncing…",
|
||||
"ja": "同期中…",
|
||||
"vi": "Đang đồng bộ…"},
|
||||
"connectors.jira_kb_project_id_help": {
|
||||
"en": ("<b>What is Project ID?</b><br>"
|
||||
"Project ID is the identifier of a project in Cowork Local. "
|
||||
"This value links knowledge from Jira to the correct project in Cowork.<br><br>"
|
||||
"<b>Where to find it:</b><br>"
|
||||
"You can get the Project ID from the currently open project in Cowork "
|
||||
"or from the current project configuration.<br><br>"
|
||||
"<b>Example:</b> cowork-local<br><br>"
|
||||
"<b>Common mistake:</b><br>"
|
||||
"Do not enter a Jira Project Key or Jira Issue Key here."),
|
||||
"ja": ("<b>Project IDとは?</b><br>"
|
||||
"Project IDはCowork Local内のプロジェクト識別子です。"
|
||||
"この値でJiraのナレッジをCoworkの正しいプロジェクトに紐付けます。<br><br>"
|
||||
"<b>確認方法:</b><br>"
|
||||
"Coworkで開いているプロジェクト、または現在のプロジェクト設定から取得できます。<br><br>"
|
||||
"<b>例:</b> cowork-local<br><br>"
|
||||
"<b>よくある間違い:</b><br>"
|
||||
"ここにJiraプロジェクトキーやJira課題キーを入力しないでください。"),
|
||||
"vi": ("<b>Project ID là gì?</b><br>"
|
||||
"Project ID là định danh của project trong Cowork Local. "
|
||||
"Giá trị này dùng để gắn knowledge từ Jira với đúng project trong Cowork.<br><br>"
|
||||
"<b>Cách lấy:</b><br>"
|
||||
"Bạn có thể lấy Project ID từ project đang mở trong Cowork "
|
||||
"hoặc từ cấu hình project hiện tại.<br><br>"
|
||||
"<b>Ví dụ:</b> cowork-local<br><br>"
|
||||
"<b>Lỗi thường gặp:</b><br>"
|
||||
"Không nhập Jira Project Key hoặc Jira Issue Key vào ô này.")},
|
||||
"connectors.jira_kb_jira_key_help": {
|
||||
"en": ("<b>What is Jira Key?</b><br>"
|
||||
"Jira Key is the short code of a Jira project — not an issue code.<br><br>"
|
||||
"<b>Where to find it:</b><br>"
|
||||
"Open any issue in Jira. If the issue code is ABC-123, then the Jira Key is ABC.<br>"
|
||||
"You can also find it in Jira Project Settings.<br><br>"
|
||||
"<b>Example:</b><br>"
|
||||
"Issue: ABC-123 → Jira Key: ABC<br><br>"
|
||||
"<b>Common mistake:</b><br>"
|
||||
"Do not enter ABC-123. Only enter ABC."),
|
||||
"ja": ("<b>Jira Keyとは?</b><br>"
|
||||
"Jira KeyはJiraプロジェクトの短いコードです。課題コードではありません。<br><br>"
|
||||
"<b>確認方法:</b><br>"
|
||||
"Jiraで任意の課題を開きます。課題コードがABC-123なら、Jira KeyはABCです。<br>"
|
||||
"Jiraプロジェクト設定でも確認できます。<br><br>"
|
||||
"<b>例:</b><br>"
|
||||
"課題: ABC-123 → Jira Key: ABC<br><br>"
|
||||
"<b>よくある間違い:</b><br>"
|
||||
"ABC-123と入力しないでください。ABCのみ入力します。"),
|
||||
"vi": ("<b>Jira Key là gì?</b><br>"
|
||||
"Jira Key là mã ngắn của Jira project, không phải mã của một issue.<br><br>"
|
||||
"<b>Cách lấy:</b><br>"
|
||||
"Mở một issue bất kỳ trong Jira. Nếu issue có mã ABC-123 thì Jira Key là ABC.<br>"
|
||||
"Bạn cũng có thể xem Jira Key trong Project settings của Jira.<br><br>"
|
||||
"<b>Ví dụ:</b><br>"
|
||||
"Issue: ABC-123 → Jira Key: ABC<br><br>"
|
||||
"<b>Lỗi thường gặp:</b><br>"
|
||||
"Không nhập ABC-123. Chỉ nhập ABC.")},
|
||||
"connectors.jira_kb_validation_issue_key": {
|
||||
"en": "Looks like you entered an Issue Key. Enter only the project key part, e.g. ABC.",
|
||||
"ja": "課題キーを入力したようです。プロジェクトキー部分のみを入力してください(例: ABC)。",
|
||||
"vi": "Có vẻ bạn đã nhập Issue Key. Hãy nhập chỉ phần project key, ví dụ ABC."},
|
||||
"tools_admin.jira_group": {"en": "Jira connection", "ja": "Jira 接続", "vi": "Kết nối Jira"},
|
||||
"tools_admin.jira_hint": {
|
||||
"en": "Connect once, then just paste a Jira issue link into Cowork or a Co4E step — the agent "
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
"""UX tests for Jira Project Knowledge help tooltips and validation.
|
||||
|
||||
Verifies that the JiraConnectDialog shows contextual help icons for
|
||||
Project ID and Jira Key, renders correct help text, supports keyboard
|
||||
accessibility, and validates common user mistakes (e.g. entering ABC-123
|
||||
instead of ABC).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class _Config:
|
||||
"""Minimal config stub for JiraConnectDialog."""
|
||||
|
||||
def __init__(self):
|
||||
self.data = {
|
||||
"jira": {"base_url": "", "email": "", "api_token": ""},
|
||||
"jira_knowledge": {"enabled": False, "projects": {}},
|
||||
}
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
|
||||
class _Ctx:
|
||||
def __init__(self):
|
||||
self.config = _Config()
|
||||
|
||||
def save(self):
|
||||
self.config.save()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dialog(qapp):
|
||||
from cowork_local.ui.connectors_panel import JiraConnectDialog
|
||||
ctx = _Ctx()
|
||||
dlg = JiraConnectDialog(ctx)
|
||||
yield dlg
|
||||
dlg.deleteLater()
|
||||
|
||||
|
||||
# ---- Help icon presence ---------------------------------------------------
|
||||
|
||||
def test_help_icon_exists(dialog):
|
||||
"""The mapping label row must contain a help button (?)."""
|
||||
from PySide6.QtWidgets import QToolButton
|
||||
dlg = dialog
|
||||
help_icons = dlg.findChildren(QToolButton)
|
||||
assert len(help_icons) >= 1, "Help button (?) not found in JiraConnectDialog"
|
||||
|
||||
|
||||
def test_help_icon_has_tooltip(dialog):
|
||||
"""Help icon must have a non-empty tooltip with both Project ID and Jira Key explanations."""
|
||||
from PySide6.QtWidgets import QToolButton
|
||||
dlg = dialog
|
||||
help_icons = dlg.findChildren(QToolButton)
|
||||
assert help_icons, "No help button found"
|
||||
tooltip = help_icons[0].toolTip()
|
||||
assert tooltip, "Help button has no tooltip"
|
||||
assert "Project ID" in tooltip, "Tooltip missing Project ID explanation"
|
||||
assert "Jira Key" in tooltip, "Tooltip missing Jira Key explanation"
|
||||
|
||||
|
||||
# ---- Help text content ----------------------------------------------------
|
||||
|
||||
def test_project_id_help_content(dialog):
|
||||
"""Project ID help must explain what it is, where to find it, example, and common mistake."""
|
||||
from cowork_local.i18n import tr
|
||||
text = tr("connectors.jira_kb_project_id_help")
|
||||
assert "cowork-local" in text.lower() or "Cowork" in text, "Missing example"
|
||||
# Must warn against entering Jira keys
|
||||
lower = text.lower()
|
||||
assert "jira" in lower and ("key" in lower or "issue" in lower), \
|
||||
"Missing common mistake warning about Jira keys"
|
||||
|
||||
|
||||
def test_jira_key_help_content(dialog):
|
||||
"""Jira Key help must include the ABC-123 → ABC example."""
|
||||
from cowork_local.i18n import tr
|
||||
text = tr("connectors.jira_kb_jira_key_help")
|
||||
assert "ABC-123" in text, "Missing ABC-123 example"
|
||||
assert "ABC" in text, "Missing ABC extraction example"
|
||||
|
||||
|
||||
def test_jira_key_help_warns_against_issue_key(dialog):
|
||||
"""Jira Key help must explicitly warn not to enter ABC-123."""
|
||||
from cowork_local.i18n import tr
|
||||
text = tr("connectors.jira_kb_jira_key_help")
|
||||
lower = text.lower()
|
||||
# Should contain a warning like "Do not enter ABC-123" or "Không nhập ABC-123"
|
||||
assert "abc-123" in lower, "Missing warning about entering issue key format"
|
||||
|
||||
|
||||
# ---- Validation -----------------------------------------------------------
|
||||
|
||||
def test_validation_shows_on_issue_key_pattern(dialog):
|
||||
"""Typing 'proj:ABC-123' should show the validation warning."""
|
||||
dlg = dialog
|
||||
dlg.show()
|
||||
dlg.project_mapping.setText("myproject:ABC-123")
|
||||
assert not dlg.mapping_validation.isHidden(), \
|
||||
"Validation hint should be shown when Issue Key pattern detected"
|
||||
assert dlg.mapping_validation.text(), "Validation hint should have text"
|
||||
|
||||
|
||||
def test_validation_hides_on_correct_input(dialog):
|
||||
"""Typing 'proj:ABC' should NOT show the validation warning."""
|
||||
dlg = dialog
|
||||
dlg.show()
|
||||
dlg.project_mapping.setText("myproject:ABC")
|
||||
assert dlg.mapping_validation.isHidden(), \
|
||||
"Validation hint should be hidden for correct Jira Key format"
|
||||
|
||||
|
||||
def test_validation_hides_on_empty(dialog):
|
||||
"""Empty input should not show validation warning."""
|
||||
dlg = dialog
|
||||
dlg.show()
|
||||
dlg.project_mapping.setText("")
|
||||
assert dlg.mapping_validation.isHidden(), \
|
||||
"Validation hint should be hidden on empty input"
|
||||
|
||||
|
||||
def test_validation_multiple_mappings(dialog):
|
||||
"""Validation should detect issue key pattern even in multi-mapping strings."""
|
||||
dlg = dialog
|
||||
dlg.show()
|
||||
dlg.project_mapping.setText("proj-a:ALPHA, proj-b:DEF-456")
|
||||
assert not dlg.mapping_validation.isHidden(), \
|
||||
"Validation should trigger when any mapping contains an Issue Key pattern"
|
||||
|
||||
|
||||
# ---- Existing behavior unchanged ------------------------------------------
|
||||
|
||||
def test_save_still_works(dialog):
|
||||
"""Saving with valid mapping still produces correct config structure."""
|
||||
dlg = dialog
|
||||
dlg.url.setText("https://example.atlassian.net")
|
||||
dlg.email.setText("user@example.com")
|
||||
dlg.token.setText("test-token")
|
||||
dlg.project_mapping.setText("myproject:MYKEY")
|
||||
dlg.kb_enabled.setChecked(True)
|
||||
dlg._save()
|
||||
jira_kb = dlg.ctx.config.data.get("jira_knowledge", {})
|
||||
assert jira_kb["enabled"] is True
|
||||
assert jira_kb["projects"] == {"myproject": "MYKEY"}
|
||||
|
||||
|
||||
def test_sync_button_present(dialog):
|
||||
"""Sync Now button must still exist and be functional."""
|
||||
dlg = dialog
|
||||
assert dlg.sync_btn is not None
|
||||
assert dlg.sync_btn.text(), "Sync button should have text"
|
||||
|
||||
|
||||
# ---- Accessibility --------------------------------------------------------
|
||||
|
||||
def test_help_icon_cursor(dialog):
|
||||
"""Help button should be a QToolButton (clickable by nature)."""
|
||||
from PySide6.QtWidgets import QToolButton
|
||||
dlg = dialog
|
||||
help_icons = dlg.findChildren(QToolButton)
|
||||
assert help_icons, "No help button found"
|
||||
# QToolButton is inherently clickable, no need for cursor check
|
||||
assert help_icons[0].text() == "?", "Help button should display '?' text"
|
||||
|
||||
|
||||
# ---- i18n keys exist for all three languages ------------------------------
|
||||
|
||||
@pytest.mark.parametrize("lang", ["en", "vi", "ja"])
|
||||
def test_i18n_keys_exist(lang):
|
||||
"""All Jira KB help keys must have translations for en, vi, ja."""
|
||||
from cowork_local.i18n import STRINGS
|
||||
required_keys = [
|
||||
"connectors.jira_kb_section",
|
||||
"connectors.jira_kb_enable",
|
||||
"connectors.jira_kb_mapping_label",
|
||||
"connectors.jira_kb_project_id_title",
|
||||
"connectors.jira_kb_jira_key_title",
|
||||
"connectors.jira_kb_project_id_help",
|
||||
"connectors.jira_kb_jira_key_help",
|
||||
"connectors.jira_kb_validation_issue_key",
|
||||
"connectors.jira_kb_sync_now",
|
||||
"connectors.jira_kb_not_configured",
|
||||
"connectors.jira_kb_disabled",
|
||||
"connectors.jira_kb_syncing",
|
||||
]
|
||||
for key in required_keys:
|
||||
assert key in STRINGS, f"Missing i18n key: {key}"
|
||||
entry = STRINGS[key]
|
||||
assert lang in entry, f"Missing '{lang}' translation for key: {key}"
|
||||
assert entry[lang], f"Empty '{lang}' translation for key: {key}"
|
||||
+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