276 lines
12 KiB
Python
276 lines
12 KiB
Python
"""
|
|
Delta Group JIRA-SharePoint Sync Manager
|
|
|
|
This module handles the synchronization of JIRA tickets to SharePoint using the new
|
|
Graph API-based connector architecture.
|
|
"""
|
|
|
|
import logging
|
|
import csv
|
|
import io
|
|
from datetime import datetime, UTC
|
|
from typing import Dict, Any, List, Optional
|
|
from modules.connectors.connectorSharepoint import ConnectorSharepoint
|
|
from modules.connectors.connectorTicketJira import ConnectorTicketJira
|
|
from modules.interfaces.interfaceAppObjects import getRootInterface
|
|
from modules.interfaces.interfaceAppModel import UserInDB
|
|
from modules.interfaces.interfaceTicketObjects import TicketSharepointSyncInterface
|
|
from modules.shared.timezoneUtils import get_utc_timestamp
|
|
from modules.shared.configuration import APP_CONFIG
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Get environment type from configuration
|
|
APP_ENV_TYPE = APP_CONFIG.get("APP_ENV_TYPE", "dev")
|
|
|
|
|
|
class ManagerSyncDelta:
|
|
"""Manages JIRA to SharePoint synchronization for Delta Group.
|
|
|
|
Supports two sync modes:
|
|
- CSV mode: Uses CSV files for synchronization (default)
|
|
- Excel mode: Uses Excel (.xlsx) files for synchronization
|
|
|
|
To change sync mode, use the set_sync_mode() method or modify SYNC_MODE class variable.
|
|
"""
|
|
SHAREPOINT_SITE_ID = "02830618-4029-4dc8-8d3d-f5168f282249"
|
|
SHAREPOINT_SITE_NAME = "SteeringBPM"
|
|
SHAREPOINT_SITE_PATH = "SteeringBPM"
|
|
SHAREPOINT_HOSTNAME = "deltasecurityag.sharepoint.com"
|
|
SHAREPOINT_MAIN_FOLDER = "/General/50 Docs hosted by SELISE"
|
|
SHAREPOINT_BACKUP_FOLDER = "/General/50 Docs hosted by SELISE/SyncHistory"
|
|
SHAREPOINT_AUDIT_FOLDER = "/General/50 Docs hosted by SELISE/SyncHistory"
|
|
SHAREPOINT_USER_ID = "patrick.motsch@delta.ch"
|
|
|
|
# Sync mode: "csv" or "xlsx"
|
|
SYNC_MODE = "xlsx" # Can be "csv" or "xlsx"
|
|
|
|
# File names for different sync modes
|
|
SYNC_FILE_CSV = "DELTAgroup x SELISE Ticket Exchange List.csv"
|
|
SYNC_FILE_XLSX = "DELTAgroup x SELISE Ticket Exchange List.xlsx"
|
|
|
|
# JIRA connection parameters (hardcoded for Delta Group)
|
|
JIRA_USERNAME = "p.motsch@valueon.ch"
|
|
JIRA_API_TOKEN = "ATATT3xFfGF0d973nNb3R1wTDI4lesmJfJAmooS-4cYMJTyLfwYv4himrE6yyCxyX3aSMfl34NHcm2fAXeFXrLHUzJx0RQVUBonCFnlgexjLQTgS5BoCbSO7dwAVjlcHZZkArHbooCUaRwJ15n6AHkm-nwdjLQ3Z74TFnKKUZC4uhuh3Aj-MuX8=2D7124FA"
|
|
JIRA_URL = "https://deltasecurity.atlassian.net"
|
|
JIRA_PROJECT_CODE = "DCS"
|
|
JIRA_ISSUE_TYPE = "Task"
|
|
|
|
# Task sync definition for field mapping (like original synchronizer)
|
|
|
|
task_sync_definition={
|
|
#key=excel-header, [get:jira>excel | put: excel>jira, jira-xml-field-list]
|
|
'ID': ['get', ['key']],
|
|
'Module Category': ['get', ['fields', 'customfield_10058', 'value']],
|
|
'Summary': ['get', ['fields', 'summary']],
|
|
'Description': ['get', ['fields', 'description']],
|
|
'References': ['get', ['fields', 'customfield_10066']],
|
|
'Priority': ['get', ['fields', 'priority', 'name']],
|
|
'Issue Status': ['get', ['fields', 'customfield_10062']],
|
|
'Assignee': ['get', ['fields', 'assignee', 'displayName']],
|
|
'Issue Created': ['get', ['fields', 'created']],
|
|
'Due Date': ['get', ['fields', 'duedate']],
|
|
'DELTA Comments': ['get', ['fields', 'customfield_10060']],
|
|
'SELISE Ticket References': ['put', ['fields', 'customfield_10067']],
|
|
'SELISE Status Values': ['put', ['fields', 'customfield_10065']],
|
|
'SELISE Comments': ['put', ['fields', 'customfield_10064']],
|
|
}
|
|
|
|
def __init__(self):
|
|
"""Initialize the sync manager with hardcoded Delta Group credentials."""
|
|
self.root_interface = getRootInterface()
|
|
self.jira_connector = None
|
|
self.sharepoint_connector = None
|
|
self.target_site = None
|
|
|
|
def get_sync_file_name(self) -> str:
|
|
"""Get the appropriate sync file name based on the sync mode."""
|
|
if self.SYNC_MODE == "xlsx":
|
|
return self.SYNC_FILE_XLSX
|
|
else: # Default to CSV
|
|
return self.SYNC_FILE_CSV
|
|
|
|
def set_sync_mode(self, mode: str) -> bool:
|
|
"""Set the sync mode to either 'csv' or 'xlsx'.
|
|
|
|
Args:
|
|
mode: Either 'csv' or 'xlsx'
|
|
|
|
Returns:
|
|
bool: True if mode was set successfully, False if invalid mode
|
|
"""
|
|
if mode.lower() in ["csv", "xlsx"]:
|
|
self.SYNC_MODE = mode.lower()
|
|
logger.info(f"Sync mode changed to: {self.SYNC_MODE}")
|
|
return True
|
|
else:
|
|
logger.error(f"Invalid sync mode: {mode}. Must be 'csv' or 'xlsx'")
|
|
return False
|
|
|
|
async def initialize_connectors(self) -> bool:
|
|
"""Initialize JIRA and SharePoint connectors."""
|
|
try:
|
|
logger.info("Initializing JIRA connector with hardcoded credentials")
|
|
|
|
# Initialize JIRA connector using class constants
|
|
self.jira_connector = await ConnectorTicketJira.create(
|
|
jira_username=self.JIRA_USERNAME,
|
|
jira_api_token=self.JIRA_API_TOKEN,
|
|
jira_url=self.JIRA_URL,
|
|
project_code=self.JIRA_PROJECT_CODE,
|
|
issue_type=self.JIRA_ISSUE_TYPE
|
|
)
|
|
|
|
# Use the admin user for SharePoint connection
|
|
adminUser = self.root_interface.getUserByUsername("admin")
|
|
if not adminUser:
|
|
logger.error("Admin user not found - SharePoint connection required")
|
|
return False
|
|
|
|
logger.info(f"Using admin user for SharePoint: {adminUser.id}")
|
|
|
|
# Get SharePoint connection for admin user
|
|
user_connections = self.root_interface.getUserConnections(adminUser.id)
|
|
sharepoint_connection = None
|
|
|
|
for connection in user_connections:
|
|
if connection.authority == "msft" and connection.externalUsername == self.SHAREPOINT_USER_ID:
|
|
sharepoint_connection = connection
|
|
break
|
|
|
|
if not sharepoint_connection:
|
|
logger.error(f"No SharePoint connection found for user: {self.SHAREPOINT_USER_ID}")
|
|
return False
|
|
|
|
logger.info(f"Found SharePoint connection: {sharepoint_connection.id}")
|
|
|
|
# Get SharePoint token for this connection
|
|
sharepoint_token = self.root_interface.getConnectionToken(sharepoint_connection.id)
|
|
if not sharepoint_token:
|
|
logger.error("No SharePoint token found for Delta Group user connection")
|
|
return False
|
|
|
|
logger.info(f"Found SharePoint token: {sharepoint_token.id}")
|
|
|
|
# Initialize SharePoint connector with Graph API
|
|
self.sharepoint_connector = ConnectorSharepoint(access_token=sharepoint_token.tokenAccess)
|
|
|
|
# Resolve the site by hostname + site path to get the real site ID
|
|
logger.info(
|
|
f"Resolving site ID via hostname+path: {self.SHAREPOINT_HOSTNAME}:/sites/{self.SHAREPOINT_SITE_PATH}"
|
|
)
|
|
resolved = await self.sharepoint_connector.find_site_by_url(
|
|
hostname=self.SHAREPOINT_HOSTNAME,
|
|
site_path=self.SHAREPOINT_SITE_PATH
|
|
)
|
|
|
|
if not resolved:
|
|
logger.error(
|
|
f"Failed to resolve site. Hostname: {self.SHAREPOINT_HOSTNAME}, Path: {self.SHAREPOINT_SITE_PATH}"
|
|
)
|
|
return False
|
|
|
|
self.target_site = {
|
|
"id": resolved.get("id"),
|
|
"displayName": resolved.get("displayName", self.SHAREPOINT_SITE_NAME),
|
|
"name": resolved.get("name", self.SHAREPOINT_SITE_NAME)
|
|
}
|
|
|
|
# Test site access by listing root of the drive
|
|
logger.info("Testing site access using resolved site ID...")
|
|
test_result = await self.sharepoint_connector.list_folder_contents(
|
|
site_id=self.target_site["id"],
|
|
folder_path=""
|
|
)
|
|
|
|
if test_result is not None:
|
|
logger.info(
|
|
f"Site access confirmed: {self.target_site['displayName']} (ID: {self.target_site['id']})"
|
|
)
|
|
else:
|
|
logger.error("Could not access site drive - check permissions")
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error initializing connectors: {str(e)}")
|
|
return False
|
|
|
|
async def sync_jira_to_sharepoint(self) -> bool:
|
|
"""Perform the main JIRA to SharePoint synchronization using sophisticated sync logic."""
|
|
try:
|
|
logger.info(f"Starting JIRA to SharePoint synchronization (Mode: {self.SYNC_MODE})")
|
|
|
|
# Initialize connectors
|
|
if not await self.initialize_connectors():
|
|
logger.error("Failed to initialize connectors")
|
|
return False
|
|
|
|
# Get the appropriate sync file name based on mode
|
|
sync_file_name = self.get_sync_file_name()
|
|
logger.info(f"Using sync file: {sync_file_name}")
|
|
|
|
# Create the sophisticated sync interface
|
|
sync_interface = await TicketSharepointSyncInterface.create(
|
|
connector_ticket=self.jira_connector,
|
|
connector_sharepoint=self.sharepoint_connector,
|
|
task_sync_definition=self.TASK_SYNC_DEFINITION,
|
|
sync_folder=self.SHAREPOINT_MAIN_FOLDER,
|
|
sync_file=sync_file_name,
|
|
backup_folder=self.SHAREPOINT_BACKUP_FOLDER,
|
|
audit_folder=self.SHAREPOINT_AUDIT_FOLDER,
|
|
site_id=self.target_site['id']
|
|
)
|
|
|
|
# Perform the sophisticated sync based on mode
|
|
if self.SYNC_MODE == "xlsx":
|
|
logger.info("Performing JIRA to Excel sync...")
|
|
await sync_interface.sync_from_jira_to_excel()
|
|
logger.info("Performing Excel to JIRA sync...")
|
|
await sync_interface.sync_from_excel_to_jira()
|
|
else: # CSV mode (default)
|
|
logger.info("Performing JIRA to CSV sync...")
|
|
await sync_interface.sync_from_jira_to_csv()
|
|
logger.info("Performing CSV to JIRA sync...")
|
|
await sync_interface.sync_from_csv_to_jira()
|
|
|
|
logger.info(f"JIRA to SharePoint synchronization completed successfully (Mode: {self.SYNC_MODE})")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during JIRA to SharePoint synchronization: {str(e)}")
|
|
return False
|
|
|
|
|
|
|
|
# Global sync function for use in app.py
|
|
async def perform_sync_jira_delta_group() -> bool:
|
|
"""Perform JIRA to SharePoint synchronization for Delta Group.
|
|
|
|
This function is called by the scheduler and can be used independently.
|
|
|
|
Returns:
|
|
bool: True if synchronization was successful, False otherwise
|
|
"""
|
|
try:
|
|
if APP_ENV_TYPE != "prod":
|
|
logger.info("JIRA to SharePoint synchronization: TASK to run only in PROD")
|
|
return True
|
|
|
|
logger.info("Starting Delta Group JIRA sync...")
|
|
|
|
|
|
sync_manager = ManagerSyncDelta()
|
|
success = await sync_manager.sync_jira_to_sharepoint()
|
|
|
|
if success:
|
|
logger.info("Delta Group JIRA sync completed successfully")
|
|
else:
|
|
logger.error("Delta Group JIRA sync failed")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in perform_sync_jira_delta_group: {str(e)}")
|
|
return False
|