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.
- checksums.yaml +4 -4
- data/README.md +199 -159
- data/lib/ticket_abstractor_client.rb +11 -39
- data/lib/ticket_abstractor_client/base/attachment.rb +52 -0
- data/lib/ticket_abstractor_client/base/client.rb +54 -0
- data/lib/ticket_abstractor_client/base/comment.rb +35 -0
- data/lib/ticket_abstractor_client/base/errors.rb +25 -0
- data/lib/ticket_abstractor_client/base/fields_filter.rb +15 -0
- data/lib/ticket_abstractor_client/base/response_handler.rb +28 -0
- data/lib/ticket_abstractor_client/base/ticket.rb +79 -0
- data/lib/ticket_abstractor_client/client_helper.rb +29 -0
- data/lib/ticket_abstractor_client/configuration.rb +61 -0
- data/lib/ticket_abstractor_client/jira/attachment.rb +42 -0
- data/lib/ticket_abstractor_client/jira/client.rb +83 -0
- data/lib/ticket_abstractor_client/jira/comment.rb +35 -0
- data/lib/ticket_abstractor_client/jira/errors.rb +25 -0
- data/lib/ticket_abstractor_client/jira/fields_meta.rb +36 -0
- data/lib/ticket_abstractor_client/jira/params_builder.rb +96 -0
- data/lib/ticket_abstractor_client/jira/ticket.rb +131 -0
- data/lib/ticket_abstractor_client/service_now/attachment.rb +41 -0
- data/lib/ticket_abstractor_client/service_now/client.rb +78 -0
- data/lib/ticket_abstractor_client/service_now/comment.rb +30 -0
- data/lib/ticket_abstractor_client/service_now/errors.rb +25 -0
- data/lib/ticket_abstractor_client/service_now/params_builder.rb +56 -0
- data/lib/ticket_abstractor_client/service_now/ticket.rb +120 -0
- data/lib/ticket_abstractor_client/version.rb +1 -1
- metadata +52 -9
- data/lib/ticket_abstractor_client/brouha_client.rb +0 -33
- data/lib/ticket_abstractor_client/client.rb +0 -31
- data/lib/ticket_abstractor_client/i_logger_client.rb +0 -5
- data/lib/ticket_abstractor_client/itrc_client.rb +0 -33
- data/lib/ticket_abstractor_client/jira_client.rb +0 -128
- data/lib/ticket_abstractor_client/service_now_client.rb +0 -61
- data/lib/ticket_abstractor_client/sev_one_client.rb +0 -21
@@ -1,39 +1,13 @@
|
|
1
1
|
module TicketAbstractorClient
|
2
|
-
|
3
|
-
|
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
|
27
|
-
@
|
5
|
+
def configuration
|
6
|
+
@configuration ||= Configuration.new
|
28
7
|
end
|
29
8
|
|
30
|
-
def
|
31
|
-
|
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/
|
46
|
-
require 'ticket_abstractor_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
|