alita-sdk 0.3.428__py3-none-any.whl → 0.3.428b2__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 alita-sdk might be problematic. Click here for more details.
- alita_sdk/tools/qtest/api_wrapper.py +220 -87
- {alita_sdk-0.3.428.dist-info → alita_sdk-0.3.428b2.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.428.dist-info → alita_sdk-0.3.428b2.dist-info}/RECORD +6 -6
- {alita_sdk-0.3.428.dist-info → alita_sdk-0.3.428b2.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.428.dist-info → alita_sdk-0.3.428b2.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.428.dist-info → alita_sdk-0.3.428b2.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ import re
|
|
|
5
5
|
from traceback import format_exc
|
|
6
6
|
from typing import Any, Optional
|
|
7
7
|
|
|
8
|
+
import requests
|
|
8
9
|
import swagger_client
|
|
9
10
|
from langchain_core.tools import ToolException
|
|
10
11
|
from pydantic import Field, PrivateAttr, model_validator, create_model, SecretStr
|
|
@@ -194,11 +195,11 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
194
195
|
|
|
195
196
|
def __get_field_definitions_cached(self) -> dict:
|
|
196
197
|
"""Get field definitions with session-level caching.
|
|
197
|
-
|
|
198
|
+
|
|
198
199
|
Field definitions are cached for the lifetime of this wrapper instance.
|
|
199
200
|
If project field configuration changes, call refresh_field_definitions_cache()
|
|
200
201
|
to reload the definitions.
|
|
201
|
-
|
|
202
|
+
|
|
202
203
|
Returns:
|
|
203
204
|
dict: Field definitions mapping
|
|
204
205
|
"""
|
|
@@ -208,11 +209,11 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
208
209
|
|
|
209
210
|
def refresh_field_definitions_cache(self) -> dict:
|
|
210
211
|
"""Manually refresh the field definitions cache.
|
|
211
|
-
|
|
212
|
+
|
|
212
213
|
Call this method if project field configuration has been updated
|
|
213
214
|
(new fields added, dropdown values changed, etc.) and you need to
|
|
214
215
|
reload the definitions within the same agent session.
|
|
215
|
-
|
|
216
|
+
|
|
216
217
|
Returns:
|
|
217
218
|
dict: Freshly loaded field definitions
|
|
218
219
|
"""
|
|
@@ -223,15 +224,15 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
223
224
|
base_properties: list = None) -> list:
|
|
224
225
|
"""
|
|
225
226
|
Convert user-friendly property names/values to QTest API PropertyResource format.
|
|
226
|
-
|
|
227
|
+
|
|
227
228
|
Args:
|
|
228
229
|
test_case_data: Dict with property names as keys (e.g., {"Status": "New", "Priority": "High"})
|
|
229
230
|
field_definitions: Output from __get_project_field_definitions()
|
|
230
231
|
base_properties: Existing properties from a test case (for updates, optional)
|
|
231
|
-
|
|
232
|
+
|
|
232
233
|
Returns:
|
|
233
234
|
list[PropertyResource]: Properties ready for API submission
|
|
234
|
-
|
|
235
|
+
|
|
235
236
|
Raises:
|
|
236
237
|
ValueError: If any field names are unknown or values are invalid (shows ALL errors)
|
|
237
238
|
"""
|
|
@@ -247,32 +248,32 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
247
248
|
'field_value': prop['field_value'],
|
|
248
249
|
'field_value_name': prop.get('field_value_name')
|
|
249
250
|
}
|
|
250
|
-
|
|
251
|
+
|
|
251
252
|
# Collect ALL validation errors before raising
|
|
252
253
|
validation_errors = []
|
|
253
|
-
|
|
254
|
+
|
|
254
255
|
# Map incoming properties from test_case_data
|
|
255
256
|
for field_name, field_value in test_case_data.items():
|
|
256
257
|
# Skip non-property fields (these are handled separately)
|
|
257
258
|
if field_name in ['Name', 'Description', 'Precondition', 'Steps', 'Id', QTEST_ID]:
|
|
258
259
|
continue
|
|
259
|
-
|
|
260
|
+
|
|
260
261
|
# Skip None or empty string values (don't update these fields)
|
|
261
262
|
if field_value is None or field_value == '':
|
|
262
263
|
continue
|
|
263
|
-
|
|
264
|
+
|
|
264
265
|
# Validate field exists in project - STRICT validation
|
|
265
266
|
if field_name not in field_definitions:
|
|
266
267
|
validation_errors.append(
|
|
267
268
|
f"❌ Unknown field '{field_name}' - not defined in project configuration"
|
|
268
269
|
)
|
|
269
270
|
continue # Skip to next field, keep collecting errors
|
|
270
|
-
|
|
271
|
+
|
|
271
272
|
field_def = field_definitions[field_name]
|
|
272
273
|
field_id = field_def['field_id']
|
|
273
274
|
data_type = field_def.get('data_type')
|
|
274
275
|
is_multiple = field_def.get('multiple', False)
|
|
275
|
-
|
|
276
|
+
|
|
276
277
|
# Normalize field_value to list for consistent processing
|
|
277
278
|
# Multi-select fields can receive: "value", ["value1", "value2"], or ["value1"]
|
|
278
279
|
# Single-select fields: "value" only
|
|
@@ -282,13 +283,13 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
282
283
|
else:
|
|
283
284
|
# Single-select: keep as single value
|
|
284
285
|
values_to_process = [field_value]
|
|
285
|
-
|
|
286
|
+
|
|
286
287
|
# Validate value(s) for dropdown fields (only if field has allowed values)
|
|
287
288
|
if field_def['values']:
|
|
288
289
|
# Field has allowed values (dropdown/combobox/user fields) - validate strictly
|
|
289
290
|
value_ids = []
|
|
290
291
|
value_names = []
|
|
291
|
-
|
|
292
|
+
|
|
292
293
|
for single_value in values_to_process:
|
|
293
294
|
if single_value not in field_def['values']:
|
|
294
295
|
available = ", ".join(sorted(field_def['values'].keys()))
|
|
@@ -297,15 +298,15 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
297
298
|
f"Allowed values: {available}"
|
|
298
299
|
)
|
|
299
300
|
continue # Skip this value, but continue validating others
|
|
300
|
-
|
|
301
|
+
|
|
301
302
|
# Valid value - add to lists
|
|
302
303
|
value_ids.append(field_def['values'][single_value])
|
|
303
304
|
value_names.append(single_value)
|
|
304
|
-
|
|
305
|
+
|
|
305
306
|
# If all values were invalid, skip this field
|
|
306
307
|
if not value_ids:
|
|
307
308
|
continue
|
|
308
|
-
|
|
309
|
+
|
|
309
310
|
# Format based on field type and value count
|
|
310
311
|
if is_multiple and len(value_ids) == 1:
|
|
311
312
|
# Single value in multi-select field: bracketed string "[419950]"
|
|
@@ -326,7 +327,7 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
326
327
|
# No validation needed - users can write anything (by design)
|
|
327
328
|
field_value_id = field_value
|
|
328
329
|
field_value_name = field_value if isinstance(field_value, str) else None
|
|
329
|
-
|
|
330
|
+
|
|
330
331
|
# Update or add property (only if no errors for this field)
|
|
331
332
|
props_dict[field_name] = {
|
|
332
333
|
'field_id': field_id,
|
|
@@ -334,7 +335,7 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
334
335
|
'field_value': field_value_id,
|
|
335
336
|
'field_value_name': field_value_name
|
|
336
337
|
}
|
|
337
|
-
|
|
338
|
+
|
|
338
339
|
# If ANY validation errors found, raise comprehensive error with all issues
|
|
339
340
|
if validation_errors:
|
|
340
341
|
available_fields = ", ".join(sorted(field_definitions.keys()))
|
|
@@ -345,7 +346,7 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
345
346
|
f"💡 Tip: Use 'get_all_test_cases_fields_for_project' tool to see all fields with their allowed values."
|
|
346
347
|
)
|
|
347
348
|
raise ValueError(error_msg)
|
|
348
|
-
|
|
349
|
+
|
|
349
350
|
# Convert to PropertyResource list, filtering out special fields
|
|
350
351
|
result = []
|
|
351
352
|
for field_name, prop_data in props_dict.items():
|
|
@@ -357,25 +358,25 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
357
358
|
field_value=prop_data['field_value'],
|
|
358
359
|
field_value_name=prop_data.get('field_value_name')
|
|
359
360
|
))
|
|
360
|
-
|
|
361
|
+
|
|
361
362
|
return result
|
|
362
363
|
|
|
363
364
|
def __build_body_for_create_test_case(self, test_cases_data: list[dict],
|
|
364
365
|
folder_to_place_test_cases_to: str = '') -> list:
|
|
365
366
|
# Get field definitions for property mapping (cached for session)
|
|
366
367
|
field_definitions = self.__get_field_definitions_cached()
|
|
367
|
-
|
|
368
|
+
|
|
368
369
|
modules = self._parse_modules()
|
|
369
370
|
parent_id = ''.join(str(module['module_id']) for module in modules if
|
|
370
371
|
folder_to_place_test_cases_to and module['full_module_name'] == folder_to_place_test_cases_to)
|
|
371
|
-
|
|
372
|
+
|
|
372
373
|
bodies = []
|
|
373
374
|
for test_case in test_cases_data:
|
|
374
375
|
# Map properties from user format to API format
|
|
375
376
|
props = self.__map_properties_to_api_format(test_case, field_definitions)
|
|
376
|
-
|
|
377
|
+
|
|
377
378
|
body = swagger_client.TestCaseWithCustomFieldResource(properties=props)
|
|
378
|
-
|
|
379
|
+
|
|
379
380
|
# Only set fields if they are explicitly provided in the input
|
|
380
381
|
# This prevents overwriting existing values with None during partial updates
|
|
381
382
|
if 'Name' in test_case:
|
|
@@ -384,10 +385,10 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
384
385
|
body.precondition = test_case['Precondition']
|
|
385
386
|
if 'Description' in test_case:
|
|
386
387
|
body.description = test_case['Description']
|
|
387
|
-
|
|
388
|
+
|
|
388
389
|
if parent_id:
|
|
389
390
|
body.parent_id = parent_id
|
|
390
|
-
|
|
391
|
+
|
|
391
392
|
# Only set test_steps if Steps are provided in the input
|
|
392
393
|
# This prevents overwriting existing steps during partial updates
|
|
393
394
|
if 'Steps' in test_case and test_case['Steps'] is not None:
|
|
@@ -397,7 +398,7 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
397
398
|
swagger_client.TestStepResource(description=step.get('Test Step Description'),
|
|
398
399
|
expected=step.get('Test Step Expected Result')))
|
|
399
400
|
body.test_steps = test_steps_resources
|
|
400
|
-
|
|
401
|
+
|
|
401
402
|
bodies.append(body)
|
|
402
403
|
return bodies
|
|
403
404
|
|
|
@@ -414,10 +415,147 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
414
415
|
Exception: \n {stacktrace}""")
|
|
415
416
|
return modules
|
|
416
417
|
|
|
418
|
+
def __get_field_definitions_from_properties_api(self) -> dict:
|
|
419
|
+
"""
|
|
420
|
+
Fallback method: Get field definitions using /properties and /properties-info APIs.
|
|
421
|
+
|
|
422
|
+
These APIs don't require Field Management permission and are available to all users.
|
|
423
|
+
Requires 2 API calls + 1 search to get a test case ID.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
dict: Same structure as __get_project_field_definitions()
|
|
427
|
+
"""
|
|
428
|
+
logger.info(
|
|
429
|
+
"Using properties API fallback (no Field Management permission). "
|
|
430
|
+
"This requires getting a template test case first."
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Step 1: Get any test case ID to query properties
|
|
434
|
+
search_instance = swagger_client.SearchApi(self._client)
|
|
435
|
+
body = swagger_client.ArtifactSearchParams(
|
|
436
|
+
object_type='test-cases',
|
|
437
|
+
fields=['*'],
|
|
438
|
+
query='' # Empty query returns all test cases
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
# Search for any test case - just need one
|
|
443
|
+
response = search_instance.search_artifact(
|
|
444
|
+
self.qtest_project_id,
|
|
445
|
+
body,
|
|
446
|
+
page_size=1,
|
|
447
|
+
page=1
|
|
448
|
+
)
|
|
449
|
+
except ApiException as e:
|
|
450
|
+
stacktrace = format_exc()
|
|
451
|
+
logger.error(f"Failed to find test case for properties API: {stacktrace}")
|
|
452
|
+
raise ValueError(
|
|
453
|
+
f"Cannot find any test case to query field definitions. "
|
|
454
|
+
f"Please create at least one test case in project {self.qtest_project_id}"
|
|
455
|
+
) from e
|
|
456
|
+
|
|
457
|
+
if not response or not response.get('items') or len(response['items']) == 0:
|
|
458
|
+
raise ValueError(
|
|
459
|
+
f"No test cases found in project {self.qtest_project_id}. "
|
|
460
|
+
f"Please create at least one test case to retrieve field definitions."
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
test_case_id = response['items'][0]['id']
|
|
464
|
+
logger.info(f"Using test case ID {test_case_id} to retrieve field definitions")
|
|
465
|
+
|
|
466
|
+
# Step 2: Call /properties API
|
|
467
|
+
headers = {
|
|
468
|
+
"Authorization": f"Bearer {self.qtest_api_token.get_secret_value()}"
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
properties_url = f"{self.base_url}/api/v3/projects/{self.qtest_project_id}/test-cases/{test_case_id}/properties"
|
|
472
|
+
properties_info_url = f"{self.base_url}/api/v3/projects/{self.qtest_project_id}/test-cases/{test_case_id}/properties-info"
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
# Get properties with current values and field metadata
|
|
476
|
+
props_response = requests.get(
|
|
477
|
+
properties_url,
|
|
478
|
+
headers=headers,
|
|
479
|
+
params={'calledBy': 'testcase_properties'}
|
|
480
|
+
)
|
|
481
|
+
props_response.raise_for_status()
|
|
482
|
+
properties_data = props_response.json()
|
|
483
|
+
|
|
484
|
+
# Get properties-info with data types and allowed values
|
|
485
|
+
info_response = requests.get(properties_info_url, headers=headers)
|
|
486
|
+
info_response.raise_for_status()
|
|
487
|
+
info_data = info_response.json()
|
|
488
|
+
|
|
489
|
+
except requests.exceptions.RequestException as e:
|
|
490
|
+
stacktrace = format_exc()
|
|
491
|
+
logger.error(f"Failed to call properties API: {stacktrace}")
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f"Unable to retrieve field definitions using properties API. "
|
|
494
|
+
f"Error: {stacktrace}"
|
|
495
|
+
) from e
|
|
496
|
+
|
|
497
|
+
# Step 3: Build field mapping by merging both responses
|
|
498
|
+
field_mapping = {}
|
|
499
|
+
|
|
500
|
+
# Create lookup by field ID from properties-info
|
|
501
|
+
metadata_by_id = {item['id']: item for item in info_data['metadata']}
|
|
502
|
+
|
|
503
|
+
# Data type mapping to determine 'multiple' flag
|
|
504
|
+
MULTI_SELECT_TYPES = {
|
|
505
|
+
'UserListDataType',
|
|
506
|
+
'MultiSelectionDataType',
|
|
507
|
+
'CheckListDataType'
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
USER_FIELD_TYPES = {'UserListDataType'}
|
|
511
|
+
|
|
512
|
+
# System fields to exclude (same as in property mapping)
|
|
513
|
+
excluded_fields = {'Shared', 'Projects Shared to'}
|
|
514
|
+
|
|
515
|
+
for prop in properties_data:
|
|
516
|
+
field_name = prop.get('name')
|
|
517
|
+
field_id = prop.get('id')
|
|
518
|
+
|
|
519
|
+
if not field_name or field_name in excluded_fields:
|
|
520
|
+
continue
|
|
521
|
+
|
|
522
|
+
# Get metadata for this field
|
|
523
|
+
metadata = metadata_by_id.get(field_id, {})
|
|
524
|
+
data_type_str = metadata.get('data_type')
|
|
525
|
+
|
|
526
|
+
# Determine data_type number (5 for user fields, None for others)
|
|
527
|
+
data_type = 5 if data_type_str in USER_FIELD_TYPES else None
|
|
528
|
+
|
|
529
|
+
# Determine if multi-select
|
|
530
|
+
is_multiple = data_type_str in MULTI_SELECT_TYPES
|
|
531
|
+
|
|
532
|
+
field_mapping[field_name] = {
|
|
533
|
+
'field_id': field_id,
|
|
534
|
+
'required': prop.get('required', False),
|
|
535
|
+
'data_type': data_type,
|
|
536
|
+
'multiple': is_multiple,
|
|
537
|
+
'values': {}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
# Map allowed values from metadata
|
|
541
|
+
allowed_values = metadata.get('allowed_values', [])
|
|
542
|
+
for allowed_val in allowed_values:
|
|
543
|
+
value_text = allowed_val.get('value_text')
|
|
544
|
+
value_id = allowed_val.get('id')
|
|
545
|
+
if value_text and value_id:
|
|
546
|
+
field_mapping[field_name]['values'][value_text] = value_id
|
|
547
|
+
|
|
548
|
+
logger.info(
|
|
549
|
+
f"Retrieved {len(field_mapping)} field definitions using properties API. "
|
|
550
|
+
f"This method works for all users without Field Management permission."
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return field_mapping
|
|
554
|
+
|
|
417
555
|
def __get_project_field_definitions(self) -> dict:
|
|
418
556
|
"""
|
|
419
557
|
Get structured field definitions for test cases in the project.
|
|
420
|
-
|
|
558
|
+
|
|
421
559
|
Returns:
|
|
422
560
|
dict: Mapping of field names to their IDs and allowed values.
|
|
423
561
|
Example: {
|
|
@@ -435,15 +573,24 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
435
573
|
"""
|
|
436
574
|
fields_api = self.__instantiate_fields_api_instance()
|
|
437
575
|
qtest_object = 'test-cases'
|
|
438
|
-
|
|
576
|
+
|
|
439
577
|
try:
|
|
440
578
|
fields = fields_api.get_fields(self.qtest_project_id, qtest_object)
|
|
441
579
|
except ApiException as e:
|
|
580
|
+
# Check if permission denied (403) - use fallback
|
|
581
|
+
if e.status == 403:
|
|
582
|
+
logger.warning(
|
|
583
|
+
"get_fields permission denied (Field Management permission required). "
|
|
584
|
+
"Using properties API fallback..."
|
|
585
|
+
)
|
|
586
|
+
return self.__get_field_definitions_from_properties_api()
|
|
587
|
+
|
|
588
|
+
# Other API errors
|
|
442
589
|
stacktrace = format_exc()
|
|
443
590
|
logger.error(f"Exception when calling FieldAPI->get_fields:\n {stacktrace}")
|
|
444
591
|
raise ValueError(
|
|
445
592
|
f"Unable to get test case fields for project {self.qtest_project_id}. Exception: \n {stacktrace}")
|
|
446
|
-
|
|
593
|
+
|
|
447
594
|
# Build structured mapping
|
|
448
595
|
field_mapping = {}
|
|
449
596
|
for field in fields:
|
|
@@ -455,7 +602,7 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
455
602
|
'multiple': getattr(field, 'multiple', False), # True = multi-select, needs array format
|
|
456
603
|
'values': {}
|
|
457
604
|
}
|
|
458
|
-
|
|
605
|
+
|
|
459
606
|
# Map allowed values if field has them (dropdown/combobox/user fields)
|
|
460
607
|
# Only include active values (is_active=True)
|
|
461
608
|
if hasattr(field, 'allowed_values') and field.allowed_values:
|
|
@@ -463,38 +610,38 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
463
610
|
# Skip inactive values (deleted/deprecated options)
|
|
464
611
|
if hasattr(allowed_value, 'is_active') and not allowed_value.is_active:
|
|
465
612
|
continue
|
|
466
|
-
|
|
613
|
+
|
|
467
614
|
# AllowedValueResource has 'label' for the display name and 'value' for the ID
|
|
468
615
|
# Note: 'value' is the field_value, not 'id'
|
|
469
616
|
# For user fields (data_type=5), label is user name and value is user ID
|
|
470
617
|
value_label = allowed_value.label
|
|
471
618
|
value_id = allowed_value.value
|
|
472
619
|
field_mapping[field_name]['values'][value_label] = value_id
|
|
473
|
-
|
|
620
|
+
|
|
474
621
|
return field_mapping
|
|
475
622
|
|
|
476
623
|
def __format_field_info_for_display(self, field_definitions: dict) -> str:
|
|
477
624
|
"""
|
|
478
625
|
Format field definitions in human-readable format for LLM.
|
|
479
|
-
|
|
626
|
+
|
|
480
627
|
Args:
|
|
481
628
|
field_definitions: Output from __get_project_field_definitions()
|
|
482
|
-
|
|
629
|
+
|
|
483
630
|
Returns:
|
|
484
631
|
Formatted string with field information
|
|
485
632
|
"""
|
|
486
633
|
output = [f"Available Test Case Fields for Project {self.qtest_project_id}:\n"]
|
|
487
|
-
|
|
634
|
+
|
|
488
635
|
for field_name, field_info in sorted(field_definitions.items()):
|
|
489
636
|
required_marker = " (Required)" if field_info.get('required') else ""
|
|
490
637
|
output.append(f"\n{field_name}{required_marker}:")
|
|
491
|
-
|
|
638
|
+
|
|
492
639
|
if field_info.get('values'):
|
|
493
640
|
for value_name, value_id in sorted(field_info['values'].items()):
|
|
494
641
|
output.append(f" - {value_name} (id: {value_id})")
|
|
495
642
|
else:
|
|
496
643
|
output.append(" Type: text")
|
|
497
|
-
|
|
644
|
+
|
|
498
645
|
output.append("\n\nUse these exact value names when creating or updating test cases.")
|
|
499
646
|
return ''.join(output)
|
|
500
647
|
|
|
@@ -502,12 +649,12 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
502
649
|
"""
|
|
503
650
|
Get formatted information about available test case fields and their values.
|
|
504
651
|
This method is exposed as a tool for LLM to query field information.
|
|
505
|
-
|
|
652
|
+
|
|
506
653
|
Args:
|
|
507
654
|
force_refresh: If True, reload field definitions from API instead of using cache.
|
|
508
655
|
Use this if project configuration has changed (new fields added,
|
|
509
656
|
dropdown values modified, etc.).
|
|
510
|
-
|
|
657
|
+
|
|
511
658
|
Returns:
|
|
512
659
|
Formatted string with field names and allowed values
|
|
513
660
|
"""
|
|
@@ -555,10 +702,10 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
555
702
|
|
|
556
703
|
def __parse_data(self, response_to_parse: dict, parsed_data: list, extract_images: bool=False, prompt: str=None):
|
|
557
704
|
import html
|
|
558
|
-
|
|
559
|
-
#
|
|
560
|
-
|
|
561
|
-
|
|
705
|
+
|
|
706
|
+
# PERMISSION-FREE: Parse properties directly from API response
|
|
707
|
+
# No get_fields call needed - works for all users
|
|
708
|
+
|
|
562
709
|
for item in response_to_parse['items']:
|
|
563
710
|
# Start with core fields (always present)
|
|
564
711
|
parsed_data_row = {
|
|
@@ -573,31 +720,17 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
573
720
|
'Test Step Expected Result': self._process_image(step[1]['expected'], extract_images, prompt)
|
|
574
721
|
}, enumerate(item['test_steps']))),
|
|
575
722
|
}
|
|
576
|
-
|
|
577
|
-
#
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
#
|
|
584
|
-
field_value =
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
# Use field_value_name if available (for dropdowns), otherwise field_value
|
|
588
|
-
field_value = prop.get('field_value_name') or prop.get('field_value') or ''
|
|
589
|
-
break
|
|
590
|
-
|
|
591
|
-
# Format based on field type
|
|
592
|
-
if is_multiple and (field_value is None or field_value == ''):
|
|
593
|
-
# Multi-select field with no value: show empty array with hint
|
|
594
|
-
parsed_data_row[field_name] = '[] (multi-select)'
|
|
595
|
-
elif field_value is not None:
|
|
596
|
-
parsed_data_row[field_name] = field_value
|
|
597
|
-
else:
|
|
598
|
-
# Regular field with no value
|
|
599
|
-
parsed_data_row[field_name] = ''
|
|
600
|
-
|
|
723
|
+
|
|
724
|
+
# Add custom fields directly from API response properties
|
|
725
|
+
for prop in item['properties']:
|
|
726
|
+
field_name = prop.get('field_name')
|
|
727
|
+
if not field_name:
|
|
728
|
+
continue
|
|
729
|
+
|
|
730
|
+
# Use field_value_name if available (for dropdowns/users), otherwise field_value
|
|
731
|
+
field_value = prop.get('field_value_name') or prop.get('field_value') or ''
|
|
732
|
+
parsed_data_row[field_name] = field_value
|
|
733
|
+
|
|
601
734
|
parsed_data.append(parsed_data_row)
|
|
602
735
|
|
|
603
736
|
def _process_image(self, content: str, extract: bool=False, prompt: str=None):
|
|
@@ -657,20 +790,20 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
657
790
|
|
|
658
791
|
def __find_qtest_requirement_id_by_id(self, requirement_id: str) -> int:
|
|
659
792
|
"""Search for requirement's internal QTest ID using requirement ID (RQ-xxx format).
|
|
660
|
-
|
|
793
|
+
|
|
661
794
|
Args:
|
|
662
795
|
requirement_id: Requirement ID in format RQ-123
|
|
663
|
-
|
|
796
|
+
|
|
664
797
|
Returns:
|
|
665
798
|
int: Internal QTest ID for the requirement
|
|
666
|
-
|
|
799
|
+
|
|
667
800
|
Raises:
|
|
668
801
|
ValueError: If requirement is not found
|
|
669
802
|
"""
|
|
670
803
|
dql = f"Id = '{requirement_id}'"
|
|
671
804
|
search_instance: SearchApi = swagger_client.SearchApi(self._client)
|
|
672
805
|
body = swagger_client.ArtifactSearchParams(object_type='requirements', fields=['*'], query=dql)
|
|
673
|
-
|
|
806
|
+
|
|
674
807
|
try:
|
|
675
808
|
response = search_instance.search_artifact(self.qtest_project_id, body)
|
|
676
809
|
if response['total'] == 0:
|
|
@@ -705,13 +838,13 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
705
838
|
|
|
706
839
|
def _get_jira_requirement_id(self, jira_issue_id: str) -> int:
|
|
707
840
|
"""Search for requirement id using the linked jira_issue_id.
|
|
708
|
-
|
|
841
|
+
|
|
709
842
|
Args:
|
|
710
843
|
jira_issue_id: External Jira issue ID (e.g., PLAN-128)
|
|
711
|
-
|
|
844
|
+
|
|
712
845
|
Returns:
|
|
713
846
|
int: Internal QTest ID for the Jira requirement
|
|
714
|
-
|
|
847
|
+
|
|
715
848
|
Raises:
|
|
716
849
|
ValueError: If Jira requirement is not found in QTest
|
|
717
850
|
"""
|
|
@@ -726,11 +859,11 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
726
859
|
|
|
727
860
|
def link_tests_to_jira_requirement(self, requirement_external_id: str, json_list_of_test_case_ids: str) -> str:
|
|
728
861
|
"""Link test cases to external Jira requirement.
|
|
729
|
-
|
|
862
|
+
|
|
730
863
|
Args:
|
|
731
864
|
requirement_external_id: Jira issue ID (e.g., PLAN-128)
|
|
732
865
|
json_list_of_test_case_ids: JSON array string of test case IDs (e.g., '["TC-123", "TC-234"]')
|
|
733
|
-
|
|
866
|
+
|
|
734
867
|
Returns:
|
|
735
868
|
Success message with linked test case IDs
|
|
736
869
|
"""
|
|
@@ -743,10 +876,10 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
743
876
|
|
|
744
877
|
try:
|
|
745
878
|
response = link_object_api_instance.link_artifacts(
|
|
746
|
-
self.qtest_project_id,
|
|
879
|
+
self.qtest_project_id,
|
|
747
880
|
object_id=requirement_id,
|
|
748
881
|
type=linked_type,
|
|
749
|
-
object_type=source_type,
|
|
882
|
+
object_type=source_type,
|
|
750
883
|
body=qtest_test_case_ids
|
|
751
884
|
)
|
|
752
885
|
linked_test_cases = [link.pid for link in response[0].objects]
|
|
@@ -765,14 +898,14 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
765
898
|
|
|
766
899
|
def link_tests_to_qtest_requirement(self, requirement_id: str, json_list_of_test_case_ids: str) -> str:
|
|
767
900
|
"""Link test cases to internal QTest requirement.
|
|
768
|
-
|
|
901
|
+
|
|
769
902
|
Args:
|
|
770
903
|
requirement_id: QTest requirement ID in format RQ-123
|
|
771
904
|
json_list_of_test_case_ids: JSON array string of test case IDs (e.g., '["TC-123", "TC-234"]')
|
|
772
|
-
|
|
905
|
+
|
|
773
906
|
Returns:
|
|
774
907
|
Success message with linked test case IDs
|
|
775
|
-
|
|
908
|
+
|
|
776
909
|
Raises:
|
|
777
910
|
ValueError: If requirement or test cases are not found
|
|
778
911
|
ToolException: If linking fails
|
|
@@ -780,11 +913,11 @@ class QtestApiWrapper(BaseToolApiWrapper):
|
|
|
780
913
|
link_object_api_instance = swagger_client.ObjectLinkApi(self._client)
|
|
781
914
|
source_type = "requirements"
|
|
782
915
|
linked_type = "test-cases"
|
|
783
|
-
|
|
916
|
+
|
|
784
917
|
# Parse and convert test case IDs
|
|
785
918
|
test_case_ids = json.loads(json_list_of_test_case_ids)
|
|
786
919
|
qtest_test_case_ids = [self.__find_qtest_id_by_test_id(tc_id) for tc_id in test_case_ids]
|
|
787
|
-
|
|
920
|
+
|
|
788
921
|
# Get internal QTest ID for the requirement
|
|
789
922
|
qtest_requirement_id = self.__find_qtest_requirement_id_by_id(requirement_id)
|
|
790
923
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alita_sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.428b2
|
|
4
4
|
Summary: SDK for building langchain agents using resources from Alita
|
|
5
5
|
Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedj27@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -303,7 +303,7 @@ alita_sdk/tools/postman/postman_analysis.py,sha256=ckc2BfKEop0xnmLPksVRE_Y94ixuq
|
|
|
303
303
|
alita_sdk/tools/pptx/__init__.py,sha256=vVUrWnj7KWJgEk9oxGSsCAQ2SMSXrp_SFOdUHYQKcAo,3444
|
|
304
304
|
alita_sdk/tools/pptx/pptx_wrapper.py,sha256=yyCYcTlIY976kJ4VfPo4dyxj4yeii9j9TWP6W8ZIpN8,29195
|
|
305
305
|
alita_sdk/tools/qtest/__init__.py,sha256=Jf0xo5S_4clXR2TfCbJbB1sFgCbcFQRM-YYX2ltWBzo,4461
|
|
306
|
-
alita_sdk/tools/qtest/api_wrapper.py,sha256=
|
|
306
|
+
alita_sdk/tools/qtest/api_wrapper.py,sha256=Cds_TnyWhR-2NaoHio9ePYMLT7RGS0nx20KAqifIOWk,50977
|
|
307
307
|
alita_sdk/tools/qtest/tool.py,sha256=kKzNPS4fUC76WQQttQ6kdVANViHEvKE8Kf174MQiNYU,562
|
|
308
308
|
alita_sdk/tools/rally/__init__.py,sha256=2BPPXJxAOKgfmaxVFVvxndfK0JxOXDLkoRmzu2dUwOE,3512
|
|
309
309
|
alita_sdk/tools/rally/api_wrapper.py,sha256=mouzU6g0KML4UNapdk0k6Q0pU3MpJuWnNo71n9PSEHM,11752
|
|
@@ -353,8 +353,8 @@ alita_sdk/tools/zephyr_scale/api_wrapper.py,sha256=kT0TbmMvuKhDUZc0i7KO18O38JM9S
|
|
|
353
353
|
alita_sdk/tools/zephyr_squad/__init__.py,sha256=0ne8XLJEQSLOWfzd2HdnqOYmQlUliKHbBED5kW_Vias,2895
|
|
354
354
|
alita_sdk/tools/zephyr_squad/api_wrapper.py,sha256=kmw_xol8YIYFplBLWTqP_VKPRhL_1ItDD0_vXTe_UuI,14906
|
|
355
355
|
alita_sdk/tools/zephyr_squad/zephyr_squad_cloud_client.py,sha256=R371waHsms4sllHCbijKYs90C-9Yu0sSR3N4SUfQOgU,5066
|
|
356
|
-
alita_sdk-0.3.
|
|
357
|
-
alita_sdk-0.3.
|
|
358
|
-
alita_sdk-0.3.
|
|
359
|
-
alita_sdk-0.3.
|
|
360
|
-
alita_sdk-0.3.
|
|
356
|
+
alita_sdk-0.3.428b2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
357
|
+
alita_sdk-0.3.428b2.dist-info/METADATA,sha256=WmL2Jo4Efefiq86rZ0hYJlMZx-epmDrwx-2qNTaWnEc,19073
|
|
358
|
+
alita_sdk-0.3.428b2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
359
|
+
alita_sdk-0.3.428b2.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
|
|
360
|
+
alita_sdk-0.3.428b2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|