mcp-ticketer 0.1.21__py3-none-any.whl → 0.1.23__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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__init__.py +7 -7
- mcp_ticketer/__version__.py +4 -2
- mcp_ticketer/adapters/__init__.py +4 -4
- mcp_ticketer/adapters/aitrackdown.py +66 -49
- mcp_ticketer/adapters/github.py +192 -125
- mcp_ticketer/adapters/hybrid.py +99 -53
- mcp_ticketer/adapters/jira.py +161 -151
- mcp_ticketer/adapters/linear.py +396 -246
- mcp_ticketer/cache/__init__.py +1 -1
- mcp_ticketer/cache/memory.py +15 -16
- mcp_ticketer/cli/__init__.py +1 -1
- mcp_ticketer/cli/configure.py +69 -93
- mcp_ticketer/cli/discover.py +43 -35
- mcp_ticketer/cli/main.py +283 -298
- mcp_ticketer/cli/mcp_configure.py +39 -15
- mcp_ticketer/cli/migrate_config.py +11 -13
- mcp_ticketer/cli/queue_commands.py +21 -58
- mcp_ticketer/cli/utils.py +121 -66
- mcp_ticketer/core/__init__.py +2 -2
- mcp_ticketer/core/adapter.py +46 -39
- mcp_ticketer/core/config.py +128 -92
- mcp_ticketer/core/env_discovery.py +69 -37
- mcp_ticketer/core/http_client.py +57 -40
- mcp_ticketer/core/mappers.py +98 -54
- mcp_ticketer/core/models.py +38 -24
- mcp_ticketer/core/project_config.py +145 -80
- mcp_ticketer/core/registry.py +16 -16
- mcp_ticketer/mcp/__init__.py +1 -1
- mcp_ticketer/mcp/server.py +199 -145
- mcp_ticketer/queue/__init__.py +2 -2
- mcp_ticketer/queue/__main__.py +1 -1
- mcp_ticketer/queue/manager.py +30 -26
- mcp_ticketer/queue/queue.py +147 -85
- mcp_ticketer/queue/run_worker.py +2 -3
- mcp_ticketer/queue/worker.py +55 -40
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/METADATA +1 -1
- mcp_ticketer-0.1.23.dist-info/RECORD +42 -0
- mcp_ticketer-0.1.21.dist-info/RECORD +0 -42
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.1.21.dist-info → mcp_ticketer-0.1.23.dist-info}/top_level.txt +0 -0
mcp_ticketer/adapters/hybrid.py
CHANGED
|
@@ -4,15 +4,14 @@ This adapter enables synchronization across multiple ticketing systems
|
|
|
4
4
|
(Linear, JIRA, GitHub, AITrackdown) with configurable sync strategies.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import builtins
|
|
8
|
+
import json
|
|
8
9
|
import logging
|
|
9
|
-
from typing import List, Optional, Dict, Any
|
|
10
|
-
from datetime import datetime
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
import
|
|
11
|
+
from typing import Any, Optional
|
|
13
12
|
|
|
14
13
|
from ..core.adapter import BaseAdapter
|
|
15
|
-
from ..core.models import
|
|
14
|
+
from ..core.models import Comment, Epic, SearchQuery, Task, TicketState
|
|
16
15
|
from ..core.registry import AdapterRegistry
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger(__name__)
|
|
@@ -29,7 +28,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
29
28
|
Maintains mapping between ticket IDs across different systems.
|
|
30
29
|
"""
|
|
31
30
|
|
|
32
|
-
def __init__(self, config:
|
|
31
|
+
def __init__(self, config: dict[str, Any]):
|
|
33
32
|
"""Initialize hybrid adapter.
|
|
34
33
|
|
|
35
34
|
Args:
|
|
@@ -38,10 +37,11 @@ class HybridAdapter(BaseAdapter):
|
|
|
38
37
|
- primary_adapter: Name of primary adapter
|
|
39
38
|
- sync_strategy: Sync strategy (primary_source, bidirectional, mirror)
|
|
40
39
|
- mapping_file: Path to ID mapping file (optional)
|
|
40
|
+
|
|
41
41
|
"""
|
|
42
42
|
super().__init__(config)
|
|
43
43
|
|
|
44
|
-
self.adapters:
|
|
44
|
+
self.adapters: dict[str, BaseAdapter] = {}
|
|
45
45
|
self.primary_adapter_name = config.get("primary_adapter")
|
|
46
46
|
self.sync_strategy = config.get("sync_strategy", "primary_source")
|
|
47
47
|
|
|
@@ -50,7 +50,9 @@ class HybridAdapter(BaseAdapter):
|
|
|
50
50
|
for name, adapter_config in adapter_configs.items():
|
|
51
51
|
try:
|
|
52
52
|
adapter_type = adapter_config.get("adapter")
|
|
53
|
-
self.adapters[name] = AdapterRegistry.get_adapter(
|
|
53
|
+
self.adapters[name] = AdapterRegistry.get_adapter(
|
|
54
|
+
adapter_type, adapter_config
|
|
55
|
+
)
|
|
54
56
|
logger.info(f"Initialized adapter: {name} ({adapter_type})")
|
|
55
57
|
except Exception as e:
|
|
56
58
|
logger.error(f"Failed to initialize adapter {name}: {e}")
|
|
@@ -59,18 +61,22 @@ class HybridAdapter(BaseAdapter):
|
|
|
59
61
|
raise ValueError("No adapters successfully initialized")
|
|
60
62
|
|
|
61
63
|
if self.primary_adapter_name not in self.adapters:
|
|
62
|
-
raise ValueError(
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Primary adapter {self.primary_adapter_name} not found in adapters"
|
|
66
|
+
)
|
|
63
67
|
|
|
64
68
|
# Load or initialize ID mapping
|
|
65
|
-
self.mapping_file = Path(
|
|
69
|
+
self.mapping_file = Path(
|
|
70
|
+
config.get("mapping_file", ".mcp-ticketer/hybrid_mapping.json")
|
|
71
|
+
)
|
|
66
72
|
self.id_mapping = self._load_mapping()
|
|
67
73
|
|
|
68
|
-
def _get_state_mapping(self) ->
|
|
74
|
+
def _get_state_mapping(self) -> dict[TicketState, str]:
|
|
69
75
|
"""Get state mapping from primary adapter."""
|
|
70
76
|
primary = self.adapters[self.primary_adapter_name]
|
|
71
77
|
return primary._get_state_mapping()
|
|
72
78
|
|
|
73
|
-
def _load_mapping(self) ->
|
|
79
|
+
def _load_mapping(self) -> dict[str, dict[str, str]]:
|
|
74
80
|
"""Load ID mapping from file.
|
|
75
81
|
|
|
76
82
|
Mapping format:
|
|
@@ -84,10 +90,11 @@ class HybridAdapter(BaseAdapter):
|
|
|
84
90
|
|
|
85
91
|
Returns:
|
|
86
92
|
Dictionary mapping universal ticket IDs to adapter-specific IDs
|
|
93
|
+
|
|
87
94
|
"""
|
|
88
95
|
if self.mapping_file.exists():
|
|
89
96
|
try:
|
|
90
|
-
with open(self.mapping_file
|
|
97
|
+
with open(self.mapping_file) as f:
|
|
91
98
|
return json.load(f)
|
|
92
99
|
except Exception as e:
|
|
93
100
|
logger.error(f"Failed to load mapping file: {e}")
|
|
@@ -98,18 +105,21 @@ class HybridAdapter(BaseAdapter):
|
|
|
98
105
|
"""Save ID mapping to file."""
|
|
99
106
|
try:
|
|
100
107
|
self.mapping_file.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
-
with open(self.mapping_file,
|
|
108
|
+
with open(self.mapping_file, "w") as f:
|
|
102
109
|
json.dump(self.id_mapping, f, indent=2)
|
|
103
110
|
except Exception as e:
|
|
104
111
|
logger.error(f"Failed to save mapping file: {e}")
|
|
105
112
|
|
|
106
|
-
def _store_ticket_mapping(
|
|
113
|
+
def _store_ticket_mapping(
|
|
114
|
+
self, universal_id: str, adapter_name: str, adapter_ticket_id: str
|
|
115
|
+
) -> None:
|
|
107
116
|
"""Store mapping between universal ID and adapter-specific ID.
|
|
108
117
|
|
|
109
118
|
Args:
|
|
110
119
|
universal_id: Universal ticket identifier
|
|
111
120
|
adapter_name: Name of adapter
|
|
112
121
|
adapter_ticket_id: Adapter-specific ticket ID
|
|
122
|
+
|
|
113
123
|
"""
|
|
114
124
|
if universal_id not in self.id_mapping:
|
|
115
125
|
self.id_mapping[universal_id] = {}
|
|
@@ -117,7 +127,9 @@ class HybridAdapter(BaseAdapter):
|
|
|
117
127
|
self.id_mapping[universal_id][adapter_name] = adapter_ticket_id
|
|
118
128
|
self._save_mapping()
|
|
119
129
|
|
|
120
|
-
def _get_adapter_ticket_id(
|
|
130
|
+
def _get_adapter_ticket_id(
|
|
131
|
+
self, universal_id: str, adapter_name: str
|
|
132
|
+
) -> Optional[str]:
|
|
121
133
|
"""Get adapter-specific ticket ID from universal ID.
|
|
122
134
|
|
|
123
135
|
Args:
|
|
@@ -126,6 +138,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
126
138
|
|
|
127
139
|
Returns:
|
|
128
140
|
Adapter-specific ticket ID or None
|
|
141
|
+
|
|
129
142
|
"""
|
|
130
143
|
return self.id_mapping.get(universal_id, {}).get(adapter_name)
|
|
131
144
|
|
|
@@ -134,8 +147,10 @@ class HybridAdapter(BaseAdapter):
|
|
|
134
147
|
|
|
135
148
|
Returns:
|
|
136
149
|
UUID-like universal ticket identifier
|
|
150
|
+
|
|
137
151
|
"""
|
|
138
152
|
import uuid
|
|
153
|
+
|
|
139
154
|
return f"hybrid-{uuid.uuid4().hex[:12]}"
|
|
140
155
|
|
|
141
156
|
async def create(self, ticket: Task | Epic) -> Task | Epic:
|
|
@@ -146,6 +161,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
146
161
|
|
|
147
162
|
Returns:
|
|
148
163
|
Created ticket with universal ID
|
|
164
|
+
|
|
149
165
|
"""
|
|
150
166
|
universal_id = self._generate_universal_id()
|
|
151
167
|
results = []
|
|
@@ -154,11 +170,17 @@ class HybridAdapter(BaseAdapter):
|
|
|
154
170
|
primary = self.adapters[self.primary_adapter_name]
|
|
155
171
|
try:
|
|
156
172
|
primary_ticket = await primary.create(ticket)
|
|
157
|
-
self._store_ticket_mapping(
|
|
173
|
+
self._store_ticket_mapping(
|
|
174
|
+
universal_id, self.primary_adapter_name, primary_ticket.id
|
|
175
|
+
)
|
|
158
176
|
results.append((self.primary_adapter_name, primary_ticket))
|
|
159
|
-
logger.info(
|
|
177
|
+
logger.info(
|
|
178
|
+
f"Created ticket in primary adapter {self.primary_adapter_name}: {primary_ticket.id}"
|
|
179
|
+
)
|
|
160
180
|
except Exception as e:
|
|
161
|
-
logger.error(
|
|
181
|
+
logger.error(
|
|
182
|
+
f"Failed to create ticket in primary adapter {self.primary_adapter_name}: {e}"
|
|
183
|
+
)
|
|
162
184
|
raise
|
|
163
185
|
|
|
164
186
|
# Create in secondary adapters
|
|
@@ -185,12 +207,15 @@ class HybridAdapter(BaseAdapter):
|
|
|
185
207
|
|
|
186
208
|
return primary_ticket
|
|
187
209
|
|
|
188
|
-
def _add_cross_references(
|
|
210
|
+
def _add_cross_references(
|
|
211
|
+
self, ticket: Task | Epic, results: list[tuple[str, Task | Epic]]
|
|
212
|
+
) -> None:
|
|
189
213
|
"""Add cross-references to ticket description.
|
|
190
214
|
|
|
191
215
|
Args:
|
|
192
216
|
ticket: Ticket to update
|
|
193
217
|
results: List of (adapter_name, ticket) tuples
|
|
218
|
+
|
|
194
219
|
"""
|
|
195
220
|
cross_refs = "\n\n---\n**Cross-Platform References:**\n"
|
|
196
221
|
for adapter_name, adapter_ticket in results:
|
|
@@ -209,13 +234,18 @@ class HybridAdapter(BaseAdapter):
|
|
|
209
234
|
|
|
210
235
|
Returns:
|
|
211
236
|
Ticket if found, None otherwise
|
|
237
|
+
|
|
212
238
|
"""
|
|
213
239
|
# Check if this is a universal ID
|
|
214
240
|
if ticket_id.startswith("hybrid-"):
|
|
215
241
|
# Get primary adapter ticket ID
|
|
216
|
-
primary_id = self._get_adapter_ticket_id(
|
|
242
|
+
primary_id = self._get_adapter_ticket_id(
|
|
243
|
+
ticket_id, self.primary_adapter_name
|
|
244
|
+
)
|
|
217
245
|
if not primary_id:
|
|
218
|
-
logger.warning(
|
|
246
|
+
logger.warning(
|
|
247
|
+
f"No primary ticket ID found for universal ID: {ticket_id}"
|
|
248
|
+
)
|
|
219
249
|
return None
|
|
220
250
|
ticket_id = primary_id
|
|
221
251
|
|
|
@@ -223,7 +253,9 @@ class HybridAdapter(BaseAdapter):
|
|
|
223
253
|
primary = self.adapters[self.primary_adapter_name]
|
|
224
254
|
return await primary.read(ticket_id)
|
|
225
255
|
|
|
226
|
-
async def update(
|
|
256
|
+
async def update(
|
|
257
|
+
self, ticket_id: str, updates: dict[str, Any]
|
|
258
|
+
) -> Optional[Task | Epic]:
|
|
227
259
|
"""Update ticket across all adapters.
|
|
228
260
|
|
|
229
261
|
Args:
|
|
@@ -232,6 +264,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
232
264
|
|
|
233
265
|
Returns:
|
|
234
266
|
Updated ticket from primary adapter
|
|
267
|
+
|
|
235
268
|
"""
|
|
236
269
|
universal_id = ticket_id
|
|
237
270
|
if not ticket_id.startswith("hybrid-"):
|
|
@@ -254,7 +287,9 @@ class HybridAdapter(BaseAdapter):
|
|
|
254
287
|
try:
|
|
255
288
|
updated_ticket = await adapter.update(adapter_ticket_id, updates)
|
|
256
289
|
results.append((adapter_name, updated_ticket))
|
|
257
|
-
logger.info(
|
|
290
|
+
logger.info(
|
|
291
|
+
f"Updated ticket in adapter {adapter_name}: {adapter_ticket_id}"
|
|
292
|
+
)
|
|
258
293
|
except Exception as e:
|
|
259
294
|
logger.error(f"Failed to update ticket in adapter {adapter_name}: {e}")
|
|
260
295
|
|
|
@@ -273,6 +308,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
273
308
|
|
|
274
309
|
Returns:
|
|
275
310
|
Universal ID if found, None otherwise
|
|
311
|
+
|
|
276
312
|
"""
|
|
277
313
|
for universal_id, mapping in self.id_mapping.items():
|
|
278
314
|
if adapter_ticket_id in mapping.values():
|
|
@@ -287,6 +323,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
287
323
|
|
|
288
324
|
Returns:
|
|
289
325
|
True if deleted from at least one adapter
|
|
326
|
+
|
|
290
327
|
"""
|
|
291
328
|
universal_id = ticket_id
|
|
292
329
|
if not ticket_id.startswith("hybrid-"):
|
|
@@ -306,9 +343,13 @@ class HybridAdapter(BaseAdapter):
|
|
|
306
343
|
try:
|
|
307
344
|
if await adapter.delete(adapter_ticket_id):
|
|
308
345
|
success_count += 1
|
|
309
|
-
logger.info(
|
|
346
|
+
logger.info(
|
|
347
|
+
f"Deleted ticket from adapter {adapter_name}: {adapter_ticket_id}"
|
|
348
|
+
)
|
|
310
349
|
except Exception as e:
|
|
311
|
-
logger.error(
|
|
350
|
+
logger.error(
|
|
351
|
+
f"Failed to delete ticket from adapter {adapter_name}: {e}"
|
|
352
|
+
)
|
|
312
353
|
|
|
313
354
|
# Remove from mapping
|
|
314
355
|
if universal_id in self.id_mapping:
|
|
@@ -318,11 +359,8 @@ class HybridAdapter(BaseAdapter):
|
|
|
318
359
|
return success_count > 0
|
|
319
360
|
|
|
320
361
|
async def list(
|
|
321
|
-
self,
|
|
322
|
-
|
|
323
|
-
offset: int = 0,
|
|
324
|
-
filters: Optional[Dict[str, Any]] = None
|
|
325
|
-
) -> List[Task | Epic]:
|
|
362
|
+
self, limit: int = 10, offset: int = 0, filters: Optional[dict[str, Any]] = None
|
|
363
|
+
) -> list[Task | Epic]:
|
|
326
364
|
"""List tickets from primary adapter.
|
|
327
365
|
|
|
328
366
|
Args:
|
|
@@ -332,11 +370,12 @@ class HybridAdapter(BaseAdapter):
|
|
|
332
370
|
|
|
333
371
|
Returns:
|
|
334
372
|
List of tickets from primary adapter
|
|
373
|
+
|
|
335
374
|
"""
|
|
336
375
|
primary = self.adapters[self.primary_adapter_name]
|
|
337
376
|
return await primary.list(limit, offset, filters)
|
|
338
377
|
|
|
339
|
-
async def search(self, query: SearchQuery) ->
|
|
378
|
+
async def search(self, query: SearchQuery) -> builtins.list[Task | Epic]:
|
|
340
379
|
"""Search tickets in primary adapter.
|
|
341
380
|
|
|
342
381
|
Args:
|
|
@@ -344,14 +383,13 @@ class HybridAdapter(BaseAdapter):
|
|
|
344
383
|
|
|
345
384
|
Returns:
|
|
346
385
|
List of tickets matching search criteria
|
|
386
|
+
|
|
347
387
|
"""
|
|
348
388
|
primary = self.adapters[self.primary_adapter_name]
|
|
349
389
|
return await primary.search(query)
|
|
350
390
|
|
|
351
391
|
async def transition_state(
|
|
352
|
-
self,
|
|
353
|
-
ticket_id: str,
|
|
354
|
-
target_state: TicketState
|
|
392
|
+
self, ticket_id: str, target_state: TicketState
|
|
355
393
|
) -> Optional[Task | Epic]:
|
|
356
394
|
"""Transition ticket state across all adapters.
|
|
357
395
|
|
|
@@ -361,6 +399,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
361
399
|
|
|
362
400
|
Returns:
|
|
363
401
|
Updated ticket from primary adapter
|
|
402
|
+
|
|
364
403
|
"""
|
|
365
404
|
universal_id = ticket_id
|
|
366
405
|
if not ticket_id.startswith("hybrid-"):
|
|
@@ -378,11 +417,17 @@ class HybridAdapter(BaseAdapter):
|
|
|
378
417
|
continue
|
|
379
418
|
|
|
380
419
|
try:
|
|
381
|
-
updated_ticket = await adapter.transition_state(
|
|
420
|
+
updated_ticket = await adapter.transition_state(
|
|
421
|
+
adapter_ticket_id, target_state
|
|
422
|
+
)
|
|
382
423
|
results.append((adapter_name, updated_ticket))
|
|
383
|
-
logger.info(
|
|
424
|
+
logger.info(
|
|
425
|
+
f"Transitioned ticket in adapter {adapter_name}: {adapter_ticket_id}"
|
|
426
|
+
)
|
|
384
427
|
except Exception as e:
|
|
385
|
-
logger.error(
|
|
428
|
+
logger.error(
|
|
429
|
+
f"Failed to transition ticket in adapter {adapter_name}: {e}"
|
|
430
|
+
)
|
|
386
431
|
|
|
387
432
|
# Return result from primary adapter
|
|
388
433
|
for adapter_name, ticket in results:
|
|
@@ -399,6 +444,7 @@ class HybridAdapter(BaseAdapter):
|
|
|
399
444
|
|
|
400
445
|
Returns:
|
|
401
446
|
Created comment from primary adapter
|
|
447
|
+
|
|
402
448
|
"""
|
|
403
449
|
universal_id = comment.ticket_id
|
|
404
450
|
if not comment.ticket_id.startswith("hybrid-"):
|
|
@@ -420,11 +466,13 @@ class HybridAdapter(BaseAdapter):
|
|
|
420
466
|
adapter_comment = Comment(
|
|
421
467
|
ticket_id=adapter_ticket_id,
|
|
422
468
|
content=comment.content,
|
|
423
|
-
author=comment.author
|
|
469
|
+
author=comment.author,
|
|
424
470
|
)
|
|
425
471
|
created_comment = await adapter.add_comment(adapter_comment)
|
|
426
472
|
results.append((adapter_name, created_comment))
|
|
427
|
-
logger.info(
|
|
473
|
+
logger.info(
|
|
474
|
+
f"Added comment to adapter {adapter_name}: {adapter_ticket_id}"
|
|
475
|
+
)
|
|
428
476
|
except Exception as e:
|
|
429
477
|
logger.error(f"Failed to add comment to adapter {adapter_name}: {e}")
|
|
430
478
|
|
|
@@ -440,11 +488,8 @@ class HybridAdapter(BaseAdapter):
|
|
|
440
488
|
raise RuntimeError("Failed to add comment to any adapter")
|
|
441
489
|
|
|
442
490
|
async def get_comments(
|
|
443
|
-
self,
|
|
444
|
-
|
|
445
|
-
limit: int = 10,
|
|
446
|
-
offset: int = 0
|
|
447
|
-
) -> List[Comment]:
|
|
491
|
+
self, ticket_id: str, limit: int = 10, offset: int = 0
|
|
492
|
+
) -> builtins.list[Comment]:
|
|
448
493
|
"""Get comments from primary adapter.
|
|
449
494
|
|
|
450
495
|
Args:
|
|
@@ -454,10 +499,13 @@ class HybridAdapter(BaseAdapter):
|
|
|
454
499
|
|
|
455
500
|
Returns:
|
|
456
501
|
List of comments from primary adapter
|
|
502
|
+
|
|
457
503
|
"""
|
|
458
504
|
if ticket_id.startswith("hybrid-"):
|
|
459
505
|
# Get primary adapter ticket ID
|
|
460
|
-
primary_id = self._get_adapter_ticket_id(
|
|
506
|
+
primary_id = self._get_adapter_ticket_id(
|
|
507
|
+
ticket_id, self.primary_adapter_name
|
|
508
|
+
)
|
|
461
509
|
if not primary_id:
|
|
462
510
|
return []
|
|
463
511
|
ticket_id = primary_id
|
|
@@ -473,17 +521,18 @@ class HybridAdapter(BaseAdapter):
|
|
|
473
521
|
except Exception as e:
|
|
474
522
|
logger.error(f"Error closing adapter: {e}")
|
|
475
523
|
|
|
476
|
-
async def sync_status(self) ->
|
|
524
|
+
async def sync_status(self) -> dict[str, Any]:
|
|
477
525
|
"""Get synchronization status across all adapters.
|
|
478
526
|
|
|
479
527
|
Returns:
|
|
480
528
|
Dictionary with sync status information
|
|
529
|
+
|
|
481
530
|
"""
|
|
482
531
|
status = {
|
|
483
532
|
"primary_adapter": self.primary_adapter_name,
|
|
484
533
|
"sync_strategy": self.sync_strategy,
|
|
485
534
|
"total_mapped_tickets": len(self.id_mapping),
|
|
486
|
-
"adapters": {}
|
|
535
|
+
"adapters": {},
|
|
487
536
|
}
|
|
488
537
|
|
|
489
538
|
for adapter_name, adapter in self.adapters.items():
|
|
@@ -494,12 +543,9 @@ class HybridAdapter(BaseAdapter):
|
|
|
494
543
|
|
|
495
544
|
status["adapters"][adapter_name] = {
|
|
496
545
|
"ticket_count": ticket_count,
|
|
497
|
-
"status": "connected"
|
|
546
|
+
"status": "connected",
|
|
498
547
|
}
|
|
499
548
|
except Exception as e:
|
|
500
|
-
status["adapters"][adapter_name] = {
|
|
501
|
-
"status": "error",
|
|
502
|
-
"error": str(e)
|
|
503
|
-
}
|
|
549
|
+
status["adapters"][adapter_name] = {"status": "error", "error": str(e)}
|
|
504
550
|
|
|
505
551
|
return status
|