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

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