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
@@ -1,39 +1,13 @@
1
1
  module TicketAbstractorClient
2
- def self.initialize_client(url, security_token = nil)
3
- Base.new url, security_token
4
- end
5
-
6
- class Base
7
- def initialize(url, security_token = nil)
8
- @url = url
9
- @security_token = security_token
10
- @clients = {}
11
- end
12
-
13
- def jira(endpoint = 'tp')
14
- client_key = "jira_#{endpoint}".to_sym
15
- @clients[client_key] ||= JiraClient.new(@url, @security_token, CGI.escape(endpoint.to_s))
16
- end
17
-
18
- def brouha
19
- @clients[:brouha] ||= BrouhaClient.new(@url, @security_token)
20
- end
21
-
22
- def itrc
23
- @clients[:itrc] ||= ItrcClient.new(@url, @security_token)
24
- end
2
+ class << self
3
+ attr_writer :configuration
25
4
 
26
- def ilogger
27
- @clients[:ilogger] ||= ILoggerClient.new(@url, @security_token)
5
+ def configuration
6
+ @configuration ||= Configuration.new
28
7
  end
29
8
 
30
- def sev_one
31
- @clients[:sev_one] ||= SevOneClient.new(@url, @security_token)
32
- end
33
-
34
- def service_now(endpoint = 'default')
35
- client_key = "service_now_#{endpoint}".to_sym
36
- @clients[client_key] ||= ServiceNowClient.new(@url, @security_token, CGI.escape(endpoint))
9
+ def configure
10
+ yield(configuration)
37
11
  end
38
12
  end
39
13
  end
@@ -41,11 +15,9 @@ end
41
15
  require 'cgi'
42
16
  require 'json'
43
17
  require 'rest_client'
18
+ require 'active_support/all'
19
+ require 'mime-types'
20
+
44
21
  require 'ticket_abstractor_client/version'
45
- require 'ticket_abstractor_client/client'
46
- require 'ticket_abstractor_client/jira_client'
47
- require 'ticket_abstractor_client/brouha_client'
48
- require 'ticket_abstractor_client/itrc_client'
49
- require 'ticket_abstractor_client/i_logger_client'
50
- require 'ticket_abstractor_client/sev_one_client'
51
- require 'ticket_abstractor_client/service_now_client'
22
+ require 'ticket_abstractor_client/configuration'
23
+ require 'ticket_abstractor_client/client_helper'
@@ -0,0 +1,52 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ class Attachment
4
+ attr_reader :file_path, :data_hash, :external_created_at
5
+
6
+ class << self
7
+ def fetch
8
+ method_not_implemented __method__
9
+ end
10
+
11
+ protected
12
+
13
+ def method_not_implemented(method_name)
14
+ raise Errors::NotImplementedError, "#{self}##{method_name} is not implemented"
15
+ end
16
+
17
+ def save_content(content, original_filename)
18
+ tmp_file = Tempfile.new(Time.now.to_s)
19
+ tmp_file.binmode
20
+ tmp_file.write(content)
21
+ tmp_file.close
22
+ set_original_name(tmp_file.path, original_filename)
23
+ end
24
+
25
+ def set_original_name(filepath, original_name)
26
+ tmp_basename = File.basename(filepath)
27
+ tmp_path = filepath.gsub(tmp_basename, '')
28
+ FileUtils.mv filepath, File.join(tmp_path, original_name)
29
+ File.join(tmp_path, original_name)
30
+ end
31
+ end
32
+
33
+ def initialize(opts)
34
+ @file_path = opts[:file_path]
35
+ @external_created_at = opts[:external_created_at].to_time.utc rescue Time.now.utc
36
+ @data_hash = opts[:data_hash]
37
+ set_data_hash! if @data_hash.blank?
38
+ end
39
+
40
+ def sync!
41
+ self.class.method_not_implemented __method__
42
+ end
43
+
44
+ def set_data_hash!
45
+ return if self.file_path.blank?
46
+ file = File.new(self.file_path, 'rb')
47
+ @data_hash = Digest::SHA1.hexdigest(file.read)
48
+ file.close
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ class Client
4
+ include ResponseHandler
5
+
6
+ def initialize
7
+ @base_url = TicketAbstractorClient.configuration.ticket_abstractor_url
8
+ @security_token = TicketAbstractorClient.configuration.security_token
9
+ @ssl_options = TicketAbstractorClient.configuration.ssl_options
10
+
11
+ raise Errors::ConfigurationError, 'TicketAbstractor url is not given' if @base_url.blank?
12
+ raise Errors::ConfigurationError, 'SecurityToken is not given' if @security_token.blank?
13
+ end
14
+
15
+ protected
16
+
17
+ def get(path, args = {}, params = {})
18
+ params.merge! args: args.to_json, security_token: @security_token
19
+ with_response_handling do
20
+ rest_client_error_handling do
21
+ response = build_request(path, 'get', params).execute
22
+ response.headers.key?(:file_request) ? { 'result' => response } : JSON.parse(response)
23
+ end
24
+ end
25
+ end
26
+
27
+ def post(path, args = {}, params = {})
28
+ params.merge! args: args.to_json, security_token: @security_token
29
+ with_response_handling do
30
+ rest_client_error_handling do
31
+ response = build_request(path, 'post', params).execute
32
+ JSON.parse response
33
+ end
34
+ end
35
+ end
36
+
37
+ def build_request(path, method, params)
38
+ request_params = @ssl_options.merge(url: "#{@base_url}/v2/#{path}", method: method)
39
+ request_params[:headers] = { params: params } if method.inquiry.get?
40
+ request_params[:payload] = params if method.inquiry.post?
41
+
42
+ RestClient::Request.new(request_params)
43
+ end
44
+
45
+ def rest_client_error_handling
46
+ yield
47
+ rescue Errno::ECONNREFUSED => exception
48
+ raise Errors::UnexpectedError, exception.message
49
+ rescue RestClient::Exception => exception
50
+ raise Errors::UnexpectedError, exception.response.body
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,35 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ class Comment
4
+ attr_reader :author, :body, :data_hash, :external_created_at
5
+
6
+ def self.fetch(*opts)
7
+ method_not_implemented __method__
8
+ end
9
+
10
+ def initialize(opts)
11
+ opts = opts.with_indifferent_access
12
+ @author = opts[:author]
13
+ @body = opts.fetch(:body, nil) || raise(Errors::CommentArgumentError, 'Body is not given')
14
+ @external_created_at = opts[:external_created_at].to_time.utc rescue Time.now.utc
15
+ @data_hash = opts[:data_hash]
16
+ set_data_hash! if @data_hash.blank?
17
+ end
18
+
19
+ def set_data_hash!
20
+ content = "#{@external_created_at}:#{@author}:#{@body}"
21
+ @data_hash = Digest::SHA1.hexdigest(content)
22
+ end
23
+
24
+ def sync!
25
+ self.class.method_not_implemented __method__
26
+ end
27
+
28
+ protected
29
+
30
+ def self.method_not_implemented(method_name)
31
+ raise Errors::NotImplementedError, "#{self}##{method_name} is not implemented"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ module Errors
4
+ class BaseError < StandardError; end
5
+ class NotImplementedError < BaseError; end
6
+ class ConfigurationError < BaseError; end
7
+ class UnexpectedError < BaseError; end
8
+
9
+ class AttachmentArgumentError < BaseError; end
10
+ class CommentArgumentError < BaseError; end
11
+ class ClientArgumentError < BaseError; end
12
+ class TicketArgumentError < BaseError; end
13
+
14
+ class AttachmentError < BaseError; end
15
+ class CommentError < BaseError; end
16
+ class TicketError < BaseError; end
17
+
18
+ class ConnectionError < BaseError; end
19
+ class NotFoundError < BaseError; end
20
+ class ParseError < BaseError; end
21
+ class RequsetFormatError < BaseError; end
22
+ class AccessError < BaseError; end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ class FieldsFilter
4
+ attr_accessor :raw_fields
5
+
6
+ def initialize(raw_fields)
7
+ @raw_fields = raw_fields
8
+ end
9
+
10
+ def filter_fields
11
+ raise Errors::NotImplementedError, "#{self}##{__method__} is not implemented"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ module ResponseHandler
4
+ extend ActiveSupport::Concern
5
+
6
+ def with_response_handling
7
+ response = yield
8
+
9
+ raise(error_by_context(response['context']), response['error'].to_s) if response.key?('error')
10
+
11
+ response['result']
12
+ end
13
+
14
+ private
15
+
16
+ def error_by_context(context)
17
+ modulo, klass = context.split('::')
18
+ error_class = "TicketAbstractorClient::#{modulo}::Errors::#{klass}".safe_constantize
19
+ error_class.presence || unexpected_error_klass
20
+ end
21
+
22
+ def unexpected_error_klass
23
+ modulo = self.class.name.deconstantize.split('::').last
24
+ "TicketAbstractorClient::#{modulo}::Errors::UnexpectedError".safe_constantize
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,79 @@
1
+ module TicketAbstractorClient
2
+ module Base
3
+ class Ticket
4
+
5
+ attr_reader :attachments, :comments, :changes, :status, :fields
6
+ attr_accessor :ticket_id, :endpoint, :project
7
+
8
+ def initialize(opts = {})
9
+ opts = opts.with_indifferent_access
10
+ @ticket_id = opts[:ticket_id]
11
+ @fields = opts.fetch(:fields, {}).with_indifferent_access
12
+ @endpoint = opts[:endpoint] || raise(Errors::TicketArgumentError, 'Endpoint is not given')
13
+ @project = opts[:project]
14
+ initialize_changes!
15
+ mark_changes!
16
+ end
17
+
18
+ def add_attachment(attachment)
19
+ self.class.not_implemented __method__
20
+ end
21
+
22
+ def add_comment(comment)
23
+ self.class.not_implemented __method__
24
+ end
25
+
26
+ def any_changes?
27
+ @changes.values.any? { |value| value.is_a?(Fixnum) ? !value.zero? : value.present? }
28
+ end
29
+
30
+ def reset_changes!
31
+ initialize_changes!
32
+ end
33
+
34
+ def status=(status)
35
+ return if @status == status
36
+ @changes[:new_status] = true
37
+ @status = status
38
+ end
39
+
40
+ def sync!
41
+ self.class.not_implemented __method__
42
+ end
43
+
44
+ def to_hash
45
+ {
46
+ ticket_id: @ticket_id,
47
+ status: @status,
48
+ external_updated_at: @external_updated_at,
49
+ fields: @fields,
50
+ comments: @comments.map(&:to_hash),
51
+ attachments: @attachments.map(&:to_hash)
52
+ }
53
+ end
54
+
55
+ def to_json
56
+ to_hash.to_json
57
+ end
58
+
59
+ def updated_at
60
+ self.class.not_implemented __method__
61
+ end
62
+
63
+ protected
64
+
65
+ def self.not_implemented(method_name)
66
+ raise Errors::NotImplementedError, "#{self}##{__method__} is not implemented"
67
+ end
68
+
69
+ def initialize_changes!
70
+ @changes = { create: false, update: false, new_status: false, new_attachments: 0, new_comments: 0 }
71
+ end
72
+
73
+ def mark_changes!
74
+ return @changes[:create] = true if @ticket_id.blank?
75
+ @changes[:update] = true if @fields.present?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,29 @@
1
+ module TicketAbstractorClient
2
+ module ClientHelper
3
+ paths_to_load = %W(
4
+ base/*.rb
5
+ jira/params_builder.rb
6
+ jira/*.rb
7
+ service_now/params_builder.rb
8
+ service_now/*.rb
9
+ )
10
+ paths_to_load.each do |path|
11
+ location = File.expand_path(path, __dir__)
12
+ Dir[location].each { |f| require f }
13
+ end
14
+
15
+ Time::DATE_FORMATS[:jira_iso8601] = '%FT%T.%L%z'
16
+ Time::DATE_FORMATS[:service_now] = '%m-%d-%y %l:%M %p'
17
+ Time::DATE_FORMATS[:service_now_display_value] = '%m-%d-%y %-l:%M %p'
18
+ Time::DATE_FORMATS[:service_now_iso8601] = '%F %T'
19
+ Time::DATE_FORMATS[:unix_timestamp] = '%s'
20
+ Time::DATE_FORMATS[:unix_timestamp_with_ms] = '%s.%5N'
21
+
22
+ def get_jira_fields_meta
23
+ Jira::Client.new.endpoints.each_with_object({}) do |(endpoint, _), result|
24
+ result[endpoint.to_sym] = Jira::FieldsMeta.new(endpoint)
25
+ end
26
+ end
27
+ module_function :get_jira_fields_meta
28
+ end
29
+ end
@@ -0,0 +1,61 @@
1
+ module TicketAbstractorClient
2
+ class Configuration
3
+ attr_accessor :ticket_abstractor_url, :security_token, :snow_display_value, :jira_fields_meta,
4
+ :jira_meta_expiration_period, :ssl_options
5
+
6
+ DEFAULT_JIRA_META_EXPIRATION_PERIOD = 604800 # 1 week
7
+
8
+ SNOW_DISPLAY_VALUES_LIST = [
9
+ SNOW_DISPLAY_VALUE_ALL = :all,
10
+ SNOW_DISPLAY_VALUE_TRUE = true,
11
+ SNOW_DISPLAY_VALUE_FALSE = false
12
+ ].freeze
13
+
14
+ DEFAULT_SNOW_DISPLAY_VALUE = SNOW_DISPLAY_VALUE_FALSE
15
+
16
+ DEFAULT_SSL_OPTIONS = { verify_ssl: true }.freeze
17
+
18
+ def jira_fields_filter_class
19
+ @jira_fields_filter_class.presence
20
+ end
21
+
22
+ def jira_fields_filter_class=(filter_klass)
23
+ raise_configuration_error(filter_klass) unless filter_klass < Base::FieldsFilter
24
+
25
+ @jira_fields_filter_class = filter_klass
26
+ end
27
+
28
+ def jira_fields_meta
29
+ @jira_fields_meta ||= ClientHelper.get_jira_fields_meta
30
+ end
31
+
32
+ def jira_meta_expiration_period
33
+ @jira_meta_expiration_period ||= DEFAULT_JIRA_META_EXPIRATION_PERIOD
34
+ end
35
+
36
+ def snow_display_value
37
+ @snow_display_value ||= DEFAULT_SNOW_DISPLAY_VALUE
38
+ end
39
+
40
+ def snow_fields_filter_class
41
+ @snow_fields_filter_class.presence
42
+ end
43
+
44
+ def snow_fields_filter_class=(filter_klass)
45
+ raise_configuration_error(filter_klass) unless filter_klass < Base::FieldsFilter
46
+
47
+ @snow_fields_filter_class = filter_klass
48
+ end
49
+
50
+ def ssl_options
51
+ @ssl_options ||= DEFAULT_SSL_OPTIONS
52
+ end
53
+
54
+ private
55
+
56
+ def raise_configuration_error(filter_klass)
57
+ message = "Filter class '#{filter_klass.to_s}' isn't a subclass of Base::FieldsFilter"
58
+ raise Base::Errors::ConfigurationError, message
59
+ end
60
+ end
61
+ end