Migración de datos de VisionFlow a ServiceNow

Una de las tareas más interesantes en el trabajo de un administrador de aplicaciones, en mi opinión, es la implementación de la migración de datos al pasar a un nuevo sistema. Hoy quiero compartir mi propia experiencia de transferir datos desde el no muy conocido servicio de asistencia técnica del sistema VisionFlow al más famoso sistema ServiceNow.





Lo que el cliente quería

  • Transferir todos los datos de VisionFlow a ServiceNow manteniendo el registro / fecha de cierre de los tickets





  • Mueva todo el historial de correspondencia para cada ticket (fue suficiente con combinar todos los comentarios en un hilo, pero fuimos un poco más lejos)





  • Mover todos los archivos adjuntos a los tickets





Que teniamos

  • Versión de servidor del sistema VisionFlow Helpdesk implementado en una máquina virtual Linux con una base de datos MySQL para el almacenamiento de datos.





  • Ejemplo de ServiceNow, con una mesa preparada con antelación para el cliente.





    En esta etapa, se discutieron todos los matices, tales como:





  • Modelo de estado





  • Campos requeridos





  • Lógica de asignación automática de tickets al ejecutor





  • Datos a transferir





Transferencia de datos

ServiceNow le permite utilizar archivos de Excel como recurso para importar datos. No describiré en detalle el proceso de importación de datos al sistema (el proceso está bien descrito en la documentación del producto), pero en términos generales se ve así:





Importación de datos

Transformar mapa nos permite establecer un campo clave por el cual el sistema entenderá que un registro con estos parámetros ya está presente en la tabla y solo los campos necesitan ser actualizados





xlsx , . VisionFlow . :





VisionFlow
SELECT
	projectissue.projectIssueId,
	projectissue.ticketId as 'Number',
    reporter.email as 'Reporter',
    projectissue.name as 'Short Description',
    projectissue.Description as 'Description',
    projectissue.companycustomfield15 as 'Product',
    projectissue.companycustomfield13 as 'Document',
    issuestatus.name as 'Status',
    assignee.name as 'Assignee',
    ADDTIME(projectissue.CreateDate, '-01:00') as 'Created',
    ADDTIME(projectissue.completionDate, '-01:00') as 'Closed',
    issuehistory.EventText as 'Comment',
    author.name as 'commentAuthor'
FROM
	projectissue
INNER JOIN issuestatus
    ON projectissue.IssueStatusId = issuestatus.IssueStatusId
INNER JOIN systemuser assignee
	ON projectissue.ResponsibleSystemUserId = assignee.SystemUserId
INNER JOIN systemuser reporter
	ON projectissue.CreatedBySystemUserId = reporter.SystemUserId
INNER JOIN issuehistory ON
	issuehistory.ProjectIssueId = projectissue.ProjectIssueId
INNER JOIN systemuser author 
	ON issuehistory.SystemUserId = author.SystemUserId
WHERE
	projectissue.ProjectId = 54 AND (issuehistory.IssueEventTypeId = 5 OR issuehistory.IssueEventTypeId = 10 OR issuehistory.IssueEventTypeId = 2)
    #projectissue.ProjectId = 54
ORDER BY projectissue.TicketId ASC, issuehistory.EventDate ASC
      
      



, , . JSON Excel . ServiceNow Data Source / .





: ServiceNow VisionFlow, , ( ) . .. , ( , ).





( ) , . , VisionFlow, , .





:





VisionFlow
SELECT 
	document.documentId,
	document.name,
    document.FullPath,
	SUBSTRING_INDEX(SUBSTRING_INDEX(document.FullPath, '/', -2), '/', 1) as 'projectIssueId',
    projectissue.ticketId as 'Number'
 FROM 
	visionflow.document
 INNER JOIN projectissue
 ON projectissue.ProjectIssueId = SUBSTRING_INDEX(SUBSTRING_INDEX(document.FullPath, '/', -2), '/', 1)
 WHERE 
	document.FullPath like '%/54/issuedocuments/%'
ORDER BY projectissueid
      
      



, VisionFlow . , , VF , , . issueId, . , , TicketId ( ServiceNow).





, ServiceNow . .. Python, , .





ServiceNow API attachments. SN endpoint .





ServiceNow code samples API. , :





file_name (Required) -





table_name (Required) - ,





table_sys_id (Required) - ID ,





Content-Type (Header) - mime type





, sys_id , ( VisionFlow). , , VisionFlow sys_id , . sys_id + ticketId ServiceNow + issueId + ticketId VisionFlow. VLOOKUP Excel :





  • old_folder_name





  • ticket_id





  • new_folder_name





Python , ( ):





import pandas as pd, os 
from tqdm import tqdm

def renameFolders():
    df = pd.read_csv('/Downloads/folder_rename.csv')
    pbar = tqdm(total=len(df))

    for _ , row in df.iterrows():
        old_name = row['old_folder_name']
        new_name = row['new_folder_name']
        try:
            os.rename(f'/Downloads/home/tomcat/vflowdocs/54/issuedocuments/{old_name}', f'/Downloads/home/tomcat/vflowdocs/54/issuedocuments/{new_name}')
            pbar.update(1)
        except:
            pbar.update(1)

def removeEmptyFolders():
    folder_list = os.listdir('/Downloads/home/tomcat/vflowdocs/54/issuedocuments/')
    for folder in folder_list:
        path = f'/Downloads/home/tomcat/vflowdocs/54/issuedocuments/{folder}'
        try:
            os.rmdir(path)
        except:
            if len(os.path.basename(path)) < 6 and os.path.basename(path) != 'nan':
                print(f'ServiceNow SysId not found for item: {os.path.basename(path)}')

renameFolders()
removeEmptyFolders()
      
      



, :





  • , , , 3000 kb ( , ) def getSize()





  • . VisionFlow def removeDuplicates()





  • mime None. - mimetypes *msg, *txt, *eml









  • ( , , ) -





import os, glob, filetype, requests, mimetypes
from tqdm import tqdm
import pandas as pd

def number_of_files():
    files_number = 0

    folder_list = os.listdir('/Downloads/home/tomcat/vflowdocs/54/issuedocuments/')
    for folder in folder_list:
       files_number += len(os.listdir(f'/Downloads/home/tomcat/vflowdocs/54/issuedocuments/{folder}/'))
    return files_number

#Progress Bar
pbar = tqdm(total=1297)
log_messages_status = []
log_messages_filepath = []
log_messages_filename = []
log_messages_target = []

def uploadAllFiles(folder_name):
    #Variables
    entire_list = glob.glob(f'/Downloads/home/tomcat/vflowdocs/54/issuedocuments/{folder_name}/*')
    my_list_updated = []

    #Get Files Size
    def getSize(fileobject):
        fileobject.seek(0,2)
        size = fileobject.tell()
        return size

    #Upload Files
    def uploadFunc(filename, sys_id, path_to_file, content_type):

        url = f'https://instance.service-now.com/api/now/attachment/file?file_name={filename}&table_name=table_name&table_sys_id={sys_id}'
        payload=open(path_to_file, 'rb').read()
        headers = {
            'Accept': 'application/json',
            'Authorization': 'Bearer ',
            'Content-Type': content_type,
            }
            
        response = requests.request("POST", url, headers=headers, data=payload)
        if response.status_code == 201:
            #print(f'Success: {filename} was uploaded to the incident with sys_id {sys_id}')
            pbar.update(1)
            log_messages_status.append('Success')
            log_messages_filename.append(filename)
            log_messages_filepath.append(path_to_file)
            log_messages_target.append(sys_id)
        else:
            pbar.update(1)
            #print(f'Error: {filename} was not uploaded to the incident with sys_id {sys_id}')
            log_messages_status.append('Error')
            log_messages_filename.append(filename)
            log_messages_filepath.append(path_to_file)
            log_messages_target.append(sys_id)

    #Remove Duplicates
    def removeDuplicatesByName(list_of_elements):
        list_of_elements.sort()
        if len(list_of_elements) > 1:
            for item in list_of_elements:
                item_to_compare = item.split('.')[0]
                for element in list_of_elements:
                    if item_to_compare in element:
                        entire_list.remove(element)
                    else:
                        pass
            return list_of_elements
        else:
            return list_of_elements

    my_list = removeDuplicatesByName(entire_list)

    for item in my_list:
        file_size = open(item, 'rb')
        if getSize(file_size) > 3000:
            my_list_updated.append(item)
        else:
            pass

    for attach in my_list_updated:
        kind = filetype.guess_mime(attach)

        if kind != None:
            uploadFunc(os.path.basename(attach), os.path.dirname(attach).split('/')[-1], attach, kind)
        elif kind == None and attach.split('.')[-1] == 'txt':
            uploadFunc(os.path.basename(attach), os.path.dirname(attach).split('/')[-1], attach, 'text/plain')
        else:
            uploadFunc(os.path.basename(attach), os.path.dirname(attach).split('/')[-1], attach, 'application/octet-stream')
        

def getFolders():
    folder_list = os.listdir('/Downloads/home/tomcat/vflowdocs/54/issuedocuments/')
    for folder in folder_list:
        if folder != '.DS_Store':
            uploadAllFiles(folder)
            

getFolders()
data_to_write = pd.DataFrame({
    'status': log_messages_status,
    'file_name' : log_messages_filename,
    'file_path' : log_messages_filepath,
    'target' : log_messages_target
})
data_to_write.to_csv('/Downloads/results_log.csv')

      
      



Teníamos 2 sobres .... ©. Teníamos 6.000.000 de registros para transferir (no tantos, el sistema antiguo no funcionaba por mucho tiempo), 2.000 archivos adjuntos y un poco de tiempo. El proceso de preparación me llevó unas 14 horas (estudio, intentos, etc.) de trabajo pausado, y el proceso de transferencia dura unos 30 minutos en total.





Por supuesto, podría haber mucho que mejorar, automatizar completamente el proceso (desde descargar datos hasta cargarlos), pero, desafortunadamente, esta tarea es única. Fue interesante probar Python para la implementación del proyecto, y puedo decir que ayudó a hacer frente a esa tarea con fuerza.





En definitiva, la tarea principal de la mudanza es hacerla lo más desapercibida posible para el cliente, lo cual fue hecho por mi parte.








All Articles