Skip to content

Commit d85c742

Browse files
committed
automatic issues
1 parent 26250cb commit d85c742

11 files changed

Lines changed: 339 additions & 6 deletions

File tree

.github/workflows/release.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ jobs:
216216
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
217217
GITHUB_REPOSITORY: ${{ github.repository }}
218218

219+
# Test tags (e.g. v1.0.3-test1.1) create a draft release: hidden from public
220+
# Releases list; only visible to maintainers at GitHub Releases when logged in.
219221
- name: Create GitHub Release
220222
uses: softprops/action-gh-release@v1
221223
with:
@@ -227,8 +229,8 @@ jobs:
227229
THIRD_PARTY_LICENSES.txt
228230
SHA256SUMS
229231
body_path: RELEASE_NOTES.md
230-
draft: false
231-
prerelease: false
232+
draft: ${{ contains(github.ref_name, '-test') }}
233+
prerelease: ${{ contains(github.ref_name, '-test') }}
232234
env:
233235
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
234236

@@ -239,6 +241,7 @@ jobs:
239241
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
240242

241243
- name: Fetch Existing Appcast Feeds
244+
if: ${{ !contains(github.ref_name, '-test') }}
242245
run: |
243246
echo "=== Fetching Existing Appcast Feeds ==="
244247
@@ -266,6 +269,7 @@ jobs:
266269
fi
267270
268271
- name: Generate Appcast Feeds
272+
if: ${{ !contains(github.ref_name, '-test') }}
269273
run: |
270274
echo "=== Generating Appcast Feeds ==="
271275
VERSION="${{ github.ref_name }}"
@@ -330,6 +334,7 @@ jobs:
330334
echo "Appcast generation complete"
331335
332336
- name: Validate Appcast Feeds
337+
if: ${{ !contains(github.ref_name, '-test') }}
333338
run: |
334339
echo "=== Validating Appcast Feeds ==="
335340
if [ -f "updates/macos/stable/appcast.xml" ] && [ -f "updates/windows/stable/appcast.xml" ]; then
@@ -342,6 +347,7 @@ jobs:
342347
fi
343348
344349
- name: Publish Appcast Feeds to GitHub Pages
350+
if: ${{ !contains(github.ref_name, '-test') }}
345351
run: |
346352
echo "=== Publishing Appcast Feeds ==="
347353

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,4 @@ build/version_info.txt
333333
*.sqlite
334334
bp_cache.sqlite*
335335

336+
scripts/delete_all_releases.py

requirements-build.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ selenium==4.39.0
4848
# Excel export support (for GUI export functionality)
4949
openpyxl==3.1.5
5050

51+
# Error reporting (optional: set SENTRY_DSN to enable)
52+
sentry-sdk>=2.0.0,<3
53+
5154
# Build tool (PyInstaller for packaging)
5255
pyinstaller==6.17.0
5356

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ selenium==4.39.0
4040
# Excel export support (for GUI export functionality)
4141
openpyxl==3.1.5
4242

43+
# Error reporting (optional: set SENTRY_DSN to enable)
44+
sentry-sdk>=2.0.0,<3
45+
4346
# Testing dependencies
4447
pytest==9.0.2
4548
pytest-qt==4.5.0
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
First-run error reporting prompt.
6+
7+
Shown once when the user runs the app for the first time (or after a fresh
8+
install). Asks whether to allow sending error reports to help fix bugs.
9+
"""
10+
11+
from typing import Optional
12+
13+
from PySide6.QtCore import QSettings, QTimer
14+
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget
15+
16+
17+
_FIRST_RUN_PROMPT_SHOWN_KEY = "error_reporting/first_run_prompt_shown"
18+
19+
20+
def show_if_first_run(parent: Optional[QWidget] = None) -> None:
21+
"""Show the error-reporting consent prompt only on first run.
22+
23+
If the user has never been asked, shows a dialog. Saves their choice
24+
and sets a flag so we do not ask again.
25+
26+
Args:
27+
parent: Optional parent widget for the dialog.
28+
"""
29+
settings = QSettings()
30+
if settings.value(_FIRST_RUN_PROMPT_SHOWN_KEY, False, type=bool):
31+
return
32+
33+
msg = QMessageBox(parent)
34+
msg.setWindowTitle("Help improve CuePoint")
35+
msg.setIcon(QMessageBox.Question)
36+
msg.setText("Send error reports to help fix bugs?")
37+
msg.setInformativeText(
38+
"When crashes or errors happen, we can send a report to the developers. "
39+
"No personal data or file contents are included. "
40+
"You can change this later in Settings → Privacy."
41+
)
42+
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
43+
msg.setDefaultButton(QMessageBox.Yes)
44+
45+
def center_on_screen() -> None:
46+
screen = QApplication.primaryScreen().availableGeometry()
47+
geom = msg.frameGeometry()
48+
geom.moveCenter(screen.center())
49+
msg.move(geom.topLeft())
50+
51+
QTimer.singleShot(0, center_on_screen)
52+
choice = msg.exec()
53+
54+
try:
55+
from cuepoint.utils.error_reporting_prefs import ErrorReportingPrefs
56+
57+
prefs = ErrorReportingPrefs()
58+
allowed = choice == QMessageBox.Yes
59+
prefs.set_enabled(allowed)
60+
prefs.set_consented(allowed)
61+
except Exception:
62+
pass
63+
64+
settings.setValue(_FIRST_RUN_PROMPT_SHOWN_KEY, True)

src/cuepoint/ui/main_window.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ def show_tool_selection_page(self) -> None:
935935
if self.tool_selection_page:
936936
self.setCentralWidget(self.tool_selection_page)
937937
self.current_page = "tool_selection"
938-
self.menuBar().hide()
938+
self.menuBar().show()
939939

940940
def show_main_interface(self) -> None:
941941
"""Show the main interface (existing tabs)"""
@@ -1233,6 +1233,11 @@ def create_menu_bar(self) -> None:
12331233
report_issue_action.triggered.connect(self.on_report_issue)
12341234
diagnostics_menu.addAction(report_issue_action)
12351235

1236+
test_sentry_action = QAction("Send &test event to Sentry", self)
1237+
test_sentry_action.setToolTip("Send a test error to Sentry to verify reporting is working")
1238+
test_sentry_action.triggered.connect(self._on_test_sentry_report)
1239+
diagnostics_menu.addAction(test_sentry_action)
1240+
12361241
help_menu.addSeparator()
12371242

12381243
# About (includes changelog)
@@ -1882,6 +1887,36 @@ def on_report_issue(self) -> None:
18821887
self, "Report Issue", f"Could not open issue reporter:\n{e}"
18831888
)
18841889

1890+
def _on_test_sentry_report(self) -> None:
1891+
"""Send a test event to Sentry so the user can verify reporting works."""
1892+
try:
1893+
from cuepoint.utils.error_reporting_prefs import ErrorReportingPrefs
1894+
1895+
prefs = ErrorReportingPrefs()
1896+
if not (prefs.is_enabled() and prefs.has_user_consented()):
1897+
QMessageBox.warning(
1898+
self,
1899+
"Sentry test",
1900+
"Error reporting is off.\n\n"
1901+
"Enable it in Settings → Privacy → \"Send error reports to help fix bugs\", "
1902+
"then try again.",
1903+
QMessageBox.Ok,
1904+
)
1905+
return
1906+
except Exception:
1907+
pass
1908+
import logging
1909+
1910+
logger = logging.getLogger(__name__)
1911+
logger.error("CuePoint Sentry test event (user-triggered)")
1912+
QMessageBox.information(
1913+
self,
1914+
"Sentry test",
1915+
"A test event was sent to Sentry.\n\n"
1916+
"Check your Sentry project (Issues) in a few seconds.",
1917+
QMessageBox.Ok,
1918+
)
1919+
18851920
def _open_folder(self, folder_path) -> None:
18861921
"""Open a folder in the OS file manager."""
18871922
try:

src/cuepoint/ui/widgets/privacy_settings.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ def _init_ui(self) -> None:
6262
)
6363
group_layout.addWidget(self.chk_telemetry)
6464

65+
# Error reporting (Sentry) – consent to send crash/error reports
66+
self.chk_error_reporting = QCheckBox("Send error reports to help fix bugs")
67+
self.chk_error_reporting.setToolTip(
68+
"When enabled, crash reports and error details are sent to the developers "
69+
"so we can fix issues. No personal data or file contents are included."
70+
)
71+
group_layout.addWidget(self.chk_error_reporting)
72+
6573
self.chk_clear_cache = QCheckBox("Clear cache on exit")
6674
self.chk_clear_logs = QCheckBox("Clear logs on exit")
6775

@@ -80,6 +88,7 @@ def _init_ui(self) -> None:
8088
layout.addWidget(group)
8189

8290
self.chk_telemetry.toggled.connect(self._on_telemetry_changed)
91+
self.chk_error_reporting.toggled.connect(self._on_error_reporting_changed)
8392
self.chk_clear_cache.toggled.connect(self._on_changed)
8493
self.chk_clear_logs.toggled.connect(self._on_changed)
8594
self.btn_manage.clicked.connect(self.open_privacy_dialog_requested.emit)
@@ -93,6 +102,15 @@ def _load(self) -> None:
93102
"telemetry.enabled", False
94103
)
95104
self.chk_telemetry.setChecked(bool(enabled))
105+
try:
106+
from cuepoint.utils.error_reporting_prefs import ErrorReportingPrefs
107+
108+
er_prefs = ErrorReportingPrefs()
109+
self.chk_error_reporting.setChecked(
110+
er_prefs.is_enabled() and er_prefs.has_user_consented()
111+
)
112+
except Exception:
113+
self.chk_error_reporting.setChecked(False)
96114

97115
def _on_telemetry_changed(self, checked: bool) -> None:
98116
if self._config_controller:
@@ -106,6 +124,16 @@ def _on_telemetry_changed(self, checked: bool) -> None:
106124
except Exception:
107125
pass
108126

127+
def _on_error_reporting_changed(self, checked: bool) -> None:
128+
try:
129+
from cuepoint.utils.error_reporting_prefs import ErrorReportingPrefs
130+
131+
er_prefs = ErrorReportingPrefs()
132+
er_prefs.set_enabled(bool(checked))
133+
er_prefs.set_consented(bool(checked))
134+
except Exception:
135+
pass
136+
109137
def _on_changed(self) -> None:
110138
self._privacy.set_clear_cache_on_exit(self.chk_clear_cache.isChecked())
111139
self._privacy.set_clear_logs_on_exit(self.chk_clear_logs.isChecked())
@@ -114,6 +142,7 @@ def get_snapshot(self) -> tuple:
114142
"""Return a comparable snapshot for change detection."""
115143
return (
116144
self.chk_telemetry.isChecked(),
145+
self.chk_error_reporting.isChecked(),
117146
self.chk_clear_cache.isChecked(),
118147
self.chk_clear_logs.isChecked(),
119148
)

src/cuepoint/update/update_checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,12 +515,12 @@ def _find_latest_update(self, items: List[Dict]) -> Optional[Dict]:
515515
continue
516516

517517
# Both on same track (both test or both non-test) - allow if newer
518-
# Prefer items with checksum (SHA256); allow items with only EdDSA signature
519-
# so we don't skip all appcast items (many feeds use sparkle:edSignature only).
518+
# Design 4.27, 4.109: require checksum; skip item if missing (no update offered).
520519
if not item.get("checksum"):
521520
logger.debug(
522-
f"Item {version} has no SHA256 checksum (may have EdDSA only); still offering as update"
521+
f"Skipping item {version}: no checksum (SHA256 required)"
523522
)
523+
continue
524524
logger.info(
525525
f"Found newer version: {version} (current: {self.current_version})"
526526
)

src/cuepoint/utils/crash_handler.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ def _handle_exception(self, exc_type, exc_value, exc_traceback):
9999
exception, traceback_str, crash_log, crash_report_path
100100
)
101101

102+
# Report to Sentry (if DSN set and user consented)
103+
try:
104+
import sentry_sdk
105+
106+
sentry_sdk.capture_exception(exception)
107+
except Exception as e:
108+
logger.debug("Sentry capture failed: %s", e)
109+
102110
# Report to GitHub Issues (Step 11.2)
103111
try:
104112
from cuepoint.utils.error_reporter import report_error
@@ -207,6 +215,14 @@ def thread_exception_handler(args):
207215
crash_report_path = crash_log.with_suffix(".json")
208216
CrashReport.save_report(crash_report, crash_report_path)
209217

218+
# Report to Sentry (if DSN set and user consented)
219+
try:
220+
import sentry_sdk
221+
222+
sentry_sdk.capture_exception(exception)
223+
except Exception as e:
224+
logger.debug("Sentry capture failed: %s", e)
225+
210226
# Report to GitHub Issues (Step 11.2)
211227
try:
212228
from cuepoint.utils.error_reporter import report_error

0 commit comments

Comments
 (0)