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.
- 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
|