Fixes issue #865 - Freshdesk import
This commit is contained in:
parent
a1d17ac45b
commit
8790e389be
96 changed files with 7963 additions and 9 deletions
|
@ -38,6 +38,7 @@ Metrics/AbcSize:
|
|||
- 'app/controllers/getting_started_controller.rb'
|
||||
- 'app/controllers/import_otrs_controller.rb'
|
||||
- 'app/controllers/import_zendesk_controller.rb'
|
||||
- 'app/controllers/import_freshdesk_controller.rb'
|
||||
- 'app/controllers/integration/check_mk_controller.rb'
|
||||
- 'app/controllers/integration/cti_controller.rb'
|
||||
- 'app/controllers/integration/idoit_controller.rb'
|
||||
|
@ -466,6 +467,7 @@ Metrics/CyclomaticComplexity:
|
|||
- 'app/controllers/getting_started_controller.rb'
|
||||
- 'app/controllers/import_otrs_controller.rb'
|
||||
- 'app/controllers/import_zendesk_controller.rb'
|
||||
- 'app/controllers/import_freshdesk_controller.rb'
|
||||
- 'app/controllers/integration/check_mk_controller.rb'
|
||||
- 'app/controllers/integration/smime_controller.rb'
|
||||
- 'app/controllers/knowledge_base/public/categories_controller.rb'
|
||||
|
|
194
app/assets/javascripts/app/controllers/import_freshdesk.coffee
Normal file
194
app/assets/javascripts/app/controllers/import_freshdesk.coffee
Normal file
|
@ -0,0 +1,194 @@
|
|||
class ImportFreshdesk extends App.ControllerWizardFullScreen
|
||||
className: 'getstarted fit'
|
||||
elements:
|
||||
'.input-feedback': 'urlStatus'
|
||||
'[data-target=freshdesk-credentials]': 'nextEnterCredentials'
|
||||
'[data-target=freshdesk-start-migration]': 'nextStartMigration'
|
||||
'#freshdesk-subdomain': 'freshdeskSubdomain'
|
||||
'#freshdesk-subdomain-addon': 'freshdeskSubdomainAddon'
|
||||
'.freshdesk-subdomain-error': 'linkErrorMessage'
|
||||
'.freshdesk-api-token-error': 'apiTokenErrorMessage'
|
||||
'#freshdesk-email': 'freshdeskEmail'
|
||||
'#freshdesk-api-token': 'freshdeskApiToken'
|
||||
'.js-ticket-count-info': 'ticketCountInfo'
|
||||
updateMigrationDisplayLoop: 0
|
||||
|
||||
events:
|
||||
'click .js-freshdesk-credentials': 'showCredentials'
|
||||
'click .js-migration-start': 'startMigration'
|
||||
'keyup #freshdesk-subdomain': 'updateUrl'
|
||||
'keyup #freshdesk-api-token': 'updateApiToken'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
# set title
|
||||
@title 'Import'
|
||||
|
||||
@freshdeskDomain = '.freshdesk.com'
|
||||
|
||||
# redirect to login if master user already exists
|
||||
if @Config.get('system_init_done')
|
||||
@navigate '#login'
|
||||
return
|
||||
|
||||
@fetch()
|
||||
|
||||
fetch: ->
|
||||
|
||||
# get data
|
||||
@ajax(
|
||||
id: 'getting_started'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/getting_started"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# check if import is active
|
||||
if data.import_mode == true && data.import_backend != 'freshdesk'
|
||||
@navigate "#import/#{data.import_backend}", { emptyEl: true }
|
||||
return
|
||||
|
||||
# render page
|
||||
@render()
|
||||
|
||||
if data.import_mode == true
|
||||
@showImportState()
|
||||
@updateMigration()
|
||||
)
|
||||
|
||||
render: ->
|
||||
@replaceWith App.view('import/freshdesk')(
|
||||
freshdeskDomain: @freshdeskDomain
|
||||
)
|
||||
|
||||
updateUrl: (e) =>
|
||||
@urlStatus.attr('data-state', 'loading')
|
||||
@freshdeskSubdomainAddon.attr('style', 'padding-right: 42px')
|
||||
@linkErrorMessage.text('')
|
||||
|
||||
# get data
|
||||
callback = =>
|
||||
@ajax(
|
||||
id: 'import_freshdesk_url'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/import/freshdesk/url_check"
|
||||
data: JSON.stringify(url: "https://#{@freshdeskSubdomain.val()}#{@freshdeskDomain}")
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# validate form
|
||||
if data.result is 'ok'
|
||||
@urlStatus.attr('data-state', 'success')
|
||||
@linkErrorMessage.text('')
|
||||
@nextEnterCredentials.removeClass('hide')
|
||||
else
|
||||
@urlStatus.attr('data-state', 'error')
|
||||
@linkErrorMessage.text( data.message_human || data.message)
|
||||
@nextEnterCredentials.addClass('hide')
|
||||
|
||||
)
|
||||
@delay( callback, 700, 'import_freshdesk_url' )
|
||||
|
||||
updateApiToken: (e) =>
|
||||
@urlStatus.attr('data-state', 'loading')
|
||||
@apiTokenErrorMessage.text('')
|
||||
|
||||
# get data
|
||||
callback = =>
|
||||
@ajax(
|
||||
id: 'import_freshdesk_api_token'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/import/freshdesk/credentials_check"
|
||||
data: JSON.stringify(token: @freshdeskApiToken.val())
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# validate form
|
||||
if data.result is 'ok'
|
||||
@urlStatus.attr('data-state', 'success')
|
||||
@apiTokenErrorMessage.text('')
|
||||
@nextStartMigration.removeClass('hide')
|
||||
else
|
||||
@urlStatus.attr('data-state', 'error')
|
||||
@apiTokenErrorMessage.text(data.message_human || data.message)
|
||||
@nextStartMigration.addClass('hide')
|
||||
|
||||
)
|
||||
@delay(callback, 700, 'import_freshdesk_api_token')
|
||||
|
||||
showCredentials: (e) =>
|
||||
e.preventDefault()
|
||||
@urlStatus.attr('data-state', '')
|
||||
@$('[data-slide=freshdesk-subdomain]').toggleClass('hide')
|
||||
@$('[data-slide=freshdesk-credentials]').toggleClass('hide')
|
||||
|
||||
showImportState: =>
|
||||
@$('[data-slide=freshdesk-subdomain]').addClass('hide')
|
||||
@$('[data-slide=freshdesk-credentials]').addClass('hide')
|
||||
@$('[data-slide=freshdesk-import]').removeClass('hide')
|
||||
|
||||
startMigration: (e) =>
|
||||
e.preventDefault()
|
||||
@showImportState()
|
||||
@ajax(
|
||||
id: 'import_start'
|
||||
type: 'POST'
|
||||
url: "#{@apiPath}/import/freshdesk/import_start"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# validate form
|
||||
if data.result is 'ok'
|
||||
@delay(@updateMigration, 3000)
|
||||
)
|
||||
|
||||
updateMigration: =>
|
||||
@updateMigrationDisplayLoop += 1
|
||||
@showImportState()
|
||||
@ajax(
|
||||
id: 'import_status'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/import/freshdesk/import_status"
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
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 !_.isEmpty(data.result['error'])
|
||||
@$('.js-error').removeClass('hide')
|
||||
@$('.js-error').html(App.i18n.translateContent(data.result['error']))
|
||||
else
|
||||
@$('.js-error').addClass('hide')
|
||||
|
||||
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
|
||||
|
||||
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')
|
||||
@delay(@updateMigration, 5000)
|
||||
)
|
||||
|
||||
App.Config.set('import/freshdesk', ImportFreshdesk, 'Routes')
|
||||
App.Config.set('freshdesk', {
|
||||
title: 'Freshdesk'
|
||||
name: 'Freshdesk'
|
||||
class: 'js-freshdesk'
|
||||
url: '#import/freshdesk'
|
||||
}, 'ImportPlugins')
|
|
@ -73,14 +73,13 @@ class ImportZendesk extends App.ControllerWizardFullScreen
|
|||
success: (data, status, xhr) =>
|
||||
|
||||
# validate form
|
||||
console.log(data)
|
||||
if data.result is 'ok'
|
||||
@urlStatus.attr('data-state', 'success')
|
||||
@linkErrorMessage.text('')
|
||||
@nextEnterCredentials.removeClass('hide')
|
||||
else
|
||||
@urlStatus.attr('data-state', 'error')
|
||||
@linkErrorMessage.text( data.message_human || data.message)
|
||||
@linkErrorMessage.text( data.message_human || data.message)
|
||||
@nextEnterCredentials.addClass('hide')
|
||||
|
||||
)
|
||||
|
@ -101,14 +100,13 @@ class ImportZendesk extends App.ControllerWizardFullScreen
|
|||
success: (data, status, xhr) =>
|
||||
|
||||
# validate form
|
||||
console.log(data)
|
||||
if data.result is 'ok'
|
||||
@urlStatus.attr('data-state', 'success')
|
||||
@apiTokenErrorMessage.text('')
|
||||
@nextStartMigration.removeClass('hide')
|
||||
else
|
||||
@urlStatus.attr('data-state', 'error')
|
||||
@apiTokenErrorMessage.text(data.message_human || data.message)
|
||||
@apiTokenErrorMessage.text(data.message_human || data.message)
|
||||
@nextStartMigration.addClass('hide')
|
||||
|
||||
)
|
||||
|
@ -139,7 +137,6 @@ class ImportZendesk extends App.ControllerWizardFullScreen
|
|||
success: (data, status, xhr) =>
|
||||
|
||||
# validate form
|
||||
console.log(data)
|
||||
if data.result is 'ok'
|
||||
@delay(@updateMigration, 3000)
|
||||
)
|
||||
|
|
109
app/assets/javascripts/app/views/import/freshdesk.jst.eco
Normal file
109
app/assets/javascripts/app/views/import/freshdesk.jst.eco
Normal file
|
@ -0,0 +1,109 @@
|
|||
<div class="main flex vertical centered darkBackground">
|
||||
<%- @Icon('full-logo', 'wizard-logo') %>
|
||||
<div class="import wizard">
|
||||
<div class="wizard-slide vertical" data-slide="freshdesk-subdomain">
|
||||
<h2><%- @T('%s URL', 'Freshdesk') %></h2>
|
||||
<div class="wizard-body flex vertical justified">
|
||||
<p>
|
||||
<%- @T('Enter the Subdomain of your %s system', 'Freshdesk') %>:
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="freshdesk-subdomain"><%- @T('%s Subdomain', 'Freshdesk') %></label>
|
||||
<div class="u-positionOrigin">
|
||||
<div class="input-group">
|
||||
<input type="text" id="freshdesk-subdomain" class="form-control" placeholder="example" name="freshdesk-subdomain" aria-describedby="freshdesk-subdomain-addon">
|
||||
<span class="input-group-addon" id="freshdesk-subdomain-addon"><%- @freshdeskDomain %></span>
|
||||
</div>
|
||||
<div class="input-feedback input-feedback--no-background centered">
|
||||
<div class="small loading icon"></div>
|
||||
<%- @Icon('diagonal-cross', 'icon-error') %>
|
||||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="error freshdesk-subdomain-error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-controls horizontal center">
|
||||
<a class="btn btn--text btn--secondary" href="#import"><%- @T('Go Back') %></a>
|
||||
<div class="btn btn--primary align-right hide js-freshdesk-credentials" data-target="freshdesk-credentials"><%- @T('Enter credentials') %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-slide vertical hide" data-slide="freshdesk-credentials">
|
||||
<h2><%- @T('%s credentials', 'Freshdesk') %></h2>
|
||||
<div class="wizard-body flex vertical justified">
|
||||
<p>
|
||||
<a class="js-freshdeskUrlApiToken" href="https://support.freshdesk.com/support/solutions/articles/215517-how-to-find-your-api-key" target="_blank"><%- @T('Enter your %s API token gained from your account profile settings.', 'Freshdesk') %></a>
|
||||
</p>
|
||||
<p>
|
||||
<%- @T('Attention: These will be your login password after the import is completed.') %>
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="freshdesk-api-token"><%- @T('API token') %></label>
|
||||
<div class="u-positionOrigin">
|
||||
<input type="text" id="freshdesk-api-token" class="form-control" placeholder="XYZ3133723421111" name="freshdesk-api-token">
|
||||
<div class="input-feedback centered">
|
||||
<div class="small loading icon"></div>
|
||||
<%- @Icon('diagonal-cross', 'icon-error') %>
|
||||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="error freshdesk-api-token-error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wizard-controls horizontal center">
|
||||
<a class="btn btn--text btn--secondary" href="#import"><%- @T('Go Back') %></a>
|
||||
<div class="btn btn--primary align-right hide js-migration-start" data-target="freshdesk-start-migration"><%- @T('Migrate %s Data', 'Freshdesk') %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-slide vertical hide" data-slide="freshdesk-import">
|
||||
<h2><%- @T('%s Migration', 'Freshdesk') %></h2>
|
||||
<div class="alert alert--danger hide js-error" role="alert"></div>
|
||||
<div class="wizard-body flex vertical justified">
|
||||
<table class="progressTable">
|
||||
<tr class="js-groups">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Groups') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
<div class="horizontal center">
|
||||
<div class="flex"><progress max="42" value="42"></progress></div>
|
||||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
<tr class="js-organizations">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Organizations') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
<div class="horizontal center">
|
||||
<div class="flex"><progress max="42" value="42"></progress></div>
|
||||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
<tr class="js-users">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Users') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
<div class="horizontal center">
|
||||
<div class="flex"><progress max="42" value="42"></progress></div>
|
||||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
<tr class="js-tickets">
|
||||
<td><span class="js-done">-</span>/<span class="js-total">-</span>
|
||||
<td><span><%- @T('Tickets') %></span>
|
||||
<td class="progressTable-progressCell">
|
||||
<div class="horizontal center">
|
||||
<div class="flex"><progress max="42" value="42"></progress></div>
|
||||
<%- @Icon('checkmark') %>
|
||||
</div>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="wizard-controls horizontal center">
|
||||
<a href="#" class="btn btn--primary align-right hide js-finished"><%- @T('done') %></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
<label for="zendesk-api-token"><%- @T('API token') %></label>
|
||||
<div class="u-positionOrigin">
|
||||
<input type="email" id="zendesk-api-token" class="form-control" placeholder="XYZ3133723421111" name="zendesk-api-token">
|
||||
<input type="text" id="zendesk-api-token" class="form-control" placeholder="XYZ3133723421111" name="zendesk-api-token">
|
||||
<div class="input-feedback centered">
|
||||
<div class="small loading icon"></div>
|
||||
<%- @Icon('diagonal-cross', 'icon-error') %>
|
||||
|
@ -108,4 +108,4 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9196,6 +9196,14 @@ label + .wizard-buttonList {
|
|||
width: 52px;
|
||||
border-radius: 0 5px 5px 0;
|
||||
background: linear-gradient(to right, rgba(255,255,255,0), white 33%);
|
||||
|
||||
&--no-background {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-feedback--no-background {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.input-feedback .icon {
|
||||
|
|
143
app/controllers/import_freshdesk_controller.rb
Normal file
143
app/controllers/import_freshdesk_controller.rb
Normal file
|
@ -0,0 +1,143 @@
|
|||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
class ImportFreshdeskController < ApplicationController
|
||||
|
||||
def url_check
|
||||
return if setup_done_response
|
||||
|
||||
# validate
|
||||
if params[:url].blank? || params[:url] !~ %r{^(http|https)://.+?$}
|
||||
render json: {
|
||||
result: 'invalid',
|
||||
message: 'Invalid URL!',
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
response = UserAgent.request(params[:url])
|
||||
|
||||
if !response.success?
|
||||
render json: {
|
||||
result: 'invalid',
|
||||
message_human: url_check_human_error_message(response.error.to_s),
|
||||
message: response.error.to_s,
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
# Check if maybe a redirect is implemented.
|
||||
if !response.body.match?(%r{#{params[:url]}})
|
||||
render json: {
|
||||
result: 'invalid',
|
||||
message_human: 'Hostname not found!',
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
endpoint = "#{params[:url]}/api/v2"
|
||||
endpoint.gsub!(%r{([^:])//+}, '\\1/')
|
||||
|
||||
Setting.set('import_freshdesk_endpoint', endpoint)
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
url: params[:url],
|
||||
}
|
||||
end
|
||||
|
||||
def credentials_check
|
||||
return if setup_done_response
|
||||
|
||||
if !params[:token]
|
||||
|
||||
render json: {
|
||||
result: 'invalid',
|
||||
message_human: 'Incomplete credentials',
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
Setting.set('import_freshdesk_endpoint_key', params[:token])
|
||||
|
||||
result = Sequencer.process('Import::Freshdesk::ConnectionTest')
|
||||
|
||||
if !result[:connected]
|
||||
|
||||
Setting.set('import_freshdesk_endpoint_key', nil)
|
||||
|
||||
render json: {
|
||||
result: 'invalid',
|
||||
message_human: 'Invalid credentials!',
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
}
|
||||
end
|
||||
|
||||
def import_start
|
||||
return if setup_done_response
|
||||
|
||||
Setting.set('import_mode', true)
|
||||
Setting.set('import_backend', 'freshdesk')
|
||||
|
||||
job = ImportJob.create(name: 'Import::Freshdesk')
|
||||
AsyncImportJob.perform_later(job)
|
||||
|
||||
render json: {
|
||||
result: 'ok',
|
||||
}
|
||||
end
|
||||
|
||||
def import_status
|
||||
job = ImportJob.find_by(name: 'Import::Freshdesk')
|
||||
|
||||
if job.finished_at.present?
|
||||
Setting.reload
|
||||
end
|
||||
|
||||
model_show_render_item(job)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_done
|
||||
count = User.all.count()
|
||||
done = true
|
||||
if count <= 2
|
||||
done = false
|
||||
end
|
||||
done
|
||||
end
|
||||
|
||||
def setup_done_response
|
||||
if !setup_done
|
||||
return false
|
||||
end
|
||||
|
||||
render json: {
|
||||
setup_done: true,
|
||||
}
|
||||
true
|
||||
end
|
||||
|
||||
def url_check_human_error_message(error)
|
||||
translation_map = {
|
||||
'No such file' => 'Hostname not found!',
|
||||
'getaddrinfo: nodename nor servname provided, or not known' => 'Hostname not found!',
|
||||
'No route to host' => 'No route to host!',
|
||||
'Connection refused' => 'Connection refused!',
|
||||
}
|
||||
|
||||
message_human = ''
|
||||
translation_map.each do |key, message|
|
||||
if error.match?(%r{#{Regexp.escape(key)}}i)
|
||||
message_human = message
|
||||
end
|
||||
end
|
||||
|
||||
message_human
|
||||
end
|
||||
|
||||
end
|
|
@ -50,6 +50,7 @@ class ImportZendeskController < ApplicationController
|
|||
|
||||
endpoint = "#{params[:url]}/api/v2"
|
||||
endpoint.gsub!(%r{([^:])//+}, '\\1/')
|
||||
|
||||
Setting.set('import_zendesk_endpoint', endpoint)
|
||||
|
||||
render json: {
|
||||
|
|
10
config/routes/import_freshdesk.rb
Normal file
10
config/routes/import_freshdesk.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
Zammad::Application.routes.draw do
|
||||
api_path = Rails.configuration.api_path
|
||||
|
||||
# import freshdesk
|
||||
match api_path + '/import/freshdesk/url_check', to: 'import_freshdesk#url_check', via: :post
|
||||
match api_path + '/import/freshdesk/credentials_check', to: 'import_freshdesk#credentials_check', via: :post
|
||||
match api_path + '/import/freshdesk/import_start', to: 'import_freshdesk#import_start', via: :post
|
||||
match api_path + '/import/freshdesk/import_status', to: 'import_freshdesk#import_status', via: :get
|
||||
|
||||
end
|
|
@ -3142,6 +3142,44 @@ Setting.create_if_not_exists(
|
|||
state: '',
|
||||
frontend: false
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Import Endpoint',
|
||||
name: 'import_freshdesk_endpoint',
|
||||
area: 'Import::Freshdesk',
|
||||
description: 'Defines Freshdesk endpoint to import users, ticket, states and articles.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: false,
|
||||
name: 'import_freshdesk_endpoint',
|
||||
tag: 'input',
|
||||
},
|
||||
],
|
||||
},
|
||||
state: 'https://yours.freshdesk.com/api/v2',
|
||||
frontend: false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Import Key for requesting the Freshdesk API',
|
||||
name: 'import_freshdesk_endpoint_key',
|
||||
area: 'Import::Freshdesk',
|
||||
description: 'Defines Freshdesk endpoint authentication key.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: false,
|
||||
name: 'import_freshdesk_endpoint_key',
|
||||
tag: 'input',
|
||||
},
|
||||
],
|
||||
},
|
||||
state: '',
|
||||
frontend: false
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Import Backends',
|
||||
name: 'import_backends',
|
||||
|
|
|
@ -57,7 +57,7 @@ module Import
|
|||
#
|
||||
# return [nil]
|
||||
def start
|
||||
raise "Missing implementation if the 'start' method."
|
||||
raise "Missing implementation of the 'start' method."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
15
lib/import/freshdesk.rb
Normal file
15
lib/import/freshdesk.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Import
|
||||
class Freshdesk < Import::Base
|
||||
include Import::Mixin::Sequence
|
||||
|
||||
def start
|
||||
process
|
||||
end
|
||||
|
||||
def sequence_name
|
||||
'Import::Freshdesk::Full'
|
||||
end
|
||||
end
|
||||
end
|
26
lib/sequencer/sequence/import/freshdesk/agent.rb
Normal file
26
lib/sequencer/sequence/import/freshdesk/agent.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Agent < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::User',
|
||||
'Import::Freshdesk::Agent::Mapping',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::FindBy::Name',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Freshdesk::MapId',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
lib/sequencer/sequence/import/freshdesk/company.rb
Normal file
27
lib/sequencer/sequence/import/freshdesk/company.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Company < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Organization',
|
||||
'Import::Freshdesk::Company::Mapping',
|
||||
'Import::Freshdesk::Mapping::CustomFields',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::FindBy::Name',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Freshdesk::MapId',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/sequence/import/freshdesk/company_field.rb
Normal file
22
lib/sequencer/sequence/import/freshdesk/company_field.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class CompanyField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Organization',
|
||||
'Import::Freshdesk::ObjectAttribute::Skip',
|
||||
'Import::Freshdesk::ObjectAttribute::SanitizedName',
|
||||
'Import::Freshdesk::ObjectAttribute::Config',
|
||||
'Import::Freshdesk::ObjectAttribute::Add',
|
||||
'Import::Freshdesk::ObjectAttribute::MigrationExecute',
|
||||
'Import::Freshdesk::ObjectAttribute::FieldMap',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
lib/sequencer/sequence/import/freshdesk/connection_test.rb
Normal file
20
lib/sequencer/sequence/import/freshdesk/connection_test.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class ConnectionTest < Sequencer::Sequence::Base
|
||||
|
||||
def self.expecting
|
||||
[:connected]
|
||||
end
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Freshdesk::Connected',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
lib/sequencer/sequence/import/freshdesk/contact.rb
Normal file
27
lib/sequencer/sequence/import/freshdesk/contact.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Contact < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::User',
|
||||
'Import::Freshdesk::Contact::Mapping',
|
||||
'Import::Freshdesk::Mapping::CustomFields',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::FindBy::Name',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Freshdesk::MapId',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/sequence/import/freshdesk/contact_field.rb
Normal file
22
lib/sequencer/sequence/import/freshdesk/contact_field.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class ContactField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::User',
|
||||
'Import::Freshdesk::ObjectAttribute::Skip',
|
||||
'Import::Freshdesk::ObjectAttribute::SanitizedName',
|
||||
'Import::Freshdesk::ObjectAttribute::Config',
|
||||
'Import::Freshdesk::ObjectAttribute::Add',
|
||||
'Import::Freshdesk::ObjectAttribute::MigrationExecute',
|
||||
'Import::Freshdesk::ObjectAttribute::FieldMap',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
lib/sequencer/sequence/import/freshdesk/conversation.rb
Normal file
23
lib/sequencer/sequence/import/freshdesk/conversation.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Conversation < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Ticket::Article',
|
||||
'Import::Freshdesk::Conversation::Mapping',
|
||||
'Import::Freshdesk::Conversation::InlineImages',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Freshdesk::MapId',
|
||||
'Import::Freshdesk::Conversation::Attachments',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/sequence/import/freshdesk/conversations.rb
Normal file
19
lib/sequencer/sequence/import/freshdesk/conversations.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Conversations < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Sequencer::Unit::Import::Freshdesk::Request',
|
||||
'Import::Freshdesk::Resources',
|
||||
'Import::Freshdesk::ModelClass',
|
||||
'Import::Freshdesk::Perform',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
lib/sequencer/sequence/import/freshdesk/description.rb
Normal file
25
lib/sequencer/sequence/import/freshdesk/description.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Description < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Ticket::Article',
|
||||
'Import::Freshdesk::Description::Mapping',
|
||||
# Handling of inline images and attachments is the same for first article (description)
|
||||
# and subsequent articles (conversation).
|
||||
'Import::Freshdesk::Conversation::InlineImages',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Freshdesk::MapId',
|
||||
'Import::Freshdesk::Conversation::Attachments',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/sequencer/sequence/import/freshdesk/full.rb
Normal file
33
lib/sequencer/sequence/import/freshdesk/full.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Full < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Common::ImportMode::Check',
|
||||
'Import::Common::SystemInitDone::Check',
|
||||
'Import::Common::ImportJob::DryRun',
|
||||
'Import::Freshdesk::IdMap',
|
||||
'Import::Freshdesk::Groups',
|
||||
'Import::Freshdesk::FieldMap',
|
||||
'Import::Freshdesk::CompanyFields',
|
||||
'Import::Freshdesk::Companies',
|
||||
'Import::Freshdesk::Agents',
|
||||
'Import::Freshdesk::Agents::GroupsPermissions',
|
||||
'Import::Freshdesk::ContactFields',
|
||||
'Import::Freshdesk::Contacts::Default',
|
||||
'Import::Freshdesk::Contacts::Blocked',
|
||||
'Import::Freshdesk::Contacts::Deleted',
|
||||
'Import::Freshdesk::TicketFields',
|
||||
'Import::Freshdesk::Tickets',
|
||||
'Import::Common::SystemInitDone::Set',
|
||||
'Import::Common::ImportMode::Unset',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/sequencer/sequence/import/freshdesk/generic_field.rb
Normal file
18
lib/sequencer/sequence/import/freshdesk/generic_field.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class GenericField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Freshdesk::Request',
|
||||
'Import::Freshdesk::Resources',
|
||||
'Import::Freshdesk::Perform',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/sequence/import/freshdesk/generic_object.rb
Normal file
22
lib/sequencer/sequence/import/freshdesk/generic_object.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class GenericObject < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Import::Freshdesk::Request',
|
||||
'Import::Freshdesk::Resources',
|
||||
'Import::Freshdesk::ModelClass',
|
||||
'Import::Freshdesk::ObjectCount',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
'Import::Freshdesk::Perform',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
lib/sequencer/sequence/import/freshdesk/group.rb
Normal file
26
lib/sequencer/sequence/import/freshdesk/group.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Group < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Group',
|
||||
'Import::Freshdesk::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::Freshdesk::MapId',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/sequencer/sequence/import/freshdesk/ticket.rb
Normal file
33
lib/sequencer/sequence/import/freshdesk/ticket.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Ticket < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Ticket',
|
||||
# Fetch additional data such as attachments which is not included
|
||||
# in the ticket list endpoint.
|
||||
'Import::Freshdesk::Ticket::Fetch',
|
||||
'Import::Freshdesk::Ticket::Mapping',
|
||||
'Import::Freshdesk::Mapping::CustomFields',
|
||||
'Import::Common::Model::Attributes::AddByIds',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
'Import::Freshdesk::MapId',
|
||||
'Import::Freshdesk::Ticket::Tags',
|
||||
'Import::Freshdesk::Ticket::TimeEntries',
|
||||
'Import::Freshdesk::Ticket::Description',
|
||||
'Import::Freshdesk::Ticket::Conversations',
|
||||
'Import::Common::Model::Statistics::Diff::ModelKey',
|
||||
'Import::Common::ImportJob::Statistics::Update',
|
||||
'Import::Common::ImportJob::Statistics::Store',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/sequence/import/freshdesk/ticket_field.rb
Normal file
22
lib/sequencer/sequence/import/freshdesk/ticket_field.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class TicketField < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Ticket',
|
||||
'Import::Freshdesk::ObjectAttribute::Skip',
|
||||
'Import::Freshdesk::ObjectAttribute::SanitizedName',
|
||||
'Import::Freshdesk::ObjectAttribute::Config',
|
||||
'Import::Freshdesk::ObjectAttribute::Add',
|
||||
'Import::Freshdesk::ObjectAttribute::MigrationExecute',
|
||||
'Import::Freshdesk::ObjectAttribute::FieldMap',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/sequence/import/freshdesk/time_entries.rb
Normal file
19
lib/sequencer/sequence/import/freshdesk/time_entries.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class TimeEntries < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Sequencer::Unit::Import::Freshdesk::Request',
|
||||
'Import::Freshdesk::Resources',
|
||||
'Import::Freshdesk::ModelClass',
|
||||
'Import::Freshdesk::Perform',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
lib/sequencer/sequence/import/freshdesk/time_entry.rb
Normal file
20
lib/sequencer/sequence/import/freshdesk/time_entry.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Sequence
|
||||
module Import
|
||||
module Freshdesk
|
||||
class TimeEntry < Sequencer::Sequence::Base
|
||||
|
||||
def self.sequence
|
||||
[
|
||||
'Common::ModelClass::Ticket::TimeAccounting',
|
||||
'Import::Freshdesk::TimeEntry::Mapping',
|
||||
'Import::Common::Model::Update',
|
||||
'Import::Common::Model::Create',
|
||||
'Import::Common::Model::Save',
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
30
lib/sequencer/unit/common/model/tags.rb
Normal file
30
lib/sequencer/unit/common/model/tags.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module Model
|
||||
class Tags < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_action :skipped, :failed
|
||||
|
||||
uses :dry_run, :instance
|
||||
|
||||
def process
|
||||
return if dry_run
|
||||
return if tags.blank?
|
||||
|
||||
Array(tags).each do |tag|
|
||||
instance.tag_add(tag, 1)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tags
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Common
|
||||
module ModelClass
|
||||
class Ticket < Sequencer::Unit::Common::ModelClass::Base
|
||||
class TimeAccounting < Sequencer::Unit::Common::ModelClass::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/unit/freshdesk/connected.rb
Normal file
22
lib/sequencer/unit/freshdesk/connected.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Freshdesk
|
||||
class Connected < Sequencer::Unit::Common::Provider::Named
|
||||
extend ::Sequencer::Unit::Import::Freshdesk::Requester
|
||||
|
||||
private
|
||||
|
||||
def connected
|
||||
response = self.class.perform_request(
|
||||
api_path: 'agents/me',
|
||||
)
|
||||
|
||||
response.is_a?(Net::HTTPOK)
|
||||
rescue => e
|
||||
logger.error e
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
76
lib/sequencer/unit/import/freshdesk/agent/mapping.rb
Normal file
76
lib/sequencer/unit/import/freshdesk/agent/mapping.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Agent
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
extend ::Sequencer::Unit::Import::Freshdesk::Requester
|
||||
|
||||
uses :resource, :id_map
|
||||
|
||||
def process
|
||||
contact = resource['contact']
|
||||
|
||||
provide_mapped do
|
||||
{
|
||||
login: contact['email'],
|
||||
firstname: contact['name'],
|
||||
email: contact['email'],
|
||||
phone: contact['phone'],
|
||||
active: contact['active'],
|
||||
group_ids: group_ids,
|
||||
password: password,
|
||||
image_source: contact['last_login_at'],
|
||||
role_ids: ::Role.where(name: role_names).pluck(:id),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def self.admin_id
|
||||
@admin_id ||= begin
|
||||
token_user = self.token_user
|
||||
token_user.try(:[], 'id')
|
||||
end
|
||||
end
|
||||
|
||||
def self.token_user
|
||||
response = request(
|
||||
api_path: 'agents/me',
|
||||
)
|
||||
|
||||
JSON.parse(response.body)
|
||||
rescue => e
|
||||
logger.error e
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_ids
|
||||
Array(resource['group_ids']).map do |group_id|
|
||||
id_map['Group'][group_id]
|
||||
end
|
||||
end
|
||||
|
||||
def role_names
|
||||
return %w[Agent Admin] if token_user?
|
||||
|
||||
'Agent'
|
||||
end
|
||||
|
||||
def password
|
||||
return Setting.get('import_freshdesk_endpoint_key') if token_user?
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def token_user?
|
||||
self.class.admin_id == resource['id']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/agents.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/agents.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Agents < Sequencer::Unit::Import::Freshdesk::SubSequence::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Agents < Sequencer::Unit::Import::Freshdesk::SubSequence::Object
|
||||
class GroupsPermissions < Sequencer::Unit::Base
|
||||
|
||||
def process
|
||||
::Role.find_by(name: 'Agent').users.each do |user|
|
||||
user.group_ids_access_map = group_ids_access_map
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_ids_access_map
|
||||
@group_ids_access_map ||= begin
|
||||
::Group.all.pluck(:id).each_with_object({}) do |group_id, result|
|
||||
result[group_id] = 'full'.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/companies.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/companies.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Companies < Sequencer::Unit::Import::Freshdesk::SubSequence::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/sequencer/unit/import/freshdesk/company/mapping.rb
Normal file
24
lib/sequencer/unit/import/freshdesk/company/mapping.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Company
|
||||
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['description'],
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/company_fields.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/company_fields.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class CompanyFields < Sequencer::Unit::Import::Freshdesk::SubSequence::Field
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
37
lib/sequencer/unit/import/freshdesk/contact/mapping.rb
Normal file
37
lib/sequencer/unit/import/freshdesk/contact/mapping.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Contact
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource, :id_map
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
{
|
||||
firstname: resource['name'],
|
||||
active: resource['active'],
|
||||
organization_id: organization_id,
|
||||
email: resource['email'],
|
||||
mobile: resource['mobile'],
|
||||
phone: resource['phone'],
|
||||
image_source: resource['avatar'],
|
||||
group_ids: [],
|
||||
role_ids: ::Role.where(name: 'Customer').pluck(:id),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def organization_id
|
||||
id_map['Organization'][resource['company_id']]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/contact_fields.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/contact_fields.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class ContactFields < Sequencer::Unit::Import::Freshdesk::SubSequence::Field
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/unit/import/freshdesk/contacts/blocked.rb
Normal file
19
lib/sequencer/unit/import/freshdesk/contacts/blocked.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Contacts
|
||||
class Blocked < Sequencer::Unit::Import::Freshdesk::Contacts::Default
|
||||
|
||||
def request_params
|
||||
super.merge(
|
||||
state: 'blocked',
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
lib/sequencer/unit/import/freshdesk/contacts/default.rb
Normal file
16
lib/sequencer/unit/import/freshdesk/contacts/default.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Contacts
|
||||
class Default < Sequencer::Unit::Import::Freshdesk::SubSequence::Object
|
||||
|
||||
def object
|
||||
'Contact'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/unit/import/freshdesk/contacts/deleted.rb
Normal file
19
lib/sequencer/unit/import/freshdesk/contacts/deleted.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Contacts
|
||||
class Deleted < Sequencer::Unit::Import::Freshdesk::Contacts::Default
|
||||
|
||||
def request_params
|
||||
super.merge(
|
||||
state: 'deleted',
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Conversation
|
||||
class Attachments < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
optional :action
|
||||
|
||||
skip_action :skipped, :failed
|
||||
|
||||
uses :resource, :instance, :model_class, :dry_run
|
||||
|
||||
def self.mutex
|
||||
@mutex ||= Mutex.new
|
||||
end
|
||||
|
||||
def process
|
||||
return if resource['attachments'].blank?
|
||||
|
||||
download_threads.each(&:join)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def download_threads
|
||||
resource['attachments'].map do |attachment|
|
||||
Thread.new do
|
||||
sync(attachment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sync(attachment)
|
||||
logger.debug { "Downloading attachment #{attachment}" }
|
||||
|
||||
response = ::UserAgent.get(
|
||||
attachment['attachment_url'],
|
||||
{},
|
||||
{
|
||||
open_timeout: 20,
|
||||
read_timeout: 240,
|
||||
},
|
||||
)
|
||||
|
||||
if !response.success?
|
||||
logger.error response.error
|
||||
return
|
||||
end
|
||||
|
||||
return if dry_run
|
||||
|
||||
store_attachment(attachment, response)
|
||||
|
||||
end
|
||||
|
||||
def store_attachment(attachment, response)
|
||||
|
||||
self.class.mutex.synchronize do
|
||||
::Store.add(
|
||||
object: model_class.name,
|
||||
o_id: instance.id,
|
||||
data: response.body,
|
||||
filename: attachment['name'],
|
||||
preferences: {
|
||||
'Content-Type' => attachment['content_type']
|
||||
},
|
||||
created_by_id: 1
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Conversation
|
||||
class InlineImages < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :mapped
|
||||
|
||||
def process
|
||||
return if !contains_inline_image?(mapped[:body])
|
||||
|
||||
provide_mapped do
|
||||
{
|
||||
body: replaced_inline_images,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def self.inline_data(freshdesk_url)
|
||||
@cache ||= {}
|
||||
return @cache[freshdesk_url] if @cache[freshdesk_url]
|
||||
|
||||
image_data = download(freshdesk_url)
|
||||
return if image_data.blank?
|
||||
|
||||
@cache[freshdesk_url] = "data:image/png;base64,#{Base64.strict_encode64(image_data)}"
|
||||
@cache[freshdesk_url]
|
||||
end
|
||||
|
||||
def self.download(freshdesk_url)
|
||||
logger.debug { "Downloading inline image from #{freshdesk_url}" }
|
||||
|
||||
response = UserAgent.get(
|
||||
freshdesk_url,
|
||||
{},
|
||||
{
|
||||
open_timeout: 20,
|
||||
read_timeout: 240,
|
||||
},
|
||||
)
|
||||
|
||||
return response.body if response.success?
|
||||
|
||||
logger.error response.error
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contains_inline_image?(string)
|
||||
return false if string.blank?
|
||||
|
||||
string.include?('freshdesk.com/inline/attachment')
|
||||
end
|
||||
|
||||
def replaced_inline_images
|
||||
body_html = Nokogiri::HTML(mapped[:body])
|
||||
|
||||
body_html.css('img').each do |node|
|
||||
next if !contains_inline_image?(node['src'])
|
||||
|
||||
node.attributes['src'].value = self.class.inline_data(node['src'])
|
||||
end
|
||||
|
||||
body_html.to_html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
66
lib/sequencer/unit/import/freshdesk/conversation/mapping.rb
Normal file
66
lib/sequencer/unit/import/freshdesk/conversation/mapping.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Conversation
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource, :id_map
|
||||
|
||||
SOURCE_MAP = {
|
||||
0 => ::Ticket::Article::Type.select(:id).find_by(name: 'email').id, # Reply
|
||||
1 => ::Ticket::Article::Type.select(:id).find_by(name: 'email').id, # Email
|
||||
2 => ::Ticket::Article::Type.select(:id).find_by(name: 'web').id, # Note
|
||||
3 => ::Ticket::Article::Type.select(:id).find_by(name: 'phone').id, # Phone
|
||||
4 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # UNKNOWN!
|
||||
5 => ::Ticket::Article::Type.select(:id).find_by(name: 'twitter status').id, # Created from tweets
|
||||
6 => ::Ticket::Article::Type.select(:id).find_by(name: 'web').id, # Created from survey feedback
|
||||
7 => ::Ticket::Article::Type.select(:id).find_by(name: 'facebook feed post').id, # Created from Facebook post
|
||||
8 => ::Ticket::Article::Type.select(:id).find_by(name: 'email').id, # Created from Forwarded Email
|
||||
9 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # Created from Phone
|
||||
10 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # Created from Mobihelp
|
||||
11 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # E-Commerce
|
||||
}.freeze
|
||||
|
||||
INCOMING_MAP = {
|
||||
true => ::Ticket::Article::Sender.select(:id).find_by(name: 'Customer').id,
|
||||
false => ::Ticket::Article::Sender.select(:id).find_by(name: 'Agent').id,
|
||||
}.freeze
|
||||
|
||||
def process # rubocop:disable Metrics/AbcSize
|
||||
provide_mapped do
|
||||
{
|
||||
from: resource['from_email'],
|
||||
to: resource['to_emails']&.join(', '),
|
||||
cc: resource['cc_emails']&.join(', '),
|
||||
ticket_id: ticket_id,
|
||||
body: resource['body'],
|
||||
content_type: 'text/html',
|
||||
internal: resource['private'].present?,
|
||||
message_id: resource['id'],
|
||||
updated_by_id: user_id,
|
||||
created_by_id: user_id,
|
||||
sender_id: INCOMING_MAP[ resource['incoming'] ],
|
||||
type_id: SOURCE_MAP[ resource['source'] ],
|
||||
created_at: resource['created_at'],
|
||||
updated_at: resource['updated_at'],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ticket_id
|
||||
id_map['Ticket'][resource['ticket_id']]
|
||||
end
|
||||
|
||||
def user_id
|
||||
id_map['User'][resource['user_id']]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
67
lib/sequencer/unit/import/freshdesk/description/mapping.rb
Normal file
67
lib/sequencer/unit/import/freshdesk/description/mapping.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Description
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource, :id_map
|
||||
|
||||
SOURCE_MAP = {
|
||||
0 => ::Ticket::Article::Type.select(:id).find_by(name: 'email').id, # Reply
|
||||
1 => ::Ticket::Article::Type.select(:id).find_by(name: 'email').id, # Email
|
||||
2 => ::Ticket::Article::Type.select(:id).find_by(name: 'web').id, # Note
|
||||
3 => ::Ticket::Article::Type.select(:id).find_by(name: 'phone').id, # Phone
|
||||
4 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # UNKNOWN!
|
||||
5 => ::Ticket::Article::Type.select(:id).find_by(name: 'twitter status').id, # Created from tweets
|
||||
6 => ::Ticket::Article::Type.select(:id).find_by(name: 'web').id, # Created from survey feedback
|
||||
7 => ::Ticket::Article::Type.select(:id).find_by(name: 'facebook feed post').id, # Created from Facebook post
|
||||
8 => ::Ticket::Article::Type.select(:id).find_by(name: 'email').id, # Created from Forwarded Email
|
||||
9 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # Created from Phone
|
||||
10 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # Created from Mobihelp
|
||||
11 => ::Ticket::Article::Type.select(:id).find_by(name: 'note').id, # E-Commerce
|
||||
}.freeze
|
||||
|
||||
def process # rubocop:disable Metrics/AbcSize
|
||||
provide_mapped do
|
||||
{
|
||||
from: from,
|
||||
to: resource['to_emails']&.join(', '),
|
||||
cc: resource['cc_emails']&.join(', '),
|
||||
ticket_id: ticket_id,
|
||||
body: resource['description'],
|
||||
content_type: 'text/html',
|
||||
internal: false,
|
||||
message_id: "ticketid#{resource['id']}@freshdesk.com",
|
||||
sender_id: ::Ticket::Article::Sender.select(:id).find_by(name: 'Customer').id,
|
||||
type_id: SOURCE_MAP[ resource['source'] ],
|
||||
updated_by_id: requester_id,
|
||||
created_by_id: requester_id,
|
||||
created_at: resource['created_at'],
|
||||
updated_at: resource['updated_at'],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def from
|
||||
return nil if resource['to_emails'].blank?
|
||||
|
||||
::User.find(requester_id).email
|
||||
end
|
||||
|
||||
def requester_id
|
||||
id_map['User'][resource['requester_id']]
|
||||
end
|
||||
|
||||
def ticket_id
|
||||
id_map['Ticket'][resource['id']]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
lib/sequencer/unit/import/freshdesk/field_map.rb
Normal file
14
lib/sequencer/unit/import/freshdesk/field_map.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class FieldMap < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
def field_map
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
lib/sequencer/unit/import/freshdesk/group/mapping.rb
Normal file
24
lib/sequencer/unit/import/freshdesk/group/mapping.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
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'],
|
||||
note: resource['description'],
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/groups.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/groups.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Groups < Sequencer::Unit::Import::Freshdesk::SubSequence::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
lib/sequencer/unit/import/freshdesk/id_map.rb
Normal file
14
lib/sequencer/unit/import/freshdesk/id_map.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class IdMap < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
def id_map
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/unit/import/freshdesk/map_id.rb
Normal file
22
lib/sequencer/unit/import/freshdesk/map_id.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class MapId < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
optional :action
|
||||
|
||||
skip_action :skipped, :failed
|
||||
|
||||
uses :id_map, :model_class, :resource, :instance
|
||||
|
||||
def process
|
||||
id_map[model_class.name] ||= {}
|
||||
id_map[model_class.name][resource['id']] = instance.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
lib/sequencer/unit/import/freshdesk/mapping/custom_fields.rb
Normal file
34
lib/sequencer/unit/import/freshdesk/mapping/custom_fields.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Mapping
|
||||
class CustomFields < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource, :field_map, :model_class
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
custom_fields
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_fields
|
||||
resource['custom_fields'].each_with_object({}) do |(freshdesk_name, value), result|
|
||||
local_name = custom_fields_map[freshdesk_name]
|
||||
result[ local_name.to_sym ] = value
|
||||
end
|
||||
end
|
||||
|
||||
def custom_fields_map
|
||||
@custom_fields_map ||= field_map[model_class.name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
lib/sequencer/unit/import/freshdesk/model_class.rb
Normal file
27
lib/sequencer/unit/import/freshdesk/model_class.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class ModelClass < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
uses :object
|
||||
|
||||
MAP = {
|
||||
'Company' => ::Organization,
|
||||
'Agent' => ::User,
|
||||
'Contact' => ::User,
|
||||
'Group' => ::Group,
|
||||
'Ticket' => ::Ticket,
|
||||
'Conversation' => ::Ticket::Article,
|
||||
}.freeze
|
||||
|
||||
private
|
||||
|
||||
def model_class
|
||||
MAP[object]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/sequencer/unit/import/freshdesk/object_attribute/add.rb
Normal file
21
lib/sequencer/unit/import/freshdesk/object_attribute/add.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module ObjectAttribute
|
||||
class Add < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_any_action
|
||||
|
||||
uses :config
|
||||
|
||||
def process
|
||||
ObjectManager::Attribute.add(config)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
127
lib/sequencer/unit/import/freshdesk/object_attribute/config.rb
Normal file
127
lib/sequencer/unit/import/freshdesk/object_attribute/config.rb
Normal file
|
@ -0,0 +1,127 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module ObjectAttribute
|
||||
class Config < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_any_action
|
||||
|
||||
uses :resource, :sanitized_name, :model_class
|
||||
provides :config
|
||||
|
||||
def process
|
||||
state.provide(:config) do
|
||||
{
|
||||
object: model_class.to_s,
|
||||
name: sanitized_name,
|
||||
display: resource['label'],
|
||||
data_type: data_type,
|
||||
data_option: data_option,
|
||||
editable: true,
|
||||
active: true,
|
||||
screens: screens,
|
||||
position: resource['position'],
|
||||
created_by_id: 1,
|
||||
updated_by_id: 1,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
DATA_TYPE_MAP = {
|
||||
'custom_date' => 'date',
|
||||
'custom_checkbox' => 'boolean',
|
||||
'custom_dropdown' => 'select',
|
||||
'custom_text' => 'input',
|
||||
'custom_number' => 'integer',
|
||||
'custom_paragraph' => 'input',
|
||||
'custom_decimal' => 'input', # Don't use 'integer' as it would cut off the fractional part.
|
||||
}.freeze
|
||||
|
||||
def data_type
|
||||
@data_type ||= DATA_TYPE_MAP[resource['type']]
|
||||
end
|
||||
|
||||
def data_option
|
||||
{
|
||||
null: true,
|
||||
note: '',
|
||||
}.merge(data_type_options)
|
||||
end
|
||||
|
||||
def data_type_options
|
||||
|
||||
case data_type
|
||||
when 'date'
|
||||
{
|
||||
future: true,
|
||||
past: true,
|
||||
diff: 0,
|
||||
}
|
||||
when 'boolean'
|
||||
{
|
||||
default: false,
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
}
|
||||
when 'select'
|
||||
{
|
||||
default: '',
|
||||
options: options,
|
||||
}
|
||||
when 'input'
|
||||
{
|
||||
type: 'text',
|
||||
maxlength: 255,
|
||||
}
|
||||
when 'integer'
|
||||
{
|
||||
min: 0,
|
||||
max: 999_999_999,
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def screens
|
||||
{
|
||||
view: {
|
||||
'-all-' => {
|
||||
shown: true,
|
||||
null: true,
|
||||
},
|
||||
Customer: {
|
||||
shown: false,
|
||||
null: true,
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
'-all-' => {
|
||||
shown: true,
|
||||
null: true,
|
||||
},
|
||||
Customer: {
|
||||
shown: false,
|
||||
null: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def options
|
||||
resource['choices'].each_with_object({}) do |choice, result|
|
||||
result[choice] = choice
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module ObjectAttribute
|
||||
class FieldMap < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_any_action
|
||||
|
||||
optional :action
|
||||
|
||||
uses :field_map, :model_class, :resource, :sanitized_name
|
||||
|
||||
def process
|
||||
field_map[model_class.name] ||= {}
|
||||
field_map[model_class.name][ resource['name'] ] = sanitized_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module ObjectAttribute
|
||||
class MigrationExecute < Sequencer::Unit::Base
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_any_action
|
||||
|
||||
def process
|
||||
ObjectManager::Attribute.migration_execute(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module ObjectAttribute
|
||||
class SanitizedName < Sequencer::Unit::Import::Common::ObjectAttribute::SanitizedName
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_any_action
|
||||
|
||||
uses :resource
|
||||
|
||||
private
|
||||
|
||||
def unsanitized_name
|
||||
# active_customer
|
||||
resource['name']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
lib/sequencer/unit/import/freshdesk/object_attribute/skip.rb
Normal file
21
lib/sequencer/unit/import/freshdesk/object_attribute/skip.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module ObjectAttribute
|
||||
class Skip < Sequencer::Unit::Base
|
||||
|
||||
uses :resource
|
||||
provides :action
|
||||
|
||||
def process
|
||||
return if !resource['default']
|
||||
|
||||
state.provide(:action, :skipped)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
27
lib/sequencer/unit/import/freshdesk/object_count.rb
Normal file
27
lib/sequencer/unit/import/freshdesk/object_count.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class ObjectCount < Sequencer::Unit::Common::Provider::Attribute
|
||||
include ::Sequencer::Unit::Import::Common::Model::Statistics::Mixin::EmptyDiff
|
||||
|
||||
uses :model_class, :resources
|
||||
|
||||
private
|
||||
|
||||
def statistics_diff
|
||||
{
|
||||
model_key => empty_diff.merge!(
|
||||
total: resources.count
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def model_key
|
||||
model_class.name.pluralize.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
lib/sequencer/unit/import/freshdesk/perform.rb
Normal file
25
lib/sequencer/unit/import/freshdesk/perform.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Perform < Sequencer::Unit::Base
|
||||
|
||||
uses :resources, :object, :import_job, :dry_run, :field_map, :id_map
|
||||
|
||||
def process
|
||||
resources.each do |resource|
|
||||
::Sequencer.process("Import::Freshdesk::#{object}",
|
||||
parameters: {
|
||||
import_job: import_job,
|
||||
dry_run: dry_run,
|
||||
resource: resource,
|
||||
field_map: field_map,
|
||||
id_map: id_map,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/sequencer/unit/import/freshdesk/request.rb
Normal file
33
lib/sequencer/unit/import/freshdesk/request.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Request < Sequencer::Unit::Common::Provider::Attribute
|
||||
extend ::Sequencer::Unit::Import::Freshdesk::Requester
|
||||
|
||||
uses :object, :request_params
|
||||
provides :response
|
||||
|
||||
private
|
||||
|
||||
def response
|
||||
|
||||
builder = backend.new(
|
||||
object: object,
|
||||
request_params: request_params
|
||||
)
|
||||
|
||||
self.class.request(
|
||||
api_path: builder.api_path,
|
||||
params: builder.params,
|
||||
)
|
||||
end
|
||||
|
||||
def backend
|
||||
"::Sequencer::Unit::Import::Freshdesk::Request::#{object}".safe_constantize || ::Sequencer::Unit::Import::Freshdesk::Request::Generic
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/unit/import/freshdesk/request/conversation.rb
Normal file
22
lib/sequencer/unit/import/freshdesk/request/conversation.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Request < Sequencer::Unit::Common::Provider::Attribute
|
||||
class Conversation < Sequencer::Unit::Import::Freshdesk::Request::Generic
|
||||
attr_reader :ticket
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
@ticket = request_params.delete(:ticket)
|
||||
end
|
||||
|
||||
def api_path
|
||||
"tickets/#{ticket['id']}/conversations"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
28
lib/sequencer/unit/import/freshdesk/request/generic.rb
Normal file
28
lib/sequencer/unit/import/freshdesk/request/generic.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Request < Sequencer::Unit::Common::Provider::Attribute
|
||||
class Generic
|
||||
attr_reader :object, :request_params
|
||||
|
||||
def initialize(object:, request_params:)
|
||||
@object = object
|
||||
@request_params = request_params
|
||||
end
|
||||
|
||||
def api_path
|
||||
object.pluralize.underscore
|
||||
end
|
||||
|
||||
def params
|
||||
request_params.merge(
|
||||
per_page: 100,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
lib/sequencer/unit/import/freshdesk/request/ticket.rb
Normal file
19
lib/sequencer/unit/import/freshdesk/request/ticket.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Request < Sequencer::Unit::Common::Provider::Attribute
|
||||
class Ticket < Sequencer::Unit::Import::Freshdesk::Request::Generic
|
||||
|
||||
def params
|
||||
super.merge(
|
||||
updated_since: '1970-01-01',
|
||||
order_type: :asc,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/sequencer/unit/import/freshdesk/request/time_entry.rb
Normal file
22
lib/sequencer/unit/import/freshdesk/request/time_entry.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Request < Sequencer::Unit::Common::Provider::Attribute
|
||||
class TimeEntry < Sequencer::Unit::Import::Freshdesk::Request::Generic
|
||||
attr_reader :ticket
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
@ticket = request_params.delete(:ticket)
|
||||
end
|
||||
|
||||
def api_path
|
||||
"tickets/#{ticket['id']}/time_entries"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
56
lib/sequencer/unit/import/freshdesk/requester.rb
Normal file
56
lib/sequencer/unit/import/freshdesk/requester.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Requester
|
||||
def request(api_path:, params: nil)
|
||||
10.times do |iteration|
|
||||
response = perform_request(
|
||||
api_path: api_path,
|
||||
params: params,
|
||||
)
|
||||
|
||||
return response if response.is_a? Net::HTTPOK
|
||||
|
||||
handle_error response, iteration
|
||||
rescue => e
|
||||
handle_exception e, iteration
|
||||
end
|
||||
end
|
||||
|
||||
def handle_error(response, iteration)
|
||||
sleep_for = 10
|
||||
case response
|
||||
when Net::HTTPTooManyRequests
|
||||
sleep_for = response.header['retry-after'].to_i + 10
|
||||
logger.info "Rate limit: #{response.header.to_hash} (429 Too Many Requests). Sleeping #{sleep_for} seconds and retry (##{iteration + 1}/10)."
|
||||
else
|
||||
logger.info "Unknown response: #{response.inspect}. Sleeping 10 seconds and retry (##{iteration + 1}/10)."
|
||||
end
|
||||
sleep sleep_for
|
||||
end
|
||||
|
||||
def handle_exception(e, iteration)
|
||||
logger.error e
|
||||
logger.info "Sleeping 10 seconds after #{e.name} and retry (##{iteration + 1}/10)."
|
||||
sleep 10
|
||||
end
|
||||
|
||||
def perform_request(api_path:, params: nil)
|
||||
uri = URI("#{Setting.get('import_freshdesk_endpoint')}/#{api_path}")
|
||||
uri.query = URI.encode_www_form(params) if params.present?
|
||||
headers = { 'Content-Type' => 'application/json' }
|
||||
|
||||
Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 600) do |http|
|
||||
# for those special moments...
|
||||
# http.set_debug_output($stdout)
|
||||
request = Net::HTTP::Get.new(uri, headers)
|
||||
request.basic_auth(Setting.get('import_freshdesk_endpoint_key'), 'X')
|
||||
return http.request(request)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/sequencer/unit/import/freshdesk/resources.rb
Normal file
18
lib/sequencer/unit/import/freshdesk/resources.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Resources < Sequencer::Unit::Common::Provider::Named
|
||||
|
||||
uses :response
|
||||
|
||||
private
|
||||
|
||||
def resources
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
lib/sequencer/unit/import/freshdesk/sub_sequence/field.rb
Normal file
16
lib/sequencer/unit/import/freshdesk/sub_sequence/field.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module SubSequence
|
||||
class Field < Sequencer::Unit::Import::Freshdesk::SubSequence::Generic
|
||||
|
||||
def sequence_name
|
||||
'Sequencer::Sequence::Import::Freshdesk::GenericField'.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
lib/sequencer/unit/import/freshdesk/sub_sequence/generic.rb
Normal file
48
lib/sequencer/unit/import/freshdesk/sub_sequence/generic.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module SubSequence
|
||||
class Generic < Sequencer::Unit::Base
|
||||
|
||||
uses :dry_run, :import_job, :field_map, :id_map
|
||||
|
||||
attr_accessor :iteration
|
||||
|
||||
def process
|
||||
loop.each_with_index do |_, iteration|
|
||||
@iteration = iteration
|
||||
|
||||
result = ::Sequencer.process(sequence_name,
|
||||
parameters: {
|
||||
request_params: request_params,
|
||||
import_job: import_job,
|
||||
dry_run: dry_run,
|
||||
object: object,
|
||||
field_map: field_map,
|
||||
id_map: id_map,
|
||||
},
|
||||
expecting: [:response])
|
||||
break if result[:response].header['link'].blank?
|
||||
end
|
||||
end
|
||||
|
||||
def request_params
|
||||
{
|
||||
page: iteration + 1,
|
||||
}
|
||||
end
|
||||
|
||||
def object
|
||||
self.class.name.demodulize.singularize
|
||||
end
|
||||
|
||||
def sequence_name
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
lib/sequencer/unit/import/freshdesk/sub_sequence/object.rb
Normal file
16
lib/sequencer/unit/import/freshdesk/sub_sequence/object.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module SubSequence
|
||||
class Object < Sequencer::Unit::Import::Freshdesk::SubSequence::Generic
|
||||
|
||||
def sequence_name
|
||||
'Sequencer::Sequence::Import::Freshdesk::GenericObject'.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/sequencer/unit/import/freshdesk/ticket/conversations.rb
Normal file
33
lib/sequencer/unit/import/freshdesk/ticket/conversations.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Ticket
|
||||
class Conversations < Sequencer::Unit::Import::Freshdesk::SubSequence::Generic
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
optional :action
|
||||
|
||||
skip_action :skipped, :failed
|
||||
|
||||
uses :resource
|
||||
|
||||
def object
|
||||
'Conversation'
|
||||
end
|
||||
|
||||
def sequence_name
|
||||
'Sequencer::Sequence::Import::Freshdesk::Conversations'.freeze
|
||||
end
|
||||
|
||||
def request_params
|
||||
super.merge(
|
||||
ticket: resource,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
28
lib/sequencer/unit/import/freshdesk/ticket/description.rb
Normal file
28
lib/sequencer/unit/import/freshdesk/ticket/description.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Ticket
|
||||
class Description < Sequencer::Unit::Import::Freshdesk::SubSequence::Generic
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
skip_action :skipped, :failed
|
||||
|
||||
uses :dry_run, :import_job, :resource, :field_map, :id_map
|
||||
|
||||
def process
|
||||
::Sequencer.process('Import::Freshdesk::Description',
|
||||
parameters: {
|
||||
import_job: import_job,
|
||||
dry_run: dry_run,
|
||||
field_map: field_map,
|
||||
id_map: id_map,
|
||||
resource: resource,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
35
lib/sequencer/unit/import/freshdesk/ticket/fetch.rb
Normal file
35
lib/sequencer/unit/import/freshdesk/ticket/fetch.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Ticket
|
||||
class Fetch < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Freshdesk::Requester
|
||||
|
||||
uses :resource
|
||||
|
||||
# Fetch additional data such as attachments which is not included
|
||||
# in the ticket list endpoint.
|
||||
def process
|
||||
resource.merge!(fetch_ticket)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_ticket
|
||||
response = request(
|
||||
api_path: "tickets/#{resource['id']}",
|
||||
)
|
||||
|
||||
JSON.parse(response.body)
|
||||
rescue => e
|
||||
logger.error "Error when fetching ticket data for ticket #{resource['id']}"
|
||||
logger.error e
|
||||
{}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
59
lib/sequencer/unit/import/freshdesk/ticket/mapping.rb
Normal file
59
lib/sequencer/unit/import/freshdesk/ticket/mapping.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Ticket
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource, :id_map
|
||||
|
||||
PRIORITY_MAP = {
|
||||
1 => ::Ticket::Priority.find_by(name: '1 low').id, # low
|
||||
2 => ::Ticket::Priority.find_by(name: '2 normal').id, # medium
|
||||
3 => ::Ticket::Priority.find_by(name: '3 high').id, # high
|
||||
4 => ::Ticket::Priority.find_by(name: '3 high').id, # urgent
|
||||
}.freeze
|
||||
|
||||
STATE_MAP = {
|
||||
2 => ::Ticket::State.find_by(name: 'open').id, # open
|
||||
3 => ::Ticket::State.find_by(name: 'open').id, # pending
|
||||
4 => ::Ticket::State.find_by(name: 'closed').id, # resolved
|
||||
5 => ::Ticket::State.find_by(name: 'closed').id, # closed
|
||||
}.freeze
|
||||
|
||||
def process # rubocop:disable Metrics/AbcSize
|
||||
provide_mapped do
|
||||
{
|
||||
title: resource['subject'],
|
||||
number: resource['id'],
|
||||
group_id: group_id,
|
||||
priority_id: PRIORITY_MAP[resource['priority']],
|
||||
state_id: STATE_MAP[resource['status']],
|
||||
owner_id: owner_id,
|
||||
customer_id: customer_id,
|
||||
created_at: resource['created_at'],
|
||||
updated_at: resource['updated_at'],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_id
|
||||
id_map.dig('Group', resource['group_id']) || ::Group.find_by(name: 'Support')&.id || 1
|
||||
end
|
||||
|
||||
def customer_id
|
||||
id_map['User'][resource['requester_id']]
|
||||
end
|
||||
|
||||
def owner_id
|
||||
id_map['User'][resource['responder_id']]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
lib/sequencer/unit/import/freshdesk/ticket/tags.rb
Normal file
20
lib/sequencer/unit/import/freshdesk/ticket/tags.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Ticket
|
||||
class Tags < Sequencer::Unit::Common::Model::Tags
|
||||
|
||||
uses :resource
|
||||
|
||||
private
|
||||
|
||||
def tags
|
||||
resource['tags']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/sequencer/unit/import/freshdesk/ticket/time_entries.rb
Normal file
33
lib/sequencer/unit/import/freshdesk/ticket/time_entries.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module Ticket
|
||||
class TimeEntries < Sequencer::Unit::Import::Freshdesk::SubSequence::Generic
|
||||
prepend ::Sequencer::Unit::Import::Common::Model::Mixin::Skip::Action
|
||||
|
||||
optional :action
|
||||
|
||||
skip_action :skipped, :failed
|
||||
|
||||
uses :resource
|
||||
|
||||
def object
|
||||
'TimeEntry'
|
||||
end
|
||||
|
||||
def sequence_name
|
||||
'Sequencer::Sequence::Import::Freshdesk::TimeEntries'.freeze
|
||||
end
|
||||
|
||||
def request_params
|
||||
super.merge(
|
||||
ticket: resource,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/ticket_fields.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/ticket_fields.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class TicketFields < Sequencer::Unit::Import::Freshdesk::SubSequence::Field
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/sequencer/unit/import/freshdesk/tickets.rb
Normal file
10
lib/sequencer/unit/import/freshdesk/tickets.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
class Tickets < Sequencer::Unit::Import::Freshdesk::SubSequence::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
60
lib/sequencer/unit/import/freshdesk/time_entry/mapping.rb
Normal file
60
lib/sequencer/unit/import/freshdesk/time_entry/mapping.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
class Sequencer
|
||||
class Unit
|
||||
module Import
|
||||
module Freshdesk
|
||||
module TimeEntry
|
||||
class Mapping < Sequencer::Unit::Base
|
||||
include ::Sequencer::Unit::Import::Common::Mapping::Mixin::ProvideMapped
|
||||
|
||||
uses :resource, :id_map
|
||||
provides :action
|
||||
|
||||
def process
|
||||
provide_mapped do
|
||||
{
|
||||
time_unit: time_unit,
|
||||
ticket_id: ticket_id,
|
||||
created_by_id: agent_id,
|
||||
created_at: resource['created_at'],
|
||||
updated_at: resource['updated_at'],
|
||||
}
|
||||
end
|
||||
rescue TypeError => e
|
||||
# TODO
|
||||
# TimeTracking is not available in the plans: Sprout, Blossom
|
||||
# In this case `resource`s value is `["code", "require_feature"]`
|
||||
# We should somehow detect that/the plan (no API available) to avoid
|
||||
# running into this error and therefore performing unnecessary requests
|
||||
# See:
|
||||
# - Ticket# 1077135
|
||||
# - https://support.freshdesk.com/support/solutions/articles/37583-keeping-track-of-time-spent
|
||||
#
|
||||
# Idea: Maybe it's possible to use the "X-Ratelimit-Total"-Value from the header, because with this it
|
||||
# should be possible to detect the plan.
|
||||
|
||||
logger.debug { e }
|
||||
|
||||
# state.provide(:action, :failed)
|
||||
state.provide(:action, :skipped)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def time_unit
|
||||
hours, minutes = resource['time_spent'].match(%r{(\d{2}):(\d{2})}).captures
|
||||
(hours.to_i * 60) + minutes.to_i
|
||||
end
|
||||
|
||||
def ticket_id
|
||||
id_map['Ticket'][resource['ticket_id']]
|
||||
end
|
||||
|
||||
def agent_id
|
||||
id_map['User'][resource['agent_id']]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -63,6 +63,19 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory :object_manager_attribute_boolean, parent: :object_manager_attribute do
|
||||
data_type { 'boolean' }
|
||||
data_option do
|
||||
{
|
||||
default: false,
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
factory :object_manager_attribute_date, parent: :object_manager_attribute do
|
||||
name { 'date_attribute' }
|
||||
data_type { 'date' }
|
||||
|
|
99
spec/integration/freshdesk_spec.rb
Normal file
99
spec/integration/freshdesk_spec.rb
Normal file
|
@ -0,0 +1,99 @@
|
|||
require 'rails_helper'
|
||||
|
||||
#
|
||||
# The purpose of this integration test is to verify that the API generally works.
|
||||
# Individual import steps are tested in spec/lib/sequencer.
|
||||
#
|
||||
|
||||
RSpec.describe 'Freshdesk import', type: :integration, use_vcr: true, db_strategy: :reset do # rubocop:disable RSpec/DescribeClass
|
||||
|
||||
before do
|
||||
|
||||
if !ENV['IMPORT_FRESHDESK_ENDPOINT']
|
||||
raise "ERROR: Need IMPORT_FRESHDESK_ENDPOINT - hint IMPORT_FRESHDESK_ENDPOINT='https://example.freshdesk.com/api/v2'"
|
||||
end
|
||||
if !ENV['IMPORT_FRESHDESK_ENDPOINT_KEY']
|
||||
raise "ERROR: Need IMPORT_FRESHDESK_ENDPOINT_KEY - hint IMPORT_FRESHDESK_ENDPOINT_KEY='01234567899876543210'"
|
||||
end
|
||||
|
||||
Setting.set('import_freshdesk_endpoint', ENV['IMPORT_FRESHDESK_ENDPOINT'])
|
||||
Setting.set('import_freshdesk_endpoint_key', ENV['IMPORT_FRESHDESK_ENDPOINT_KEY'])
|
||||
Setting.set('import_mode', true)
|
||||
Setting.set('system_init_done', false)
|
||||
|
||||
VCR.configure do |c|
|
||||
%w[
|
||||
IMPORT_FRESHDESK_ENDPOINT
|
||||
IMPORT_FRESHDESK_ENDPOINT_KEY
|
||||
IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN
|
||||
].each do |env_key|
|
||||
c.filter_sensitive_data("<#{env_key}>") { ENV[env_key] }
|
||||
end
|
||||
|
||||
# The API key is used only inside the base64 encoded Basic Auth string, so mask that as well.
|
||||
%w[
|
||||
IMPORT_FRESHDESK_ENDPOINT_BASIC_AUTH
|
||||
].each do |env_key|
|
||||
c.filter_sensitive_data("<#{env_key}>") { Base64.encode64( "#{ENV['IMPORT_FRESHDESK_ENDPOINT_KEY']}:X" ).chomp }
|
||||
end
|
||||
end
|
||||
|
||||
VCR.use_cassette 'freshdesk_import' do
|
||||
ImportJob.create(name: 'Import::Freshdesk').start
|
||||
end
|
||||
end
|
||||
|
||||
context 'when performing the full Freshdesk import' do
|
||||
|
||||
let(:job) { ImportJob.last }
|
||||
let(:expected_stats) do
|
||||
{
|
||||
'Groups' => {
|
||||
'skipped' => 0,
|
||||
'created' => 9,
|
||||
'updated' => 0,
|
||||
'unchanged' => 0,
|
||||
'failed' => 0,
|
||||
'deactivated' => 0,
|
||||
'sum' => 9,
|
||||
'total' => 9,
|
||||
},
|
||||
'Users' => {
|
||||
'skipped' => 0,
|
||||
'created' => 19,
|
||||
'updated' => 0,
|
||||
'unchanged' => 0,
|
||||
'failed' => 0,
|
||||
'deactivated' => 0,
|
||||
'sum' => 19,
|
||||
'total' => 19,
|
||||
},
|
||||
'Organizations' => {
|
||||
'skipped' => 0,
|
||||
'created' => 0,
|
||||
'updated' => 1,
|
||||
'unchanged' => 0,
|
||||
'failed' => 0,
|
||||
'deactivated' => 0,
|
||||
'sum' => 1,
|
||||
'total' => 1,
|
||||
},
|
||||
'Tickets' => {
|
||||
'skipped' => 0,
|
||||
'created' => 13,
|
||||
'updated' => 0,
|
||||
'unchanged' => 0,
|
||||
'failed' => 0,
|
||||
'deactivated' => 0,
|
||||
'sum' => 13,
|
||||
'total' => 13,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports the correct number of expected objects' do
|
||||
expect(job.result).to eq expected_stats
|
||||
end
|
||||
end
|
||||
|
||||
end
|
88
spec/lib/sequencer/sequence/import/freshdesk/agent_spec.rb
Normal file
88
spec/lib/sequencer/sequence/import/freshdesk/agent_spec.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::Agent, sequencer: :sequence do
|
||||
|
||||
context 'when importing agents from Freshdesk' do
|
||||
|
||||
let(:groups) do
|
||||
create_list(:group, 3)
|
||||
end
|
||||
|
||||
let(:resource) do
|
||||
{
|
||||
'available' => false,
|
||||
'occasional' => false,
|
||||
'id' => 1001,
|
||||
'ticket_scope' => 1,
|
||||
'created_at' => '2021-04-09T13:23:58Z',
|
||||
'updated_at' => '2021-05-10T09:14:20Z',
|
||||
'last_active_at' => '2021-05-10T09:14:20Z',
|
||||
'available_since' => nil,
|
||||
'type' => 'support_agent',
|
||||
'contact' => {
|
||||
'active' => true,
|
||||
'email' => 'freshdesk@example.com',
|
||||
'job_title' => nil,
|
||||
'language' => 'en',
|
||||
'last_login_at' => '2021-05-10T07:52:58Z',
|
||||
'mobile' => nil,
|
||||
'name' => 'John Doe',
|
||||
'phone' => nil,
|
||||
'time_zone' => 'Eastern Time (US & Canada)',
|
||||
'created_at' => '2021-04-09T13:23:58Z',
|
||||
'updated_at' => '2021-04-09T13:31:00Z'
|
||||
},
|
||||
'signature' => nil,
|
||||
'group_ids' => [1001, 1002, 1003]
|
||||
}
|
||||
end
|
||||
|
||||
let(:id_map) do
|
||||
{
|
||||
'Group' => {
|
||||
1001 => groups[0].id,
|
||||
1002 => groups[1].id,
|
||||
1003 => groups[2].id,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: {},
|
||||
id_map: id_map,
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports user correctly' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength
|
||||
expect { process(process_payload) }.to change(User, :count).by(1)
|
||||
expect(User.last).to have_attributes(
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
login: 'freshdesk@example.com',
|
||||
email: 'freshdesk@example.com',
|
||||
active: true,
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets user roles correctly for admin user' do
|
||||
allow( Sequencer::Unit::Import::Freshdesk::Agent::Mapping).to receive(:admin_id).and_return(1001)
|
||||
process(process_payload)
|
||||
expect(User.last.roles.sort.map(&:name)).to eq %w[Admin Agent]
|
||||
end
|
||||
|
||||
it 'sets user roles correctly for non-admin user' do
|
||||
process(process_payload)
|
||||
expect(User.last.roles.sort.map(&:name)).to eq ['Agent']
|
||||
end
|
||||
|
||||
it 'sets user groups correctly ' do
|
||||
process(process_payload)
|
||||
expect(User.last.groups_access('full').sort).to eq groups
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::CompanyField, sequencer: :sequence do
|
||||
|
||||
context 'when trying to import company fields from Freshdesk', db_strategy: :reset do
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: {},
|
||||
id_map: {},
|
||||
}
|
||||
end
|
||||
|
||||
# Other field types are checked in ticket_field_spec.rb.
|
||||
context 'when fields are valid' do
|
||||
let(:resource) do
|
||||
{
|
||||
'id' => 80_000_387_409,
|
||||
'name' => 'custom_dropdown',
|
||||
'label' => 'custom_dropdown',
|
||||
'position' => 9,
|
||||
'required_for_agents' => false,
|
||||
'type' => 'custom_dropdown',
|
||||
'default' => false,
|
||||
'created_at' => '2021-04-12T20:24:41Z',
|
||||
'updated_at' => '2021-04-12T20:24:41Z',
|
||||
'choices' => [
|
||||
'First Choice',
|
||||
'Second Choice',
|
||||
],
|
||||
}
|
||||
end
|
||||
|
||||
it 'adds custom fields' do
|
||||
expect { process(process_payload) }.to change(Organization, :column_names).by(['custom_dropdown'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when fields are invalid' do
|
||||
|
||||
let(:resource) do
|
||||
{
|
||||
'id' => 80_000_382_712,
|
||||
'name' => 'name',
|
||||
'label' => 'Company Name',
|
||||
'position' => 1,
|
||||
'required_for_agents' => true,
|
||||
'type' => 'default_name',
|
||||
'default' => true,
|
||||
'created_at' => '2021-04-09T13:23:59Z',
|
||||
'updated_at' => '2021-04-09T13:23:59Z'
|
||||
}
|
||||
end
|
||||
|
||||
it 'ignores other fields' do
|
||||
expect { process(process_payload) }.not_to change(Organization, :column_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
68
spec/lib/sequencer/sequence/import/freshdesk/company_spec.rb
Normal file
68
spec/lib/sequencer/sequence/import/freshdesk/company_spec.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::Company, sequencer: :sequence, db_strategy: :reset do
|
||||
|
||||
context 'when importing companies from Freshdesk' do
|
||||
|
||||
let(:resource) do
|
||||
{ 'id' => 80_000_602_705,
|
||||
'name' => 'Test Foundation',
|
||||
'description' => nil,
|
||||
'note' => nil,
|
||||
'domains' => ['acmecorp.com'],
|
||||
'created_at' => '2021-04-09T13:24:00Z',
|
||||
'updated_at' => '2021-04-12T20:25:36Z',
|
||||
'custom_fields' => {
|
||||
'cf_test_checkbox' => true,
|
||||
'cf_custom_integer' => 999,
|
||||
'cf_custom_dropdown' => 'key_2',
|
||||
'cf_custom_decimal' => '1.1',
|
||||
},
|
||||
'health_score' => nil,
|
||||
'account_tier' => 'Basic',
|
||||
'renewal_date' => nil,
|
||||
'industry' => nil }
|
||||
end
|
||||
|
||||
let(:field_map) do
|
||||
{
|
||||
'Organization' => {
|
||||
'cf_test_checkbox' => 'cf_test_checkbox',
|
||||
'cf_custom_integer' => 'cf_custom_integer',
|
||||
'cf_custom_dropdown' => 'cf_custom_dropdown',
|
||||
'cf_custom_decimal' => 'cf_custom_decimal'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: field_map,
|
||||
id_map: {},
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
create :object_manager_attribute_select, object_name: 'Organization', name: 'cf_custom_dropdown'
|
||||
create :object_manager_attribute_integer, object_name: 'Organization', name: 'cf_custom_integer'
|
||||
create :object_manager_attribute_boolean, object_name: 'Organization', name: 'cf_test_checkbox'
|
||||
create :object_manager_attribute_text, object_name: 'Organization', name: 'cf_custom_decimal'
|
||||
ObjectManager::Attribute.migration_execute
|
||||
end
|
||||
|
||||
it 'adds organizations' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength
|
||||
expect { process(process_payload) }.to change(Organization, :count).by(1)
|
||||
expect(Organization.last).to have_attributes(
|
||||
name: 'Test Foundation',
|
||||
note: nil,
|
||||
cf_custom_dropdown: 'key_2',
|
||||
cf_custom_integer: 999,
|
||||
cf_test_checkbox: true,
|
||||
cf_custom_decimal: '1.1',
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::ContactField, sequencer: :sequence do
|
||||
|
||||
context 'when tryping to import contact fields from Freshdesk', db_strategy: :reset do
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: {},
|
||||
id_map: {},
|
||||
}
|
||||
end
|
||||
|
||||
# Other field types are checked in ticket_field_spec.rb.
|
||||
context 'when fields are valid' do
|
||||
let(:resource) do
|
||||
{
|
||||
'editable_in_signup' => false,
|
||||
'id' => 80_000_776_200,
|
||||
'name' => 'custom_dropdown',
|
||||
'label' => 'custom_dropdown',
|
||||
'position' => 16,
|
||||
'required_for_agents' => false,
|
||||
'type' => 'custom_dropdown',
|
||||
'default' => false,
|
||||
'customers_can_edit' => true,
|
||||
'label_for_customers' => 'custom_dropdown',
|
||||
'required_for_customers' => false,
|
||||
'displayed_for_customers' => true,
|
||||
'created_at' => '2021-04-12T20:19:46Z',
|
||||
'updated_at' => '2021-04-12T20:19:46Z',
|
||||
'choices' => [ 'First Choice', 'Second Choice']
|
||||
}
|
||||
end
|
||||
|
||||
it 'adds custom fields' do
|
||||
expect { process(process_payload) }.to change(User, :column_names).by(['custom_dropdown'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when fields are invalid' do
|
||||
|
||||
let(:resource) do
|
||||
{
|
||||
'editable_in_signup' => false,
|
||||
'id' => 80_000_766_844,
|
||||
'name' => 'twitter_followers_count',
|
||||
'label' => 'Twitter Follower Count',
|
||||
'position' => 15,
|
||||
'required_for_agents' => false,
|
||||
'type' => 'default_twitter_followers_count',
|
||||
'default' => true,
|
||||
'customers_can_edit' => false,
|
||||
'label_for_customers' => 'Twitter Follower Count',
|
||||
'required_for_customers' => false,
|
||||
'displayed_for_customers' => false,
|
||||
'created_at' => '2021-04-09T13:24:02Z',
|
||||
'updated_at' => '2021-04-09T13:24:02Z'
|
||||
}
|
||||
end
|
||||
|
||||
it 'ignores other fields' do
|
||||
expect { process(process_payload) }.not_to change(User, :column_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
86
spec/lib/sequencer/sequence/import/freshdesk/contact_spec.rb
Normal file
86
spec/lib/sequencer/sequence/import/freshdesk/contact_spec.rb
Normal file
|
@ -0,0 +1,86 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::Contact, sequencer: :sequence, db_strategy: :reset do
|
||||
|
||||
context 'when importing customers from Freshdesk' do
|
||||
|
||||
let(:resource) do
|
||||
{
|
||||
'active' => false,
|
||||
'address' => nil,
|
||||
'company_id' => 1001,
|
||||
'description' => nil,
|
||||
'email' => 'sam.ozzy@freshdesk.com',
|
||||
'id' => 80_014_400_819,
|
||||
'job_title' => nil,
|
||||
'language' => 'en',
|
||||
'mobile' => nil,
|
||||
'name' => 'Sam Osborne',
|
||||
'phone' => nil,
|
||||
'time_zone' => 'Eastern Time (US & Canada)',
|
||||
'twitter_id' => nil,
|
||||
'custom_fields' => {
|
||||
'cf_test_checkbox' => true,
|
||||
'cf_custom_integer' => 999,
|
||||
'cf_custom_dropdown' => 'key_2',
|
||||
'cf_custom_decimal' => '1.1',
|
||||
},
|
||||
'facebook_id' => nil,
|
||||
'created_at' => '2021-04-09T13:29:43Z',
|
||||
'updated_at' => '2021-04-09T13:29:43Z',
|
||||
'csat_rating' => 103,
|
||||
'preferred_source' => 'email',
|
||||
}
|
||||
end
|
||||
|
||||
let(:field_map) do
|
||||
{
|
||||
'User' => {
|
||||
'cf_test_checkbox' => 'cf_test_checkbox',
|
||||
'cf_custom_integer' => 'cf_custom_integer',
|
||||
'cf_custom_dropdown' => 'cf_custom_dropdown',
|
||||
'cf_custom_decimal' => 'cf_custom_decimal'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:id_map) do
|
||||
{
|
||||
'Organization' => { 1001 => 1 }
|
||||
}
|
||||
end
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: field_map,
|
||||
id_map: id_map,
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
create :object_manager_attribute_select, object_name: 'User', name: 'cf_custom_dropdown'
|
||||
create :object_manager_attribute_integer, object_name: 'User', name: 'cf_custom_integer'
|
||||
create :object_manager_attribute_boolean, object_name: 'User', name: 'cf_test_checkbox'
|
||||
create :object_manager_attribute_text, object_name: 'User', name: 'cf_custom_decimal'
|
||||
ObjectManager::Attribute.migration_execute
|
||||
end
|
||||
|
||||
it 'imports customers correctly' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength
|
||||
expect { process(process_payload) }.to change(User, :count).by(1)
|
||||
expect(User.last).to have_attributes(
|
||||
firstname: 'Sam',
|
||||
lastname: 'Osborne',
|
||||
login: 'sam.ozzy@freshdesk.com',
|
||||
email: 'sam.ozzy@freshdesk.com',
|
||||
active: false,
|
||||
cf_custom_dropdown: 'key_2',
|
||||
cf_custom_integer: 999,
|
||||
cf_test_checkbox: true,
|
||||
cf_custom_decimal: '1.1',
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,102 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::Conversation, sequencer: :sequence do
|
||||
|
||||
context 'when importing conversations from Freshdesk' do
|
||||
|
||||
let(:resource) do
|
||||
{
|
||||
'body' => "<div style=\"font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif; font-size:14px\">\n<div dir=\"ltr\">Let's see if inline images work in a subsequent article:</div>\n<div dir=\"ltr\"><img src=\"https://eucattachment.freshdesk.com/inline/attachment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ODAwMTIyMjY4NTMsImRvbWFpbiI6InphbW1hZC5mcmVzaGRlc2suY29tIiwiYWNjb3VudF9pZCI6MTg5MDU2MH0.705lNehzm--aO36CGFg0SW73j0NG3UWcRcN1_DXgtwc\" style=\"width: auto\" class=\"fr-fil fr-dib\" data-id=\"80012226853\"></div>\n</div>", 'body_text' => "Let's see if inline images work in a subsequent article:",
|
||||
'id' => 80_027_218_656,
|
||||
'incoming' => false,
|
||||
'private' => true,
|
||||
'user_id' => 80_014_400_475,
|
||||
'support_email' => nil,
|
||||
'source' => 2,
|
||||
'category' => 2,
|
||||
'to_emails' => ['info@zammad.org'],
|
||||
'from_email' => nil,
|
||||
'cc_emails' => [],
|
||||
'bcc_emails' => nil,
|
||||
'email_failure_count' => nil,
|
||||
'outgoing_failures' => nil,
|
||||
'created_at' => '2021-05-14T12:30:19Z',
|
||||
'updated_at' => '2021-05-14T12:30:19Z',
|
||||
'attachments' => [
|
||||
{
|
||||
'id' => 80_012_226_885,
|
||||
'name' => 'standalone_attachment.png',
|
||||
'content_type' => 'image/png',
|
||||
'size' => 11_447,
|
||||
'created_at' => '2021-05-14T12:30:16Z',
|
||||
'updated_at' => '2021-05-14T12:30:19Z',
|
||||
'attachment_url' => 'https://s3.eu-central-1.amazonaws.com/euc-cdn.freshdesk.com/data/helpdesk/attachments/production/80012226885/original/standalone_attachment.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS6FNSMY2RG7BSUFP%2F20210514%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210514T123300Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=750988d37a6f2f43830bfd19c895517aa051aa13b4ab26a1333369d414fef0be',
|
||||
'thumb_url' => 'https://s3.eu-central-1.amazonaws.com/euc-cdn.freshdesk.com/data/helpdesk/attachments/production/80012226885/thumb/standalone_attachment.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS6FNSMY2RG7BSUFP%2F20210514%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210514T123300Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=40b5fe1d7d418bcbd1e639b273a1038c7a73781c16d9881c2f31a11c6bebfdf9'
|
||||
}
|
||||
],
|
||||
'auto_response' => false,
|
||||
'ticket_id' => 1001,
|
||||
'source_additional_info' => nil
|
||||
}
|
||||
end
|
||||
let(:used_urls) do
|
||||
[
|
||||
'https://eucattachment.freshdesk.com/inline/attachment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ODAwMTIyMjY4NTMsImRvbWFpbiI6InphbW1hZC5mcmVzaGRlc2suY29tIiwiYWNjb3VudF9pZCI6MTg5MDU2MH0.705lNehzm--aO36CGFg0SW73j0NG3UWcRcN1_DXgtwc',
|
||||
'https://s3.eu-central-1.amazonaws.com/euc-cdn.freshdesk.com/data/helpdesk/attachments/production/80012226885/original/standalone_attachment.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS6FNSMY2RG7BSUFP%2F20210514%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210514T123300Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=750988d37a6f2f43830bfd19c895517aa051aa13b4ab26a1333369d414fef0be',
|
||||
]
|
||||
end
|
||||
|
||||
let(:ticket) { create :ticket }
|
||||
let(:id_map) do
|
||||
{
|
||||
'Ticket' => {
|
||||
1001 => ticket.id,
|
||||
},
|
||||
'User' => {
|
||||
80_014_400_475 => 1,
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: {},
|
||||
id_map: id_map,
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
# Mock the attachment and inline image download requests.
|
||||
used_urls.each do |used_url|
|
||||
stub_request(:get, used_url).to_return(status: 200, body: '123', headers: {})
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds article with inline image' do # rubocop:disable RSpec/MultipleExpectations
|
||||
expect { process(process_payload) }.to change(Ticket::Article, :count).by(1)
|
||||
expect(Ticket::Article.last).to have_attributes(
|
||||
to: 'info@zammad.org',
|
||||
body: "\n<div>\n<div dir=\"ltr\">Let's see if inline images work in a subsequent article:</div>\n<div dir=\"ltr\"><img src=\"data:image/png;base64,MTIz\" style=\"width: auto;\"></div>\n</div>\n",
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds correct number of attachments' do
|
||||
process(process_payload)
|
||||
expect(Ticket::Article.last.attachments.size).to eq 1
|
||||
end
|
||||
|
||||
it 'adds attachment content' do # rubocop:disable RSpec/ExampleLength
|
||||
process(process_payload)
|
||||
expect(Ticket::Article.last.attachments.last).to have_attributes(
|
||||
'filename' => 'standalone_attachment.png',
|
||||
'size' => '3',
|
||||
'preferences' => {
|
||||
'Content-Type' => 'image/png',
|
||||
'resizable' => false,
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
39
spec/lib/sequencer/sequence/import/freshdesk/group_spec.rb
Normal file
39
spec/lib/sequencer/sequence/import/freshdesk/group_spec.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::Group, sequencer: :sequence do
|
||||
|
||||
context 'when importing groups from Freshdesk' do
|
||||
|
||||
let(:resource) do
|
||||
{
|
||||
'id' => 80_000_374_715,
|
||||
'name' => 'QA',
|
||||
'description' => 'Members of the QA team belong to this group',
|
||||
'escalate_to' => nil,
|
||||
'unassigned_for' => nil,
|
||||
'business_hour_id' => nil,
|
||||
'group_type' => 'support_agent_group',
|
||||
'created_at' => '2021-04-09T13:23:59Z',
|
||||
'updated_at' => '2021-04-09T13:23:59Z'
|
||||
}
|
||||
end
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: {},
|
||||
id_map: {},
|
||||
}
|
||||
end
|
||||
|
||||
it 'adds groups' do # rubocop:disable RSpec/MultipleExpectations
|
||||
expect { process(process_payload) }.to change(Group, :count).by(1)
|
||||
expect(Group.last).to have_attributes(
|
||||
name: 'QA',
|
||||
active: true,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,157 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::TicketField, sequencer: :sequence do
|
||||
|
||||
context 'when trying to import ticket fields from Freshdesk', db_strategy: :reset do
|
||||
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: {},
|
||||
id_map: {},
|
||||
}
|
||||
end
|
||||
|
||||
let(:base_resource) do
|
||||
{
|
||||
'id' => 80_000_561_223,
|
||||
'label' => 'My custom field',
|
||||
'description' => nil,
|
||||
'position' => 14,
|
||||
'required_for_closure' => false,
|
||||
'required_for_agents' => false,
|
||||
'default' => false,
|
||||
'customers_can_edit' => true,
|
||||
'label_for_customers' => 'custom_dropdown',
|
||||
'required_for_customers' => false,
|
||||
'displayed_to_customers' => true,
|
||||
'created_at' => '2021-04-12T20:48:40Z',
|
||||
'updated_at' => '2021-04-12T20:48:40Z',
|
||||
}
|
||||
end
|
||||
|
||||
context 'when field is a dropdown' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_dropdown',
|
||||
'type' => 'custom_dropdown',
|
||||
'choices' => %w[key1 key2],
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
p resource
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_dropdown'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is a decimal' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_integer',
|
||||
'type' => 'custom_decimal',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_integer'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is a number' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_integer',
|
||||
'type' => 'custom_number',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_integer'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is a date' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_date',
|
||||
'type' => 'custom_date',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_date'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is a checkbox' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_checkbox',
|
||||
'type' => 'custom_checkbox',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_checkbox'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is a text' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_text',
|
||||
'type' => 'custom_text',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_text'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is a paragraph' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_paragraph',
|
||||
'type' => 'custom_paragraph',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a custom field' do
|
||||
expect { process(process_payload) }.to change(Ticket, :column_names).by(['cf_custom_paragraph'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when field is invalid' do
|
||||
let(:resource) do
|
||||
base_resource.merge(
|
||||
{
|
||||
'name' => 'cf_custom_unknown',
|
||||
'type' => 'custom_unknown',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { process(process_payload) }.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
174
spec/lib/sequencer/sequence/import/freshdesk/ticket_spec.rb
Normal file
174
spec/lib/sequencer/sequence/import/freshdesk/ticket_spec.rb
Normal file
|
@ -0,0 +1,174 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ::Sequencer::Sequence::Import::Freshdesk::Ticket, sequencer: :sequence, db_strategy: 'reset' do
|
||||
|
||||
context 'when importing tickets from Freshdesk' do
|
||||
|
||||
let(:group) { create :group }
|
||||
let(:resource) do
|
||||
{
|
||||
'cc_emails' => [],
|
||||
'fwd_emails' => [],
|
||||
'reply_cc_emails' => [],
|
||||
'ticket_cc_emails' => [],
|
||||
'fr_escalated' => false,
|
||||
'spam' => false,
|
||||
'email_config_id' => nil,
|
||||
'group_id' => 80_000_374_718,
|
||||
'priority' => 1,
|
||||
'requester_id' => 80_014_400_475,
|
||||
'responder_id' => 80_014_400_475,
|
||||
'source' => 3,
|
||||
'company_id' => nil,
|
||||
'status' => 2,
|
||||
'subject' => 'Inline Images Failing?',
|
||||
'association_type' => nil,
|
||||
'support_email' => nil,
|
||||
'to_emails' => ['info@zammad.org'],
|
||||
'product_id' => nil,
|
||||
'id' => 13,
|
||||
'type' => nil,
|
||||
'due_by' => '2021-05-17T12:29:27Z',
|
||||
'fr_due_by' => '2021-05-15T12:29:27Z',
|
||||
'is_escalated' => false,
|
||||
'custom_fields' => {
|
||||
'cf_test_checkbox' => true,
|
||||
'cf_custom_integer' => 999,
|
||||
'cf_custom_dropdown' => 'key_2',
|
||||
'cf_custom_decimal' => '1.1'
|
||||
},
|
||||
'created_at' => '2021-05-14T12:29:27Z',
|
||||
'updated_at' => '2021-05-14T12:30:19Z',
|
||||
'associated_tickets_count' => nil,
|
||||
'tags' => [],
|
||||
'description' => "<div style=\"font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif; font-size:14px\">\n<div dir=\"ltr\">Inline images in the first article might not be working, see following:</div>\n<div dir=\"ltr\"><img src=\"https://eucattachment.freshdesk.com/inline/attachment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ODAwMTIyMjY0NzksImRvbWFpbiI6InphbW1hZC5mcmVzaGRlc2suY29tIiwiYWNjb3VudF9pZCI6MTg5MDU2MH0.cdYIOOSi7ckCFIZlQ9eynELMzJp1ECVeTLlQMCDgKo4\" style=\"width: auto\" class=\"fr-fil fr-dib\" data-id=\"80012226479\"></div>\n</div>", 'description_text' => 'Inline images in the first article might not be working, see following:'
|
||||
}
|
||||
|
||||
end
|
||||
let(:field_map) do
|
||||
{
|
||||
'Ticket' => {
|
||||
'cf_test_checkbox' => 'cf_test_checkbox',
|
||||
'cf_custom_integer' => 'cf_custom_integer',
|
||||
'cf_custom_dropdown' => 'cf_custom_dropdown',
|
||||
'cf_custom_decimal' => 'cf_custom_decimal'
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:id_map) do
|
||||
{
|
||||
'User' => {
|
||||
80_014_400_475 => owner.id,
|
||||
},
|
||||
'Group' => {
|
||||
80_000_374_718 => group.id,
|
||||
},
|
||||
}
|
||||
end
|
||||
let(:process_payload) do
|
||||
{
|
||||
import_job: build_stubbed(:import_job, name: 'Import::Freshdesk', payload: {}),
|
||||
dry_run: false,
|
||||
resource: resource,
|
||||
field_map: field_map,
|
||||
id_map: id_map,
|
||||
}
|
||||
end
|
||||
let(:owner) { create :agent, group_ids: [group.id] }
|
||||
|
||||
let(:ticket_get_response_payload) do
|
||||
attachment_payload = {
|
||||
'attachments' => [
|
||||
{
|
||||
'id' => 80_012_226_885,
|
||||
'name' => 'standalone_attachment.png',
|
||||
'content_type' => 'image/png',
|
||||
'size' => 11_447,
|
||||
'created_at' => '2021-05-14T12:30:16Z',
|
||||
'updated_at' => '2021-05-14T12:30:19Z',
|
||||
'attachment_url' => 'https://s3.eu-central-1.amazonaws.com/euc-cdn.freshdesk.com/data/helpdesk/attachments/production/80012226885/original/standalone_attachment.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS6FNSMY2RG7BSUFP%2F20210514%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210514T123300Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=750988d37a6f2f43830bfd19c895517aa051aa13b4ab26a1333369d414fef0be',
|
||||
'thumb_url' => 'https://s3.eu-central-1.amazonaws.com/euc-cdn.freshdesk.com/data/helpdesk/attachments/production/80012226885/thumb/standalone_attachment.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS6FNSMY2RG7BSUFP%2F20210514%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210514T123300Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=40b5fe1d7d418bcbd1e639b273a1038c7a73781c16d9881c2f31a11c6bebfdf9'
|
||||
}
|
||||
],
|
||||
}
|
||||
resource.merge(attachment_payload)
|
||||
end
|
||||
|
||||
let(:used_urls) do
|
||||
[
|
||||
'https://eucattachment.freshdesk.com/inline/attachment?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ODAwMTIyMjY0NzksImRvbWFpbiI6InphbW1hZC5mcmVzaGRlc2suY29tIiwiYWNjb3VudF9pZCI6MTg5MDU2MH0.cdYIOOSi7ckCFIZlQ9eynELMzJp1ECVeTLlQMCDgKo4',
|
||||
'https://s3.eu-central-1.amazonaws.com/euc-cdn.freshdesk.com/data/helpdesk/attachments/production/80012226885/original/standalone_attachment.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAS6FNSMY2RG7BSUFP%2F20210514%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20210514T123300Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=750988d37a6f2f43830bfd19c895517aa051aa13b4ab26a1333369d414fef0be',
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
create :object_manager_attribute_select, name: 'cf_custom_dropdown'
|
||||
create :object_manager_attribute_integer, name: 'cf_custom_integer'
|
||||
create :object_manager_attribute_boolean, name: 'cf_test_checkbox'
|
||||
create :object_manager_attribute_text, name: 'cf_custom_decimal'
|
||||
ObjectManager::Attribute.migration_execute
|
||||
|
||||
# Mock the attachment and inline image download requests.
|
||||
used_urls.each do |used_url|
|
||||
stub_request(:get, used_url).to_return(status: 200, body: '123', headers: {})
|
||||
end
|
||||
# Mock the ticket get request (Import::Freshdesk::Ticket::Fetch).
|
||||
stub_request(:get, 'https://yours.freshdesk.com/api/v2/tickets/13').to_return(status: 200, body: JSON.generate(ticket_get_response_payload), headers: {})
|
||||
end
|
||||
|
||||
# We only want to test here the Ticket API, so disable other modules in the sequence
|
||||
# that make their own HTTP requests.
|
||||
custom_sequence = Sequencer::Sequence::Import::Freshdesk::Ticket.sequence.dup
|
||||
custom_sequence.delete('Import::Freshdesk::Ticket::TimeEntries')
|
||||
custom_sequence.delete('Import::Freshdesk::Ticket::Conversations')
|
||||
|
||||
it 'adds tickets' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength
|
||||
allow(Sequencer::Sequence::Import::Freshdesk::Ticket).to receive(:sequence) { custom_sequence }
|
||||
expect { process(process_payload) }.to change(Ticket, :count).by(1)
|
||||
expect(Ticket.last).to have_attributes(
|
||||
title: 'Inline Images Failing?',
|
||||
note: nil,
|
||||
create_article_type_id: 5,
|
||||
create_article_sender_id: 2,
|
||||
article_count: 1,
|
||||
state_id: 2,
|
||||
group_id: group.id,
|
||||
priority_id: 1,
|
||||
owner_id: owner.id,
|
||||
customer_id: User.last.id,
|
||||
cf_custom_dropdown: 'key_2',
|
||||
cf_custom_integer: 999,
|
||||
cf_test_checkbox: true,
|
||||
cf_custom_decimal: '1.1',
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds article with inline image' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength
|
||||
allow(Sequencer::Sequence::Import::Freshdesk::Ticket).to receive(:sequence) { custom_sequence }
|
||||
expect { process(process_payload) }.to change(Ticket::Article, :count).by(1)
|
||||
expect(Ticket::Article.last).to have_attributes(
|
||||
to: 'info@zammad.org',
|
||||
body: "\n<div>\n<div dir=\"ltr\">Inline images in the first article might not be working, see following:</div>\n<div dir=\"ltr\"><img src=\"data:image/png;base64,MTIz\" style=\"width: auto;\"></div>\n</div>\n",
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds correct number of attachments' do
|
||||
allow(Sequencer::Sequence::Import::Freshdesk::Ticket).to receive(:sequence) { custom_sequence }
|
||||
process(process_payload)
|
||||
expect(Ticket::Article.last.attachments.size).to eq 1
|
||||
end
|
||||
|
||||
it 'adds attachment content' do # rubocop:disable RSpec/ExampleLength
|
||||
allow(Sequencer::Sequence::Import::Freshdesk::Ticket).to receive(:sequence) { custom_sequence }
|
||||
process(process_payload)
|
||||
expect(Ticket::Article.last.attachments.last).to have_attributes(
|
||||
'filename' => 'standalone_attachment.png',
|
||||
'size' => '3',
|
||||
'preferences' => {
|
||||
'Content-Type' => 'image/png',
|
||||
'resizable' => false,
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
29
spec/lib/sequencer/unit/freshdesk/connected_spec.rb
Normal file
29
spec/lib/sequencer/unit/freshdesk/connected_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Sequencer::Unit::Freshdesk::Connected, sequencer: :unit do
|
||||
|
||||
context 'when checking the connection to Freshdesk' do
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
dry_run: false,
|
||||
import_job: instance_double(ImportJob),
|
||||
field_map: {},
|
||||
id_map: {},
|
||||
}
|
||||
end
|
||||
|
||||
let(:response_ok) { Net::HTTPOK.new(1.0, '200', 'OK') }
|
||||
let(:response_unauthorized) { Net::HTTPUnauthorized.new(1.0, '401', 'Unauthorized') }
|
||||
|
||||
it 'check for correct connection' do
|
||||
allow(described_class).to receive(:perform_request).with(any_args).and_return(response_ok)
|
||||
expect(process(params)).to eq({ connected: true })
|
||||
end
|
||||
|
||||
it 'check for unauthorized connection' do
|
||||
allow(described_class).to receive(:perform_request).with(any_args).and_return(response_unauthorized)
|
||||
expect(process(params)).to eq({ connected: false })
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
RSpec.shared_examples 'ApplicationModel::ChecksImport' do
|
||||
describe '#id (for referential integrity during OTRS/Zendesk import)' do
|
||||
describe '#id (for referential integrity during (e.g. OTRS/Zendesk/Freshdesk) import)' do
|
||||
subject { build(described_class.name.underscore, id: next_id + 1) }
|
||||
|
||||
let(:next_id) do
|
||||
|
|
137
spec/system/import/freshdesk_spec.rb
Normal file
137
spec/system/import/freshdesk_spec.rb
Normal file
|
@ -0,0 +1,137 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Import Freshdesk', type: :system, set_up: false, authenticated_as: false do
|
||||
before(:all) do # rubocop:disable RSpec/BeforeAfterAll
|
||||
required_envs = %w[IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN IMPORT_FRESHDESK_ENDPOINT_KEY]
|
||||
required_envs.each do |key|
|
||||
skip("NOTICE: Missing environment variable #{key} for test! (Please fill up: #{required_envs.join(' && ')})") if ENV[key].blank?
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: check/clarify how the ENV-works in the CI-Envoirment?
|
||||
|
||||
# TODO: check https://git.znuny.com/zammad/zammad/-/merge_requests/1506/
|
||||
# comment out bellow block to use VCR tape for running freshdesk import
|
||||
# this allows to work around freshdesk rate limiting
|
||||
# works great for debugging freshdesk locally
|
||||
# around do |example|
|
||||
# VCR.temporary_ignore_regexps += [/^(\S+\.|)freshdesk.com$/, /^\S+.zdusercontent.com$/]
|
||||
|
||||
# example.run
|
||||
|
||||
# VCR.temporary_ignore_regexps -= [/^(\S+\.|)freshdesk.com$/, /^\S+.zdusercontent.com$/]
|
||||
# end
|
||||
|
||||
describe 'fields validation', :use_vcr do
|
||||
before do
|
||||
visit '#import'
|
||||
find('.js-freshdesk').click
|
||||
end
|
||||
|
||||
let(:subdomain_field) { find('#freshdesk-subdomain') }
|
||||
let(:token_field) { find('#freshdesk-api-token') }
|
||||
|
||||
it 'invalid hostname' do
|
||||
subdomain_field.fill_in with: 'reallybadexample'
|
||||
|
||||
expect(page).to have_css('.freshdesk-subdomain-error', text: 'Hostname not found!')
|
||||
end
|
||||
|
||||
it 'valid hostname' do
|
||||
subdomain_field.fill_in with: 'reallybadexample'
|
||||
|
||||
# wait for error to appear to validate it's hidden successfully
|
||||
find('.freshdesk-subdomain-error', text: 'Hostname not found!')
|
||||
|
||||
subdomain_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN']
|
||||
|
||||
expect(page).to have_no_css('.freshdesk-subdomain-error', text: 'Hostname not found!')
|
||||
end
|
||||
|
||||
it 'invalid credentials' do
|
||||
subdomain_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN']
|
||||
find('.js-freshdesk-credentials').click
|
||||
token_field.fill_in with: '1nv4l1dT0K3N'
|
||||
|
||||
expect(page).to have_css('.freshdesk-api-token-error', text: 'Invalid credentials!')
|
||||
end
|
||||
|
||||
it 'valid credentials' do
|
||||
subdomain_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN']
|
||||
find('.js-freshdesk-credentials').click
|
||||
token_field.fill_in with: '1nv4l1dT0K3N'
|
||||
|
||||
# wait for error to appear to validate it's hidden successfully
|
||||
expect(page).to have_css('.freshdesk-api-token-error', text: 'Invalid credentials!')
|
||||
|
||||
token_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_KEY']
|
||||
|
||||
expect(page).to have_no_css('.freshdesk-api-token-error', text: 'Invalid credentials!')
|
||||
end
|
||||
|
||||
it 'shows start button' do
|
||||
subdomain_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN']
|
||||
find('.js-freshdesk-credentials').click
|
||||
token_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_KEY']
|
||||
|
||||
expect(page).to have_css('.js-migration-start')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'import progress', :use_vcr do
|
||||
let(:subdomain_field) { find('#freshdesk-subdomain') }
|
||||
let(:token_field) { find('#freshdesk-api-token') }
|
||||
let(:job) { ImportJob.find_by(name: 'Import::Freshdesk') }
|
||||
|
||||
before do
|
||||
VCR.use_cassette 'system/import/freshdesk/import_progress_setup' do
|
||||
visit '#import'
|
||||
find('.js-freshdesk').click
|
||||
|
||||
subdomain_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_SUBDOMAIN']
|
||||
find('.js-freshdesk-credentials').click
|
||||
token_field.fill_in with: ENV['IMPORT_FRESHDESK_ENDPOINT_KEY']
|
||||
|
||||
find('.js-migration-start').click
|
||||
|
||||
await_empty_ajax_queue
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows groups progress' do
|
||||
job.update! result: { Groups: { sum: 3, total: 5 } }
|
||||
|
||||
expect(page).to have_css('.js-groups .js-done', text: '3')
|
||||
.and(have_css('.js-groups .js-total', text: '5'))
|
||||
end
|
||||
|
||||
it 'shows users progress' do
|
||||
job.update! result: { Users: { sum: 5, total: 9 } }
|
||||
|
||||
expect(page).to have_css('.js-users .js-done', text: '5')
|
||||
.and(have_css('.js-users .js-total', text: '9'))
|
||||
end
|
||||
|
||||
it 'shows organizations progress' do
|
||||
job.update! result: { Organizations: { sum: 3, total: 5 } }
|
||||
|
||||
expect(page).to have_css('.js-organizations .js-done', text: '3')
|
||||
.and(have_css('.js-organizations .js-total', text: '5'))
|
||||
end
|
||||
|
||||
it 'shows tickets progress' do
|
||||
job.update! result: { Tickets: { sum: 3, total: 5 } }
|
||||
|
||||
expect(page).to have_css('.js-tickets .js-done', text: '3')
|
||||
.and(have_css('.js-tickets .js-total', text: '5'))
|
||||
end
|
||||
|
||||
it 'shows login after import is finished' do
|
||||
job.update! finished_at: Time.zone.now
|
||||
|
||||
Rake::Task['zammad:setup:auto_wizard'].execute
|
||||
|
||||
expect(page).to have_text('Login')
|
||||
end
|
||||
end
|
||||
end
|
4300
test/data/vcr_cassettes/freshdesk_import.yml
Normal file
4300
test/data/vcr_cassettes/freshdesk_import.yml
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue