Replaced old Zendesk import with refactored Sequencer based version 🚀.
This commit is contained in:
parent
fc1b66d646
commit
f0cf7c3605
181 changed files with 2567 additions and 3121 deletions
|
@ -154,35 +154,35 @@ class Index extends App.ControllerContent
|
|||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
if data.result is 'import_done'
|
||||
window.location.reload()
|
||||
return
|
||||
|
||||
if data.result is 'error'
|
||||
@$('.js-error').removeClass('hide')
|
||||
@$('.js-error').html(App.i18n.translateContent(data.message))
|
||||
else
|
||||
@$('.js-error').addClass('hide')
|
||||
|
||||
if data.message is 'not running' && @updateMigrationDisplayLoop > 16
|
||||
if _.isEmpty(data.result) && @updateMigrationDisplayLoop > 16
|
||||
@$('.js-error').removeClass('hide')
|
||||
@$('.js-error').html(App.i18n.translateContent('Background process did not start or has not finished! Please contact your support.'))
|
||||
return
|
||||
|
||||
if data.result is 'in_progress'
|
||||
for key, item of data.data
|
||||
if item.done > item.total
|
||||
item.done = item.total
|
||||
if !_.isEmpty(data.result['error'])
|
||||
@$('.js-error').removeClass('hide')
|
||||
@$('.js-error').html(App.i18n.translateContent(data.result['error']))
|
||||
else
|
||||
@$('.js-error').addClass('hide')
|
||||
|
||||
if key == 'Ticket' && item.total >= 1000
|
||||
if !_.isEmpty(data.finished_at) && _.isEmpty(data.result['error'])
|
||||
window.location.reload()
|
||||
return
|
||||
|
||||
if !_.isEmpty(data.result)
|
||||
for model, stats of data.result
|
||||
if stats.sum > stats.total
|
||||
stats.sum = stats.total
|
||||
|
||||
if model == 'Ticket' && stats.total >= 1000
|
||||
@ticketCountInfo.removeClass('hide')
|
||||
|
||||
element = @$('.js-' + key.toLowerCase() )
|
||||
element.find('.js-done').text(item.done)
|
||||
element.find('.js-total').text(item.total)
|
||||
element.find('progress').attr('max', item.total )
|
||||
element.find('progress').attr('value', item.done )
|
||||
if item.total <= item.done
|
||||
element = @$('.js-' + model.toLowerCase() )
|
||||
element.find('.js-done').text(stats.sum)
|
||||
element.find('.js-total').text(stats.total)
|
||||
element.find('progress').attr('max', stats.total )
|
||||
element.find('progress').attr('value', stats.sum )
|
||||
if stats.total <= stats.sum
|
||||
element.addClass('is-done')
|
||||
else
|
||||
element.removeClass('is-done')
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<div class="alert alert--info hide js-ticket-count-info" role="alert"><%- @T("There are more than 1000 tickets in the Zendesk system. Due to API rate limit restrictions we can't get the exact number of tickets yet and have to fetch them in batches of 1000. This might take some time, better grab a cup of coffee. The total number of tickets gets updated as soon as the currently known number is surpassed.") %></div>
|
||||
<div class="wizard-body flex vertical justified">
|
||||
<table class="progressTable">
|
||||
<tr class="js-group">
|
||||
<tr class="js-groups">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Groups') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
|
@ -73,7 +73,7 @@
|
|||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
<tr class="js-organization">
|
||||
<tr class="js-organizations">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Organizations') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
|
@ -82,7 +82,7 @@
|
|||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
<tr class="js-user">
|
||||
<tr class="js-users">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Users') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
|
@ -91,7 +91,7 @@
|
|||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
<tr class="js-ticket">
|
||||
<tr class="js-tickets">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Tickets') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
|
|
|
@ -74,7 +74,9 @@ class ImportZendeskController < ApplicationController
|
|||
Setting.set('import_zendesk_endpoint_username', params[:username])
|
||||
Setting.set('import_zendesk_endpoint_key', params[:token])
|
||||
|
||||
if !Import::Zendesk.connection_test
|
||||
result = Sequencer.process('Import::Zendesk::ConnectionTest')
|
||||
|
||||
if !result[:connected]
|
||||
|
||||
Setting.set('import_zendesk_endpoint_username', nil)
|
||||
Setting.set('import_zendesk_endpoint_key', nil)
|
||||
|
@ -96,8 +98,8 @@ class ImportZendeskController < ApplicationController
|
|||
Setting.set('import_mode', true)
|
||||
Setting.set('import_backend', 'zendesk')
|
||||
|
||||
# start migration
|
||||
Import::Zendesk.delay.start_bg
|
||||
job = ImportJob.create(name: 'Import::Zendesk')
|
||||
job.delay.start
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
|
@ -105,11 +107,13 @@ class ImportZendeskController < ApplicationController
|
|||
end
|
||||
|
||||
def import_status
|
||||
result = Import::Zendesk.status_bg
|
||||
if result[:result] == 'import_done'
|
||||
job = ImportJob.find_by(name: 'Import::Zendesk')
|
||||
|
||||
if job.finished_at.present?
|
||||
Setting.reload
|
||||
end
|
||||
render json: result
|
||||
|
||||
model_show_render_item(job)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,256 +0,0 @@
|
|||
module Import
|
||||
class BaseResource
|
||||
include Import::Helper
|
||||
|
||||
attr_reader :resource, :errors
|
||||
|
||||
def initialize(resource, *args)
|
||||
@action = :unknown
|
||||
handle_args(resource, *args)
|
||||
initialize_associations_states
|
||||
import(resource, *args)
|
||||
end
|
||||
|
||||
def import_class
|
||||
raise NoMethodError, "#{self.class.name} has no implementation of the needed 'import_class' method"
|
||||
end
|
||||
|
||||
def source
|
||||
self.class.source
|
||||
end
|
||||
|
||||
def remote_id(resource, *_args)
|
||||
@remote_id ||= resource.delete(:id)
|
||||
end
|
||||
|
||||
def action
|
||||
return :failed if errors.present?
|
||||
return :skipped if @resource.blank?
|
||||
return :unchanged if !attributes_changed?
|
||||
@action
|
||||
end
|
||||
|
||||
def attributes_changed?
|
||||
changed_attributes.present? || changed_associations.present?
|
||||
end
|
||||
|
||||
def changed_attributes
|
||||
return if @resource.blank?
|
||||
# dry run
|
||||
return @resource.changes_to_save if @resource.has_changes_to_save?
|
||||
# live run
|
||||
@resource.previous_changes
|
||||
end
|
||||
|
||||
def changed_associations
|
||||
changes = {}
|
||||
tracked_associations.each do |association|
|
||||
# skip if no new value will get assigned (no change is performed)
|
||||
next if !@associations[:after].key?(association)
|
||||
# skip if both values are equal
|
||||
next if @associations[:before][association] == @associations[:after][association]
|
||||
# skip if both values are blank
|
||||
next if @associations[:before][association].blank? && @associations[:after][association].blank?
|
||||
# store changes
|
||||
changes[association] = [@associations[:before][association], @associations[:after][association]]
|
||||
end
|
||||
changes
|
||||
end
|
||||
|
||||
def self.source
|
||||
import_class_namespace
|
||||
end
|
||||
|
||||
def self.import_class_namespace
|
||||
@import_class_namespace ||= name.to_s.sub('Import::', '')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize_associations_states
|
||||
@associations = {}
|
||||
%i[before after].each do |state|
|
||||
@associations[state] ||= {}
|
||||
end
|
||||
end
|
||||
|
||||
def import(resource, *args)
|
||||
create_or_update(map(resource, *args), *args)
|
||||
rescue => e
|
||||
# Don't catch own thrown exceptions from above
|
||||
raise if e.is_a?(NoMethodError)
|
||||
handle_error(e)
|
||||
end
|
||||
|
||||
def create_or_update(resource, *args)
|
||||
return if updated?(resource, *args)
|
||||
create(resource, *args)
|
||||
end
|
||||
|
||||
def updated?(resource, *args)
|
||||
@resource = lookup_existing(resource, *args)
|
||||
return false if !@resource
|
||||
|
||||
# lock the current resource for write access
|
||||
@resource.with_lock do
|
||||
|
||||
# delete since we have an update and
|
||||
# the record is already created
|
||||
resource.delete(:created_by_id)
|
||||
|
||||
# store the current state of the associations
|
||||
# from the resource hash because if we assign
|
||||
# them to the instance some (e.g. has_many)
|
||||
# will get stored even in the dry run :/
|
||||
store_associations(:after, resource)
|
||||
|
||||
associations = tracked_associations
|
||||
@resource.assign_attributes(resource.except(*associations))
|
||||
|
||||
# the return value here is kind of misleading
|
||||
# and should not be trusted to indicate if a
|
||||
# resource was actually updated.
|
||||
# Use .action instead
|
||||
return true if !attributes_changed?
|
||||
|
||||
@action = :updated
|
||||
|
||||
return true if @dry_run
|
||||
@resource.assign_attributes(resource.slice(*associations))
|
||||
@resource.save!
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def lookup_existing(resource, *_args)
|
||||
|
||||
synced_instance = ExternalSync.find_by(
|
||||
source: source,
|
||||
source_id: remote_id(resource),
|
||||
object: import_class.name,
|
||||
)
|
||||
return if !synced_instance
|
||||
instance = import_class.find_by(id: synced_instance.o_id)
|
||||
|
||||
store_associations(:before, instance)
|
||||
|
||||
instance
|
||||
end
|
||||
|
||||
def store_associations(state, source)
|
||||
@associations[state] = associations_state(source)
|
||||
end
|
||||
|
||||
def associations_state(source)
|
||||
state = {}
|
||||
tracked_associations.each do |association|
|
||||
# we have to support instances and (resource) hashes
|
||||
# here since in case of an update we only have the
|
||||
# hash as a source but on create we have an instance
|
||||
if source.is_a?(Hash)
|
||||
# ignore if there is no key for the association
|
||||
# of the Hash (update)
|
||||
# otherwise wrong changes may get detected
|
||||
next if !source.key?(association)
|
||||
state[association] = source[association]
|
||||
else
|
||||
state[association] = source.send(association)
|
||||
end
|
||||
|
||||
# sort arrays to avoid wrong change detection
|
||||
next if !state[association].respond_to?(:sort!)
|
||||
state[association].sort!
|
||||
end
|
||||
state
|
||||
end
|
||||
|
||||
def tracked_associations
|
||||
# loop over all reflections
|
||||
import_class.reflect_on_all_associations.collect do |reflection|
|
||||
# refection name is something like groups or organization (singular/plural)
|
||||
reflection_name = reflection.name.to_s
|
||||
# key is something like group_id or organization_id (singular)
|
||||
key = reflection.klass.name.foreign_key
|
||||
|
||||
# add trailing 's' to get pluralized key
|
||||
if reflection_name.singularize != reflection_name
|
||||
key = "#{key}s"
|
||||
end
|
||||
|
||||
key.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
def create(resource, *_args)
|
||||
@resource = import_class.new(resource)
|
||||
store_associations(:after, @resource)
|
||||
@action = :created
|
||||
return if @dry_run
|
||||
@resource.save!
|
||||
external_sync_create(
|
||||
local: @resource,
|
||||
remote: resource,
|
||||
)
|
||||
end
|
||||
|
||||
def external_sync_create(local:, remote:)
|
||||
ExternalSync.create(
|
||||
source: source,
|
||||
source_id: remote_id(remote),
|
||||
object: import_class.name,
|
||||
o_id: local.id
|
||||
)
|
||||
end
|
||||
|
||||
def defaults(_resource, *_args)
|
||||
{
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
}
|
||||
end
|
||||
|
||||
def map(resource, *args)
|
||||
mapped = from_mapping(resource, *args)
|
||||
attributes = defaults(resource, *args).merge(mapped)
|
||||
attributes.symbolize_keys
|
||||
end
|
||||
|
||||
def from_mapping(resource, *args)
|
||||
mapping = mapping(*args)
|
||||
return resource if !mapping
|
||||
|
||||
ExternalSync.map(
|
||||
mapping: mapping,
|
||||
source: resource
|
||||
)
|
||||
end
|
||||
|
||||
def mapping(*args)
|
||||
Setting.get(mapping_config(*args))
|
||||
end
|
||||
|
||||
def mapping_config(*_args)
|
||||
self.class.import_class_namespace.gsub('::', '_').underscore + '_mapping'
|
||||
end
|
||||
|
||||
def handle_args(_resource, *args)
|
||||
return if !args
|
||||
return if !args.is_a?(Array)
|
||||
return if args.blank?
|
||||
|
||||
last_arg = args.last
|
||||
return if !last_arg.is_a?(Hash)
|
||||
handle_modifiers(last_arg)
|
||||
end
|
||||
|
||||
def handle_modifiers(modifiers)
|
||||
@dry_run = modifiers.fetch(:dry_run, false)
|
||||
end
|
||||
|
||||
def handle_error(e)
|
||||
@errors ||= []
|
||||
@errors.push(e)
|
||||
Rails.logger.error e
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,68 +1,15 @@
|
|||
require 'base64'
|
||||
require 'zendesk_api'
|
||||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Import
|
||||
end
|
||||
module Import::Zendesk
|
||||
extend Import::Helper
|
||||
extend Import::Zendesk::Async
|
||||
extend Import::Zendesk::ImportStats
|
||||
class Zendesk < Import::Base
|
||||
include Import::Mixin::Sequence
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
def start
|
||||
process
|
||||
end
|
||||
|
||||
def start
|
||||
log 'Start import...'
|
||||
|
||||
checks
|
||||
|
||||
Import::Zendesk::GroupFactory.import(client.groups)
|
||||
|
||||
Import::Zendesk::OrganizationFieldFactory.import(client.organization_fields)
|
||||
Import::Zendesk::OrganizationFactory.import(client.organizations)
|
||||
|
||||
Import::Zendesk::UserFieldFactory.import(client.user_fields)
|
||||
Import::Zendesk::UserFactory.import(client.users)
|
||||
|
||||
Import::Zendesk::TicketFieldFactory.import(client.ticket_fields)
|
||||
Import::Zendesk::TicketFactory.import(all_tickets)
|
||||
|
||||
# TODO
|
||||
Setting.set( 'system_init_done', true )
|
||||
Setting.set( 'import_mode', false )
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def connection_test
|
||||
Import::Zendesk::Requester.connection_test
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# this special ticket logic is needed since Zendesk archives tickets
|
||||
# after 120 days and doesn't return them via the client.tickets
|
||||
# endpoint as described here:
|
||||
# https://github.com/zammad/zammad/issues/558#issuecomment-267951351
|
||||
# the proper way is to use the 'incremental' endpoint which is not available
|
||||
# via the ruby gem yet but a pull request is pending:
|
||||
# https://github.com/zendesk/zendesk_api_client_rb/pull/287
|
||||
# the following workaround is needed to use this functionality
|
||||
def all_tickets
|
||||
ZendeskAPI::Collection.new(
|
||||
client,
|
||||
ZendeskAPI::Ticket,
|
||||
path: 'incremental/tickets?start_time=1'
|
||||
)
|
||||
end
|
||||
|
||||
def client
|
||||
Import::Zendesk::Requester.client
|
||||
end
|
||||
|
||||
def checks
|
||||
check_import_mode
|
||||
check_system_init_done
|
||||
connection_test
|
||||
def sequence_name
|
||||
'Import::Zendesk::Full'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module Async
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def start_bg
|
||||
Setting.reload
|
||||
|
||||
Import::Zendesk.connection_test
|
||||
|
||||
# get statistic before starting import
|
||||
statistic
|
||||
|
||||
# start thread to observe current state
|
||||
status_update_thread = Thread.new do
|
||||
loop do
|
||||
result = {
|
||||
data: current_state,
|
||||
result: 'in_progress',
|
||||
}
|
||||
Cache.write('import:state', result, expires_in: 10.minutes)
|
||||
sleep 8
|
||||
end
|
||||
end
|
||||
sleep 2
|
||||
|
||||
# start import data
|
||||
begin
|
||||
Import::Zendesk.start
|
||||
rescue => e
|
||||
status_update_thread.exit
|
||||
status_update_thread.join
|
||||
Rails.logger.error e
|
||||
result = {
|
||||
message: e.message,
|
||||
result: 'error',
|
||||
}
|
||||
Cache.write('import:state', result, expires_in: 10.hours)
|
||||
return false
|
||||
end
|
||||
sleep 16 # wait until new finished import state is on client
|
||||
status_update_thread.exit
|
||||
status_update_thread.join
|
||||
|
||||
result = {
|
||||
result: 'import_done',
|
||||
}
|
||||
Cache.write('import:state', result, expires_in: 10.hours)
|
||||
|
||||
Setting.set('system_init_done', true)
|
||||
Setting.set('import_mode', false)
|
||||
end
|
||||
|
||||
def status_bg
|
||||
state = Cache.get('import:state')
|
||||
return state if state
|
||||
{
|
||||
message: 'not running',
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module BaseFactory
|
||||
include Import::Factory
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
private
|
||||
|
||||
def import_loop(records, *_args, &import_block)
|
||||
records.all!(&import_block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Group
|
||||
include Import::Helper
|
||||
|
||||
attr_reader :zendesk_id, :id
|
||||
|
||||
def initialize(group)
|
||||
local_group = ::Group.create_if_not_exists(
|
||||
name: group.name,
|
||||
active: !group.deleted,
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1
|
||||
)
|
||||
|
||||
@zendesk_id = group.id
|
||||
@id = local_group.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module GroupFactory
|
||||
extend Import::Zendesk::BaseFactory
|
||||
extend Import::Zendesk::LocalIDMapperHook
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module Helper
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
private
|
||||
|
||||
def get_fields(zendesk_fields)
|
||||
return {} if !zendesk_fields
|
||||
fields = {}
|
||||
zendesk_fields.each do |key, value|
|
||||
fields[key] = value
|
||||
end
|
||||
fields
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,72 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module ImportStats
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def current_state
|
||||
|
||||
data = statistic
|
||||
|
||||
{
|
||||
Group: {
|
||||
done: ::Group.count,
|
||||
total: data['Groups'] || 0,
|
||||
},
|
||||
Organization: {
|
||||
done: ::Organization.count,
|
||||
total: data['Organizations'] || 0,
|
||||
},
|
||||
User: {
|
||||
done: ::User.count,
|
||||
total: data['Users'] || 0,
|
||||
},
|
||||
Ticket: {
|
||||
done: ::Ticket.count,
|
||||
total: data['Tickets'] || 0,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def statistic
|
||||
|
||||
# check cache
|
||||
cache = Cache.get('import_zendesk_stats')
|
||||
return cache if cache
|
||||
|
||||
# retrive statistic
|
||||
result = {
|
||||
'Tickets' => 0,
|
||||
'TicketFields' => 0,
|
||||
'UserFields' => 0,
|
||||
'OrganizationFields' => 0,
|
||||
'Groups' => 0,
|
||||
'Organizations' => 0,
|
||||
'Users' => 0,
|
||||
'GroupMemberships' => 0,
|
||||
'Macros' => 0,
|
||||
'Views' => 0,
|
||||
'Automations' => 0,
|
||||
}
|
||||
|
||||
result.each_key do |object|
|
||||
result[ object ] = statistic_count(object)
|
||||
end
|
||||
|
||||
Cache.write('import_zendesk_stats', result)
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def statistic_count(object)
|
||||
statistic_count_data(object).count!
|
||||
end
|
||||
|
||||
def statistic_count_data(object)
|
||||
return all_tickets if object == 'Tickets'
|
||||
Import::Zendesk::Requester.client.send( object.underscore.to_sym )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module LocalIDMapperHook
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def local_id(zendesk_id)
|
||||
init_mapping
|
||||
@zendesk_mapping[ zendesk_id ]
|
||||
end
|
||||
|
||||
def post_import_hook(_record, backend_instance)
|
||||
init_mapping
|
||||
@zendesk_mapping[ backend_instance.zendesk_id ] = backend_instance.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_mapping
|
||||
@zendesk_mapping ||= {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,74 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
|
||||
def initialize(object, name, attribute)
|
||||
|
||||
initialize_data_option(attribute)
|
||||
init_callback(attribute)
|
||||
|
||||
add(object, name, attribute)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_callback(_attribute); end
|
||||
|
||||
def add(object, name, attribute)
|
||||
ObjectManager::Attribute.add( attribute_config(object, name, attribute) )
|
||||
ObjectManager::Attribute.migration_execute(false)
|
||||
rescue => e
|
||||
# rubocop:disable Style/SpecialGlobalVars
|
||||
raise $!, "Problem with ObjectManager Attribute '#{name}': #{$!}", $!.backtrace
|
||||
end
|
||||
|
||||
def attribute_config(object, name, attribute)
|
||||
{
|
||||
object: object,
|
||||
name: name,
|
||||
display: attribute.title,
|
||||
data_type: data_type(attribute),
|
||||
data_option: @data_option,
|
||||
editable: !attribute.removable,
|
||||
active: attribute.active,
|
||||
screens: screens(attribute),
|
||||
position: attribute.position,
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
}
|
||||
end
|
||||
|
||||
def screens(attribute)
|
||||
config = {
|
||||
view: {
|
||||
'-all-' => {
|
||||
shown: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return config if !attribute.visible_in_portal && attribute.required_in_portal
|
||||
|
||||
{
|
||||
edit: {
|
||||
Customer: {
|
||||
shown: attribute.visible_in_portal,
|
||||
null: !attribute.required_in_portal,
|
||||
},
|
||||
}.merge(config)
|
||||
}
|
||||
end
|
||||
|
||||
def initialize_data_option(attribute)
|
||||
@data_option = {
|
||||
null: !attribute.required,
|
||||
note: attribute.description,
|
||||
}
|
||||
end
|
||||
|
||||
def data_type(attribute)
|
||||
attribute.type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
76
lib/import/zendesk/object_attribute/base.rb
Normal file
76
lib/import/zendesk/object_attribute/base.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
module Import
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Base
|
||||
|
||||
def initialize(object, name, attribute)
|
||||
|
||||
initialize_data_option(attribute)
|
||||
init_callback(attribute)
|
||||
|
||||
add(object, name, attribute)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_callback(_attribute); end
|
||||
|
||||
def add(object, name, attribute)
|
||||
ObjectManager::Attribute.add( attribute_config(object, name, attribute) )
|
||||
ObjectManager::Attribute.migration_execute(false)
|
||||
rescue => e
|
||||
# rubocop:disable Style/SpecialGlobalVars
|
||||
raise $!, "Problem with ObjectManager Attribute '#{name}': #{$!}", $!.backtrace
|
||||
end
|
||||
|
||||
def attribute_config(object, name, attribute)
|
||||
{
|
||||
object: object,
|
||||
name: name,
|
||||
display: attribute.title,
|
||||
data_type: data_type(attribute),
|
||||
data_option: @data_option,
|
||||
editable: !attribute.removable,
|
||||
active: attribute.active,
|
||||
screens: screens(attribute),
|
||||
position: attribute.position,
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
}
|
||||
end
|
||||
|
||||
def screens(attribute)
|
||||
config = {
|
||||
view: {
|
||||
'-all-' => {
|
||||
shown: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return config if !attribute.visible_in_portal && attribute.required_in_portal
|
||||
|
||||
{
|
||||
edit: {
|
||||
Customer: {
|
||||
shown: attribute.visible_in_portal,
|
||||
null: !attribute.required_in_portal,
|
||||
},
|
||||
}.merge(config)
|
||||
}
|
||||
end
|
||||
|
||||
def initialize_data_option(attribute)
|
||||
@data_option = {
|
||||
null: !attribute.required,
|
||||
note: attribute.description,
|
||||
}
|
||||
end
|
||||
|
||||
def data_type(attribute)
|
||||
attribute.type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,13 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Checkbox < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Checkbox < Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
def init_callback(_object_attribte)
|
||||
@data_option.merge!(
|
||||
default: false,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute'
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Date < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Date < Import::Zendesk::ObjectAttribute::Base
|
||||
def init_callback(_object_attribte)
|
||||
@data_option.merge!(
|
||||
future: true,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Decimal < Import::Zendesk::ObjectAttribute::Text
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Dropdown < Import::Zendesk::ObjectAttribute::Select
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute'
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Integer < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Integer < Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
def init_callback(_object_attribte)
|
||||
@data_option.merge!(
|
||||
min: 0,
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute'
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Regexp < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Regexp < Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
def init_callback(object_attribte)
|
||||
@data_option.merge!(
|
||||
type: 'text',
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Select < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Select < Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
def init_callback(object_attribte)
|
||||
@data_option.merge!(
|
||||
default: '',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Tagger < Import::Zendesk::ObjectAttribute::Select
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Text < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Text < Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
def init_callback(_object_attribte)
|
||||
@data_option.merge!(
|
||||
type: 'text',
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/object_attribute/base'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectAttribute
|
||||
class Textarea < Import::Zendesk::ObjectAttribute
|
||||
class Zendesk
|
||||
module ObjectAttribute
|
||||
class Textarea < Import::Zendesk::ObjectAttribute::Base
|
||||
|
||||
def init_callback(_object_attribte)
|
||||
@data_option.merge!(
|
||||
type: 'textarea',
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class ObjectField
|
||||
|
||||
attr_reader :zendesk_id, :id
|
||||
|
||||
def initialize(object_field)
|
||||
|
||||
import(object_field)
|
||||
|
||||
@zendesk_id = object_field.id
|
||||
@id = local_name(object_field)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local_name(object_field)
|
||||
@local_name ||= remote_name(object_field).gsub(%r{[\s\/]}, '_').underscore.gsub(/_{2,}/, '_').gsub(/_id(s?)$/, '_no\1')
|
||||
end
|
||||
|
||||
def remote_name(object_field)
|
||||
object_field['key'] # TODO: y?!
|
||||
end
|
||||
|
||||
def import(object_field)
|
||||
backend_class(object_field).new(object_name, local_name(object_field), object_field)
|
||||
end
|
||||
|
||||
def backend_class(object_field)
|
||||
"Import::Zendesk::ObjectAttribute::#{object_field.type.capitalize}".constantize
|
||||
end
|
||||
|
||||
def object_name
|
||||
self.class.name.to_s.sub(/Import::Zendesk::/, '').sub(/Field/, '')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
# https://developer.zendesk.com/rest_api/docs/core/organizations
|
||||
module Import
|
||||
module Zendesk
|
||||
class Organization
|
||||
include Import::Zendesk::Helper
|
||||
|
||||
attr_reader :zendesk_id, :id
|
||||
|
||||
def initialize(organization)
|
||||
local_organization = ::Organization.create_if_not_exists(local_organization_fields(organization))
|
||||
@zendesk_id = organization.id
|
||||
@id = local_organization.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local_organization_fields(organization)
|
||||
{
|
||||
name: organization.name,
|
||||
note: organization.note,
|
||||
shared: organization.shared_tickets,
|
||||
# shared: organization.shared_comments, # TODO, not yet implemented
|
||||
# }.merge(organization.organization_fields) # TODO
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1
|
||||
}.merge(custom_fields(organization))
|
||||
end
|
||||
|
||||
def custom_fields(organization)
|
||||
get_fields(organization.organization_fields)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module OrganizationFactory
|
||||
# we need to loop over each instead of all!
|
||||
# so we can use the default import factory here
|
||||
extend Import::Factory
|
||||
extend Import::Zendesk::LocalIDMapperHook
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class OrganizationField < Import::Zendesk::ObjectField
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module OrganizationFieldFactory
|
||||
extend Import::Zendesk::BaseFactory
|
||||
extend Import::Zendesk::LocalIDMapperHook
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Priority
|
||||
|
||||
MAPPING = {
|
||||
'low' => '1 low',
|
||||
nil => '2 normal',
|
||||
'normal' => '2 normal',
|
||||
'high' => '3 high',
|
||||
'urgent' => '3 high',
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
|
||||
def lookup(ticket)
|
||||
remote_priority = ticket.priority
|
||||
@mapping ||= {}
|
||||
if @mapping[ remote_priority ]
|
||||
return @mapping[ remote_priority ]
|
||||
end
|
||||
@mapping[ remote_priority ] = ::Ticket::Priority.lookup( name: map(remote_priority) )
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def map(priority)
|
||||
MAPPING.fetch(priority, MAPPING[nil])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,44 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module Requester
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def connection_test
|
||||
# make sure to reinitialize client
|
||||
# to react to config changes
|
||||
initialize_client
|
||||
return true if client.users.first
|
||||
false
|
||||
end
|
||||
|
||||
def client
|
||||
return @client if @client
|
||||
initialize_client
|
||||
@client
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize_client
|
||||
@client = ZendeskAPI::Client.new do |config|
|
||||
config.url = Setting.get('import_zendesk_endpoint')
|
||||
|
||||
# Basic / Token Authentication
|
||||
config.username = Setting.get('import_zendesk_endpoint_username')
|
||||
config.token = Setting.get('import_zendesk_endpoint_key')
|
||||
|
||||
# when hitting the rate limit, sleep automatically,
|
||||
# then retry the request.
|
||||
config.retry = true
|
||||
|
||||
# disable cache to avoid unneeded memory consumption
|
||||
# since we are using each object only once
|
||||
# Inspired by: https://medium.com/swiftype-engineering/using-jmat-to-find-analyze-memory-in-jruby-1c4196c1ec72
|
||||
config.cache = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class State
|
||||
|
||||
MAPPING = {
|
||||
'pending' => 'pending reminder',
|
||||
'solved' => 'closed',
|
||||
'deleted' => 'removed',
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
|
||||
def lookup(ticket)
|
||||
remote_state = ticket.status
|
||||
@mapping ||= {}
|
||||
if @mapping[ remote_state ]
|
||||
return @mapping[ remote_state ]
|
||||
end
|
||||
@mapping[ remote_state ] = ::Ticket::State.lookup( name: map( remote_state ) )
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def map(state)
|
||||
MAPPING.fetch(state, state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,74 +0,0 @@
|
|||
# https://developer.zendesk.com/rest_api/docs/core/tickets
|
||||
# https://developer.zendesk.com/rest_api/docs/core/ticket_comments#ticket-comments
|
||||
# https://developer.zendesk.com/rest_api/docs/core/ticket_audits#the-via-object
|
||||
# https://developer.zendesk.com/rest_api/docs/help_center/article_attachments
|
||||
# https://developer.zendesk.com/rest_api/docs/core/ticket_audits # v2
|
||||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
include Import::Helper
|
||||
|
||||
def initialize(ticket)
|
||||
create_or_update(ticket)
|
||||
Import::Zendesk::Ticket::TagFactory.import(ticket.tags, @local_ticket, ticket)
|
||||
Import::Zendesk::Ticket::CommentFactory.import(ticket.comments, @local_ticket, ticket)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_or_update(ticket)
|
||||
mapped_ticket = local_ticket_fields(ticket)
|
||||
return if updated?(mapped_ticket)
|
||||
create(mapped_ticket)
|
||||
end
|
||||
|
||||
def updated?(ticket)
|
||||
@local_ticket = ::Ticket.find_by(id: ticket[:id])
|
||||
return false if !@local_ticket
|
||||
@local_ticket.update!(ticket)
|
||||
true
|
||||
end
|
||||
|
||||
def create(ticket)
|
||||
@local_ticket = ::Ticket.create(ticket)
|
||||
reset_primary_key_sequence('tickets')
|
||||
end
|
||||
|
||||
def local_ticket_fields(ticket)
|
||||
local_user_id = Import::Zendesk::UserFactory.local_id( ticket.requester_id ) || 1
|
||||
|
||||
{
|
||||
id: ticket.id,
|
||||
title: ticket.subject || ticket.description || '-',
|
||||
owner_id: Import::Zendesk::UserFactory.local_id( ticket.assignee ) || 1,
|
||||
note: ticket.description,
|
||||
group_id: Import::Zendesk::GroupFactory.local_id( ticket.group_id ) || 1,
|
||||
customer_id: local_user_id,
|
||||
organization_id: Import::Zendesk::OrganizationFactory.local_id( ticket.organization_id ),
|
||||
priority: Import::Zendesk::Priority.lookup(ticket),
|
||||
state: Import::Zendesk::State.lookup(ticket),
|
||||
pending_time: ticket.due_at,
|
||||
updated_at: ticket.updated_at,
|
||||
created_at: ticket.created_at,
|
||||
updated_by_id: local_user_id,
|
||||
created_by_id: local_user_id,
|
||||
create_article_sender_id: Import::Zendesk::Ticket::Comment::Sender.local_id(local_user_id),
|
||||
create_article_type_id: Import::Zendesk::Ticket::Comment::Type.local_id(ticket),
|
||||
}.merge(custom_fields(ticket))
|
||||
end
|
||||
|
||||
def custom_fields(ticket)
|
||||
custom_fields = ticket.custom_fields
|
||||
fields = {}
|
||||
return fields if !custom_fields
|
||||
custom_fields.each do |custom_field|
|
||||
field_name = Import::Zendesk::TicketFieldFactory.local_id(custom_field['id'])
|
||||
field_value = custom_field['value']
|
||||
next if field_value.nil?
|
||||
fields[ field_name.to_sym ] = field_value
|
||||
end
|
||||
fields
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
class Comment
|
||||
|
||||
def initialize(comment, local_ticket, _zendesk_ticket)
|
||||
create_or_update(comment, local_ticket)
|
||||
import_attachments(comment)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_or_update(comment, local_ticket)
|
||||
mapped_article = local_article_fields(comment, local_ticket)
|
||||
return if updated?(mapped_article)
|
||||
create(mapped_article)
|
||||
end
|
||||
|
||||
def updated?(article)
|
||||
@local_article = ::Ticket::Article.find_by(message_id: article[:message_id])
|
||||
return false if !@local_article
|
||||
@local_article.update!(article)
|
||||
true
|
||||
end
|
||||
|
||||
def create(article)
|
||||
@local_article = ::Ticket::Article.create(article)
|
||||
end
|
||||
|
||||
def local_article_fields(comment, local_ticket)
|
||||
|
||||
local_user_id = Import::Zendesk::UserFactory.local_id( comment.author_id ) || 1
|
||||
|
||||
{
|
||||
ticket_id: local_ticket.id,
|
||||
body: comment.html_body,
|
||||
content_type: 'text/html',
|
||||
internal: !comment.public,
|
||||
message_id: comment.id,
|
||||
updated_by_id: local_user_id,
|
||||
created_by_id: local_user_id,
|
||||
sender_id: Import::Zendesk::Ticket::Comment::Sender.local_id( local_user_id ),
|
||||
type_id: Import::Zendesk::Ticket::Comment::Type.local_id(comment),
|
||||
}.merge(from_to(comment))
|
||||
end
|
||||
|
||||
def from_to(comment)
|
||||
if comment.via.channel == 'email'
|
||||
{
|
||||
from: comment.via.source.from.address,
|
||||
to: comment.via.source.to.address # Notice comment.via.from.original_recipients = [\"another@gmail.com\", \"support@example.zendesk.com\"]
|
||||
}
|
||||
elsif comment.via.channel == 'facebook'
|
||||
{
|
||||
from: comment.via.source.from.facebook_id,
|
||||
to: comment.via.source.to.facebook_id
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def import_attachments(comment)
|
||||
attachments = comment.attachments
|
||||
return if attachments.blank?
|
||||
Import::Zendesk::Ticket::Comment::AttachmentFactory.import(attachments, @local_article)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,46 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
class Comment
|
||||
class Attachment
|
||||
include Import::Helper
|
||||
|
||||
def initialize(attachment, local_article)
|
||||
|
||||
response = request(attachment)
|
||||
return if !response
|
||||
|
||||
::Store.add(
|
||||
object: 'Ticket::Article',
|
||||
o_id: local_article.id,
|
||||
data: response.body,
|
||||
filename: attachment.file_name,
|
||||
preferences: {
|
||||
'Content-Type' => attachment.content_type
|
||||
},
|
||||
created_by_id: 1
|
||||
)
|
||||
rescue => e
|
||||
log e.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(attachment)
|
||||
response = UserAgent.get(
|
||||
attachment.content_url,
|
||||
{},
|
||||
{
|
||||
open_timeout: 10,
|
||||
read_timeout: 60,
|
||||
},
|
||||
)
|
||||
return response if response.success?
|
||||
log response.error
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
class Comment
|
||||
module AttachmentFactory
|
||||
# we need to loop over each instead of all!
|
||||
# so we can use the default import factory here
|
||||
extend Import::Factory
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
private
|
||||
|
||||
# special handling which only starts import if needed
|
||||
# Attention: skip? method can't be used since it (currently)
|
||||
# only checks for single records - not all
|
||||
def import_loop(records, *args, &import_block)
|
||||
local_article = args[0]
|
||||
local_attachments = local_article.attachments
|
||||
|
||||
return if local_attachments.count == records.count
|
||||
# get a common ground
|
||||
local_attachments.each(&:delete)
|
||||
return if records.blank?
|
||||
|
||||
records.each(&import_block)
|
||||
end
|
||||
|
||||
def create_instance(record, *args)
|
||||
local_article = args[0]
|
||||
backend_class(record).new(record, local_article)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,49 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
class Comment
|
||||
module Sender
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def local_id(user_id)
|
||||
author = author_lookup(user_id)
|
||||
sender_id(author)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def author_lookup(user_id)
|
||||
::User.find( user_id )
|
||||
end
|
||||
|
||||
def sender_id(author)
|
||||
if author.role?('Customer')
|
||||
article_sender_customer
|
||||
elsif author.role?('Agent')
|
||||
article_sender_agent
|
||||
else
|
||||
article_sender_system
|
||||
end
|
||||
end
|
||||
|
||||
def article_sender_customer
|
||||
return @article_sender_customer if @article_sender_customer
|
||||
@article_sender_customer = ::Ticket::Article::Sender.lookup(name: 'Customer').id
|
||||
end
|
||||
|
||||
def article_sender_agent
|
||||
return @article_sender_agent if @article_sender_agent
|
||||
@article_sender_agent = ::Ticket::Article::Sender.lookup(name: 'Agent').id
|
||||
end
|
||||
|
||||
def article_sender_system
|
||||
return @article_sender_system if @article_sender_system
|
||||
@article_sender_system = ::Ticket::Article::Sender.lookup(name: 'System').id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,60 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
class Comment
|
||||
module Type
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def local_id(object)
|
||||
case object.via.channel
|
||||
when 'web'
|
||||
article_type_id[:web]
|
||||
when 'email'
|
||||
article_type_id[:email]
|
||||
when 'sample_ticket'
|
||||
article_type_id[:note]
|
||||
when 'twitter'
|
||||
if object.via.source.rel == 'mention'
|
||||
article_type_id[:twitter_status]
|
||||
else
|
||||
article_type_id[:twitter_direct_message]
|
||||
end
|
||||
when 'facebook'
|
||||
if object.via.source.rel == 'post'
|
||||
article_type_id[:facebook_feed_post]
|
||||
else
|
||||
article_type_id[:facebook_feed_comment]
|
||||
end
|
||||
# fallback for other not (yet) supported article types
|
||||
# See:
|
||||
# https://support.zendesk.com/hc/en-us/articles/203661746-Zendesk-Glossary#topic_zie_aqe_tf
|
||||
# https://support.zendesk.com/hc/en-us/articles/203661596-About-Zendesk-Support-channels
|
||||
else
|
||||
article_type_id[:web]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def article_type_id
|
||||
return @article_type_id if @article_type_id
|
||||
|
||||
article_types = ['web', 'note', 'email', 'twitter status',
|
||||
'twitter direct-message', 'facebook feed post',
|
||||
'facebook feed comment']
|
||||
@article_type_id = {}
|
||||
article_types.each do |article_type|
|
||||
|
||||
article_type_key = article_type.gsub(/\s|\-/, '_').to_sym
|
||||
|
||||
@article_type_id[article_type_key] = ::Ticket::Article::Type.lookup(name: article_type).id
|
||||
end
|
||||
@article_type_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
module CommentFactory
|
||||
extend Import::Zendesk::Ticket::SubObjectFactory
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
module SubObjectFactory
|
||||
# we need to loop over each instead of all!
|
||||
# so we can use the default import factory here
|
||||
include Import::Factory
|
||||
|
||||
private
|
||||
|
||||
def create_instance(record, *args)
|
||||
|
||||
local_ticket = args[0]
|
||||
zendesk_ticket = args[1]
|
||||
|
||||
backend_class(record).new(record, local_ticket, zendesk_ticket)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
class Tag
|
||||
def initialize(tag, local_ticket, zendesk_ticket)
|
||||
::Tag.tag_add(
|
||||
object: 'Ticket',
|
||||
o_id: local_ticket.id,
|
||||
item: tag.id,
|
||||
created_by_id: Import::Zendesk::UserFactory.local_id(zendesk_ticket.requester_id) || 1,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class Ticket
|
||||
module TagFactory
|
||||
extend Import::Zendesk::Ticket::SubObjectFactory
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,46 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module TicketFactory
|
||||
extend Import::Zendesk::BaseFactory
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
private
|
||||
|
||||
def import_loop(records, *args)
|
||||
|
||||
count_update_hook = proc do |record|
|
||||
yield(record)
|
||||
update_ticket_count(records)
|
||||
end
|
||||
|
||||
super(records, *args, &count_update_hook)
|
||||
end
|
||||
|
||||
def update_ticket_count(collection)
|
||||
|
||||
cache_key = 'import_zendesk_stats'
|
||||
count_variable = :@count
|
||||
page_variable = :@next_page
|
||||
|
||||
next_page = collection.instance_variable_get(page_variable)
|
||||
@last_page ||= next_page
|
||||
|
||||
return if @last_page == next_page
|
||||
return if !collection.instance_variable_get(count_variable)
|
||||
|
||||
@last_page = next_page
|
||||
|
||||
# check cache
|
||||
cache = Cache.get(cache_key)
|
||||
return if !cache
|
||||
|
||||
cache['Tickets'] ||= 0
|
||||
cache['Tickets'] += collection.instance_variable_get(count_variable)
|
||||
|
||||
Cache.write(cache_key, cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class TicketField < Import::Zendesk::ObjectField
|
||||
|
||||
private
|
||||
|
||||
def remote_name(ticket_field)
|
||||
ticket_field.title
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module TicketFieldFactory
|
||||
extend Import::Zendesk::BaseFactory
|
||||
extend Import::Zendesk::LocalIDMapperHook
|
||||
|
||||
MAPPING = {
|
||||
'subject' => 'title',
|
||||
'description' => 'note',
|
||||
'status' => 'state_id',
|
||||
'tickettype' => 'type',
|
||||
'priority' => 'priority_id',
|
||||
'basic_priority' => 'priority_id',
|
||||
'group' => 'group_id',
|
||||
'assignee' => 'owner_id',
|
||||
}.freeze
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def skip?(field, *_args)
|
||||
# check if the Ticket object already has a same named column / attribute
|
||||
# so we want to skip instead of importing it
|
||||
::Ticket.column_names.include?( local_attribute(field) )
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local_attribute(field)
|
||||
MAPPING.fetch(field.type, field.type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,75 +0,0 @@
|
|||
# Rails autoload has some issues with same namend sub-classes
|
||||
# in the importer folder require AND simultaniuos requiring
|
||||
# of the same file in different threads so we need to
|
||||
# require them ourself
|
||||
require 'import/zendesk/user/group'
|
||||
require 'import/zendesk/user/role'
|
||||
|
||||
# https://developer.zendesk.com/rest_api/docs/core/users
|
||||
module Import
|
||||
module Zendesk
|
||||
class User
|
||||
include Import::Zendesk::Helper
|
||||
|
||||
attr_reader :zendesk_id, :id
|
||||
|
||||
def initialize(user)
|
||||
local_user = ::User.create_or_update( local_user_fields(user) )
|
||||
@zendesk_id = user.id
|
||||
@id = local_user.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def local_user_fields(user)
|
||||
{
|
||||
login: login(user),
|
||||
firstname: user.name,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
password: password(user),
|
||||
active: !user.suspended,
|
||||
groups: Import::Zendesk::User::Group.for(user),
|
||||
roles: roles(user),
|
||||
note: user.notes,
|
||||
verified: user.verified,
|
||||
organization_id: Import::Zendesk::OrganizationFactory.local_id( user.organization_id ),
|
||||
last_login: user.last_login_at,
|
||||
image_source: photo(user),
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1
|
||||
}.merge(custom_fields(user))
|
||||
end
|
||||
|
||||
def login(user)
|
||||
return user.email if user.email
|
||||
# Zendesk users may have no other identifier than the ID, e.g. twitter users
|
||||
user.id.to_s
|
||||
end
|
||||
|
||||
def password(user)
|
||||
return Setting.get('import_zendesk_endpoint_key') if import_user?(user)
|
||||
''
|
||||
end
|
||||
|
||||
def roles(user)
|
||||
return Import::Zendesk::User::Role.map(user, 'admin') if import_user?(user)
|
||||
Import::Zendesk::User::Role.for(user)
|
||||
end
|
||||
|
||||
def import_user?(user)
|
||||
return false if user.email.blank?
|
||||
user.email == Setting.get('import_zendesk_endpoint_username')
|
||||
end
|
||||
|
||||
def photo(user)
|
||||
return if !user.photo
|
||||
user.photo.content_url
|
||||
end
|
||||
|
||||
def custom_fields(user)
|
||||
get_fields(user.user_fields)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,51 +0,0 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/user'
|
||||
|
||||
# https://developer.zendesk.com/rest_api/docs/core/groups
|
||||
module Import
|
||||
module Zendesk
|
||||
class User
|
||||
module Group
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def for(user)
|
||||
groups = []
|
||||
return groups if mapping[user.id].blank?
|
||||
|
||||
mapping[user.id].each do |zendesk_group_id|
|
||||
|
||||
local_group_id = Import::Zendesk::GroupFactory.local_id(zendesk_group_id)
|
||||
|
||||
next if !local_group_id
|
||||
|
||||
group = ::Group.find( local_group_id )
|
||||
|
||||
groups.push(group)
|
||||
end
|
||||
groups
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mapping
|
||||
|
||||
return @mapping if !@mapping.nil?
|
||||
|
||||
@mapping = {}
|
||||
|
||||
Import::Zendesk::Requester.client.group_memberships.all! do |group_membership|
|
||||
|
||||
@mapping[ group_membership.user_id ] ||= []
|
||||
@mapping[ group_membership.user_id ].push( group_membership.group_id )
|
||||
end
|
||||
|
||||
@mapping
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
# this require is required (hehe) because of Rails autoloading
|
||||
# which causes strange behavior not inheriting correctly
|
||||
# from Import::OTRS::DynamicField
|
||||
require 'import/zendesk/user'
|
||||
|
||||
module Import
|
||||
module Zendesk
|
||||
class User
|
||||
module Role
|
||||
extend Import::Helper
|
||||
|
||||
# rubocop:disable Style/ModuleFunction
|
||||
extend self
|
||||
|
||||
def for(user)
|
||||
map(user, group_method( user.role.name ))
|
||||
end
|
||||
|
||||
def map(user, role)
|
||||
send(role.to_sym, user)
|
||||
rescue NoMethodError => e
|
||||
log "Unknown mapping for role '#{user.role.name}' and user with id '#{user.id}'"
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def end_user(_user)
|
||||
[role_customer]
|
||||
end
|
||||
|
||||
def agent(user)
|
||||
return [ role_agent ] if user.restricted_agent
|
||||
admin(user)
|
||||
end
|
||||
|
||||
def admin(_user)
|
||||
[role_admin, role_agent]
|
||||
end
|
||||
|
||||
def group_method(role)
|
||||
role.tr('-', '_')
|
||||
end
|
||||
|
||||
def role_admin
|
||||
@role_admin ||= lookup('Admin')
|
||||
end
|
||||
|
||||
def role_agent
|
||||
@role_agent ||= lookup('Agent')
|
||||
end
|
||||
|
||||
def role_customer
|
||||
@role_customer ||= lookup('Customer')
|
||||
end
|
||||
|
||||
def lookup(role_name)
|
||||
::Role.lookup(name: role_name)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module UserFactory
|
||||
extend Import::Zendesk::BaseFactory
|
||||
extend Import::Zendesk::LocalIDMapperHook
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
class UserField < Import::Zendesk::ObjectField
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module Import
|
||||
module Zendesk
|
||||
module UserFieldFactory
|
||||
extend Import::Zendesk::BaseFactory
|
||||
extend Import::Zendesk::LocalIDMapperHook
|
||||
end
|
||||
end
|
||||
end
|
21
lib/sequencer/sequence/import/zendesk/connection_test.rb
Normal file
21
lib/sequencer/sequence/import/zendesk/connection_test.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class ConnectionTest < Sequencer::Sequence::Base
|
||||
|
||||
def self.expecting
|
||||
[:connected]
|
||||
end
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Zendesk::Client',
|
||||
'Zendesk::Connected',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
32
lib/sequencer/sequence/import/zendesk/full.rb
Normal file
32
lib/sequencer/sequence/import/zendesk/full.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Full < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Common::ImportMode::Check',
|
||||
'Import::Common::SystemInitDone::Check',
|
||||
'Zendesk::Client',
|
||||
'Import::Zendesk::ObjectsTotalCount',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
'Import::Common::ImportJob::DryRun',
|
||||
'Import::Zendesk::Groups',
|
||||
'Import::Zendesk::OrganizationFields',
|
||||
'Import::Zendesk::Organizations',
|
||||
'Import::Zendesk::UserFields',
|
||||
'Import::Zendesk::UserGroupMap',
|
||||
'Import::Zendesk::Users',
|
||||
'Import::Zendesk::TicketFields',
|
||||
'Import::Zendesk::Tickets',
|
||||
'Import::Common::SystemInitDone::Set',
|
||||
'Import::Common::ImportMode::Unset',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
lib/sequencer/sequence/import/zendesk/group.rb
Normal file
25
lib/sequencer/sequence/import/zendesk/group.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Group < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Group',
|
||||
'Import::Zendesk::Group::Mapping',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::FindBy::Name',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
lib/sequencer/sequence/import/zendesk/organization.rb
Normal file
26
lib/sequencer/sequence/import/zendesk/organization.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Organization < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Organization',
|
||||
'Import::Zendesk::Organization::Mapping',
|
||||
'Import::Zendesk::Organization::CustomFields',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::FindBy::Name',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/sequencer/sequence/import/zendesk/organization_field.rb
Normal file
18
lib/sequencer/sequence/import/zendesk/organization_field.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class OrganizationField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Organization',
|
||||
'Import::Zendesk::ObjectAttribute::SanitizedName',
|
||||
'Import::Zendesk::ObjectAttribute::Add',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
37
lib/sequencer/sequence/import/zendesk/ticket.rb
Normal file
37
lib/sequencer/sequence/import/zendesk/ticket.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Ticket < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Zendesk::Ticket::UserID',
|
||||
'Import::Zendesk::Ticket::OwnerID',
|
||||
'Import::Zendesk::Ticket::GroupID',
|
||||
'Import::Zendesk::Ticket::OrganizationID',
|
||||
'Import::Zendesk::Ticket::PriorityID',
|
||||
'Import::Zendesk::Ticket::StateID',
|
||||
'Import::Zendesk::Common::ArticleSenderID',
|
||||
'Import::Zendesk::Common::ArticleTypeID',
|
||||
'Import::Zendesk::Ticket::Subject',
|
||||
'Import::Zendesk::Ticket::CustomFields',
|
||||
'Import::Zendesk::Ticket::Mapping',
|
||||
'Common::ModelClass::Ticket',
|
||||
'Import::Common::Model::FindBy::Id',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Common::Model::ResetPrimaryKeySequence',
|
||||
'Import::Zendesk::Ticket::Tags',
|
||||
'Import::Zendesk::Ticket::Comments',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
30
lib/sequencer/sequence/import/zendesk/ticket/comment.rb
Normal file
30
lib/sequencer/sequence/import/zendesk/ticket/comment.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Ticket < Sequencer::Sequence::Base
|
||||
class Comment < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Zendesk::Ticket::Comment::UserID',
|
||||
'Import::Zendesk::Common::ArticleSenderID',
|
||||
'Import::Zendesk::Common::ArticleTypeID',
|
||||
'Import::Zendesk::Ticket::Comment::From',
|
||||
'Import::Zendesk::Ticket::Comment::To',
|
||||
'Import::Zendesk::Ticket::Comment::Mapping',
|
||||
'Import::Zendesk::Ticket::Comment::UnsetInstance',
|
||||
'Common::ModelClass::Ticket::Article',
|
||||
'Import::Common::Model::FindBy::Id',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Zendesk::Ticket::Comment::Attachments',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Ticket < Sequencer::Sequence::Base
|
||||
class Comment < Sequencer::Sequence::Base
|
||||
class Attachment < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Zendesk::Ticket::Comment::Attachment::Request',
|
||||
'Import::Zendesk::Ticket::Comment::Attachment::Add',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/sequence/import/zendesk/ticket/tag.rb
Normal file
19
lib/sequencer/sequence/import/zendesk/ticket/tag.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class Ticket < Sequencer::Sequence::Base
|
||||
class Tag < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Zendesk::Ticket::Tag::Item',
|
||||
'Common::Tag::Add',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/sequence/import/zendesk/ticket_field.rb
Normal file
19
lib/sequencer/sequence/import/zendesk/ticket_field.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class TicketField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Ticket',
|
||||
'Import::Zendesk::TicketField::CheckCustom',
|
||||
'Import::Zendesk::TicketField::SanitizedName',
|
||||
'Import::Zendesk::TicketField::Add',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/sequencer/sequence/import/zendesk/user.rb
Normal file
33
lib/sequencer/sequence/import/zendesk/user.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class User < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Zendesk::User::Initiator',
|
||||
'Import::Zendesk::User::Roles',
|
||||
'Import::Zendesk::User::Groups',
|
||||
'Import::Zendesk::User::Login',
|
||||
'Import::Zendesk::User::Password',
|
||||
'Import::Zendesk::User::ImageSource',
|
||||
'Import::Zendesk::User::OrganizationID',
|
||||
'Common::ModelClass::User',
|
||||
'Import::Zendesk::User::Mapping',
|
||||
'Import::Zendesk::User::CustomFields',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::FindBy::UserAttributes',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/sequencer/sequence/import/zendesk/user_field.rb
Normal file
18
lib/sequencer/sequence/import/zendesk/user_field.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Zendesk
|
||||
class UserField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::User',
|
||||
'Import::Zendesk::ObjectAttribute::SanitizedName',
|
||||
'Import::Zendesk::ObjectAttribute::Add',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/common/model_class/group.rb
Normal file
10
lib/sequencer/unit/common/model_class/group.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module ModelClass
|
||||
class Group < Sequencer::Unit::Common::ModelClass::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/common/model_class/organization.rb
Normal file
10
lib/sequencer/unit/common/model_class/organization.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module ModelClass
|
||||
class Organization < Sequencer::Unit::Common::ModelClass::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/common/model_class/ticket.rb
Normal file
10
lib/sequencer/unit/common/model_class/ticket.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module ModelClass
|
||||
class Ticket < Sequencer::Unit::Common::ModelClass::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
lib/sequencer/unit/common/model_class/ticket/article.rb
Normal file
12
lib/sequencer/unit/common/model_class/ticket/article.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module ModelClass
|
||||
class Ticket < Sequencer::Unit::Common::ModelClass::Base
|
||||
class Article < Sequencer::Unit::Common::ModelClass::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/sequencer/unit/common/tag/add.rb
Normal file
21
lib/sequencer/unit/common/tag/add.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module Tag
|
||||
class Add < Sequencer::Unit::Base
|
||||
|
||||
uses :model_class, :instance, :item, :user_id
|
||||
|
||||
def process
|
||||
::Tag.tag_add(
|
||||
object: model_class.name,
|
||||
o_id: instance.id,
|
||||
item: item,
|
||||
created_by_id: user_id,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/sequencer/unit/common/unset_attributes.rb
Normal file
17
lib/sequencer/unit/common/unset_attributes.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
class UnsetAttributes < Sequencer::Unit::Base
|
||||
|
||||
def process
|
||||
uses = self.class.uses
|
||||
return if uses.blank?
|
||||
|
||||
uses.each do |attribute|
|
||||
state.unset(attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/sequencer/unit/import/common/import_mode/check.rb
Normal file
18
lib/sequencer/unit/import/common/import_mode/check.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module ImportMode
|
||||
class Check < Sequencer::Unit::Base
|
||||
|
||||
def process
|
||||
# check if system is in import mode
|
||||
return if Setting.get('import_mode')
|
||||
raise 'System is not in import mode!'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
lib/sequencer/unit/import/common/import_mode/unset.rb
Normal file
16
lib/sequencer/unit/import/common/import_mode/unset.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module ImportMode
|
||||
class Unset < Sequencer::Unit::Base
|
||||
|
||||
def process
|
||||
Setting.set('import_mode', false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
lib/sequencer/unit/import/common/model/find_by/id.rb
Normal file
14
lib/sequencer/unit/import/common/model/find_by/id.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module Model
|
||||
module FindBy
|
||||
class Id < Sequencer::Unit::Import::Common::Model::FindBy::SameNamedAttribute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
lib/sequencer/unit/import/common/model/find_by/name.rb
Normal file
14
lib/sequencer/unit/import/common/model/find_by/name.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module Model
|
||||
module FindBy
|
||||
class Name < Sequencer::Unit::Import::Common::Model::FindBy::SameNamedAttribute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module Model
|
||||
module FindBy
|
||||
class SameNamedAttribute < Sequencer::Unit::Import::Common::Model::Lookup::Attributes
|
||||
|
||||
private
|
||||
|
||||
def attribute
|
||||
self.class.name.demodulize.underscore.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module Model
|
||||
module FindBy
|
||||
class UserAttributes < Sequencer::Unit::Import::Common::Model::Lookup::Attributes
|
||||
|
||||
private
|
||||
|
||||
def attributes
|
||||
%i[login email]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module Model
|
||||
class ResetPrimaryKeySequence < Sequencer::Unit::Base
|
||||
|
||||
uses :model_class
|
||||
|
||||
delegate table_name: :model_class
|
||||
|
||||
def process
|
||||
DbHelper.import_post(table_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,12 +12,16 @@ class Sequencer
|
|||
%i[skipped created updated unchanged failed deactivated]
|
||||
end
|
||||
|
||||
def results
|
||||
%i[sum total]
|
||||
end
|
||||
|
||||
def empty_diff
|
||||
possible_actions.collect { |key| [key, 0] }.to_h
|
||||
end
|
||||
|
||||
def possible_actions
|
||||
@possible_actions ||= actions
|
||||
@possible_actions ||= actions + results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
29
lib/sequencer/unit/import/common/model/statistics/total.rb
Normal file
29
lib/sequencer/unit/import/common/model/statistics/total.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module Model
|
||||
module Statistics
|
||||
class Total < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Model::Statistics::Mixin::EmptyDiff
|
||||
|
||||
def process
|
||||
state.provide(:statistics_diff) do
|
||||
diff.merge(
|
||||
total: total
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def total
|
||||
raise "Missing implementation if total method for class #{self.class.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module ObjectAttribute
|
||||
class SanitizedName < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
private
|
||||
|
||||
def sanitized_name
|
||||
# model_no
|
||||
# model_nos
|
||||
# model_name
|
||||
# model_name
|
||||
without_double_underscores.gsub(/_id(s?)$/, '_no\1')
|
||||
end
|
||||
|
||||
def without_double_underscores
|
||||
# model_id
|
||||
# model_ids
|
||||
# model_name
|
||||
# model_name
|
||||
without_spaces_and_slashes.gsub(/_{2,}/, '_')
|
||||
end
|
||||
|
||||
def without_spaces_and_slashes
|
||||
# model_id
|
||||
# model_ids
|
||||
# model___name
|
||||
# model_name
|
||||
unsanitized_name.gsub(%r{[\s\/]}, '_').underscore
|
||||
end
|
||||
|
||||
def unsanitized_name
|
||||
# Model ID
|
||||
# Model IDs
|
||||
# Model / Name
|
||||
# Model Name
|
||||
raise 'Missing implementation for unsanitized_name method'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/sequencer/unit/import/common/system_init_done/check.rb
Normal file
17
lib/sequencer/unit/import/common/system_init_done/check.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module SystemInitDone
|
||||
class Check < Sequencer::Unit::Base
|
||||
|
||||
def process
|
||||
return if !Setting.get('system_init_done')
|
||||
raise 'System is already system_init_done!'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
lib/sequencer/unit/import/common/system_init_done/set.rb
Normal file
16
lib/sequencer/unit/import/common/system_init_done/set.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Common
|
||||
module SystemInitDone
|
||||
class Set < Sequencer::Unit::Base
|
||||
|
||||
def process
|
||||
Setting.set('system_init_done', true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Common
|
||||
class ArticleSenderID < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
uses :user_id
|
||||
|
||||
private
|
||||
|
||||
def article_sender_id
|
||||
return article_sender('Customer') if author.role?('Customer')
|
||||
return article_sender('Agent') if author.role?('Agent')
|
||||
article_sender('System')
|
||||
end
|
||||
|
||||
def author
|
||||
@author ||= ::User.find(user_id)
|
||||
end
|
||||
|
||||
def article_sender(name)
|
||||
::Ticket::Article::Sender.select(:id).find_by(name: name).id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
52
lib/sequencer/unit/import/zendesk/common/article_type_id.rb
Normal file
52
lib/sequencer/unit/import/zendesk/common/article_type_id.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Common
|
||||
class ArticleTypeID < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
uses :resource
|
||||
|
||||
private
|
||||
|
||||
def article_type_id
|
||||
::Ticket::Article::Type.select(:id).find_by(name: name).id
|
||||
end
|
||||
|
||||
def name
|
||||
known_channel || 'web'
|
||||
end
|
||||
|
||||
def known_channel
|
||||
channel = resource.via.channel
|
||||
direct_mapping.fetch(channel, indirect_map(channel))
|
||||
end
|
||||
|
||||
def indirect_map(channel)
|
||||
method_name = "remote_name_#{channel}".to_sym
|
||||
send(method_name) if respond_to?(method_name, true)
|
||||
end
|
||||
|
||||
def remote_name_facebook
|
||||
return 'facebook feed post' if resource.via.source.rel == 'post'
|
||||
'facebook feed comment'
|
||||
end
|
||||
|
||||
def remote_name_twitter
|
||||
return 'twitter status' if resource.via.source.rel == 'mention'
|
||||
'twitter direct message'
|
||||
end
|
||||
|
||||
def direct_mapping
|
||||
{
|
||||
'web' => 'web',
|
||||
'email' => 'email',
|
||||
'sample_ticket' => 'note',
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
39
lib/sequencer/unit/import/zendesk/common/custom_fields.rb
Normal file
39
lib/sequencer/unit/import/zendesk/common/custom_fields.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Common
|
||||
class CustomFields < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
attributes_hash
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remote_fields
|
||||
raise 'Missing implementation of remote_fields method'
|
||||
end
|
||||
|
||||
def fields
|
||||
@fields ||= remote_fields
|
||||
end
|
||||
|
||||
def attributes_hash
|
||||
return {} if fields.blank?
|
||||
fields.each_with_object({}) do |(key, value), result|
|
||||
next if value.nil?
|
||||
result[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/sequencer/unit/import/zendesk/group/mapping.rb
Normal file
24
lib/sequencer/unit/import/zendesk/group/mapping.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Group
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
{
|
||||
name: resource.name,
|
||||
active: !resource.deleted,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/zendesk/groups.rb
Normal file
10
lib/sequencer/unit/import/zendesk/groups.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
class Groups < Sequencer::Unit::Import::Zendesk::SubSequence::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
lib/sequencer/unit/import/zendesk/object_attribute/add.rb
Normal file
25
lib/sequencer/unit/import/zendesk/object_attribute/add.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module ObjectAttribute
|
||||
class Add < Sequencer::Unit::Base
|
||||
|
||||
uses :model_class, :sanitized_name, :resource
|
||||
provides :instance
|
||||
|
||||
def process
|
||||
state.provide(:instance) do
|
||||
backend_class.new(model_class, sanitized_name, resource)
|
||||
end
|
||||
end
|
||||
|
||||
def backend_class
|
||||
"Import::Zendesk::ObjectAttribute::#{resource.type.capitalize}".constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module ObjectAttribute
|
||||
class SanitizedName < Sequencer::Unit::Import::Common::ObjectAttribute::SanitizedName
|
||||
|
||||
uses :resource
|
||||
|
||||
private
|
||||
|
||||
def unsanitized_name
|
||||
# Model ID
|
||||
# Model IDs
|
||||
# Model / Name
|
||||
# Model Name
|
||||
resource['key']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
50
lib/sequencer/unit/import/zendesk/objects_total_count.rb
Normal file
50
lib/sequencer/unit/import/zendesk/objects_total_count.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
class ObjectsTotalCount < Sequencer::Unit::Common::Provider::Attribute
|
||||
include ::Sequencer::Unit::Import::Common::Model::Statistics::Mixin::EmptyDiff
|
||||
|
||||
uses :client
|
||||
|
||||
private
|
||||
|
||||
def statistics_diff
|
||||
%i[Groups Users Organizations Tickets].each_with_object({}) do |object, stats|
|
||||
stats[object] = empty_diff.merge(
|
||||
total: request(object).count!
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def request(object)
|
||||
return tickets if object == 'Tickets'
|
||||
generic(object)
|
||||
end
|
||||
|
||||
def generic(object)
|
||||
client.send(object.to_s.underscore.to_sym)
|
||||
end
|
||||
|
||||
# this special ticket logic is needed since Zendesk archives tickets
|
||||
# after 120 days and doesn't return them via the client.tickets
|
||||
# endpoint as described here:
|
||||
# https://github.com/zammad/zammad/issues/558#issuecomment-267951351
|
||||
# the proper way is to use the 'incremental' endpoint which is not available
|
||||
# via the ruby gem yet but a pull request is pending:
|
||||
# https://github.com/zendesk/zendesk_api_client_rb/pull/287
|
||||
# the following workaround is needed to use this functionality
|
||||
# Counting Tickets has the limitations that max. 1000 are returned
|
||||
# that's why we need to update the number when it's exceeded while importing
|
||||
def tickets
|
||||
ZendeskAPI::Collection.new(
|
||||
client,
|
||||
ZendeskAPI::Ticket,
|
||||
path: 'incremental/tickets?start_time=1'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Organization
|
||||
class CustomFields < Sequencer::Unit::Import::Zendesk::Common::CustomFields
|
||||
|
||||
private
|
||||
|
||||
def remote_fields
|
||||
resource.organization_fields
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
lib/sequencer/unit/import/zendesk/organization/mapping.rb
Normal file
25
lib/sequencer/unit/import/zendesk/organization/mapping.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Organization
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
{
|
||||
name: resource.name,
|
||||
note: resource.note,
|
||||
shared: resource.shared_tickets,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/zendesk/organization_fields.rb
Normal file
10
lib/sequencer/unit/import/zendesk/organization_fields.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
class OrganizationFields < Sequencer::Unit::Import::Zendesk::SubSequence::ObjectFields
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
lib/sequencer/unit/import/zendesk/organizations.rb
Normal file
16
lib/sequencer/unit/import/zendesk/organizations.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
class Organizations < Sequencer::Unit::Import::Zendesk::SubSequence::Object
|
||||
|
||||
private
|
||||
|
||||
def resource_iteration_method
|
||||
:all!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
60
lib/sequencer/unit/import/zendesk/sub_sequence/base.rb
Normal file
60
lib/sequencer/unit/import/zendesk/sub_sequence/base.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module SubSequence
|
||||
module Base
|
||||
module ClassMethods
|
||||
|
||||
def resource_klass
|
||||
@resource_klass ||= name.split('::').last.singularize
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.uses :dry_run, :import_job
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_params
|
||||
{
|
||||
dry_run: dry_run,
|
||||
import_job: import_job,
|
||||
}
|
||||
end
|
||||
|
||||
def resource_klass
|
||||
# base.instance_delegate [:resource_klass] => base
|
||||
# doesn't work since we are included and then inherited
|
||||
# there might be multiple inherited hooks which overwrite
|
||||
# each other :/
|
||||
self.class.resource_klass
|
||||
end
|
||||
|
||||
def sequence_name
|
||||
"Import::Zendesk::#{resource_klass}"
|
||||
end
|
||||
|
||||
def resource_iteration(&block)
|
||||
resource_collection.public_send(resource_iteration_method, &block)
|
||||
end
|
||||
|
||||
def resource_collection
|
||||
collection_provider.public_send(resource_collection_attribute)
|
||||
end
|
||||
|
||||
def resource_iteration_method
|
||||
:all!
|
||||
end
|
||||
|
||||
def resource_collection_attribute
|
||||
@resource_collection_attribute ||= resource_klass.pluralize.underscore
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
80
lib/sequencer/unit/import/zendesk/sub_sequence/mapped.rb
Normal file
80
lib/sequencer/unit/import/zendesk/sub_sequence/mapped.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module SubSequence
|
||||
module Mapped
|
||||
module ClassMethods
|
||||
|
||||
def resource_map
|
||||
"#{resource_klass.underscore}_map".to_sym
|
||||
end
|
||||
|
||||
def inherited(base)
|
||||
base.provides(base.resource_map)
|
||||
|
||||
base.extend(Forwardable)
|
||||
base.instance_delegate [:resource_map] => base
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.uses :client
|
||||
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def process
|
||||
state.provide(resource_map) do
|
||||
process_sub_sequence
|
||||
mapping
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expecting
|
||||
raise 'Missing implementation of expecting method'
|
||||
end
|
||||
|
||||
def collection_provider
|
||||
client
|
||||
end
|
||||
|
||||
def process_sub_sequence
|
||||
resource_iteration do |resource|
|
||||
|
||||
expected_value = expected(resource)
|
||||
|
||||
next if expected_value.blank?
|
||||
|
||||
mapping[resource.id] = mapping_value(expected_value)
|
||||
end
|
||||
end
|
||||
|
||||
def expected(resource)
|
||||
result = sub_sequence(resource)
|
||||
result[expecting]
|
||||
end
|
||||
|
||||
def sub_sequence(resource)
|
||||
::Sequencer.process(sequence_name,
|
||||
parameters: default_params.merge(
|
||||
resource: resource
|
||||
),
|
||||
expecting: [expecting])
|
||||
end
|
||||
|
||||
def mapping_value(expected_value)
|
||||
expected_value
|
||||
end
|
||||
|
||||
def mapping
|
||||
@mapping ||= {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/sequencer/unit/import/zendesk/sub_sequence/object.rb
Normal file
24
lib/sequencer/unit/import/zendesk/sub_sequence/object.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module SubSequence
|
||||
class Object < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Zendesk::SubSequence::Base
|
||||
include ::Sequencer::Unit::Import::Zendesk::SubSequence::Mapped
|
||||
|
||||
private
|
||||
|
||||
def expecting
|
||||
:instance
|
||||
end
|
||||
|
||||
def mapping_value(expected_value)
|
||||
expected_value.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module SubSequence
|
||||
class ObjectFields < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Zendesk::SubSequence::Base
|
||||
include ::Sequencer::Unit::Import::Zendesk::SubSequence::Mapped
|
||||
|
||||
private
|
||||
|
||||
def expecting
|
||||
:sanitized_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
39
lib/sequencer/unit/import/zendesk/sub_sequence/sub_object.rb
Normal file
39
lib/sequencer/unit/import/zendesk/sub_sequence/sub_object.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module SubSequence
|
||||
class SubObject < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Zendesk::SubSequence::Base
|
||||
|
||||
uses :resource, :instance, :user_id, :model_class
|
||||
|
||||
def process
|
||||
resource_iteration do |sub_resource|
|
||||
|
||||
::Sequencer.process(sequence_name,
|
||||
parameters: default_params.merge(
|
||||
resource: sub_resource
|
||||
),)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection_provider
|
||||
resource
|
||||
end
|
||||
|
||||
def default_params
|
||||
super.merge(
|
||||
instance: instance,
|
||||
user_id: user_id,
|
||||
model_class: model_class,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module SubSequence
|
||||
class TicketSubObject < Sequencer::Unit::Import::Zendesk::SubSequence::SubObject
|
||||
|
||||
private
|
||||
|
||||
def sequence_name
|
||||
"Import::Zendesk::Ticket::#{resource_klass}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Zendesk
|
||||
module Ticket
|
||||
module Comment
|
||||
module Attachment
|
||||
class Add < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
include ::Sequencer::Unit::Import::Common::Model::Mixin::HandleFailure
|
||||
|
||||
skip_action :skipped
|
||||
|
||||
uses :instance, :resource, :response, :model_class
|
||||
|
||||
def process
|
||||
::Store.add(
|
||||
object: model_class.name,
|
||||
o_id: instance.id,
|
||||
data: response.body,
|
||||
filename: resource.file_name,
|
||||
preferences: {
|
||||
'Content-Type' => resource.content_type
|
||||
},
|
||||
created_by_id: 1
|
||||
)
|
||||
rescue => e
|
||||
handle_failure(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue