intercom_export 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ module IntercomExport
2
+ module Listener
3
+ class Std
4
+ def initialize(stdout: STDOUT, stderr: STDERR)
5
+ @stdout = stdout
6
+ @stderr = stderr
7
+ end
8
+
9
+ def executing(command)
10
+ puts "Importing: #{command.inspect}"
11
+ puts
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :stdout, :stderr
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ require 'intercom_export/reference'
2
+
3
+ require 'virtus'
4
+
5
+ module IntercomExport
6
+ module Model
7
+ class IntercomAdmin
8
+ include Virtus.value_object
9
+
10
+ values do
11
+ attribute :id, Integer
12
+ attribute :name, String
13
+ attribute :email, String
14
+ end
15
+
16
+ def reference
17
+ IntercomExport::Reference.new("intercom-admin-#{id}")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'intercom_export/reference'
2
+
3
+ require 'virtus'
4
+
5
+ module IntercomExport
6
+ module Model
7
+ class IntercomConversation
8
+ include Virtus.value_object
9
+
10
+ values do
11
+ attribute :id, String
12
+ attribute :name, String
13
+ attribute :user
14
+ attribute :assignee
15
+ attribute :conversation_message, Hash[Symbol => Object]
16
+ attribute :created_at
17
+ attribute :updated_at
18
+ attribute :tags
19
+ attribute :open
20
+ attribute :conversation_parts, Array[Hash[Symbol => Object]]
21
+ end
22
+
23
+ def reference
24
+ IntercomExport::Reference.new("intercom-conversation-#{id}")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ require 'intercom_export/reference'
2
+
3
+ require 'virtus'
4
+
5
+ module IntercomExport
6
+ module Model
7
+ class IntercomUser
8
+ include Virtus.value_object
9
+
10
+ values do
11
+ attribute :id, String
12
+ attribute :name, String
13
+ attribute :email, String
14
+ end
15
+
16
+ def reference
17
+ IntercomExport::Reference.new("intercom-user-#{id}")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ require 'virtus'
2
+
3
+ module IntercomExport
4
+ module Model
5
+ class ZendeskTicket
6
+ include Virtus.value_object
7
+
8
+ values do
9
+ attribute :id, Integer
10
+ attribute :url, String
11
+ attribute :external_id, String
12
+ attribute :type, String
13
+ attribute :subject, String
14
+ attribute :raw_subject, String
15
+ attribute :description, String
16
+ attribute :priority, String
17
+ attribute :status, String
18
+ attribute :recipient, String
19
+ attribute :requester_id, Integer
20
+ attribute :submitter_id, Integer
21
+ attribute :assignee_id, Integer
22
+ attribute :organization_id, Integer
23
+ attribute :group_id, Integer
24
+ attribute :collaborator_ids, Array
25
+ attribute :forum_topic_id, Integer
26
+ attribute :problem_id, Integer
27
+ attribute :has_incidents, Boolean
28
+ attribute :due_at, Time
29
+ attribute :tags, Array
30
+ attribute :via
31
+ attribute :custom_fields, Array
32
+ attribute :satisfaction_rating
33
+ attribute :sharing_agreement_ids, Array
34
+ attribute :followup_ids, Array
35
+ attribute :ticket_form_id, Integer
36
+ attribute :brand_id, Integer
37
+ attribute :created_at, Time
38
+ attribute :updated_at, Time
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ require 'intercom_export/reference'
2
+ require 'virtus'
3
+ module IntercomExport
4
+ module Model
5
+ class ZendeskUser
6
+ include Virtus.value_object
7
+
8
+ values do
9
+ attribute :id, Integer
10
+ attribute :url, String
11
+ attribute :name, String
12
+ attribute :email, String
13
+ attribute :created_at, Time
14
+ attribute :updated_at, Time
15
+ attribute :time_zone, String
16
+ attribute :phone
17
+ attribute :photo
18
+ attribute :locale_id, Integer
19
+ attribute :locale, String
20
+ attribute :organization_id
21
+ attribute :role, String
22
+ attribute :verified, Boolean
23
+ attribute :external_id
24
+ attribute :tags, Array[String]
25
+ attribute :alias, String
26
+ attribute :active, Boolean
27
+ attribute :shared, Boolean
28
+ attribute :shared_agent, Boolean
29
+ attribute :last_login_at, Time
30
+ attribute :two_factor_auth_enabled
31
+ attribute :signature, String
32
+ attribute :details, String
33
+ attribute :notes, String
34
+ attribute :custom_role_id
35
+ attribute :moderator, Boolean
36
+ attribute :ticket_restriction
37
+ attribute :only_private_comments, Boolean
38
+ attribute :restricted_agent, Boolean
39
+ attribute :suspended, Boolean
40
+ attribute :chat_only, Boolean
41
+ attribute :user_fields, Hash
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ module IntercomExport
2
+ class Reference
3
+ def initialize(value)
4
+ @value = value
5
+ end
6
+
7
+ def ==(other)
8
+ other.is_a?(self.class) && value == other.value
9
+ end
10
+ alias_method :eql?, :==
11
+
12
+ def hash
13
+ self.value.hash
14
+ end
15
+
16
+ attr_reader :value
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module IntercomExport
2
+ module Source
3
+ class IntercomConversations
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+
8
+ include Enumerable
9
+
10
+ def each(&block)
11
+ client.conversations.find_all({}).lazy.map { |c| client.conversations.find(id: c.id) }.each(&block)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :client
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,81 @@
1
+ require 'intercom_export/reference'
2
+ require 'intercom_export/model/intercom_conversation'
3
+ require 'intercom_export/model/intercom_user'
4
+ require 'intercom_export/model/intercom_admin'
5
+
6
+ module IntercomExport
7
+ module Splitter
8
+ class Intercom
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def split(conversation)
14
+ conversation_hash = flatten(conversation)
15
+
16
+ dependencies_for_conversation!(conversation_hash) + [
17
+ IntercomExport::Model::IntercomConversation.new(conversation_hash)
18
+ ]
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :client
24
+
25
+ def flatten(conversation)
26
+ hash = conversation.to_hash
27
+ hash['conversation_parts'] = hash.fetch('conversation_parts').map { |p| p.to_hash }
28
+ hash['conversation_message'] = hash.fetch('conversation_message').to_hash
29
+ hash
30
+ end
31
+
32
+ def model_for(object)
33
+ case object
34
+ when ::Intercom::User
35
+ load_user(object)
36
+ when ::Intercom::Admin
37
+ load_admin(object)
38
+ when ::Intercom::NobodyAdmin
39
+ nil
40
+ else
41
+ raise "Unrecognised object #{object.inspect}"
42
+ end
43
+ end
44
+
45
+ def load_user(object)
46
+ IntercomExport::Model::IntercomUser.new(client.users.find(id: object.id).to_hash)
47
+ end
48
+
49
+ def load_admin(object)
50
+ admin = client.admins.all.find { |a| a.id == Integer(object.id) }
51
+ return unless admin
52
+ IntercomExport::Model::IntercomAdmin.new(admin.to_hash)
53
+ end
54
+
55
+ def replace_reference!(hash, key)
56
+ value = hash[key]
57
+ return nil unless value
58
+ model = model_for(value)
59
+ hash[key] = model && model.reference
60
+ model
61
+ end
62
+
63
+ def dependencies_for_conversation!(conversation_hash)
64
+ dependencies = []
65
+
66
+ conversation_hash['conversation_parts'] = conversation_hash.fetch('conversation_parts').select { |p|
67
+ p.fetch('body', nil)
68
+ }.map { |part|
69
+ dependencies.push(replace_reference!(part, 'assigned_to'))
70
+ dependencies.push(replace_reference!(part, 'author'))
71
+ part
72
+ }
73
+
74
+ dependencies.push(replace_reference!(conversation_hash, 'user'))
75
+ dependencies.push(replace_reference!(conversation_hash, 'assignee'))
76
+ dependencies.push(replace_reference!(conversation_hash.fetch('conversation_message'), 'author'))
77
+ dependencies.compact.uniq
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module IntercomExport
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+ require 'intercom_export/cli'
3
+ require 'intercom_export/coordinator'
4
+
5
+ RSpec.describe IntercomExport::Cli do
6
+
7
+ let(:coordinator_class) { class_double(IntercomExport::Coordinator) }
8
+ let(:expected_coordinator) { instance_double(IntercomExport::Coordinator) }
9
+
10
+ subject do
11
+ IntercomExport::Cli.new(
12
+ 'the_program_name',
13
+ [
14
+ '--intercom-app-id', 'foobar',
15
+ '--intercom-api-key', 'asdf',
16
+ '--zendesk-address', 'example.zendesk.com',
17
+ '--zendesk-username', 'admin@example.com',
18
+ '--zendesk-token', 'secret'
19
+ ],
20
+ coordinator_class: coordinator_class
21
+ )
22
+ end
23
+
24
+ describe '#run' do
25
+ let(:expected_intercom_client) { instance_double(Intercom::Client) }
26
+ let(:expected_zendesk_client) { instance_double(ZendeskAPI::Client) }
27
+ let(:expected_intercom_conversation_source) {
28
+ instance_double(IntercomExport::Source::IntercomConversations)
29
+ }
30
+ let(:expected_intercom_splitter) { instance_double(IntercomExport::Splitter::Intercom) }
31
+ let(:expected_intercom_zendesk_finder) { instance_double(IntercomExport::Finder::IntercomZendesk) }
32
+ let(:expected_intercom_zendesk_differ) { instance_double(IntercomExport::Differ::IntercomZendesk) }
33
+ let(:expected_zendesk_executor) { instance_double(IntercomExport::Executor::Zendesk) }
34
+
35
+ before do
36
+ allow(Intercom::Client).to receive(:new)
37
+ .with(app_id: 'foobar', api_key: 'asdf')
38
+ .and_return(expected_intercom_client)
39
+
40
+ allow(ZendeskAPI::Client).to receive(:new)
41
+ .and_return(expected_zendesk_client)
42
+
43
+ allow(IntercomExport::Source::IntercomConversations).to receive(:new)
44
+ .with(expected_intercom_client)
45
+ .and_return(expected_intercom_conversation_source)
46
+
47
+ allow(IntercomExport::Splitter::Intercom).to receive(:new)
48
+ .with(expected_intercom_client)
49
+ .and_return(expected_intercom_splitter)
50
+
51
+ allow(IntercomExport::Finder::IntercomZendesk).to receive(:new)
52
+ .with(expected_zendesk_client)
53
+ .and_return(expected_intercom_zendesk_finder)
54
+
55
+ allow(IntercomExport::Differ::IntercomZendesk).to receive(:new)
56
+ .and_return(expected_intercom_zendesk_differ)
57
+
58
+ allow(IntercomExport::Executor::Zendesk).to receive(:new)
59
+ .with(expected_zendesk_client, anything)
60
+ .and_return(expected_zendesk_executor)
61
+ end
62
+
63
+ it 'takes the arguments and initializes the coordinator' do
64
+ allow(coordinator_class).to receive(:new).with(
65
+ source: expected_intercom_conversation_source,
66
+ splitter: expected_intercom_splitter,
67
+ finder: expected_intercom_zendesk_finder,
68
+ differ: expected_intercom_zendesk_differ,
69
+ executor: expected_zendesk_executor
70
+ ).and_return(expected_coordinator)
71
+
72
+ expect(expected_coordinator).to receive(:run)
73
+
74
+ subject.run
75
+ end
76
+
77
+ context 'when the required arguments arent specified' do
78
+ let(:stderr) { StringIO.new }
79
+
80
+ subject do
81
+ IntercomExport::Cli.new(
82
+ 'the_program_name', ['--intercom-app-id', 'foobar'],
83
+ stderr: stderr
84
+ )
85
+ end
86
+
87
+ it 'prints the help' do
88
+ subject.run
89
+
90
+ expect(stderr.tap(&:rewind).read.length).to be > 10
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require 'intercom_export/coordinator'
3
+
4
+ require 'intercom'
5
+ require 'zendesk_api'
6
+
7
+ RSpec.describe IntercomExport::Coordinator do
8
+ let(:source) { ['a thing'] }
9
+ let(:splitter) { double('splitter') }
10
+ let(:finder) { double('finder') }
11
+ let(:differ) { double('differ') }
12
+ let(:executor) { double('executor') }
13
+
14
+ describe '#run' do
15
+ let(:expected_parts) { [double('part1', id: 1), double('part2', id: 2)] }
16
+ let(:expected_find_for_part2) { double('part2-found1') }
17
+ let(:expected_commands_for_part1) { double('part1-diffs') }
18
+ let(:expected_commands_for_part2) { double('part2-diffs') }
19
+
20
+ before do
21
+ allow(splitter).to receive(:split).with('a thing').and_return(expected_parts)
22
+
23
+ allow(finder).to receive(:find).with(expected_parts[0]).and_return(nil)
24
+ allow(finder).to receive(:find).with(expected_parts[1]).and_return(expected_find_for_part2)
25
+
26
+ allow(differ).to receive(:diff).with(
27
+ expected_parts[0], nil
28
+ ).and_return(expected_commands_for_part1)
29
+
30
+ allow(differ).to receive(:diff).with(
31
+ expected_parts[1], expected_find_for_part2
32
+ ).and_return(expected_commands_for_part2)
33
+ end
34
+
35
+ subject do
36
+ IntercomExport::Coordinator.new(
37
+ source: source, splitter: splitter, finder: finder, differ: differ, executor: executor
38
+ )
39
+ end
40
+
41
+ it 'sends the commands to the correct executor' do
42
+ expect(executor).to receive(:call).with(expected_commands_for_part1).ordered
43
+ expect(executor).to receive(:call).with(expected_commands_for_part2).ordered
44
+
45
+ subject.run
46
+ end
47
+
48
+ context 'with duplicate items' do
49
+ let(:source) { ['a thing', 'a similar thing'] }
50
+
51
+ before do
52
+ allow(splitter).to receive(:split).with('a similar thing').and_return(expected_parts)
53
+ end
54
+
55
+ it 'does minimum number of commands' do
56
+ expect(executor).to receive(:call).with(expected_commands_for_part1).ordered
57
+ expect(executor).to receive(:call).with(expected_commands_for_part2).ordered
58
+
59
+ subject.run
60
+ end
61
+ end
62
+ end
63
+ end