jay_api 27.1.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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +786 -0
  3. data/README.md +61 -0
  4. data/jay_api.gemspec +38 -0
  5. data/lib/jay_api/abstract/connection.rb +50 -0
  6. data/lib/jay_api/abstract/constant_wait.rb +17 -0
  7. data/lib/jay_api/abstract/geometric_wait.rb +35 -0
  8. data/lib/jay_api/abstract/wait_strategy.rb +43 -0
  9. data/lib/jay_api/configuration.rb +115 -0
  10. data/lib/jay_api/elasticsearch/async.rb +72 -0
  11. data/lib/jay_api/elasticsearch/batch_counter.rb +76 -0
  12. data/lib/jay_api/elasticsearch/client.rb +96 -0
  13. data/lib/jay_api/elasticsearch/client_factory.rb +100 -0
  14. data/lib/jay_api/elasticsearch/errors/elasticsearch_error.rb +13 -0
  15. data/lib/jay_api/elasticsearch/errors/end_of_query_results_error.rb +22 -0
  16. data/lib/jay_api/elasticsearch/errors/query_execution_error.rb +15 -0
  17. data/lib/jay_api/elasticsearch/errors/query_execution_failure.rb +17 -0
  18. data/lib/jay_api/elasticsearch/errors/query_execution_timeout.rb +13 -0
  19. data/lib/jay_api/elasticsearch/errors/search_after_error.rb +13 -0
  20. data/lib/jay_api/elasticsearch/index.rb +223 -0
  21. data/lib/jay_api/elasticsearch/query_builder/aggregations/aggregation.rb +66 -0
  22. data/lib/jay_api/elasticsearch/query_builder/aggregations/avg.rb +56 -0
  23. data/lib/jay_api/elasticsearch/query_builder/aggregations/errors/aggregations_error.rb +17 -0
  24. data/lib/jay_api/elasticsearch/query_builder/aggregations/errors.rb +14 -0
  25. data/lib/jay_api/elasticsearch/query_builder/aggregations/filter.rb +67 -0
  26. data/lib/jay_api/elasticsearch/query_builder/aggregations/max.rb +51 -0
  27. data/lib/jay_api/elasticsearch/query_builder/aggregations/scripted_metric.rb +72 -0
  28. data/lib/jay_api/elasticsearch/query_builder/aggregations/sum.rb +57 -0
  29. data/lib/jay_api/elasticsearch/query_builder/aggregations/terms.rb +73 -0
  30. data/lib/jay_api/elasticsearch/query_builder/aggregations/top_hits.rb +49 -0
  31. data/lib/jay_api/elasticsearch/query_builder/aggregations/value_count.rb +50 -0
  32. data/lib/jay_api/elasticsearch/query_builder/aggregations.rb +168 -0
  33. data/lib/jay_api/elasticsearch/query_builder/errors/query_builder_error.rb +16 -0
  34. data/lib/jay_api/elasticsearch/query_builder/query_clauses/bool.rb +179 -0
  35. data/lib/jay_api/elasticsearch/query_builder/query_clauses/exists.rb +33 -0
  36. data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_all.rb +22 -0
  37. data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_clauses.rb +140 -0
  38. data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_none.rb +22 -0
  39. data/lib/jay_api/elasticsearch/query_builder/query_clauses/match_phrase.rb +35 -0
  40. data/lib/jay_api/elasticsearch/query_builder/query_clauses/negator.rb +42 -0
  41. data/lib/jay_api/elasticsearch/query_builder/query_clauses/query_clause.rb +17 -0
  42. data/lib/jay_api/elasticsearch/query_builder/query_clauses/query_string.rb +50 -0
  43. data/lib/jay_api/elasticsearch/query_builder/query_clauses/range.rb +49 -0
  44. data/lib/jay_api/elasticsearch/query_builder/query_clauses/regexp.rb +39 -0
  45. data/lib/jay_api/elasticsearch/query_builder/query_clauses/term.rb +37 -0
  46. data/lib/jay_api/elasticsearch/query_builder/query_clauses/terms.rb +37 -0
  47. data/lib/jay_api/elasticsearch/query_builder/query_clauses/wildcard.rb +37 -0
  48. data/lib/jay_api/elasticsearch/query_builder/query_clauses.rb +163 -0
  49. data/lib/jay_api/elasticsearch/query_builder/script.rb +36 -0
  50. data/lib/jay_api/elasticsearch/query_builder.rb +196 -0
  51. data/lib/jay_api/elasticsearch/query_results.rb +111 -0
  52. data/lib/jay_api/elasticsearch/response.rb +43 -0
  53. data/lib/jay_api/elasticsearch/search_after_results.rb +58 -0
  54. data/lib/jay_api/elasticsearch/tasks.rb +36 -0
  55. data/lib/jay_api/elasticsearch/time.rb +18 -0
  56. data/lib/jay_api/errors/configuration_error.rb +22 -0
  57. data/lib/jay_api/errors/error.rb +8 -0
  58. data/lib/jay_api/git/errors/invalid_repository_error.rb +11 -0
  59. data/lib/jay_api/git/errors/missing_url_error.rb +13 -0
  60. data/lib/jay_api/git/gerrit/gitiles_helper.rb +58 -0
  61. data/lib/jay_api/git/repository.rb +356 -0
  62. data/lib/jay_api/id_builder.rb +52 -0
  63. data/lib/jay_api/mergeables/merge_selector/configuration.rb +29 -0
  64. data/lib/jay_api/mergeables/merge_selector/merger.rb +58 -0
  65. data/lib/jay_api/mergeables/merge_selector.rb +15 -0
  66. data/lib/jay_api/prior_version_fetcher_base.rb +66 -0
  67. data/lib/jay_api/properties_fetcher.rb +196 -0
  68. data/lib/jay_api/rspec/configuration.rb +46 -0
  69. data/lib/jay_api/rspec/git.rb +60 -0
  70. data/lib/jay_api/rspec/test_data_collector.rb +189 -0
  71. data/lib/jay_api/rspec.rb +9 -0
  72. data/lib/jay_api/version.rb +6 -0
  73. data/lib/jay_api.rb +9 -0
  74. metadata +215 -0
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'merge_selector/configuration'
4
+
5
+ module JayAPI
6
+ # This class declaration serves as an extension of the already
7
+ # defined JayAPI::Configuration class.
8
+ class Configuration
9
+ # @return [JayAPI::Mergeables::MergeSelector::Configuration] A Configuration object
10
+ # that contains the merging functionality.
11
+ def with_merge_selector
12
+ JayAPI::Mergeables::MergeSelector::Configuration.from_hash(deep_to_h)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'elasticsearch/index'
4
+ require_relative 'elasticsearch/query_results'
5
+ require_relative 'elasticsearch/query_builder'
6
+
7
+ module JayAPI
8
+ # Fetches the previous software version depending on the given version.
9
+ class PriorVersionFetcherBase
10
+ attr_reader :client
11
+
12
+ # @param [JayAPI::Elasticsearch::Client] client The +Client+ object to use
13
+ # when connecting to Elasticsearch to execute queries.
14
+ # @param [String] index_name The index of the build properties in Jay
15
+ def initialize(client:, index_name:)
16
+ @client = client
17
+ @index_name = index_name
18
+ end
19
+
20
+ # Calculates the prior software version given the current version if
21
+ # it exists.
22
+ # @param [String] current_version The version for which to find a prior
23
+ # version.
24
+ # @return [String, nil] the previous software version or nil if none is
25
+ # existent.
26
+ def prior_version(current_version:)
27
+ versions = []
28
+ results = index_obj.search(query)
29
+
30
+ results.all do |document|
31
+ # Since the version is inside an array we have to call first
32
+ versions << document['fields']['build_properties.version_code.keyword'].first
33
+ end
34
+
35
+ # This function must be injected by the project-specific fetcher
36
+ compute_last_version(current_version: current_version, existent_versions: versions)
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :cluster_url, :port, :index_name
42
+
43
+ # Computes the last version using a predefined logic.
44
+ # This function should be injected from the inheriting class, calling from
45
+ # the base class will raise an error.
46
+ # @param [String] current_version The current version for which the prior
47
+ # version should be computed.
48
+ # @param [Array<Sting>] existent_versions An array containing versions which
49
+ # exist on JAY.
50
+ # @raise [NotImplementedError] if called from the base class or the subclass
51
+ # has not overridden this method.
52
+ def compute_last_version(current_version:, existent_versions:)
53
+ raise NotImplementedError, "Please implement the method #{__method__} in #{self.class}!"
54
+ end
55
+
56
+ # @return [JayAPI::Elasticsearch::Index]
57
+ def index_obj
58
+ @index_obj ||= JayAPI::Elasticsearch::Index.new(client: client, index_name: index_name)
59
+ end
60
+
61
+ # @return [Hash] returns the query hash
62
+ def query
63
+ @query ||= JayAPI::Elasticsearch::QueryBuilder.new.collapse('build_properties.version_code.keyword').to_query
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jay_api/errors/configuration_error'
4
+ require 'jay_api/elasticsearch/query_builder'
5
+
6
+ require_relative 'elasticsearch/time'
7
+
8
+ module JayAPI
9
+ # Fetches build properties from the provided information. ATTENTION: There's
10
+ # a maximum number of data hashes that are returned (see MAX_SIZE).
11
+ class PropertiesFetcher
12
+ include JayAPI::Elasticsearch::Time
13
+
14
+ MAX_SIZE = 100
15
+
16
+ # Name of the field used when querying the index for data using the name of
17
+ # the build job.
18
+ BUILD_NAME_FIELD = 'build_properties.build_name.keyword'
19
+ BUILD_NUMBER_FIELD = 'build_properties.build_number'
20
+
21
+ # Name of the field used when querying the index for data using the version
22
+ # of the software.
23
+ VERSION_CODE_FIELD = 'build_properties.version_code.keyword'
24
+ BUILD_RELEASE_FIELD = 'build_properties.build_release'
25
+
26
+ # Name of the field used when querying the index for data using the SUT Revision.
27
+ SUT_REVISION_FIELD = 'sut_revision.keyword'
28
+ TIMESTAMP_FIELD = 'timestamp'
29
+
30
+ attr_reader :index
31
+
32
+ # Initializes a PropertiesFetcher object
33
+ # @param [JsyAPI::Elasticsearch::Index] index The Elasticsearch index to use
34
+ # to fetch build properties data.
35
+ def initialize(index:)
36
+ @index = index
37
+ end
38
+
39
+ # Constraints the results to those properties belonging to the given
40
+ # SUT Revision.
41
+ # @param [String] sut_revision For example: '23-08-02/master'.
42
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
43
+ # chained.
44
+ def by_sut_revision(sut_revision)
45
+ query_builder.query.bool.must.match_phrase(field: SUT_REVISION_FIELD, phrase: sut_revision)
46
+ self
47
+ end
48
+
49
+ # Constraints the results to those properties belonging to the given build
50
+ # job
51
+ # @param [String] build_job The name of the build job, for example:
52
+ # 'Release-XYZ01-Master'.
53
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
54
+ # chained.
55
+ def by_build_job(build_job)
56
+ query_builder.query.bool.must.match_phrase(field: BUILD_NAME_FIELD, phrase: build_job)
57
+ self
58
+ end
59
+
60
+ # Constraints the results to those properties belonging to the given build
61
+ # number
62
+ # @param [String, Integer] build_number The build number, for example 432
63
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
64
+ # chained.
65
+ def by_build_number(build_number)
66
+ query_builder.query.bool.must.query_string(fields: BUILD_NUMBER_FIELD, query: build_number)
67
+ self
68
+ end
69
+
70
+ # Constraints the results to those properties for which the software version
71
+ # matches the given one.
72
+ # @param [String] software_version The software version, for example 'D310'.
73
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
74
+ # chained.
75
+ def by_software_version(software_version)
76
+ query_builder.query.bool.must.match_phrase(field: VERSION_CODE_FIELD, phrase: software_version)
77
+ self
78
+ end
79
+
80
+ # Constraint the results to properties that belong or don't belong to
81
+ # (depending on the +release_tag+ parameter) a release build.
82
+ # @param [Boolean] release_tag True If build properties should come from a
83
+ # release, false if they SHOULD NOT.
84
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
85
+ # chained.
86
+ def by_release_tag(release_tag)
87
+ query_builder.query.bool.must.query_string(fields: BUILD_RELEASE_FIELD, query: release_tag)
88
+ self
89
+ end
90
+
91
+ # Constraints the results to properties that were pushed after the given
92
+ # +timestamp+.
93
+ # @param [Time, String] timestamp A timestamp to filter the build properties
94
+ # with. If a +String+ is given it is assumed to be in the right format for
95
+ # Elasticsearch. No conversion / checking will be performed over it.
96
+ # @return [JayAPI::PropertiesFetcher] itself, so that other methods can be
97
+ # chained.
98
+ def after(timestamp)
99
+ # noinspection RubyMismatchedParameterType (checked by the if modifier)
100
+ timestamp = format_time(timestamp) if timestamp.is_a?(Time)
101
+ query_builder.query.bool.must.query_string(fields: TIMESTAMP_FIELD, query: "> \"#{timestamp}\"")
102
+ self
103
+ end
104
+
105
+ # Constraints the results to properties that were pushed before the given
106
+ # +timestamp+.
107
+ # @param [Time, String] timestamp A timestamp to filter the build properties
108
+ # with. If a +String+ is given it is assumed to be in the right format for
109
+ # Elasticsearch. No conversion / checking will be performed over it.
110
+ # @return [JayAPI::PropertiesFetcher] itself, so that other methods can be
111
+ # chained.
112
+ def before(timestamp)
113
+ # noinspection RubyMismatchedParameterType (checked by the if modifier)
114
+ timestamp = format_time(timestamp) if timestamp.is_a?(Time)
115
+ query_builder.query.bool.must.query_string(fields: TIMESTAMP_FIELD, query: "< \"#{timestamp}\"")
116
+ self
117
+ end
118
+
119
+ # This method is here only for readability. It is meant to act as a
120
+ # conjunction between two method calls.
121
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
122
+ # chained.
123
+ # @example
124
+ # properties_fetcher.by_build_job('Release-XYZ01-Master').and.by_build_number(106)
125
+ def and
126
+ self
127
+ end
128
+
129
+ # @return [Hash, nil] The last set of properties (ordered chronologically)
130
+ # or +nil+ if no properties are found.
131
+ def last
132
+ sort_records('desc').size(1)
133
+ fetch_properties.last
134
+ end
135
+
136
+ # @return [Hash, nil] The first set of properties (ordered chronologically)
137
+ # or +nil+ if no properties are found
138
+ def first
139
+ sort_records('asc').size(1)
140
+ fetch_properties.first
141
+ end
142
+
143
+ # Limits the amount of records returned to the given number.
144
+ # @param [Integer] size The number of records to return. If this number is
145
+ # greater than +MAX_SIZE+ then +MAX_SIZE+ will be used instead.
146
+ # @return [JayAPI::PropertiesFetcher] Itself, so that other methods can be
147
+ # chained.
148
+ def limit(size)
149
+ size = [size, MAX_SIZE].min
150
+ # noinspection RubyMismatchedParameterType (The array will never be empty)
151
+ query_builder.size(size)
152
+ self
153
+ end
154
+
155
+ alias size limit
156
+
157
+ # Allows the caller to retrieve or iterate over the set of Build Properties
158
+ # entries. The method can be called with or without a block. If a block is
159
+ # given then each of the Build Properties entries will be yielded to the
160
+ # block, if no block is given then an +Enumerator+ is returned.
161
+ #
162
+ # @yield [Hash] One of the Build Properties entries.
163
+ # @return [Enumerator] If no block is given an +Enumerator+ object for all
164
+ # the property sets that were found is returned (might be empty). With a
165
+ # block the return value is undefined.
166
+ def all(&block)
167
+ fetch_properties.all(&block)
168
+ end
169
+
170
+ private
171
+
172
+ # Fetches the properties given the provided query hash.
173
+ # @return [QueryResults] The fetched data as a QueryResults object.
174
+ def fetch_properties
175
+ properties = index.search(query_builder.to_query)
176
+ @query_builder = nil
177
+ properties
178
+ end
179
+
180
+ # @return [JayAPI::Elasticsearch::QueryBuilder] The current QueryBuilder
181
+ # object. The QueryBuilder is only reset when records are fetched so that
182
+ # multiple conditions can be put together into it before the query is
183
+ # actually carried out.
184
+ def query_builder
185
+ @query_builder ||= JayAPI::Elasticsearch::QueryBuilder.new
186
+ end
187
+
188
+ # Adds a sort clause to the +QueryComposer+ object to sort the records
189
+ # chronologically.
190
+ # @param [String] direction The direction of the sorting, either +'asc'+ or
191
+ # +'desc'+
192
+ def sort_records(direction)
193
+ query_builder.sort('timestamp' => direction)
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../configuration'
4
+ require_relative '../errors/configuration_error'
5
+
6
+ module JayAPI
7
+ # A module that contains the classes and method needed to interact with RSpec
8
+ # data (test cases, test results, etc).
9
+ module RSpec
10
+ # @return [JayAPI::Configuration] The current configuration for JayAPI::RSpec
11
+ # @raise [JayAPI::Errors::ConfigurationError] If the module hasn't been
12
+ # configured yet.
13
+ def self.configuration
14
+ unless @configuration
15
+ raise JayAPI::Errors::ConfigurationError,
16
+ 'No configuration has been set for the JayAPI::RSpec module. ' \
17
+ 'Please call JayAPI::RSpec.configure to load/create configuration.'
18
+ end
19
+
20
+ @configuration
21
+ end
22
+
23
+ # Called to configure the JayAPI::RSpec module.
24
+ # @yield [Class] Yields +JayAPI::Configuration+ (the class itself), on it
25
+ # the yielded block can call either, +new+, +from_string+ or +from_file+
26
+ # to create the configuration.
27
+ # @yieldreturn [JayAPI::Configuration] The block should return an instance
28
+ # of the +JayAPI::Configuration+ class with the configuration for the
29
+ # module.
30
+ # @raise [JayAPI::Errors::ConfigurationError] If the block returns anything
31
+ # but an instance of +JayAPI::Configuration+ or one of its subclasses.
32
+ def self.configure
33
+ configuration = yield JayAPI::Configuration
34
+
35
+ return unless configuration
36
+
37
+ unless configuration.is_a?(JayAPI::Configuration)
38
+ raise JayAPI::Errors::ConfigurationError,
39
+ 'Expected a JayAPI::Configuration or a subclass. ' \
40
+ "Got a #{configuration.class} instead."
41
+ end
42
+
43
+ @configuration = configuration
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../git/repository'
4
+
5
+ module JayAPI
6
+ module RSpec
7
+ # Git-related methods for the +TestDataCollector+
8
+ module Git
9
+ private
10
+
11
+ # @return [JayAPI::Git::Repository] An instance of the +Repository+ class
12
+ # set to target the Git repository in the current working directory.
13
+ def git_repository
14
+ @git_repository ||= JayAPI::Git::Repository.new(url: nil, clone_path: Dir.pwd)
15
+ end
16
+
17
+ # @return [String] The SHA1 of the current revision for the repository
18
+ # from which RSpec is running. If JayAPI's Git library cannot obtain it
19
+ # then an attempt is made to get it directly from Git's CLI.
20
+ def fetch_git_revision
21
+ git_repository.log(count: 1).first.sha
22
+ rescue ::Git::GitExecuteError
23
+ # It wasn't possible to obtain the SHA1 of the repository via the Git
24
+ # Library. it may still be possible to get it via Git's CLI.
25
+ `git rev-parse HEAD`.chomp
26
+ end
27
+
28
+ # @return [String] The SHA1 of current repository's revision.
29
+ # @see #fetch_git_revision
30
+ def git_revision
31
+ @git_revision ||= fetch_git_revision
32
+ end
33
+
34
+ # Attempts to get the URL of the first remote associated with the Git
35
+ # repository in the current working directory.
36
+ # @return [String] The URL of the remote, or an empty string if the
37
+ # attempt fails.
38
+ def remote_from_command
39
+ `git remote get-url $(git remote show | head -n 1)`.chomp
40
+ end
41
+
42
+ # @return [String] The URL of the first remote associated with the
43
+ # repository from which
44
+ def fetch_git_remote
45
+ git_repository.remote_url || remote_from_command
46
+ rescue ::Git::GitExecuteError
47
+ # It wasn't possible to obtain the repository's remote URL via the Git
48
+ # Library. it may still be possible to get it via Git's CLI.
49
+ remote_from_command
50
+ end
51
+
52
+ # @return [String] The URL of the first remote associated with the current
53
+ # repository.
54
+ # @see #fetch_git_remote
55
+ def git_remote
56
+ @git_remote ||= fetch_git_remote
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'rspec'
6
+ require 'rspec/core/formatters'
7
+ require 'rspec/core/configuration'
8
+
9
+ require_relative '../elasticsearch/client_factory'
10
+ require_relative '../elasticsearch/index'
11
+ require_relative '../elasticsearch/time'
12
+ require_relative '../id_builder'
13
+ require_relative 'configuration'
14
+ require_relative 'git'
15
+
16
+ module JayAPI
17
+ module RSpec
18
+ # Collects RSpec Test Data and pushes it to Jay's Elasticsearch backend.
19
+ class TestDataCollector
20
+ include JayAPI::Elasticsearch::Time
21
+ include JayAPI::RSpec::Git
22
+
23
+ DEFAULT_BATCH_SIZE = 100
24
+
25
+ # Meta-dara keys that can be used to annotate requirements in tests.
26
+ REQUIREMENTS_KEYS = %i[requirements refs].freeze
27
+
28
+ ::RSpec::Core::Formatters.register self, :start, :close, :example_finished
29
+
30
+ # Executed by RSpec during initialization.
31
+ # Sets the +@push_enabled+ instance variable according to configuration.
32
+ def initialize(_output)
33
+ @push_enabled = configuration.push_enabled # When the push is disabled the class behaviour is inhibited.
34
+ end
35
+
36
+ # Called by RSpec at the beginning of the test run. If the push is enabled
37
+ # the method initializes the Elasticsearch::Index instance. This is done
38
+ # so that, if it cannot be initialized the tests will fail right away and
39
+ # not after execution.
40
+ def start(_notification)
41
+ return unless push_enabled
42
+
43
+ elasticsearch_index
44
+ end
45
+
46
+ # Called by RSpec when an example is finished. The method extracts all the
47
+ # information from the given notification and creates a data +Hash+ to
48
+ # push to Elasticsearch.
49
+ # @param [RSpec::Core::Notifications::ExampleNotification] notification
50
+ # The +Notification+ object passed by RSpec.
51
+ def example_finished(notification)
52
+ return unless push_enabled
53
+
54
+ example = notification.example
55
+ identifier = example.full_description
56
+ ex_result = example.execution_result
57
+ metadata = example.metadata
58
+
59
+ data = {
60
+ test_env: {
61
+ build_number: ENV['BUILD_NUMBER'],
62
+ build_job_name: ENV['JOB_NAME'],
63
+ repository: git_remote.presence,
64
+ revision: git_revision.presence,
65
+ hostname: hostname
66
+ },
67
+ test_case: {
68
+ name: identifier,
69
+ started_at: format_time(ex_result.started_at),
70
+ finished_at: format_time(ex_result.finished_at),
71
+ runtime: ex_result.run_time,
72
+ id_long: identifier,
73
+ id: build_short_id(identifier),
74
+ location: metadata[:location],
75
+ requirements: requirements_from(metadata),
76
+ expectation: example.description,
77
+ result: translate_result(ex_result.status),
78
+ exception: ex_result.exception&.message&.strip
79
+ }
80
+ }
81
+
82
+ elasticsearch_index.push(data)
83
+ end
84
+
85
+ # Executed by RSpec at the end of the test run. If the push is enabled,
86
+ # any test cases still being held in the Elasticsearch buffer are flushed.
87
+ def close(_notification)
88
+ return unless push_enabled
89
+
90
+ elasticsearch_index.flush
91
+ end
92
+
93
+ private
94
+
95
+ attr_reader :push_enabled
96
+
97
+ # @return [Hash] The configuration set for Elasticsearch as a hash. If no
98
+ # value has been set for the batch size a reasonable default is set.
99
+ # @raise [JayAPI::Errors::ConfigurationError] If no configuration for
100
+ # Elasticsearch has been provided.
101
+ def elasticsearch_config
102
+ @elasticsearch_config ||= configuration.elasticsearch.tap do |config|
103
+ unless config
104
+ raise JayAPI::Errors::ConfigurationError,
105
+ 'No Elasticsearch configuration provided for the JayAPI::RSpec module.'
106
+ end
107
+ end.to_h
108
+ end
109
+
110
+ # @return [JayAPI::Elasticsearch::Client] The +Elasticsearch::Client+
111
+ # object that the importer uses to initialize the +Elasticsearch::Index+
112
+ # class.
113
+ def client
114
+ @client ||= JayAPI::Elasticsearch::ClientFactory.new(**elasticsearch_config).create
115
+ end
116
+
117
+ # @return [Hash] A Hash with the required parameters for the
118
+ # +Elasticsearch::Index+ class
119
+ def index_params
120
+ @index_params ||= {
121
+ index_name: elasticsearch_config.delete(:index_name),
122
+ batch_size: elasticsearch_config.delete(:batch_size) || DEFAULT_BATCH_SIZE,
123
+ client: client
124
+ }
125
+ end
126
+
127
+ # @return [JayAPI::Elasticsearch::Index] An +Elasticsearch::Index+
128
+ # instance that can be used to push data or search for it.
129
+ # @raise [ArgumentError] If any of the needed parameters to initialize the
130
+ # Elasticsearch Index is missing.
131
+ def elasticsearch_index
132
+ @elasticsearch_index ||= JayAPI::Elasticsearch::Index.new(**index_params)
133
+ end
134
+
135
+ # @return [JayAPI::Configuration] The configuration for the JayAPI::RSpec
136
+ # module
137
+ # @raise [JayAPI::Errors::ConfigurationError] If the module hasn't been
138
+ # configured yet.
139
+ def configuration
140
+ @configuration ||= JayAPI::RSpec.configuration
141
+ end
142
+
143
+ # Builds a short identifier for the Test Case from its long identifier.
144
+ # @param [String] long_id The Long Identifier of the Test Case
145
+ # @return [String] The short identifier for the Test Case.
146
+ def build_short_id(long_id)
147
+ JayAPI::IDBuilder.new(test_case_id: long_id, project: project).short_id
148
+ end
149
+
150
+ # @return [String] The name of the configured project.
151
+ def project
152
+ @project ||= configuration.project
153
+ end
154
+
155
+ # Translates the given test result
156
+ # @param [Symbol] result The test result (provided by RSpec)
157
+ # @return [String] The translated result
158
+ def translate_result(result)
159
+ case result
160
+ when :passed then 'pass'
161
+ when :failed then 'fail'
162
+ when :pending then 'skip'
163
+ else 'error'
164
+ end
165
+ end
166
+
167
+ # @return [String] The name of the computer running the tests
168
+ def hostname
169
+ @hostname ||= `hostname`.chomp
170
+ end
171
+
172
+ # Extracts the requirements from the given meta-data. The methods checks
173
+ # the given meta-data hash and creates an array with all the requirements
174
+ # found in either of the keys which can contain them.
175
+ #
176
+ # If more than one key contains requirements they are put together into a
177
+ # single array. If none of the keys have requirements +nil+ is returned.
178
+ #
179
+ # @param [Hash] metadata The meta-data Hash (normally comes from RSpec).
180
+ # @return [Array<Object>, nil] An array with all the requirements found
181
+ # in the given meta-data Hash or +nil+ if none was found.
182
+ def requirements_from(metadata)
183
+ REQUIREMENTS_KEYS.each_with_object([]) do |key, array|
184
+ array.concat(Array(metadata[key]))
185
+ end.presence
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rspec/git'
4
+ require_relative 'rspec/test_data_collector'
5
+
6
+ module JayAPI
7
+ # Namespace for RSpec related classes and modules.
8
+ module RSpec; end
9
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JayAPI
4
+ # JayAPI gem's semantic version
5
+ VERSION = '27.1.0'
6
+ end
data/lib/jay_api.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jay_api/version'
4
+
5
+ require_relative 'jay_api/rspec'
6
+
7
+ # Top-level namespace for the Gem
8
+ module JayAPI
9
+ end