/
dictator
/
ragflow_sync
Обзор
Документация
Войти
/
dictator
/
ragflow_sync
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
ttjson2md/attributes.py
364 строки
13 KB
Developer
feat: v2 enrichment for wiki children, 2 new customfields, docs update
22 июн 2026, 16:37
22 июн 2026, 16:37
bc836e8
Код
Авторство
О чём код?
""" Attribute definitions and formatters for TaskTracker tickets. Defines all available ticket attributes with their display names, type formatters, and inclusion/exclusion configuration. """ # Attribute definitions: code -> metadata ATTRIBUTES_CONFIG: dict[str, dict] = { # Core fields "suit": {"name": "Type", "enabled": True}, "space": {"name": "Space", "enabled": True}, "workflow_status": {"name": "Status", "enabled": True}, "priority": {"name": "Priority", "enabled": True}, # People "assigned_to": {"name": "Assignee", "enabled": True}, "reporter": {"name": "Reporter", "enabled": True}, "owner": {"name": "Owner", "enabled": True}, "created_by": {"name": "Created By", "enabled": True}, "updated_by": {"name": "Updated By", "enabled": True}, "business_customer": {"name": "Business Customer", "enabled": True}, "client": {"name": "Client", "enabled": True}, # Dates "created_at": {"name": "Created", "enabled": True}, "updated_at": {"name": "Updated", "enabled": True}, "due_date": {"name": "Due Date", "enabled": True}, "Due_date": {"name": "Due Date (alt)", "enabled": True}, "Resolved_date": {"name": "Resolved Date", "enabled": True}, "customfield_16700": {"name": "Planned Start", "enabled": True}, "customfield_16701": {"name": "Planned End", "enabled": True}, # Planning & estimation "estimate": {"name": "Estimate", "enabled": True}, "story_points": {"name": "Story Points", "enabled": True}, "price": {"name": "Price", "enabled": True}, "residual_estimate": {"name": "Residual Estimate", "enabled": True}, "scrum_board_plugin_sprint": {"name": "Sprint", "enabled": True}, "rank": {"name": "Rank", "enabled": True}, # Versions & components "fix_version": {"name": "Fix Version", "enabled": True}, "affects_version": {"name": "Affects Version", "enabled": True}, "sber_component": {"name": "Component", "enabled": True}, "component_code": {"name": "Component Code", "enabled": True}, "product_code": {"name": "Product Code", "enabled": True}, "customfield_24900": {"name": "Product Code (alt)", "enabled": True}, "product_name": {"name": "Product Name", "enabled": True}, "product_version": {"name": "Product Version", "enabled": True}, "component_version": {"name": "Component Version", "enabled": True}, # Resolution & business "resolution_wmb": {"name": "Resolution", "enabled": True}, "businesspoints": {"name": "BusinessPoints", "enabled": True}, "contract": {"name": "Contract", "enabled": True}, "specification": {"name": "Specification", "enabled": True}, "external_issue_ID": {"name": "External Issue ID", "enabled": True}, "issue_key": {"name": "Jira Object", "enabled": True}, "external_link": {"name": "External Link", "enabled": True}, # Custom & tech debt "duty": {"name": "Tech Debt", "enabled": True}, "customfield_23700": {"name": "Solution", "enabled": True}, "customfield_11957": {"name": "Issue Type", "enabled": True}, "customfield_25100": {"name": "Custom Field 25100", "enabled": True}, # Labels & watchers "label": {"name": "Labels", "enabled": True}, "watchers": {"name": "Watchers", "enabled": True}, # Test case specific "type_of_testing": {"name": "Type of Testing", "enabled": True}, "test_step": {"name": "Test Steps", "enabled": True}, "automated": {"name": "Automated", "enabled": True}, "folder": {"name": "Folder", "enabled": True}, "test_level": {"name": "Test Level", "enabled": True}, "pmi": {"name": "PMI", "enabled": True}, "test_case_status": {"name": "Test Case Status", "enabled": True}, "precondition": {"name": "Precondition", "enabled": True}, "test_type": {"name": "Test Type", "enabled": True}, "spec_for": {"name": "Specific For", "enabled": True}, "old_jira_key": {"name": "Old Jira Key", "enabled": True}, "premigration_author": {"name": "Author Before Migration", "enabled": True}, "target_fp": {"name": "Target FP", "enabled": True}, "AftTestCaseName": {"name": "After Test Case Name", "enabled": True}, "fb_testcase": {"name": "FB Test Case", "enabled": True}, "subsystem_szi": {"name": "SZI Subsystem", "enabled": True}, "module_szi": {"name": "SZI Module", "enabled": True}, "CRPV_STS_SUPPORT": {"name": "CRPV/STS/SUPPORT", "enabled": True}, "interfaces": {"name": "Interfaces", "enabled": True}, "more_than_1": {"name": "Requires More Than 1 Environment", "enabled": True}, "case_version_relevant_from": {"name": "Case Version Relevant From", "enabled": True}, "not_updated_since_version": {"name": "Not Updated Since Version", "enabled": True}, # Wiki page body (handled separately, not in attribute output) "wiki_page_body": {"name": "Wiki Page Body", "enabled": False}, } def format_user_value(value: dict | None) -> str | None: """Format a user attribute to 'FirstName LastName'.""" if not value: return None first = value.get("firstName", "") last = value.get("lastName", "") middle = value.get("middleName", "") parts = [first, middle, last] full_name = " ".join(p for p in parts if p).strip() return full_name if full_name else None def format_taxonomy_value(value: dict | None) -> str | None: """Format a taxonomy attribute to its name.""" if not value: return None if isinstance(value, dict): return value.get("name") or value.get("code") return str(value) if value else None def format_multiuser_value(value: list | None) -> str | None: """Format a multiuser attribute to comma-separated names.""" if not value or not isinstance(value, list): return None names = [] for user in value: name = format_user_value(user) if isinstance(user, dict) else None if name: names.append(name) return ", ".join(names) if names else None def format_version_value(value: list | None) -> str | None: """Format a version attribute to comma-separated version strings.""" if not value or not isinstance(value, list): return None versions = [] for v in value: if isinstance(v, dict): ver = v.get("name") or v.get("code") or str(v) else: ver = str(v) if ver: versions.append(ver) return ", ".join(versions) if versions else None def format_label_value(value: list | None) -> str | None: """Format a label attribute to comma-separated labels.""" if not value or not isinstance(value, list): return None labels = [] for label in value: if isinstance(label, dict): lbl = label.get("name") or label.get("code") or str(label) else: lbl = str(label) if lbl: labels.append(lbl) return ", ".join(labels) if labels else None def format_folder_value(value: dict | None) -> str | None: """Format a folder attribute to its title.""" if not value: return None if isinstance(value, dict): return value.get("title") or value.get("code") return str(value) if value else None def format_workflow_status_value(value: dict | None) -> str | None: """Format a workflow_status attribute to its name.""" if not value: return None if isinstance(value, dict): return value.get("name") or value.get("code") return str(value) if value else None def format_sprint_value(value: dict | None) -> str | None: """Format a sprint attribute to its name only.""" if not value: return None if isinstance(value, dict): return value.get("name") or value.get("code") return str(value) if value else None def format_sber_component_value(value: list | None) -> str | None: """Format a sber_component attribute to comma-separated names.""" if not value or not isinstance(value, list): return None components = [] for comp in value: if isinstance(comp, dict): name = comp.get("name") or comp.get("code") or str(comp) else: name = str(comp) if name: components.append(name) return ", ".join(components) if components else None def format_multitaxonomy_value(value: list | None) -> str | None: """Format a multitaxonomy attribute to comma-separated names.""" if not value or not isinstance(value, list): return None items = [] for item in value: if isinstance(item, dict): name = item.get("name") or item.get("code") or str(item) else: name = str(item) if name: items.append(name) return ", ".join(items) if items else None def format_test_step_value(value: list | None) -> str | None: """Format test_step attribute as a Markdown table.""" if not value or not isinstance(value, list): return None rows = [] for step in value: if not isinstance(step, dict): continue step_num = step.get("stepNumber", "") step_desc = step.get("stepDescription", {}) step_data = step.get("stepData", {}) step_result = step.get("stepResult", {}) # Extract text from ProseMirror structures def extract_text_from_step(field_val): if isinstance(field_val, dict) and "text" in field_val: text_val = field_val["text"] if isinstance(text_val, str) and text_val.startswith("{"): try: import json parsed = json.loads(text_val) # Simple text extraction from ProseMirror parts = [] for content in parsed.get("content", []): for item in content.get("content", []): if item.get("type") == "text": parts.append(item.get("text", "")) return " ".join(parts) except (json.JSONDecodeError, KeyError): return text_val return text_val return str(field_val) if field_val else "" desc = extract_text_from_step(step_desc) data = extract_text_from_step(step_data) result = extract_text_from_step(step_result) rows.append((str(step_num), desc, data, result)) if not rows: return None lines = ["| Step | Description | Data | Expected Result |"] lines.append("|------|-------------|------|-----------------|") for row in rows: lines.append(f"| {row[0]} | {row[1]} | {row[2]} | {row[3]} |") return "\n".join(lines) def format_wisiwig_value(value: str | dict | None) -> str | None: """Format a wisiwig/wiki_page_wisiwig attribute (ProseMirror to Markdown).""" if not value: return None # Import from core module to reuse parsing from .core import parse_document_content if isinstance(value, str): if value.startswith("{"): try: import json parsed = json.loads(value) return parse_document_content(parsed) except (json.JSONDecodeError, KeyError): return value return value elif isinstance(value, dict): return parse_document_content(value) return str(value) if value else None def format_attribute_value(value, attr_type: str) -> str | None: """Format an attribute value based on its type. Returns formatted string or None if value is empty/null. """ if value is None: return None if isinstance(value, str) and value.strip() == "": return None if isinstance(value, list) and len(value) == 0: return None # Dispatch to type-specific formatter formatters = { "user": format_user_value, "multiuser": format_multiuser_value, "taxonomy": format_taxonomy_value, "multitaxonomy": format_multitaxonomy_value, "searchable_taxonomy": format_taxonomy_value, "version": format_version_value, "label": format_label_value, "folder": format_folder_value, "workflow_status": format_workflow_status_value, "sber_component": format_sber_component_value, "scrum_board_plugin_sprint": format_sprint_value, "test_step": format_test_step_value, "wisiwig": format_wisiwig_value, "wiki_page_wisiwig": format_wisiwig_value, } formatter = formatters.get(attr_type) if formatter: result = formatter(value) return result # Default: convert to string result = str(value).strip() return result if result else None def get_enabled_attributes(include_attrs: set[str] | None = None, exclude_attrs: set[str] | None = None) -> dict[str, dict]: """Get the set of enabled attributes, filtered by include/exclude sets. Args: include_attrs: If provided, only include these attribute codes exclude_attrs: If provided, exclude these attribute codes Returns: Dict of attribute code -> config for enabled attributes """ result = {} for code, config in ATTRIBUTES_CONFIG.items(): # Skip wiki_page_body always (handled separately) if code == "wiki_page_body": continue # Apply include filter if include_attrs and code not in include_attrs: continue # Apply exclude filter if exclude_attrs and code in exclude_attrs: continue # Skip disabled attributes if not config.get("enabled", True): continue result[code] = config return result