# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ Merge Ticket Data action for JIRA operations. Merges JIRA export data with existing SharePoint data. """ import logging import json from typing import Dict, Any, List from modules.workflows.methods.methodBase import action from modules.datamodels.datamodelChat import ActionResult, ActionDocument logger = logging.getLogger(__name__) @action async def mergeTicketData(self, parameters: Dict[str, Any]) -> ActionResult: """ Merge JIRA export data with existing SharePoint data. Parameters: - jiraData (str, required): Document reference containing JIRA ticket data as JSON array - existingData (str, required): Document reference containing existing SharePoint data as JSON array - taskSyncDefinition (str or dict, required): Field mapping definition - idField (str, optional): Field name to use as ID for merging (default: "ID") Returns: - ActionResult with ActionDocument containing merged data and merge details """ try: jiraDataParam = parameters.get("jiraData") if not jiraDataParam: return ActionResult.isFailure(error="jiraData parameter is required") existingDataParam = parameters.get("existingData") if not existingDataParam: return ActionResult.isFailure(error="existingData parameter is required") taskSyncDefinitionParam = parameters.get("taskSyncDefinition") if not taskSyncDefinitionParam: return ActionResult.isFailure(error="taskSyncDefinition parameter is required") idField = parameters.get("idField", "ID") # Parse taskSyncDefinition if isinstance(taskSyncDefinitionParam, str): try: taskSyncDefinition = json.loads(taskSyncDefinitionParam) except json.JSONDecodeError as e: return ActionResult.isFailure(error=f"taskSyncDefinition is not valid JSON: {str(e)}") elif isinstance(taskSyncDefinitionParam, dict): taskSyncDefinition = taskSyncDefinitionParam else: return ActionResult.isFailure(error=f"taskSyncDefinition must be a dict or JSON string, got {type(taskSyncDefinitionParam)}") # Get data from documents jiraDataJson = self.documentParsing.parseJsonFromDocument(jiraDataParam) if jiraDataJson is None or not isinstance(jiraDataJson, list): return ActionResult.isFailure(error="Could not parse jiraData as JSON array") existingDataJson = self.documentParsing.parseJsonFromDocument(existingDataParam) if existingDataJson is None or not isinstance(existingDataJson, list): # Empty existing data is OK existingDataJson = [] # Perform merge existingLookup = {row.get(idField): row for row in existingDataJson if row.get(idField)} mergedData: List[dict] = [] changes: List[str] = [] updatedCount = addedCount = unchangedCount = 0 for jiraRow in jiraDataJson: jiraId = jiraRow.get(idField) if jiraId and jiraId in existingLookup: existingRow = existingLookup[jiraId].copy() rowChanges: List[str] = [] for fieldName, fieldConfig in taskSyncDefinition.items(): if fieldConfig[0] == 'get': oldValue = "" if existingRow.get(fieldName) is None else str(existingRow.get(fieldName)) newValue = "" if jiraRow.get(fieldName) is None else str(jiraRow.get(fieldName)) # Convert ADF data to readable text for logging if isinstance(newValue, dict) and newValue.get("type") == "doc": newValueReadable = self.adfConverter.convertAdfToText(newValue) if oldValue != newValueReadable: rowChanges.append(f"{fieldName}: '{oldValue[:100]}...' -> '{newValueReadable[:100]}...'") elif oldValue != newValue: # Truncate long values for logging oldTruncated = oldValue[:100] + "..." if len(oldValue) > 100 else oldValue newTruncated = newValue[:100] + "..." if len(newValue) > 100 else newValue rowChanges.append(f"{fieldName}: '{oldTruncated}' -> '{newTruncated}'") existingRow[fieldName] = jiraRow.get(fieldName) mergedData.append(existingRow) if rowChanges: updatedCount += 1 changes.append(f"Row ID {jiraId} updated: {', '.join(rowChanges)}") else: unchangedCount += 1 del existingLookup[jiraId] else: mergedData.append(jiraRow) addedCount += 1 changes.append(f"Row ID {jiraId} added as new record") # Add remaining existing rows for remaining in existingLookup.values(): mergedData.append(remaining) unchangedCount += 1 mergeDetails = { "updated": updatedCount, "added": addedCount, "unchanged": unchangedCount, "changes": changes } logger.info(f"Merged ticket data: {updatedCount} updated, {addedCount} added, {unchangedCount} unchanged") # Generate filename workflowContext = self.services.chat.getWorkflowContext() if hasattr(self.services, 'chat') else None filename = self._generateMeaningfulFileName( "merged_ticket_data", "json", workflowContext, "mergeTicketData" ) result = { "data": mergedData, "mergeDetails": mergeDetails } validationMetadata = self._createValidationMetadata( "mergeTicketData", updated=updatedCount, added=addedCount, unchanged=unchangedCount ) document = ActionDocument( documentName=filename, documentData=json.dumps(result, indent=2, ensure_ascii=False), mimeType="application/json", validationMetadata=validationMetadata ) return ActionResult.isSuccess(documents=[document]) except Exception as e: errorMsg = f"Error merging ticket data: {str(e)}" logger.error(errorMsg) return ActionResult.isFailure(error=errorMsg)