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.