ticket_abstractor_client 1.29.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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