Migrando dados de VisionFlow para ServiceNow

Uma das tarefas mais interessantes no trabalho de um administrador de aplicativos, na minha opinião, é a implementação da migração de dados ao mudar para um novo sistema. Hoje, quero compartilhar minha própria experiência de transferência de dados do helpdesk não muito conhecido do sistema VisionFlow para o sistema ServiceNow mais famoso.





O que o cliente queria

  • Transfira todos os dados do VisionFlow para o ServiceNow mantendo o registro / data de fechamento dos tíquetes





  • Mova todo o histórico de correspondência para cada tíquete (foi o suficiente para combinar todos os comentários em um tópico, mas fomos um pouco mais longe)





  • Mova todos os arquivos anexados aos tíquetes





O que nós temos

  • Versão do servidor do sistema VisionFlow Helpdesk implantado em uma máquina Linux virtual com banco de dados MySQL para armazenamento de dados.





  • Instância do ServiceNow, com mesa preparada com antecedência para o cliente.





    Nessa etapa, todas as nuances foram discutidas, como:





  • Modelo de status





  • Os campos obrigatórios





  • Lógica de atribuição automática de tickets ao executor





  • Dados a serem transferidos





Transferência de dados

O ServiceNow permite que você use arquivos do Excel como um recurso para importar dados. Não vou descrever em detalhes o processo de importação de dados para o sistema (o processo está bem descrito na documentação do produto), mas em termos gerais é assim:





Importação de dados

O mapa de transformação nos permite definir um campo-chave pelo qual o sistema entenderá que um registro com esses parâmetros já está presente na tabela e apenas os campos precisam ser atualizados





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

      
      



Tínhamos 2 saquetas .... ©. Tínhamos 6.000.000 de registros para transferir (não tantos, o sistema antigo não funcionava por muito tempo), 2.000 anexos e pouco tempo. O processo de preparação levou cerca de 14 horas (estudo, tentativas, etc.) de trabalho vagaroso, e o processo de transferência leva cerca de 30 minutos no total.





Claro, pode haver muito a melhorar, automatizar completamente o processo (desde o descarregamento de dados até o upload), mas, infelizmente, essa tarefa é única. Foi interessante experimentar o Python para a implementação do projeto, e posso dizer que ele ajudou a lidar com tal tarefa com estrondo.





Em última análise, a principal tarefa da mudança é fazê-lo o mais despercebido possível para o cliente, o que foi feito da minha parte.








All Articles