connectors_service 8.6.0.4.pre.20221104T200814Z → 8.6.0.4.pre.20221114T233727Z

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/config/connectors.yml +6 -6
  3. data/lib/app/dispatcher.rb +12 -0
  4. data/lib/app/preflight_check.rb +11 -0
  5. data/lib/connectors/base/connector.rb +19 -12
  6. data/lib/connectors/base/simple_rules_parser.rb +42 -0
  7. data/lib/connectors/example/connector.rb +15 -0
  8. data/lib/connectors/gitlab/connector.rb +15 -1
  9. data/lib/connectors/mongodb/connector.rb +55 -36
  10. data/lib/connectors/mongodb/mongo_rules_parser.rb +81 -0
  11. data/lib/core/configuration.rb +3 -1
  12. data/lib/core/connector_job.rb +137 -0
  13. data/lib/core/connector_settings.rb +24 -11
  14. data/lib/core/elastic_connector_actions.rb +263 -24
  15. data/lib/core/filtering/post_process_engine.rb +39 -0
  16. data/lib/core/filtering/post_process_result.rb +27 -0
  17. data/lib/core/filtering/simple_rule.rb +141 -0
  18. data/lib/core/filtering/validation_job_runner.rb +53 -0
  19. data/lib/core/filtering/validation_status.rb +17 -0
  20. data/lib/core/filtering.rb +17 -0
  21. data/lib/core/ingestion/es_sink.rb +59 -0
  22. data/lib/core/ingestion/ingester.rb +90 -0
  23. data/lib/core/{output_sink.rb → ingestion.rb} +2 -5
  24. data/lib/core/scheduler.rb +40 -10
  25. data/lib/core/sync_job_runner.rb +65 -17
  26. data/lib/core.rb +2 -0
  27. data/lib/utility/bulk_queue.rb +85 -0
  28. data/lib/utility/constants.rb +2 -0
  29. data/lib/utility/filtering.rb +22 -0
  30. data/lib/utility/logger.rb +2 -1
  31. data/lib/utility.rb +5 -4
  32. metadata +16 -7
  33. data/lib/core/output_sink/base_sink.rb +0 -33
  34. data/lib/core/output_sink/combined_sink.rb +0 -38
  35. data/lib/core/output_sink/console_sink.rb +0 -51
  36. data/lib/core/output_sink/es_sink.rb +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23b78b5c5228f761b884af1bfd75b163ad912dc53331ce5a64cd3edb8f45ef86
4
- data.tar.gz: 5c74e06f315ab9af4161e88fac626e84b6d2789c0b5b080c713e780a4560ef97
3
+ metadata.gz: 8f69f05260d34b07ce34d569ce7c41fdd10349b33121823697ebbb6a4ebf9206
4
+ data.tar.gz: a2957118c80d0e2bc9ea6a8046307485c11e4f809efb01cabbb96e341dc947c2
5
5
  SHA512:
6
- metadata.gz: 75295f0d26061a977089e999190f4974f4929ca92c25c09e7ecbbd1f0117476f758b7f200b719461bdd9e7b9e6193df455d3e52bcec0634fc85a7b512721ab6a
7
- data.tar.gz: fa12af986f72d10081245e782a9152a887512e62f48ad525af59e59a4ef72238727e7bfd521f516fd452a2e74a1101c5bd1965f1e9b97f7b236b8ae2ccf626ff
6
+ metadata.gz: 63775eded9d9953b41950edd7ca86176200c4ae7510564f6f7995c336d6e78bbe40d494cb0a152a984b14b45055e7b7847a2779888d70905d4956b8c78d4bda1
7
+ data.tar.gz: 52b00d122ef43fc5afa0b4cb50bbe428111e7fe2cb7cee437dd2d2b6b32516cbd01c5803b0a11609a5021e85605a2e6bd2b30973807df3cae1420864e2fcb185
@@ -1,10 +1,10 @@
1
1
  # general metadata
2
- version: 8.6.0.4-20221104T200814Z
2
+ version: 8.6.0.4-20221114T233727Z
3
3
  repository: git@github.com:elastic/ent-search-connectors.git
4
- revision: 2051b3907639a1fe2ae68efdc33c06cf12d38383
4
+ revision: f506d5e5ebedfb0c6058d347d8ce22adc42e2cc0
5
5
  elasticsearch:
6
6
  hosts: http://localhost:9200
7
- api_key: OW1FalJJUUI1clBtUVh5RVo1QmU6QVp5LV9pU3RRUXFYb2VVYnlCRWNZdw==
7
+ api_key: WXNYeWQ0UUJ4Y3ZQV3ctbjVibnU6REx4eE8tbFhUMU94N2JoU2hIeVFMQQ==
8
8
  retry_on_failure: 3
9
9
  request_timeout: 120
10
10
  disable_warnings: true
@@ -14,11 +14,11 @@ thread_pool:
14
14
  min_threads: 0
15
15
  max_threads: 5
16
16
  max_queue: 100
17
- log_level: info
17
+ log_level: debug
18
18
  ecs_logging: false
19
19
  poll_interval: 3
20
20
  termination_timeout: 60
21
21
  heartbeat_interval: 1800
22
22
  native_mode: false
23
- connector_id: 9WEjRIQB5rPmQXyEWJB2
24
- service_type: example
23
+ connector_id: YcXyd4QBxcvPWw-n2bkA
24
+ service_type: mongodb
@@ -73,6 +73,8 @@ module App
73
73
  start_heartbeat_task(connector_settings)
74
74
  when :configuration
75
75
  start_configuration_task(connector_settings)
76
+ when :filter_validation
77
+ start_filter_validation_task(connector_settings)
76
78
  else
77
79
  Utility::Logger.error("Unknown task type: #{task}. Skipping...")
78
80
  end
@@ -120,6 +122,16 @@ module App
120
122
  Utility::ExceptionTracking.log_exception(e, "Configuration task for #{connector_settings.formatted} failed due to unexpected error.")
121
123
  end
122
124
  end
125
+
126
+ def start_filter_validation_task(connector_settings)
127
+ pool.post do
128
+ Utility::Logger.info("Validating filters for #{connector_settings.formatted}...")
129
+ validation_job_runner = Core::Filtering::ValidationJobRunner.new(connector_settings)
130
+ validation_job_runner.execute
131
+ rescue StandardError => e
132
+ Utility::ExceptionTracking.log_exception(e, "Filter validation task for #{connector_settings.formatted} failed due to unexpected error.")
133
+ end
134
+ end
123
135
  end
124
136
  end
125
137
  end
@@ -23,6 +23,7 @@ module App
23
23
  check_es_connection!
24
24
  check_es_version!
25
25
  check_system_indices!
26
+ check_single_connector!
26
27
  end
27
28
 
28
29
  private
@@ -59,6 +60,16 @@ module App
59
60
  )
60
61
  end
61
62
 
63
+ #-------------------------------------------------------------------------------------------------
64
+ # Ensures the connector is supported when running in non-native mode
65
+ def check_single_connector!
66
+ if App::Config.native_mode
67
+ Utility::Logger.info('Skip single connector check for native mode.')
68
+ elsif !Connectors::REGISTRY.registered?(App::Config.service_type)
69
+ fail_check!("The service type #{App::Config.service_type} is not supported. Terminating...")
70
+ end
71
+ end
72
+
62
73
  def check_es_connection_with_retries!(retry_interval:, retry_timeout:)
63
74
  started_at = Time.now
64
75
 
@@ -7,8 +7,9 @@
7
7
  # frozen_string_literal: true
8
8
 
9
9
  require 'bson'
10
- require 'core/output_sink'
10
+ require 'core/ingestion'
11
11
  require 'utility'
12
+ require 'utility/filtering'
12
13
  require 'app/config'
13
14
  require 'active_support/core_ext/hash/indifferent_access'
14
15
 
@@ -32,16 +33,27 @@ module Connectors
32
33
  raise 'Not implemented for this connector'
33
34
  end
34
35
 
36
+ def self.kibana_features
37
+ [
38
+ Utility::Constants::FILTERING_RULES_FEATURE,
39
+ Utility::Constants::FILTERING_ADVANCED_FEATURE
40
+ ]
41
+ end
42
+
43
+ def self.validate_filtering(_filtering = {})
44
+ raise 'Not implemented for this connector'
45
+ end
46
+
35
47
  attr_reader :rules, :advanced_filter_config
36
48
 
37
49
  def initialize(configuration: {}, job_description: {})
38
50
  @configuration = configuration.dup || {}
39
51
  @job_description = job_description&.dup || {}
40
52
 
41
- filter = get_filter(@job_description[:filtering])
53
+ filtering = Utility::Filtering.extract_filter(@job_description.dig(:connector, :filtering))
42
54
 
43
- @rules = Utility::Common.return_if_present(filter[:rules], [])
44
- @advanced_filter_config = Utility::Common.return_if_present(filter[:advanced_config], {})
55
+ @rules = filtering[:rules] || []
56
+ @advanced_filter_config = filtering[:advanced_snippet] || {}
45
57
  end
46
58
 
47
59
  def yield_documents; end
@@ -67,16 +79,11 @@ module Connectors
67
79
  end
68
80
 
69
81
  def filtering_present?
70
- @advanced_filter_config.present? || @rules.present?
82
+ @advanced_filter_config.present? && !@advanced_filter_config.empty? || @rules.present?
71
83
  end
72
84
 
73
- private
74
-
75
- def get_filter(filtering)
76
- # assume for now, that first object in filtering array or a filter object itself is the only filtering object
77
- filter = filtering.is_a?(Array) ? filtering[0] : filtering
78
-
79
- filter.present? ? filter : {}
85
+ def metadata
86
+ {}
80
87
  end
81
88
  end
82
89
  end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+ # frozen_string_literal: true
7
+
8
+ require 'active_support/core_ext/hash/indifferent_access'
9
+ require 'active_support/core_ext/object/blank'
10
+ require 'core/filtering/simple_rule'
11
+
12
+ module Connectors
13
+ module Base
14
+ class SimpleRulesParser
15
+ def initialize(rules)
16
+ @rules = (rules || []).map(&:with_indifferent_access).filter { |r| r[:id] != 'DEFAULT' }.sort_by { |r| r[:order] }
17
+ end
18
+
19
+ def parse
20
+ merge_rules(@rules.map do |rule_hash|
21
+ rule = Core::Filtering::SimpleRule.new(rule_hash)
22
+ unless rule.is_include? || rule.is_exclude?
23
+ raise "Unknown policy: #{rule.policy}"
24
+ end
25
+ parse_rule(rule)
26
+ end)
27
+ end
28
+
29
+ private
30
+
31
+ # merge all rules into a filter object or array
32
+ # in a base case, does no transformations
33
+ def merge_rules(rules)
34
+ rules || []
35
+ end
36
+
37
+ def parse_rule(_rule)
38
+ raise 'Not implemented'
39
+ end
40
+ end
41
+ end
42
+ end
@@ -7,6 +7,7 @@
7
7
  # frozen_string_literal: true
8
8
 
9
9
  require 'connectors/base/connector'
10
+ require 'core/filtering/validation_status'
10
11
  require 'utility'
11
12
 
12
13
  module Connectors
@@ -45,6 +46,20 @@ module Connectors
45
46
  # raise 'something went wrong'
46
47
  end
47
48
 
49
+ def self.validate_filtering(filtering = {})
50
+ # TODO: real filtering validation will follow later
51
+ errors = [
52
+ {
53
+ :ids => ['missing-implementation'],
54
+ :messages => ['Filtering is not implemented yet for the example connector']
55
+ }
56
+ ]
57
+
58
+ return { :state => Core::Filtering::ValidationStatus::INVALID, :errors => errors } if filtering.present?
59
+
60
+ { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
61
+ end
62
+
48
63
  def yield_documents
49
64
  attachments = [
50
65
  load_attachment('first_attachment.txt'),
@@ -11,7 +11,7 @@ require 'connectors/base/connector'
11
11
  require 'connectors/gitlab/extractor'
12
12
  require 'connectors/gitlab/custom_client'
13
13
  require 'connectors/gitlab/adapter'
14
- require 'core/output_sink'
14
+ require 'core/ingestion'
15
15
 
16
16
  module Connectors
17
17
  module GitLab
@@ -36,6 +36,20 @@ module Connectors
36
36
  }
37
37
  end
38
38
 
39
+ def self.validate_filtering(filtering = {})
40
+ # TODO: real filtering validation will follow later
41
+ errors = [
42
+ {
43
+ :ids => ['missing-implementation'],
44
+ :messages => ['Filtering is not implemented yet for the GitLab connector']
45
+ }
46
+ ]
47
+
48
+ return { :state => Core::Filtering::ValidationStatus::INVALID, :errors => errors } if filtering.present?
49
+
50
+ { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
51
+ end
52
+
39
53
  def initialize(configuration: {}, job_description: {})
40
54
  super
41
55
 
@@ -7,6 +7,8 @@
7
7
  # frozen_string_literal: true
8
8
 
9
9
  require 'connectors/base/connector'
10
+ require 'core/filtering/validation_status'
11
+ require 'connectors/mongodb/mongo_rules_parser'
10
12
  require 'mongo'
11
13
  require 'utility'
12
14
 
@@ -28,27 +30,46 @@ module Connectors
28
30
 
29
31
  def self.configurable_fields
30
32
  {
31
- :host => {
32
- :label => 'Server Hostname'
33
- },
34
- :user => {
35
- :label => 'Username'
36
- },
37
- :password => {
38
- :label => 'Password'
39
- },
40
- :database => {
41
- :label => 'Database'
42
- },
43
- :collection => {
44
- :label => 'Collection'
45
- },
46
- :direct_connection => {
47
- :label => 'Direct connection? (true/false)'
48
- }
33
+ :host => {
34
+ :label => 'Server Hostname'
35
+ },
36
+ :user => {
37
+ :label => 'Username'
38
+ },
39
+ :password => {
40
+ :label => 'Password'
41
+ },
42
+ :database => {
43
+ :label => 'Database'
44
+ },
45
+ :collection => {
46
+ :label => 'Collection'
47
+ },
48
+ :direct_connection => {
49
+ :label => 'Direct connection? (true/false)'
50
+ }
49
51
  }
50
52
  end
51
53
 
54
+ def self.validate_filtering(filtering = {})
55
+ valid_filtering = { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
56
+
57
+ return valid_filtering unless filtering.present?
58
+
59
+ filter = Utility::Filtering.extract_filter(filtering)
60
+
61
+ advanced_filter_config = filter[:advanced_snippet] || {}
62
+ filter_keys = advanced_filter_config&.keys
63
+
64
+ if !filter_keys&.empty? && (filter_keys.size != 1 || !ALLOWED_TOP_LEVEL_FILTER_KEYS.include?(filter_keys[0]&.to_s))
65
+ return { :state => Core::Filtering::ValidationStatus::INVALID,
66
+ :errors => [{ :ids => ['wrong-keys'],
67
+ :messages => ["Only one of #{ALLOWED_TOP_LEVEL_FILTER_KEYS} is allowed in the filtering object. Keys present: '#{filter_keys}'."] }] }
68
+ end
69
+
70
+ valid_filtering
71
+ end
72
+
52
73
  def initialize(configuration: {}, job_description: {})
53
74
  super
54
75
 
@@ -61,8 +82,6 @@ module Connectors
61
82
  end
62
83
 
63
84
  def yield_documents
64
- check_filtering
65
-
66
85
  with_client do |client|
67
86
  # We do paging using skip().limit() here to make Ruby recycle the memory for each page pulled from the server after it's not needed any more.
68
87
  # This gives us more control on the usage of the memory (we can adjust PAGE_SIZE constant for that to decrease max memory consumption).
@@ -89,6 +108,7 @@ module Connectors
89
108
  loop do
90
109
  found_in_page = 0
91
110
 
111
+ Utility::Logger.info("Requesting #{PAGE_SIZE} documents from MongoDB (Starting at #{skip})")
92
112
  view = cursor.skip(skip).limit(PAGE_SIZE)
93
113
  view.each do |document|
94
114
  yield serialize(document)
@@ -117,20 +137,9 @@ module Connectors
117
137
 
118
138
  return create_aggregate_cursor(collection) if @advanced_filter_config[:aggregate].present?
119
139
 
120
- collection.find
121
- end
122
-
123
- def check_filtering
124
- return unless filtering_present?
140
+ return create_simple_rules_cursor(collection) if @rules.present?
125
141
 
126
- check_find_and_aggregate
127
- end
128
-
129
- def check_find_and_aggregate
130
- if @advanced_filter_config.keys.size != 1
131
- invalid_keys_msg = "Only one of #{ALLOWED_TOP_LEVEL_FILTER_KEYS} is allowed in the filtering object. Keys present: '#{@advanced_filter_config.keys}'."
132
- raise Utility::InvalidFilterConfigError.new(invalid_keys_msg)
133
- end
142
+ collection.find
134
143
  end
135
144
 
136
145
  def create_aggregate_cursor(collection)
@@ -159,6 +168,16 @@ module Connectors
159
168
  [collection.find(filter, options), options]
160
169
  end
161
170
 
171
+ def create_simple_rules_cursor(collection)
172
+ filter = {}
173
+ if @rules.present?
174
+ parser = MongoRulesParser.new(@rules)
175
+ filter = parser.parse
176
+ end
177
+ Utility::Logger.info("Filtering with simple rules filter: #{filter}")
178
+ filter.present? ? collection.find(filter) : collection.find
179
+ end
180
+
162
181
  def extract_options(mongodb_function)
163
182
  mongodb_function[:options].present? ? mongodb_function[:options] : {}
164
183
  end
@@ -173,9 +192,9 @@ module Connectors
173
192
  raise "Invalid value for 'Direct connection' : #{@direct_connection}." unless %w[true false].include?(@direct_connection.to_s.strip.downcase)
174
193
 
175
194
  args = {
176
- database: @database,
177
- direct_connection: to_boolean(@direct_connection)
178
- }
195
+ database: @database,
196
+ direct_connection: to_boolean(@direct_connection)
197
+ }
179
198
 
180
199
  if @user.present? || @password.present?
181
200
  args[:user] = @user
@@ -0,0 +1,81 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'active_support/core_ext/object'
10
+ require 'connectors/base/simple_rules_parser'
11
+ require 'core/filtering/simple_rule'
12
+
13
+ module Connectors
14
+ module MongoDB
15
+ class MongoRulesParser < Connectors::Base::SimpleRulesParser
16
+ def parse_rule(rule)
17
+ field = rule.field
18
+ value = rule.value
19
+ unless value.present?
20
+ raise "value is required for field: #{field}"
21
+ end
22
+ unless field.present?
23
+ raise "field is required for rule: #{rule}"
24
+ end
25
+ op = rule.rule
26
+ case op
27
+ when Core::Filtering::SimpleRule::Rule::EQUALS
28
+ parse_equals(rule)
29
+ when Core::Filtering::SimpleRule::Rule::GREATER_THAN
30
+ parse_greater_than(rule)
31
+ when Core::Filtering::SimpleRule::Rule::LESS_THAN
32
+ parse_less_than(rule)
33
+ when Core::Filtering::SimpleRule::Rule::REGEX
34
+ parse_regex(rule)
35
+ else
36
+ raise "Unknown operator: #{op}"
37
+ end
38
+ end
39
+
40
+ def merge_rules(rules)
41
+ return {} if rules.empty?
42
+ return rules[0] if rules.size == 1
43
+ { '$and' => rules }
44
+ end
45
+
46
+ private
47
+
48
+ def parse_equals(rule)
49
+ if rule.is_include?
50
+ { rule.field => rule.value }
51
+ else
52
+ { rule.field => { '$ne' => rule.value } }
53
+ end
54
+ end
55
+
56
+ def parse_greater_than(rule)
57
+ if rule.is_include?
58
+ { rule.field => { '$gt' => rule.value } }
59
+ else
60
+ { rule.field => { '$lte' => rule.value } }
61
+ end
62
+ end
63
+
64
+ def parse_less_than(rule)
65
+ if rule.is_include?
66
+ { rule.field => { '$lt' => rule.value } }
67
+ else
68
+ { rule.field => { '$gte' => rule.value } }
69
+ end
70
+ end
71
+
72
+ def parse_regex(rule)
73
+ if rule.is_include?
74
+ { rule.field => /#{rule.value}/ }
75
+ else
76
+ { rule.field => { '$not' => /#{rule.value}/ } }
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -24,8 +24,10 @@ module Core
24
24
  return
25
25
  end
26
26
  configuration = connector_class.configurable_fields_indifferent_access
27
+ features = connector_class.kibana_features.each_with_object({}) { |feature, hsh| hsh[feature] = true }
27
28
  doc = {
28
- :configuration => configuration
29
+ :configuration => configuration,
30
+ :features => features
29
31
  }
30
32
 
31
33
  doc[:service_type] = service_type if service_type && connector_settings.needs_service_type?
@@ -0,0 +1,137 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'active_support/core_ext/hash/indifferent_access'
10
+ require 'connectors/sync_status'
11
+ require 'core/elastic_connector_actions'
12
+ require 'utility'
13
+
14
+ module Core
15
+ class ConnectorJob
16
+ DEFAULT_PAGE_SIZE = 100
17
+
18
+ # Error Classes
19
+ class ConnectorJobNotFoundError < StandardError; end
20
+
21
+ def self.fetch_by_id(job_id)
22
+ es_response = ElasticConnectorActions.get_job(job_id)
23
+
24
+ raise ConnectorJobNotFoundError.new("Connector job with id=#{job_id} was not found.") unless es_response[:found]
25
+ new(es_response)
26
+ end
27
+
28
+ def self.pending_jobs(page_size = DEFAULT_PAGE_SIZE)
29
+ query = { terms: { status: Connectors::SyncStatus::PENDING_STATUES } }
30
+ fetch_jobs_by_query(query, page_size)
31
+ end
32
+
33
+ def self.orphaned_jobs(_page_size = DEFAULT_PAGE_SIZE)
34
+ []
35
+ end
36
+
37
+ def self.stuck_jobs(_page_size = DEFAULT_PAGE_SIZE)
38
+ []
39
+ end
40
+
41
+ def self.enqueue(_connector_id)
42
+ nil
43
+ end
44
+
45
+ def id
46
+ @elasticsearch_response[:_id]
47
+ end
48
+
49
+ def [](property_name)
50
+ @elasticsearch_response[:_source][property_name]
51
+ end
52
+
53
+ def status
54
+ self[:status]
55
+ end
56
+
57
+ def in_progress?
58
+ status == Connectors::SyncStatus::IN_PROGRESS
59
+ end
60
+
61
+ def canceling?
62
+ status == Connectors::SyncStatus::CANCELING
63
+ end
64
+
65
+ def connector_snapshot
66
+ self[:connector]
67
+ end
68
+
69
+ def connector_id
70
+ connector_snapshot[:id]
71
+ end
72
+
73
+ def index_name
74
+ connector_snapshot[:configuration]
75
+ end
76
+
77
+ def language
78
+ connector_snapshot[:language]
79
+ end
80
+
81
+ def service_type
82
+ connector_snapshot[:service_type]
83
+ end
84
+
85
+ def configuration
86
+ connector_snapshot[:configuration]
87
+ end
88
+
89
+ def filtering
90
+ Utility::Filtering.extract_filter(connector_snapshot[:filtering])
91
+ end
92
+
93
+ def pipeline
94
+ connector_snapshot[:pipeline]
95
+ end
96
+
97
+ def connector
98
+ @connector ||= ConnectorSettings.fetch_by_id(connector_id)
99
+ end
100
+
101
+ def reload_connector!
102
+ @connector = nil
103
+ connector
104
+ end
105
+
106
+ def reload
107
+ es_response = ElasticConnectorActions.get_job(id)
108
+ raise ConnectorJobNotFoundError.new("Connector job with id=#{id} was not found.") unless es_response[:found]
109
+ # TODO: remove the usage of with_indifferent_access. get_id method is expected to return a hash
110
+ @elasticsearch_response = es_response.with_indifferent_access
111
+ @connector = nil
112
+ end
113
+
114
+ private
115
+
116
+ def initialize(es_response)
117
+ # TODO: remove the usage of with_indifferent_access. The initialize method should expect a hash argument
118
+ @elasticsearch_response = es_response.with_indifferent_access
119
+ end
120
+
121
+ def self.fetch_jobs_by_query(query, page_size)
122
+ results = []
123
+ offset = 0
124
+ loop do
125
+ response = ElasticConnectorActions.search_jobs(query, page_size, offset)
126
+
127
+ hits = response.dig('hits', 'hits') || []
128
+ total = response.dig('hits', 'total', 'value') || 0
129
+ results += hits.map { |hit| new(hit) }
130
+ break if results.size >= total
131
+ offset += hits.size
132
+ end
133
+
134
+ results
135
+ end
136
+ end
137
+ end