Skip to content

Instantly share code, notes, and snippets.

@mrxinu
Forked from cw2908/import_whd_tickets.rb
Created February 4, 2020 13:38
Show Gist options
  • Save mrxinu/94e2411a3c12eff3297b34a5902b1784 to your computer and use it in GitHub Desktop.
Save mrxinu/94e2411a3c12eff3297b34a5902b1784 to your computer and use it in GitHub Desktop.
Import Incidents from WHD export
# frozen_string_literal: true
#################################################################
##
## First install the wrapper & xml parser `gem install samanage nokogiri`
## To run `ruby import_whd_tickets.rb api-token export-file.csv`
##
##
## The CSV input columns were:
##
## 'No.'
## 'Status'
## 'Priority'
## 'Tech'
## 'tech email'
## 'site'
## 'Department'
## 'Category'
## 'Sub Category'
## 'Title'
## 'Description'
## 'Client'
## 'client email'
## 'Notes'
require "nokogiri"
require "samanage"
require "csv"
api_token, csv_file = ARGV
@output_file = "Errors-#{csv_file.split('.').first}-#{DateTime.now.strftime("%b-%d-%Y %H%M")}.csv"
@samanage = Samanage::Api.new(token: api_token)
unless csv_file
puts "Please enter a CSV file"
exit
end
unless @samanage.authorize
puts "Invalid API Token: #{api_token}"
exit
end
def log_error(hsh:, filename: @output_file)
write_headers = !File.exist?(filename)
CSV.open(filename, "a+", write_headers: write_headers, headers: hsh.keys) do |csv|
csv << hsh.values
end
nil
end
puts "Reading #{csv_file}"
whd_tickets = CSV.read(csv_file, headers: true, encoding: "utf-8")
.map(&:to_h)
def import_ticket(csv_data:)
# Tickets can only be created as Closed or New
initial_state = nil
if !["Closed", "New"].include?(csv_data["Status"])
initial_state = "New"
end
title = "WHD-#{csv_data['No.']} #{csv_data['Title']}"
payload = { incident: {} }
payload[:incident]
payload[:incident][:requester] = { email: csv_data["client email"] } # required in email format (this column may have been added manually)
payload[:incident][:name] = title # required 256 char limit
payload[:incident][:description] = csv_data["Description"]
payload[:incident][:state] = initial_state || csv_data["Status"]
payload[:incident][:custom_fields_values] = {}
payload[:incident][:custom_fields_values][:custom_fields_value] = [
# { name: "Some Field", value: csv_data["Custom CSV field"] },
# { name: "Some Other Field", value: csv_data["Custom Data etc"] }
]
## I create the payload this convoluted way because it is problematic to send nested nil values for the fields below eg:
## =====> {incident: {category: {name: nil}, subcategory: {name: nil}}}
## You can check the existence of these properties in these resources
## api.samanage.com/users.json
## api.samanage.com/categories.json (contains subcategories also)
## api.samanage.com/sites.json
## api.samanage.com/departments.json
## Some customers prefer to see a list of nonimported errors
## others are okay with inserting blank values if they don't match...
payload[:incident][:category] = { name: csv_data["Category"] } if csv_data["Category"]
payload[:incident][:subcategory] = { name: csv_data["Sub Category"] } if csv_data["Sub Category"]
payload[:incident][:site] = { name: csv_data["Site"] } if csv_data["Site"]
payload[:incident][:department] = { name: csv_data["Department"] } if csv_data["Department"]
payload[:incident][:assignee] = { email: csv_data["tech email"] } if csv_data["tech email"] # this column may have been added manually
puts "\nImporting: #{title}"
# This is just a post request to /incidents with error handling
incident = @samanage.create_incident(payload: payload)
add_comments(incident_id: incident.dig(:data, "id"), csv_data: csv_data)
# If there was an initial_state then finish with updating to the correct state
if initial_state
puts "Setting final incident state to #{csv_data['Status']}"
@samanage.update_incident(
id: incident.dig(:data, "id"),
payload: { incident: { state: csv_data["Status"] } }
)
end
rescue Samanage::Error, Samanage::InvalidRequest => e
log_error(hsh: csv_data.merge(error_reason: "Creating Incident", error: "#{e.status_code}: #{e.response}"))
end
def add_comments(incident_id:, csv_data:)
notes = csv_data["Notes"]
return unless notes
doc = Nokogiri::XML(notes)
## Not sure if there can be multiple notes in the regular export the examples I've seen only have 1
parsed_note = doc.at("note")
## Commenter must be the email of a user. If commenter is nil then the UI will show the API user as the commenter
commenter = parsed_note.attribute("author") ? "#{parsed_note.attribute('author')}@domain.com" : nil
payload = {
comment: {
body: parsed_note.content || parsed_note.text,
commenter: commenter,
is_private: parsed_note.attribute("type").to_s == "hidden"
}
}
puts "Adding comments"
@samanage.create_comment(incident_id: incident_id, comment: payload)\
rescue Samanage::Error, Samanage::InvalidRequest => e
log_error(hsh: csv_data.merge(error_reason: "Adding comment", error: "#{e.status_code}: #{e.response}"))
rescue => e
log_error(hsh: csv_data.merge(error_reason: "Adding comment", error: "Unexpected Error #{e.class}: #{e.inspect}"))
end
whd_tickets.each do |whd_ticket|
import_ticket(csv_data: whd_ticket)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment