[MySQL, Python, Service Desk] Перенос данных из VisionFlow в ServiceNow

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
29-Мар-2021 01:30

Одни из самых интересных задач в работе администратора приложений, на мой взгляд, это осуществление миграции данных при переходе на новую систему. Сегодня я хочу поделится собственным опытом переноса данных с не очень известной helpdesk системы VisionFlow в более известную систему ServiceNow.Что хотел заказчик
  • Перенести все данные из VisionFlow в ServiceNow с сохранением даты регистрации / закрытия тикетов
  • Перенести всю историю переписки по каждому тикету (достаточно было объединить все комментарии в один тред, но мы пошли чуть дальше)
  • Перенести все прикреплённые к тикетам файлы
Что мы имели
  • Серверную версию Helpdesk системы VisionFlow развёрнутую на виртуальной линукс машине с БД MySQL для хранения данных.
  • ServiceNow инстанс, с подготовленной заранее таблицей для заказчика.На данном этапе были обговорены все нюансы, такие как:
  • Статусная модель
  • Требуемые поля
  • Логика автоматического назначения тикетов на исполнителя
  • Данные требующие переноса
Перенос данныхServiceNow позволяет использовать excel файлы в качестве ресурса для импорта данных. Не буду подробно расписывать процесс импорта данных в систему (процесс неплохо описан в документации к продукту), но в общих чертах он выглядит так:Импорт данныхTransform map позволяет нам задать ключевое поле, по которому система будет понимать, что запись с данными параметрами уже присутствует в таблице и требуется только обновление полейТак как было решено использовать 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')
ЗаключениеУ нас было 2 пакетика….©. У нас было 6000 тысяч записей к переносу (не так много, старая система работала не долго), 2000 вложений и немного времени. Процесс подготовки занял у меня около 14 часов (изучение, попытки и т.д.) неспешной работы, а процесс переноса занимает около 30 минут суммарно. Конечно, можно было бы многое улучшить, полностью автоматизировать процесс (начиная с выгрузки данных, заканчивая их проливкой), но, к сожалению, данная задача одноразовая. Было интересно попробовать Python для реализации проекта, и могу сказать, что с такой задачей он помог справится на ура.В конечном счёте, основная задача переезда - сделать это максимально незаметно для заказчика, что и было сделано с моей стороны.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_mysql, #_python, #_service_desk, #_perenos (перенос), #_servicenow, #_python, #_mysql, #_python, #_service_desk
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 20:17
Часовой пояс: UTC + 5