gitflow-analytics 3.6.1__py3-none-any.whl → 3.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitflow_analytics/__init__.py +8 -12
- gitflow_analytics/_version.py +1 -1
- gitflow_analytics/cli.py +156 -175
- gitflow_analytics/cli_wizards/install_wizard.py +5 -5
- gitflow_analytics/core/cache.py +3 -3
- gitflow_analytics/models/database.py +279 -45
- gitflow_analytics/security/reports/__init__.py +5 -0
- gitflow_analytics/security/reports/security_report.py +358 -0
- {gitflow_analytics-3.6.1.dist-info → gitflow_analytics-3.7.0.dist-info}/METADATA +2 -4
- {gitflow_analytics-3.6.1.dist-info → gitflow_analytics-3.7.0.dist-info}/RECORD +14 -25
- gitflow_analytics/tui/__init__.py +0 -5
- gitflow_analytics/tui/app.py +0 -726
- gitflow_analytics/tui/progress_adapter.py +0 -313
- gitflow_analytics/tui/screens/__init__.py +0 -8
- gitflow_analytics/tui/screens/analysis_progress_screen.py +0 -857
- gitflow_analytics/tui/screens/configuration_screen.py +0 -523
- gitflow_analytics/tui/screens/loading_screen.py +0 -348
- gitflow_analytics/tui/screens/main_screen.py +0 -321
- gitflow_analytics/tui/screens/results_screen.py +0 -735
- gitflow_analytics/tui/widgets/__init__.py +0 -7
- gitflow_analytics/tui/widgets/data_table.py +0 -255
- gitflow_analytics/tui/widgets/export_modal.py +0 -301
- gitflow_analytics/tui/widgets/progress_widget.py +0 -187
- {gitflow_analytics-3.6.1.dist-info → gitflow_analytics-3.7.0.dist-info}/WHEEL +0 -0
- {gitflow_analytics-3.6.1.dist-info → gitflow_analytics-3.7.0.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-3.6.1.dist-info → gitflow_analytics-3.7.0.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-3.6.1.dist-info → gitflow_analytics-3.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
"""Progress adapter for TUI to connect with core analysis progress."""
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import time
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
from gitflow_analytics.core.progress import ProgressContext, ProgressService
|
|
8
|
-
from gitflow_analytics.tui.widgets.progress_widget import AnalysisProgressWidget
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TUIProgressAdapter(ProgressService):
|
|
12
|
-
"""
|
|
13
|
-
Adapter that bridges the core ProgressService with TUI progress widgets.
|
|
14
|
-
|
|
15
|
-
This allows the GitAnalyzer's progress updates to be reflected in the TUI
|
|
16
|
-
interface in real-time.
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, widget: Optional[AnalysisProgressWidget] = None):
|
|
20
|
-
"""Initialize the TUI progress adapter.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
widget: The progress widget to update
|
|
24
|
-
"""
|
|
25
|
-
super().__init__()
|
|
26
|
-
self.widget = widget
|
|
27
|
-
self.current_progress = 0.0
|
|
28
|
-
self.total_items = 100.0
|
|
29
|
-
self._loop = None
|
|
30
|
-
self.processing_stats = {
|
|
31
|
-
"total": 0,
|
|
32
|
-
"processed": 0,
|
|
33
|
-
"success": 0,
|
|
34
|
-
"failed": 0,
|
|
35
|
-
"timeout": 0,
|
|
36
|
-
}
|
|
37
|
-
self.repositories_in_progress = {}
|
|
38
|
-
|
|
39
|
-
def set_widget(self, widget: AnalysisProgressWidget) -> None:
|
|
40
|
-
"""Set or update the progress widget."""
|
|
41
|
-
self.widget = widget
|
|
42
|
-
|
|
43
|
-
def set_event_loop(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
44
|
-
"""Set the event loop for async updates."""
|
|
45
|
-
self._loop = loop
|
|
46
|
-
|
|
47
|
-
def create_progress(
|
|
48
|
-
self,
|
|
49
|
-
total: int,
|
|
50
|
-
description: str = "",
|
|
51
|
-
unit: str = "items",
|
|
52
|
-
nested: bool = False,
|
|
53
|
-
leave: bool = True,
|
|
54
|
-
position: Optional[int] = None,
|
|
55
|
-
) -> ProgressContext:
|
|
56
|
-
"""Create a progress context for tracking.
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
total: Total number of items to process
|
|
60
|
-
description: Description of the task
|
|
61
|
-
unit: Unit of work (e.g., "commits", "files")
|
|
62
|
-
nested: Whether this is a nested progress bar
|
|
63
|
-
leave: Whether to leave the progress bar on screen after completion
|
|
64
|
-
position: Explicit position for the progress bar (for nested contexts)
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
Progress context for this task
|
|
68
|
-
"""
|
|
69
|
-
# Pass all parameters to parent class with correct signature
|
|
70
|
-
context = super().create_progress(total, description, unit, nested, leave, position)
|
|
71
|
-
self.total_items = float(total)
|
|
72
|
-
self.current_progress = 0.0
|
|
73
|
-
|
|
74
|
-
# Update widget if available
|
|
75
|
-
if self.widget and self._loop:
|
|
76
|
-
self._loop.call_soon_threadsafe(self._update_widget_sync, 0.0, description)
|
|
77
|
-
|
|
78
|
-
return context
|
|
79
|
-
|
|
80
|
-
def update(self, context: ProgressContext, advance: int = 1) -> None:
|
|
81
|
-
"""Update progress by advancing the counter.
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
context: Progress context to update
|
|
85
|
-
advance: Number of items completed
|
|
86
|
-
"""
|
|
87
|
-
super().update(context, advance)
|
|
88
|
-
|
|
89
|
-
# Calculate percentage
|
|
90
|
-
if self.total_items > 0:
|
|
91
|
-
self.current_progress += advance
|
|
92
|
-
percentage = (self.current_progress / self.total_items) * 100.0
|
|
93
|
-
|
|
94
|
-
# Update widget if available
|
|
95
|
-
if self.widget and self._loop:
|
|
96
|
-
description = getattr(context, "description", "")
|
|
97
|
-
self._loop.call_soon_threadsafe(self._update_widget_sync, percentage, description)
|
|
98
|
-
|
|
99
|
-
def set_description(self, context: ProgressContext, description: str) -> None:
|
|
100
|
-
"""Update the description of a progress context.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
context: Progress context to update
|
|
104
|
-
description: New description
|
|
105
|
-
"""
|
|
106
|
-
super().set_description(context, description)
|
|
107
|
-
|
|
108
|
-
# Update widget description if available
|
|
109
|
-
if self.widget and self._loop:
|
|
110
|
-
percentage = (
|
|
111
|
-
(self.current_progress / self.total_items) * 100.0 if self.total_items > 0 else 0
|
|
112
|
-
)
|
|
113
|
-
self._loop.call_soon_threadsafe(self._update_widget_sync, percentage, description)
|
|
114
|
-
|
|
115
|
-
def complete(self, context: ProgressContext) -> None:
|
|
116
|
-
"""Mark a progress context as complete.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
context: Progress context to complete
|
|
120
|
-
"""
|
|
121
|
-
super().complete(context)
|
|
122
|
-
|
|
123
|
-
# Update widget to 100% if available
|
|
124
|
-
if self.widget and self._loop:
|
|
125
|
-
self._loop.call_soon_threadsafe(self._update_widget_sync, 100.0, "Complete")
|
|
126
|
-
|
|
127
|
-
def _update_widget_sync(self, percentage: float, description: str) -> None:
|
|
128
|
-
"""Synchronously update the widget (called from event loop).
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
percentage: Progress percentage (0-100)
|
|
132
|
-
description: Status description
|
|
133
|
-
"""
|
|
134
|
-
if self.widget:
|
|
135
|
-
try:
|
|
136
|
-
# Format description with processing statistics if available
|
|
137
|
-
if self.processing_stats["total"] > 0:
|
|
138
|
-
stats_str = (
|
|
139
|
-
f"Processed: {self.processing_stats['processed']}/{self.processing_stats['total']}, "
|
|
140
|
-
f"Success: {self.processing_stats['success']}, "
|
|
141
|
-
f"Failed: {self.processing_stats['failed']}"
|
|
142
|
-
)
|
|
143
|
-
if self.processing_stats["timeout"] > 0:
|
|
144
|
-
stats_str += f", Timeout: {self.processing_stats['timeout']}"
|
|
145
|
-
|
|
146
|
-
full_description = f"{description}\n{stats_str}"
|
|
147
|
-
else:
|
|
148
|
-
full_description = description
|
|
149
|
-
|
|
150
|
-
self.widget.update_progress(percentage, full_description)
|
|
151
|
-
# Force a refresh of the TUI to ensure updates are visible
|
|
152
|
-
if hasattr(self.widget, "refresh"):
|
|
153
|
-
self.widget.refresh()
|
|
154
|
-
except Exception as e:
|
|
155
|
-
# Log error but don't crash the progress tracking
|
|
156
|
-
import logging
|
|
157
|
-
|
|
158
|
-
logging.getLogger(__name__).error(f"Failed to update TUI widget: {e}")
|
|
159
|
-
|
|
160
|
-
def start_repository(self, repo_name: str, total_commits: int) -> None:
|
|
161
|
-
"""Track the start of repository processing.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
repo_name: Name of the repository
|
|
165
|
-
total_commits: Expected number of commits to process
|
|
166
|
-
"""
|
|
167
|
-
self.repositories_in_progress[repo_name] = {
|
|
168
|
-
"started_at": time.time(),
|
|
169
|
-
"total_commits": total_commits,
|
|
170
|
-
"status": "processing",
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
# Update the widget with repository info
|
|
174
|
-
if self.widget and self._loop:
|
|
175
|
-
# Calculate correct percentage based on actual processed count
|
|
176
|
-
if self.processing_stats["total"] > 0:
|
|
177
|
-
percentage = (
|
|
178
|
-
self.processing_stats["processed"] / self.processing_stats["total"]
|
|
179
|
-
) * 100.0
|
|
180
|
-
else:
|
|
181
|
-
percentage = 0
|
|
182
|
-
|
|
183
|
-
description = f"Processing: {repo_name} ({len(self.repositories_in_progress)}/{self.processing_stats['total']})"
|
|
184
|
-
self._loop.call_soon_threadsafe(self._update_widget_sync, percentage, description)
|
|
185
|
-
|
|
186
|
-
def finish_repository(
|
|
187
|
-
self, repo_name: str, success: bool = True, error_message: str = None, stats: dict = None
|
|
188
|
-
) -> None:
|
|
189
|
-
"""Mark a repository as finished processing.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
repo_name: Name of the repository
|
|
193
|
-
success: Whether processing was successful
|
|
194
|
-
error_message: Error message if failed
|
|
195
|
-
stats: Updated processing statistics
|
|
196
|
-
"""
|
|
197
|
-
if repo_name in self.repositories_in_progress:
|
|
198
|
-
self.repositories_in_progress[repo_name]["status"] = "success" if success else "failed"
|
|
199
|
-
if error_message:
|
|
200
|
-
self.repositories_in_progress[repo_name]["error"] = error_message
|
|
201
|
-
|
|
202
|
-
# Update stats if provided
|
|
203
|
-
if stats:
|
|
204
|
-
self.processing_stats.update(stats)
|
|
205
|
-
|
|
206
|
-
# Update the widget with completion info
|
|
207
|
-
if self.widget and self._loop:
|
|
208
|
-
# Calculate correct percentage based on processed count
|
|
209
|
-
if self.processing_stats["total"] > 0:
|
|
210
|
-
percentage = (
|
|
211
|
-
self.processing_stats["processed"] / self.processing_stats["total"]
|
|
212
|
-
) * 100.0
|
|
213
|
-
else:
|
|
214
|
-
percentage = 100.0 if self.processing_stats["processed"] > 0 else 0
|
|
215
|
-
|
|
216
|
-
status = "✅" if success else "❌"
|
|
217
|
-
description = f"{status} {repo_name} completed"
|
|
218
|
-
if error_message:
|
|
219
|
-
description += f" - {error_message[:30]}..."
|
|
220
|
-
|
|
221
|
-
self._loop.call_soon_threadsafe(
|
|
222
|
-
self._update_widget_sync, min(percentage, 100.0), description # Cap at 100%
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
def update_stats(
|
|
226
|
-
self,
|
|
227
|
-
processed: int = 0,
|
|
228
|
-
success: int = 0,
|
|
229
|
-
failed: int = 0,
|
|
230
|
-
timeout: int = 0,
|
|
231
|
-
total: int = 0,
|
|
232
|
-
) -> None:
|
|
233
|
-
"""Update processing statistics.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
processed: Number of repositories processed
|
|
237
|
-
success: Number of successful repositories
|
|
238
|
-
failed: Number of failed repositories
|
|
239
|
-
timeout: Number of timed out repositories
|
|
240
|
-
total: Total number of repositories
|
|
241
|
-
"""
|
|
242
|
-
if processed > 0:
|
|
243
|
-
self.processing_stats["processed"] = processed
|
|
244
|
-
if success > 0:
|
|
245
|
-
self.processing_stats["success"] = success
|
|
246
|
-
if failed > 0:
|
|
247
|
-
self.processing_stats["failed"] = failed
|
|
248
|
-
if timeout > 0:
|
|
249
|
-
self.processing_stats["timeout"] = timeout
|
|
250
|
-
if total > 0:
|
|
251
|
-
self.processing_stats["total"] = total
|
|
252
|
-
|
|
253
|
-
# Update widget with latest stats
|
|
254
|
-
if self.widget and self._loop:
|
|
255
|
-
percentage = (
|
|
256
|
-
(self.processing_stats["processed"] / self.processing_stats["total"]) * 100.0
|
|
257
|
-
if self.processing_stats["total"] > 0
|
|
258
|
-
else 0
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
self._loop.call_soon_threadsafe(
|
|
262
|
-
self._update_widget_sync,
|
|
263
|
-
min(percentage, 100.0), # Cap at 100%
|
|
264
|
-
"Processing repositories...",
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
class TUIProgressService:
|
|
269
|
-
"""Service to manage TUI progress adapters for different analysis phases."""
|
|
270
|
-
|
|
271
|
-
def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None):
|
|
272
|
-
"""Initialize the TUI progress service.
|
|
273
|
-
|
|
274
|
-
Args:
|
|
275
|
-
loop: Event loop for async updates
|
|
276
|
-
"""
|
|
277
|
-
self.loop = loop or asyncio.get_event_loop()
|
|
278
|
-
self.adapters = {}
|
|
279
|
-
|
|
280
|
-
def create_adapter(self, name: str, widget: AnalysisProgressWidget) -> TUIProgressAdapter:
|
|
281
|
-
"""Create a progress adapter for a specific widget.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
name: Name/ID of the adapter
|
|
285
|
-
widget: The progress widget to connect
|
|
286
|
-
|
|
287
|
-
Returns:
|
|
288
|
-
The created adapter
|
|
289
|
-
"""
|
|
290
|
-
adapter = TUIProgressAdapter(widget)
|
|
291
|
-
adapter.set_event_loop(self.loop)
|
|
292
|
-
self.adapters[name] = adapter
|
|
293
|
-
return adapter
|
|
294
|
-
|
|
295
|
-
def get_adapter(self, name: str) -> Optional[TUIProgressAdapter]:
|
|
296
|
-
"""Get an existing adapter by name.
|
|
297
|
-
|
|
298
|
-
Args:
|
|
299
|
-
name: Name/ID of the adapter
|
|
300
|
-
|
|
301
|
-
Returns:
|
|
302
|
-
The adapter if found, None otherwise
|
|
303
|
-
"""
|
|
304
|
-
return self.adapters.get(name)
|
|
305
|
-
|
|
306
|
-
def remove_adapter(self, name: str) -> None:
|
|
307
|
-
"""Remove an adapter.
|
|
308
|
-
|
|
309
|
-
Args:
|
|
310
|
-
name: Name/ID of the adapter to remove
|
|
311
|
-
"""
|
|
312
|
-
if name in self.adapters:
|
|
313
|
-
del self.adapters[name]
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"""TUI screens for GitFlow Analytics."""
|
|
2
|
-
|
|
3
|
-
from .analysis_progress_screen import AnalysisProgressScreen
|
|
4
|
-
from .configuration_screen import ConfigurationScreen
|
|
5
|
-
from .main_screen import MainScreen
|
|
6
|
-
from .results_screen import ResultsScreen
|
|
7
|
-
|
|
8
|
-
__all__ = ["MainScreen", "ConfigurationScreen", "AnalysisProgressScreen", "ResultsScreen"]
|