ticket_abstractor_client 1.29.0 → 2.0.0

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +199 -159
  3. data/lib/ticket_abstractor_client.rb +11 -39
  4. data/lib/ticket_abstractor_client/base/attachment.rb +52 -0
  5. data/lib/ticket_abstractor_client/base/client.rb +54 -0
  6. data/lib/ticket_abstractor_client/base/comment.rb +35 -0
  7. data/lib/ticket_abstractor_client/base/errors.rb +25 -0
  8. data/lib/ticket_abstractor_client/base/fields_filter.rb +15 -0
  9. data/lib/ticket_abstractor_client/base/response_handler.rb +28 -0
  10. data/lib/ticket_abstractor_client/base/ticket.rb +79 -0
  11. data/lib/ticket_abstractor_client/client_helper.rb +29 -0
  12. data/lib/ticket_abstractor_client/configuration.rb +61 -0
  13. data/lib/ticket_abstractor_client/jira/attachment.rb +42 -0
  14. data/lib/ticket_abstractor_client/jira/client.rb +83 -0
  15. data/lib/ticket_abstractor_client/jira/comment.rb +35 -0
  16. data/lib/ticket_abstractor_client/jira/errors.rb +25 -0
  17. data/lib/ticket_abstractor_client/jira/fields_meta.rb +36 -0
  18. data/lib/ticket_abstractor_client/jira/params_builder.rb +96 -0
  19. data/lib/ticket_abstractor_client/jira/ticket.rb +131 -0
  20. data/lib/ticket_abstractor_client/service_now/attachment.rb +41 -0
  21. data/lib/ticket_abstractor_client/service_now/client.rb +78 -0
  22. data/lib/ticket_abstractor_client/service_now/comment.rb +30 -0
  23. data/lib/ticket_abstractor_client/service_now/errors.rb +25 -0
  24. data/lib/ticket_abstractor_client/service_now/params_builder.rb +56 -0
  25. data/lib/ticket_abstractor_client/service_now/ticket.rb +120 -0
  26. data/lib/ticket_abstractor_client/version.rb +1 -1
  27. metadata +52 -9
  28. data/lib/ticket_abstractor_client/brouha_client.rb +0 -33
  29. data/lib/ticket_abstractor_client/client.rb +0 -31
  30. data/lib/ticket_abstractor_client/i_logger_client.rb +0 -5
  31. data/lib/ticket_abstractor_client/itrc_client.rb +0 -33
  32. data/lib/ticket_abstractor_client/jira_client.rb +0 -128
  33. data/lib/ticket_abstractor_client/service_now_client.rb +0 -61
  34. data/lib/ticket_abstractor_client/sev_one_client.rb +0 -21
@@ -0,0 +1,42 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ class Attachment < Base::Attachment
4
+ attr_accessor :ticket_id, :endpoint, :project
5
+
6
+ class << self
7
+ def fetch_by_id_and_name(id, name, endpoint)
8
+ file_path = download_attachment(id, name, endpoint)
9
+ new(file_path: file_path, endpoint: endpoint)
10
+ end
11
+
12
+ def fetch_by_ticket_id(ticket_id, endpoint)
13
+ ticket = Client.new(endpoint).get_ticket_by_id(ticket_id: ticket_id, fields: :attachment)
14
+
15
+ ticket['fields']['attachment'].each_with_object([]) do |hash, attachments|
16
+ attachment = fetch_by_id_and_name(hash['id'], hash['filename'], endpoint)
17
+ attachment.ticket_id = ticket_id
18
+ attachments << attachment
19
+ end
20
+ end
21
+ alias_method :fetch, :fetch_by_ticket_id
22
+
23
+ protected
24
+
25
+ def download_attachment(id, name, endpoint)
26
+ file_content = Client.new(endpoint).get_attachment(id: id, name: name)
27
+ save_content(file_content, "#{id}_#{name}")
28
+ end
29
+ end
30
+
31
+ def initialize(opts)
32
+ super(opts)
33
+ @ticket_id, @endpoint, @project = opts.values_at(:ticket_id, :endpoint, :project)
34
+ end
35
+
36
+ def sync!
37
+ response = Client.new(@endpoint).create_attachment(self)
38
+ @external_created_at = response.first['created']
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,83 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ class Client < Base::Client
4
+ include Jira::ParamsBuilder
5
+
6
+ def initialize(endpoint = 'tp')
7
+ super()
8
+ @jira_endpoint = endpoint
9
+ end
10
+
11
+ def create_attachment(attachment)
12
+ args, params = build_attachment_params(attachment)
13
+ post(__method__, args, params)
14
+ end
15
+
16
+ def create_comment(comment)
17
+ post(__method__, build_comment_params(comment))
18
+ end
19
+
20
+ def create_ticket(ticket)
21
+ post(__method__, build_ticket_params(ticket))
22
+ end
23
+
24
+ def endpoints
25
+ get(__method__)
26
+ end
27
+
28
+ def get_all_fields
29
+ get(__method__)
30
+ end
31
+
32
+ def get_attachment(opts)
33
+ get(__method__, opts)
34
+ end
35
+
36
+ def get_comments(opts)
37
+ get(__method__, opts)
38
+ end
39
+
40
+ def get_fields_by_project(opts)
41
+ get(__method__, opts)
42
+ end
43
+
44
+ def get_ticket_by_id(opts)
45
+ get(__method__, opts)
46
+ end
47
+
48
+ def get_ticket_status(opts)
49
+ get(__method__, build_status_params(opts))
50
+ end
51
+
52
+ def get_tickets_by_jql(opts)
53
+ get(__method__, opts)
54
+ end
55
+
56
+ def get_tickets_statuses(opts)
57
+ get(__method__, opts)
58
+ end
59
+
60
+ def update_ticket(ticket)
61
+ post(__method__, build_ticket_params(ticket))
62
+ end
63
+
64
+ def transit_ticket(ticket)
65
+ post(__method__, build_transitions_params(ticket))
66
+ end
67
+
68
+ def transitions_list(ticket_id)
69
+ get(__method__, { ticket_id: ticket_id })
70
+ end
71
+
72
+ protected
73
+
74
+ def get(path, args = {}, params = {})
75
+ super("jira/#{path}", args, params.merge(jira_endpoint: @jira_endpoint))
76
+ end
77
+
78
+ def post(path, args = {}, params = {})
79
+ super("jira/#{path}", args, params.merge(jira_endpoint: @jira_endpoint))
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,35 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ class Comment < Base::Comment
4
+ attr_accessor :ticket_id, :endpoint, :project, :author_display_name
5
+
6
+ def self.fetch(ticket_id, endpoint)
7
+ comments_list = Client.new(endpoint).get_comments(ticket_id: ticket_id)
8
+ comments_list['comments'].map do |raw_comment|
9
+ new(
10
+ author: raw_comment['author']['name'],
11
+ author_display_name: raw_comment['author']['displayName'],
12
+ body: raw_comment['body'],
13
+ external_created_at: raw_comment['created'],
14
+ ticket_id: ticket_id,
15
+ endpoint: endpoint
16
+ )
17
+ end
18
+ end
19
+
20
+ def initialize(opts)
21
+ super(opts)
22
+ @author_display_name = opts[:author_display_name]
23
+ @ticket_id, @endpoint, @project = opts.values_at(:ticket_id, :endpoint, :project)
24
+ end
25
+
26
+ def sync!
27
+ created_comment_hash = Client.new(@endpoint).create_comment(self)
28
+ @author = created_comment_hash['author']['name']
29
+ @author_display_name = created_comment_hash['author']['displayName']
30
+ @external_created_at = created_comment_hash['created']
31
+ self.set_data_hash!
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ module Errors
4
+ class BaseError < Base::Errors::BaseError; end
5
+ class NotImplementedError < BaseError; end
6
+ class ConfigurationError < Base::Errors::ConfigurationError; end
7
+ class UnexpectedError < Base::Errors::UnexpectedError; end
8
+
9
+ class AttachmentArgumentError < Base::Errors::AttachmentArgumentError; end
10
+ class CommentArgumentError < Base::Errors::CommentArgumentError; end
11
+ class ClientArgumentError < Base::Errors::ClientArgumentError; end
12
+ class TicketArgumentError < Base::Errors::TicketArgumentError; end
13
+
14
+ class AttachmentError < Base::Errors::AttachmentError; end
15
+ class CommentError < Base::Errors::CommentError; end
16
+ class TicketError < Base::Errors::TicketError; end
17
+
18
+ class ConnectionError < Base::Errors::ConnectionError; end
19
+ class NotFoundError < Base::Errors::NotFoundError; end
20
+ class ParseError < Base::Errors::ParseError; end
21
+ class RequsetFormatError < Base::Errors::RequsetFormatError; end
22
+ class AccessError < Base::Errors::AccessError; end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ class FieldsMeta < Hash
4
+ attr_accessor :endpoint
5
+ attr_reader :updated_at
6
+
7
+ def initialize(endpoint)
8
+ super()
9
+ @endpoint = endpoint
10
+ update!
11
+ end
12
+
13
+ def update!
14
+ get_meta_data
15
+ update(@meta_data)
16
+ end
17
+
18
+ def expired?
19
+ expiration_period = TicketAbstractorClient.configuration.jira_meta_expiration_period
20
+ (Time.now.to_i - @updated_at) > expiration_period
21
+ end
22
+
23
+ private
24
+
25
+ def get_meta_data
26
+ raw_meta_data = Client.new(@endpoint).get_all_fields
27
+ @meta_data = filter_meta_data(raw_meta_data)
28
+ @updated_at = Time.now.to_i
29
+ end
30
+
31
+ def filter_meta_data(raw_meta_data)
32
+ raw_meta_data.each_with_object({}) { |field, meta| meta[field['id']] = field['schema'] }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,96 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ module ParamsBuilder
4
+ extend ActiveSupport::Concern
5
+
6
+ def build_attachment_params(attachment)
7
+ [
8
+ { ticket_id: attachment.ticket_id },
9
+ { attachments: [File.new(attachment.file_path, 'rb')] }
10
+ ]
11
+ end
12
+
13
+ def build_comment_params(comment)
14
+ { ticket_id: comment.ticket_id, comment: { body: comment.body } }
15
+ end
16
+
17
+ def build_ticket_params(ticket)
18
+ params_hash = { ticket_params: {} }
19
+ params_hash[:ticket_id] = ticket.ticket_id if ticket.ticket_id.present?
20
+
21
+ fields = ticket.fields.each_with_object({}) do |(field_name, field_value), fields|
22
+ value = jira_field_value(ticket.endpoint, field_name, field_value)
23
+ fields[field_name] = value
24
+ end
25
+
26
+ params_hash[:ticket_params].merge!(fields: fields)
27
+ params_hash
28
+ end
29
+
30
+ def build_transitions_params(ticket)
31
+ new_status = ticket.status
32
+ transition = ticket.available_statuses[new_status]
33
+
34
+ raise Errors::ClientArgumentError, "Status '#{new_status}' is incorrect or prohibited" if transition.blank?
35
+
36
+ { ticket_id: ticket.ticket_id, transition_params: { transition: { id: transition } } }
37
+ end
38
+
39
+ def jira_field_value(endpoint, name, value)
40
+ field_meta = fields_meta(endpoint)[name]
41
+ return unless field_meta
42
+
43
+ if field_meta['system'] # system fields mapping
44
+ case field_meta['type']
45
+ when 'project'
46
+ { key: value }
47
+ when 'string'
48
+ value
49
+ when 'datetime'
50
+ Time.parse(value).to_s(:jira_iso8601) rescue value
51
+ when 'date'
52
+ Date.parse(value).to_s rescue value
53
+ when 'number'
54
+ value.to_i
55
+ when 'array'
56
+ Array.wrap(value)
57
+ else # when 'user', 'status', 'issuetype', 'priority'
58
+ { name: value }
59
+ end
60
+
61
+ elsif custom_type = field_meta['custom'] # custom fields mapping
62
+ case custom_type
63
+ when /(:cascadingselect|:radiobuttons|:select)\Z/
64
+ { value: value }
65
+ when /:datepicker\Z/
66
+ Date.parse(value).to_s rescue value
67
+ when /:datetime\Z/
68
+ Time.parse(value).to_s(:jira_iso8601) rescue value
69
+ when /(:freetextfield|:textfield|:textarea|:url)\Z/
70
+ value
71
+ when /(:grouppicker|:singleversionpicker|:userpicker)\Z/
72
+ { name: value }
73
+ when /(:numberfield|:float)\Z/
74
+ value.to_i
75
+ when /:projectpicker\Z/
76
+ { key: value }
77
+ when /:labels\Z/
78
+ Array.wrap(value)
79
+ when /(:multigrouppicker|:multiuserpicker|:versionpicker)\Z/
80
+ Array.wrap(value).map { |v| { name: v } }
81
+ when /:multiselect\Z/
82
+ Array.wrap(value).map { |v| { value: v } }
83
+ else
84
+ raise Errors::ClientArgumentError, "Unknown customfield type #{custom_type}"
85
+ end
86
+ end
87
+ end
88
+
89
+ def fields_meta(endpoint)
90
+ meta = TicketAbstractorClient.configuration.jira_fields_meta[endpoint.to_sym]
91
+ meta.update! if meta.expired?
92
+ meta
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,131 @@
1
+ module TicketAbstractorClient
2
+ module Jira
3
+ class Ticket < Base::Ticket
4
+
5
+ def self.fetch_by_id(opts)
6
+ opts[:fields] ||= []
7
+ opts[:fields] += %w(-attachment -comment)
8
+ fetch(:by_id, opts).first
9
+ end
10
+
11
+ def self.fetch_tickets_by_jql(opts)
12
+ fetch(:by_jql, opts)
13
+ end
14
+
15
+ def initialize(opts)
16
+ super(opts)
17
+ @fields['project'] = @project if @project.present?
18
+ @status = @fields.delete('status')
19
+ end
20
+
21
+ def add_attachment(attachment)
22
+ @attachments ||= []
23
+ @attachments << Attachment.new(attachment.merge!(ticket_id: @ticket_id, endpoint: @endpoint))
24
+ @changes[:new_attachments] += 1
25
+ self
26
+ end
27
+
28
+ def add_comment(comment)
29
+ @comments ||= []
30
+ @comments << Comment.new(comment.merge!(ticket_id: @ticket_id, endpoint: @endpoint))
31
+ @changes[:new_comments] += 1
32
+ self
33
+ end
34
+
35
+ def attachments
36
+ return @attachments if @attachments.present?
37
+
38
+ return [] if @ticket_id.blank? && @attachments.blank?
39
+
40
+ @attachments = Attachment.fetch(@ticket_id, @endpoint)
41
+ end
42
+
43
+ def available_statuses
44
+ raw_response = Client.new(@endpoint).transitions_list(@ticket_id)
45
+ raw_response['transitions'].each_with_object({}) do |transition, transitions_map|
46
+ transitions_map[transition['to']['name']] = transition['id']
47
+ end
48
+ end
49
+
50
+ def comments
51
+ return @comments if @comments.present?
52
+
53
+ return [] if @ticket_id.blank? && @comments.blank?
54
+
55
+ @comments = Comment.fetch(@ticket_id, @endpoint)
56
+ end
57
+
58
+ def reload!
59
+ ticket = Ticket.fetch_by_id(ticket_id: @ticket_id, project: @project, endpoint: @endpoint)
60
+ @fields = ticket.fields
61
+ @status = ticket.status
62
+ end
63
+
64
+ def sync!
65
+ raise(Errors::TicketArgumentError, 'No changes to apply') unless self.any_changes?
66
+
67
+ return self.create_ticket if @changes[:create]
68
+
69
+ update_ticket if @changes[:update]
70
+ update_status if @changes[:new_status]
71
+ sync_comments unless @changes[:new_comments].zero?
72
+ sync_attachments unless @changes[:new_attachments].zero?
73
+ self.reload!
74
+ self.reset_changes!
75
+ end
76
+
77
+ def updated_at
78
+ @fields['updated']
79
+ end
80
+
81
+ private
82
+
83
+ def self.fetch(selektor, opts)
84
+ methods_map = { by_id: :get_ticket_by_id, by_jql: :get_tickets_by_jql }
85
+ opts = opts.with_indifferent_access
86
+ endpoint = opts.delete(:endpoint)
87
+ raw_tickets = Client.new(endpoint).public_send(methods_map.fetch(selektor.to_sym), opts)
88
+ raw_tickets = raw_tickets['issues'] if raw_tickets.key?('issues')
89
+
90
+ Array.wrap(raw_tickets).map do |raw_ticket|
91
+ fields = filter_fields(raw_ticket['fields'])
92
+ ticket = new(ticket_id: raw_ticket['key'], endpoint: endpoint, fields: fields)
93
+ ticket.reset_changes!
94
+ ticket
95
+ end
96
+ end
97
+
98
+ def self.filter_fields(fields)
99
+ fields.extract!('attachment', 'comment')
100
+ filter_fields_klass = TicketAbstractorClient.configuration.jira_fields_filter_class
101
+
102
+ return fields if filter_fields_klass.blank?
103
+
104
+ filter_fields_klass.new(fields).filter_fields
105
+ end
106
+
107
+ def create_ticket
108
+ response = Client.new(@endpoint).create_ticket(self)
109
+ @ticket_id = response['key']
110
+ self.reload!
111
+ self.reset_changes!
112
+ end
113
+
114
+ def update_status
115
+ Client.new(@endpoint).transit_ticket(self)
116
+ end
117
+
118
+ def update_ticket
119
+ Client.new(@endpoint).update_ticket(self)
120
+ end
121
+
122
+ def sync_attachments
123
+ @attachments.last(@changes[:new_attachments]).map(&:sync!)
124
+ end
125
+
126
+ def sync_comments
127
+ @comments.last(@changes[:new_comments]).map(&:sync!)
128
+ end
129
+ end
130
+ end
131
+ end