gateway/modules/workflows/methods/methodJira/actions/mergeTicketData.py
2025-12-17 10:45:09 +01:00

157 lines
6.6 KiB
Python

# 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)