inferno_core 0.4.43 → 0.5.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/cli/execute/console_outputter.rb +152 -0
  3. data/lib/inferno/apps/cli/execute.rb +269 -0
  4. data/lib/inferno/apps/cli/main.rb +73 -4
  5. data/lib/inferno/apps/cli/templates/.env.development +2 -1
  6. data/lib/inferno/apps/cli/templates/.env.production +2 -1
  7. data/lib/inferno/apps/cli/templates/.env.test +1 -0
  8. data/lib/inferno/apps/cli/templates/.gitignore +1 -0
  9. data/lib/inferno/apps/cli/templates/config/nginx.background.conf.tt +15 -0
  10. data/lib/inferno/apps/cli/templates/docker-compose.background.yml.tt +5 -0
  11. data/lib/inferno/apps/cli/templates/docker-compose.yml.tt +4 -0
  12. data/lib/inferno/apps/cli.rb +0 -1
  13. data/lib/inferno/apps/web/controllers/test_runs/create.rb +7 -32
  14. data/lib/inferno/apps/web/serializers/serializer.rb +3 -0
  15. data/lib/inferno/config/boot/executor.rb +14 -0
  16. data/lib/inferno/dsl/auth_info.rb +77 -72
  17. data/lib/inferno/dsl/fhir_client_builder.rb +2 -2
  18. data/lib/inferno/dsl/fhir_resource_validation.rb +4 -0
  19. data/lib/inferno/dsl/http_client_builder.rb +2 -2
  20. data/lib/inferno/dsl/links.rb +100 -0
  21. data/lib/inferno/dsl/runnable.rb +6 -1
  22. data/lib/inferno/entities/has_runnable.rb +1 -1
  23. data/lib/inferno/entities/request.rb +2 -2
  24. data/lib/inferno/entities/test.rb +4 -4
  25. data/lib/inferno/entities/test_group.rb +2 -2
  26. data/lib/inferno/entities/test_suite.rb +2 -24
  27. data/lib/inferno/exceptions.rb +12 -0
  28. data/lib/inferno/ext/fhir_models.rb +3 -3
  29. data/lib/inferno/jobs.rb +4 -4
  30. data/lib/inferno/public/237.bundle.js +1 -0
  31. data/lib/inferno/public/assets.json +1 -1
  32. data/lib/inferno/public/bundle.js +42 -63
  33. data/lib/inferno/public/bundle.js.LICENSE.txt +5 -33
  34. data/lib/inferno/repositories/in_memory_repository.rb +5 -8
  35. data/lib/inferno/repositories/repository.rb +1 -1
  36. data/lib/inferno/result_summarizer.rb +1 -1
  37. data/lib/inferno/test_runner.rb +2 -2
  38. data/lib/inferno/utils/middleware/request_logger.rb +1 -1
  39. data/lib/inferno/utils/persist_inputs.rb +30 -0
  40. data/lib/inferno/utils/preset_template_generator.rb +1 -1
  41. data/lib/inferno/utils/verify_runnable.rb +15 -0
  42. data/lib/inferno/version.rb +1 -1
  43. data/spec/factories/result.rb +4 -0
  44. data/spec/fixtures/basic_test_group.rb +2 -1
  45. data/spec/fixtures/run_as_group_test_group.rb +11 -0
  46. metadata +24 -171
  47. data/lib/inferno/public/175.bundle.js +0 -1
@@ -0,0 +1,14 @@
1
+ Inferno::Application.register_provider(:executor) do
2
+ prepare do
3
+ target_container.start :logging
4
+
5
+ require 'oj'
6
+ require 'blueprinter'
7
+
8
+ Blueprinter.configure do |config|
9
+ config.generator = Oj
10
+ end
11
+
12
+ target_container.start :suites
13
+ end
14
+ end
@@ -3,71 +3,73 @@ require_relative 'jwks'
3
3
 
4
4
  module Inferno
5
5
  module DSL
6
- # AuthInfo provide a user with a single input which contains the information
7
- # needed for a fhir client to perform authorization and refresh an access
6
+ # AuthInfo provides a user with a single input which contains the information
7
+ # needed for a FHIR client to perform authorization and refresh an access
8
8
  # token when necessary.
9
9
  #
10
10
  # AuthInfo supports the following `auth_type`:
11
- # - `public` - client id only
12
- # - `symmetric` - Symmetric confidential (i.e., with a static client id and
13
- # secret)
14
- # - `asymmetric` - Symmetric confidential (i.e., a client id with a signed
15
- # JWT rather than a client secret)
16
- # - `backend_services`
11
+ #
12
+ # - `public` - Client id only
13
+ # - `symmetric` - Confidential symmetric (i.e., with a static client id and
14
+ # secret)
15
+ # - `asymmetric` - Confidential asymmetric (i.e., a client id with a signed
16
+ # JWT rather than a client secret)
17
+ # - `backend_services`
17
18
  #
18
19
  # When configuring an AuthInfo input, the invdidual fields are exposed as
19
20
  # `components` in the input's options, and can be configured there similar
20
21
  # to normal inputs.
21
22
  #
22
23
  # The AuthInfo input type supports two different modes in the UI. Different
23
- # fields will be presented to the user depending on which mode is selected.
24
- # - `auth` - This presents the inputs needed to perform authorization, and
25
- # is appropriate to use as an input to test groups which perform
26
- # authorization
27
- # - `access` - This presents the inputs needed to access resources assuming
28
- # that authorization has already happened, and is appropriate to use as an
29
- # input to test groups which access resources using previously granted
30
- # authorization
24
+ # fields will be presented to the user depending on which mode is selected:
25
+ #
26
+ # - `auth` - This presents the inputs needed to perform authorization, and
27
+ # is appropriate to use as an input to test groups which perform
28
+ # authorization.
29
+ # - `access` - This presents the inputs needed to access resources assuming
30
+ # that authorization has already happened, and is appropriate to use as an
31
+ # input to test groups which access resources using previously granted
32
+ # authorization.
31
33
  #
32
34
  # @example
33
- # class AuthInfoExampleSuite < Inferno::TestSuite
34
- # input :url,
35
- # title: 'Base FHIR url'
35
+ # class AuthInfoExampleSuite < Inferno::TestSuite
36
+ # input :url,
37
+ # title: 'Base FHIR url'
36
38
  #
37
- # group do
38
- # title 'Perform public authorization'
39
- # input :fhir_auth,
40
- # type: :auth_info,
41
- # options: {
42
- # mode: 'auth',
43
- # components: [
44
- # {
45
- # name: :auth_type,
46
- # default: 'public',
47
- # locked: true
48
- # }
49
- # ]
50
- # }
39
+ # group do
40
+ # title 'Perform public authorization'
41
+ # input :fhir_auth,
42
+ # type: :auth_info,
43
+ # options: {
44
+ # mode: 'auth',
45
+ # components: [
46
+ # {
47
+ # name: :auth_type,
48
+ # default: 'public',
49
+ # locked: true
50
+ # }
51
+ # ]
52
+ # }
51
53
  #
52
- # # Some tests here to perform authorization
53
- # end
54
+ # # Some tests here to perform authorization
55
+ # end
54
56
  #
55
- # group do
56
- # title 'FHIR API Tests'
57
- # input :fhir_auth,
58
- # type: :auth_info,
59
- # options: {
60
- # mode: 'access'
61
- # }
57
+ # group do
58
+ # title 'FHIR API Tests'
59
+ # input :fhir_auth,
60
+ # type: :auth_info,
61
+ # options: {
62
+ # mode: 'access'
63
+ # }
62
64
  #
63
- # fhir_client do
64
- # url :url
65
- # auth_info :fhir_auth # NOT YET IMPLEMENTED
66
- # end
65
+ # fhir_client do
66
+ # url :url
67
+ # auth_info :fhir_auth
68
+ # end
67
69
  #
68
- # # Some tests here to access FHIR API
70
+ # # Some tests here to access FHIR API
71
+ # end
69
72
  # end
70
- # end
71
73
  class AuthInfo
72
74
  ATTRIBUTES = [
73
75
  :auth_type,
@@ -95,35 +97,38 @@ module Inferno
95
97
 
96
98
  attr_accessor :client
97
99
 
98
- # @!attribute [rw] auth_type The type of authorization to be performed.
99
- # One of `public`, `symmetric`, `asymmetric`, or `backend_services`
100
- # @!attribute [rw] token_url The url of the auth server's token endpoint
101
- # @!attribute [rw] auth_url The url of the authorization endpoint
102
- # @!attribute [rw] requested_scopes The scopes which will be requested
103
- # during authorization
100
+ # @!attribute [rw] auth_type
101
+ # The type of authorization to be performed. One of `public`, `symmetric`, `asymmetric`, or `backend_services`
102
+ # @!attribute [rw] token_url
103
+ # The url of the auth server's token endpoint
104
+ # @!attribute [rw] auth_url
105
+ # The url of the authorization endpoint
106
+ # @!attribute [rw] requested_scopes
107
+ # The scopes which will be requested during authorization
104
108
  # @!attribute [rw] client_id
105
109
  # @!attribute [rw] client_secret
106
110
  # @!attribute [rw] redirect_url
107
- # @!attribute [rw] pkce_support Whether PKCE will be used during
108
- # authorization. Either `enabled` or `disabled`.
109
- # @!attribute [rw] pkce_code_challenge_method Either `S256` (default) or
110
- # `plain`
111
- # @!attribute [rw] auth_request_method The http method which will be used
112
- # to perform the request to the authorization endpoint. Either `get`
113
- # (default) or `post`
114
- # @!attribute [rw] encryption_algorithm The encryption algorithm which
115
- # will be used to sign the JWT client credentials. Either `es384`
116
- # (default) or `rs384`
117
- # @!attribute [rw] kid The key id for the keys to be used to sign the JWT
118
- # client credentials. When blank, the first key for the selected
119
- # encryption algorithm will be used
120
- # @!attribute [rw] jwks A JWKS (including private keys) which will be used
121
- # instead of Inferno's default JWKS if provided
111
+ # @!attribute [rw] pkce_support
112
+ # Whether PKCE will be used during authorization. Either `enabled` or `disabled`.
113
+ # @!attribute [rw] pkce_code_challenge_method
114
+ # Either `S256` (default) or `plain`
115
+ # @!attribute [rw] auth_request_method
116
+ # The http method which will be used to perform the request to the authorization endpoint.
117
+ # Either `get` (default) or `post`
118
+ # @!attribute [rw] encryption_algorithm
119
+ # The encryption algorithm which will be used to sign the JWT client credentials.
120
+ # Either `es384` (default) or `rs384`
121
+ # @!attribute [rw] kid
122
+ # The key id for the keys to be used to sign the JWT client credentials.
123
+ # When blank, the first key for the selected encryption algorithm will be used
124
+ # @!attribute [rw] jwks
125
+ # A JWKS (including private keys) which will be used instead of Inferno's default JWKS if provided
122
126
  # @!attribute [rw] access_token
123
127
  # @!attribute [rw] refresh_token
124
- # @!attribute [rw] issue_time An iso8601 formatted string representing the
125
- # time the access token was issued
126
- # @!attribute [rw] expires_in The lifetime of the access token in seconds
128
+ # @!attribute [rw] issue_time
129
+ # An iso8601 formatted string representing the time the access token was issued
130
+ # @!attribute [rw] expires_in
131
+ # The lifetime of the access token in seconds
127
132
  # @!attribute [rw] name
128
133
 
129
134
  # @private
@@ -118,8 +118,8 @@ module Inferno
118
118
  end
119
119
 
120
120
  # @private
121
- def method_missing(name, *args, &)
122
- return runnable.send(name, *args, &) if runnable.respond_to? name
121
+ def method_missing(name, ...)
122
+ return runnable.send(name, ...) if runnable.respond_to? name
123
123
 
124
124
  super
125
125
  end
@@ -73,19 +73,23 @@ module Inferno
73
73
  # there is no check that the fields are correct.
74
74
  #
75
75
  # @example
76
+ # # Passing fields in a block
76
77
  # fhir_resource_validator do
77
78
  # url 'http://example.com/validator'
78
79
  # cli_context do
79
80
  # noExtensibleBindingMessages true
81
+ # allowExampleUrls true
80
82
  # txServer nil
81
83
  # end
82
84
  # end
83
85
  #
84
86
  # @example
87
+ # # Passing fields in a Hash
85
88
  # fhir_resource_validator do
86
89
  # url 'http://example.org/validator'
87
90
  # cli_context({
88
91
  # noExtensibleBindingMessages: true,
92
+ # allowExampleUrls: true,
89
93
  # txServer: nil
90
94
  # })
91
95
  # end
@@ -44,8 +44,8 @@ module Inferno
44
44
  end
45
45
 
46
46
  # @private
47
- def method_missing(name, *args, &)
48
- return runnable.send(name, *args, &) if runnable.respond_to? name
47
+ def method_missing(name, ...)
48
+ return runnable.send(name, ...) if runnable.respond_to? name
49
49
 
50
50
  super
51
51
  end
@@ -0,0 +1,100 @@
1
+ module Inferno
2
+ module DSL
3
+ # This module contains methods to add test suite links which are displayed in the footer of the UI
4
+ module Links
5
+ DEFAULT_TYPES = {
6
+ 'report_issue' => 'Report Issue',
7
+ 'source_code' => 'Open Source',
8
+ 'download' => 'Download',
9
+ 'ig' => 'Implementation Guide'
10
+ }.freeze
11
+
12
+ # Set/get a list of links which are displayed in the footer of the UI.
13
+ #
14
+ # @param links [Array<Hash>] A list of Hashes for the links to be
15
+ # displayed. Each hash needs a `type:`, `label:`, and `url:` entry.
16
+ # Default types: `report_issue`, `source_code`, `download`, or `ig`.
17
+ #
18
+ # @return [Array<Hash>] an array of hashes or an empty array
19
+ #
20
+ # @example
21
+ # links [
22
+ # {
23
+ # type: 'report_issue',
24
+ # label: 'Report Issue',
25
+ # url: 'https://github.com/onc-healthit/onc-certification-g10-test-kit/issues/'
26
+ # },
27
+ # {
28
+ # type: 'source_code'
29
+ # label: 'Open Source',
30
+ # url: 'https://github.com/onc-healthit/onc-certification-g10-test-kit/'
31
+ # }
32
+ # ]
33
+ def links(links = nil)
34
+ @links ||= []
35
+ return @links if links.nil?
36
+
37
+ @links.concat(links)
38
+ end
39
+
40
+ # Add a link to the test suit links list.
41
+ #
42
+ # @param type [String] The type of the link. Default types: report_issue, source_code, download, or ig.
43
+ # Custom types are also allowed.
44
+ # @param label [String] The label for the link, describing its purpose.
45
+ # @param url [String] The URL the link points to.
46
+ # @return [Array<Hash>] The updated array of links.
47
+ #
48
+ # @example
49
+ # add_link('source_code', 'Source Code', 'https://github.com/onc-healthit/onc-certification-g10-test-kit/')
50
+ # add_link('custom_type', 'Custom Link', 'https://custom-link.com')
51
+ def add_link(type, label, url)
52
+ links << { type:, label:, url: }
53
+ end
54
+
55
+ # Add a link to the source code repository.
56
+ #
57
+ # @param url [String] The URL to the source code repository.
58
+ # @param label [String] (optional) A custom label for the link.
59
+ # @return [Array<Hash>] The updated array of links.
60
+ def source_code_url(url, label: nil)
61
+ add_predefined_link('source_code', url, label)
62
+ end
63
+
64
+ # Add a link to the implementation guide.
65
+ #
66
+ # @param url [String] The URL to the implementation guide.
67
+ # @param label [String] (optional) A custom label for the link.
68
+ # @return [Array<Hash>] The updated array of links.
69
+ def ig_url(url, label: nil)
70
+ add_predefined_link('ig', url, label)
71
+ end
72
+
73
+ # Add a link to the latest release version of the test kit.
74
+ #
75
+ # @param url [String] The URL to the latest release version of the test kit.
76
+ # @param label [String] (optional) A custom label for the link.
77
+ # @return [Array<Hash>] The updated array of links.
78
+ def download_url(url, label: nil)
79
+ add_predefined_link('download', url, label)
80
+ end
81
+
82
+ # Add a link to report an issue in the footer of the UI.
83
+ #
84
+ # @param url [String] The URL for reporting an issue.
85
+ # @param label [String] (optional) A custom label for the link.
86
+ # @return [Array<Hash>] The updated array of links.
87
+ def report_issue_url(url, label: nil)
88
+ add_predefined_link('report_issue', url, label)
89
+ end
90
+
91
+ # @private
92
+ def add_predefined_link(type, url, label = nil)
93
+ label ||= DEFAULT_TYPES[type]
94
+ raise ArgumentError, "Invalid link type: #{type}" unless label
95
+
96
+ add_link(type, label, url)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,6 +1,7 @@
1
1
  require_relative 'configurable'
2
2
  require_relative 'input_output_handling'
3
3
  require_relative 'resume_test_route'
4
+ require_relative '../exceptions'
4
5
  require_relative '../utils/markdown_formatter'
5
6
 
6
7
  module Inferno
@@ -200,7 +201,11 @@ module Inferno
200
201
 
201
202
  @base_id = new_id || @base_id || default_id
202
203
 
203
- @id = "#{prefix}#{@base_id}"
204
+ final_id = "#{prefix}#{@base_id}"
205
+
206
+ raise Exceptions::InvalidRunnableIdException, final_id if final_id.length > 255
207
+
208
+ @id = final_id
204
209
  end
205
210
 
206
211
  # Set/Get a runnable's title
@@ -7,7 +7,7 @@ module Inferno
7
7
  def runnable
8
8
  return @runnable if @runnable
9
9
 
10
- @runnable = (test || test_group || test_suite || load_runnable)
10
+ @runnable = test || test_group || test_suite || load_runnable
11
11
  end
12
12
 
13
13
  private
@@ -181,7 +181,7 @@ module Inferno
181
181
  .map { |header_name, value| Header.new(name: header_name.downcase, value:, type: 'response') }
182
182
 
183
183
  new(
184
- verb: response.env.method,
184
+ verb: response.env.method.downcase,
185
185
  url: response.env.url.to_s,
186
186
  direction:,
187
187
  name:,
@@ -210,7 +210,7 @@ module Inferno
210
210
  end
211
211
 
212
212
  new(
213
- verb: request[:method],
213
+ verb: request[:method].downcase,
214
214
  url: request[:url],
215
215
  direction:,
216
216
  name:,
@@ -44,10 +44,10 @@ module Inferno
44
44
  end
45
45
 
46
46
  # @private
47
- def method_missing(name, *args, &)
47
+ def method_missing(name, ...)
48
48
  parent_instance = self.class.parent&.new
49
49
  if parent_instance.respond_to?(name)
50
- parent_instance.send(name, *args, &)
50
+ parent_instance.send(name, ...)
51
51
  else
52
52
  super
53
53
  end
@@ -133,10 +133,10 @@ module Inferno
133
133
  end
134
134
 
135
135
  # @private
136
- def method_missing(name, *args, &)
136
+ def method_missing(name, ...)
137
137
  parent_instance = parent&.new
138
138
  if parent_instance.respond_to?(name)
139
- parent_instance.send(name, *args, &)
139
+ parent_instance.send(name, ...)
140
140
  else
141
141
  super
142
142
  end
@@ -25,10 +25,10 @@ module Inferno
25
25
  end
26
26
 
27
27
  # @private
28
- def method_missing(name, *args, &)
28
+ def method_missing(name, ...)
29
29
  parent_instance = self.class.parent&.new
30
30
  if parent_instance.respond_to?(name)
31
- parent_instance.send(name, *args, &)
31
+ parent_instance.send(name, ...)
32
32
  else
33
33
  super
34
34
  end
@@ -2,6 +2,7 @@ require_relative 'test_group'
2
2
  require_relative '../dsl/runnable'
3
3
  require_relative '../dsl/suite_option'
4
4
  require_relative '../dsl/messages'
5
+ require_relative '../dsl/links'
5
6
  require_relative '../repositories/test_groups'
6
7
  require_relative '../repositories/test_suites'
7
8
  require_relative '../result_collection'
@@ -12,6 +13,7 @@ module Inferno
12
13
  # single Implementation Guide
13
14
  class TestSuite
14
15
  extend DSL::Runnable
16
+ extend DSL::Links
15
17
  extend DSL::FHIRClient::ClassMethods
16
18
  extend DSL::HTTPClient::ClassMethods
17
19
  include DSL::FHIRValidation
@@ -173,30 +175,6 @@ module Inferno
173
175
  @suite_options ||= []
174
176
  end
175
177
 
176
- # Set/get a list of links which are displayed in the footer of the UI.
177
- #
178
- # @param links [Array<Hash>] A list of Hashes for the links to be
179
- # displayed. Each hash needs a `label:` and `url:` entry.
180
- #
181
- # @return [Array<Hash>, nil]
182
- #
183
- # @example
184
- # links [
185
- # {
186
- # label: 'Report Issue',
187
- # url: 'https://github.com/onc-healthit/onc-certification-g10-test-kit/issues/'
188
- # },
189
- # {
190
- # label: 'Open Source',
191
- # url: 'https://github.com/onc-healthit/onc-certification-g10-test-kit/'
192
- # }
193
- # ]
194
- def links(links = nil)
195
- return @links if links.nil?
196
-
197
- @links = links
198
- end
199
-
200
178
  # Set/get a description which for this test suite which will be
201
179
  # displayed in the UI.
202
180
  #
@@ -113,5 +113,17 @@ module Inferno
113
113
  super("Expected '#{name}' to be a #{expected_class_names}, but found a #{actual_class.name}.")
114
114
  end
115
115
  end
116
+
117
+ class InvalidRunnableIdException < StandardError
118
+ def initialize(id)
119
+ super("ID '#{id}' exceeds the maximum id length of 255 characters")
120
+ end
121
+ end
122
+
123
+ class DuplicateEntityIdException < StandardError
124
+ def initialize(id)
125
+ super("ID '#{id}' already exists. Ensure the uniqueness of the IDs.")
126
+ end
127
+ end
116
128
  end
117
129
  end
@@ -7,7 +7,7 @@ module InfernoFHIRModelExtensions
7
7
  attr_accessor :source_hash, :source_text
8
8
 
9
9
  def initialize(hash = {})
10
- super(hash)
10
+ super
11
11
  @source_hash = hash
12
12
  end
13
13
 
@@ -26,7 +26,7 @@ end
26
26
  # allows us to call super() on from_json
27
27
  module InfernoJson
28
28
  def from_json(json)
29
- resource = super(json)
29
+ resource = super
30
30
  resource&.source_text = json
31
31
  resource
32
32
  end
@@ -36,7 +36,7 @@ end
36
36
  # allows us to call super() on from_xml
37
37
  module InfernoXml
38
38
  def from_xml(xml)
39
- resource = super(xml)
39
+ resource = super
40
40
  resource&.source_text = xml
41
41
  resource
42
42
  end
data/lib/inferno/jobs.rb CHANGED
@@ -6,11 +6,11 @@ require_relative 'jobs/invoke_validator_session'
6
6
 
7
7
  module Inferno
8
8
  module Jobs
9
- def self.perform(job_klass, *params)
10
- if Application['async_jobs']
11
- job_klass.perform_async(*params)
12
- else
9
+ def self.perform(job_klass, *params, force_synchronous: false)
10
+ if force_synchronous || (Application['async_jobs'] == false)
13
11
  job_klass.new.perform(*params)
12
+ else
13
+ job_klass.perform_async(*params)
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunkinferno_web_app=self.webpackChunkinferno_web_app||[]).push([[237],{237:(e,n,t)=>{t.r(n),t.d(n,{CLSThresholds:()=>P,FCPThresholds:()=>w,FIDThresholds:()=>ne,INPThresholds:()=>j,LCPThresholds:()=>G,TTFBThresholds:()=>Q,onCLS:()=>A,onFCP:()=>I,onFID:()=>te,onINP:()=>z,onLCP:()=>K,onTTFB:()=>V});var r,i,o,a,c,u=-1,s=function(e){addEventListener("pageshow",(function(n){n.persisted&&(u=n.timeStamp,e(n))}),!0)},f=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},d=function(){var e=f();return e&&e.activationStart||0},l=function(e,n){var t=f(),r="navigate";return u>=0?r="back-forward-cache":t&&(document.prerendering||d()>0?r="prerender":document.wasDiscarded?r="restore":t.type&&(r=t.type.replace(/_/g,"-"))),{name:e,value:void 0===n?-1:n,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},p=function(e,n,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{})),r}}catch(e){}},v=function(e,n,t,r){var i,o;return function(a){n.value>=0&&(a||r)&&((o=n.value-(i||0))||void 0===i)&&(i=n.value,n.delta=o,n.rating=function(e,n){return e>n[1]?"poor":e>n[0]?"needs-improvement":"good"}(n.value,t),e(n))}},m=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},h=function(e){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&e()}))},g=function(e){var n=!1;return function(){n||(e(),n=!0)}},T=-1,y=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},E=function(e){"hidden"===document.visibilityState&&T>-1&&(T="visibilitychange"===e.type?e.timeStamp:0,b())},C=function(){addEventListener("visibilitychange",E,!0),addEventListener("prerenderingchange",E,!0)},b=function(){removeEventListener("visibilitychange",E,!0),removeEventListener("prerenderingchange",E,!0)},L=function(){return T<0&&(T=y(),C(),s((function(){setTimeout((function(){T=y(),C()}),0)}))),{get firstHiddenTime(){return T}}},S=function(e){document.prerendering?addEventListener("prerenderingchange",(function(){return e()}),!0):e()},w=[1800,3e3],I=function(e,n){n=n||{},S((function(){var t,r=L(),i=l("FCP"),o=p("paint",(function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(o.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-d(),0),i.entries.push(e),t(!0)))}))}));o&&(t=v(e,i,w,n.reportAllChanges),s((function(r){i=l("FCP"),t=v(e,i,w,n.reportAllChanges),m((function(){i.value=performance.now()-r.timeStamp,t(!0)}))})))}))},P=[.1,.25],A=function(e,n){n=n||{},I(g((function(){var t,r=l("CLS",0),i=0,o=[],a=function(e){e.forEach((function(e){if(!e.hadRecentInput){var n=o[0],t=o[o.length-1];i&&e.startTime-t.startTime<1e3&&e.startTime-n.startTime<5e3?(i+=e.value,o.push(e)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},c=p("layout-shift",a);c&&(t=v(e,r,P,n.reportAllChanges),h((function(){a(c.takeRecords()),t(!0)})),s((function(){i=0,r=l("CLS",0),t=v(e,r,P,n.reportAllChanges),m((function(){return t()}))})),setTimeout(t,0))})))},F=0,k=1/0,M=0,D=function(e){e.forEach((function(e){e.interactionId&&(k=Math.min(k,e.interactionId),M=Math.max(M,e.interactionId),F=M?(M-k)/7+1:0)}))},B=function(){return r?F:performance.interactionCount||0},R=function(){"interactionCount"in performance||r||(r=p("event",D,{type:"event",buffered:!0,durationThreshold:0}))},_=[],x=new Map,H=0,N=[],q=function(e){if(N.forEach((function(n){return n(e)})),e.interactionId||"first-input"===e.entryType){var n=_[_.length-1],t=x.get(e.interactionId);if(t||_.length<10||e.duration>n.latency){if(t)e.duration>t.latency?(t.entries=[e],t.latency=e.duration):e.duration===t.latency&&e.startTime===t.entries[0].startTime&&t.entries.push(e);else{var r={id:e.interactionId,latency:e.duration,entries:[e]};x.set(r.id,r),_.push(r)}_.sort((function(e,n){return n.latency-e.latency})),_.length>10&&_.splice(10).forEach((function(e){return x.delete(e.id)}))}}},O=function(e){var n=self.requestIdleCallback||self.setTimeout,t=-1;return e=g(e),"hidden"===document.visibilityState?e():(t=n(e),h(e)),t},j=[200,500],z=function(e,n){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(n=n||{},S((function(){var t;R();var r,i=l("INP"),o=function(e){O((function(){e.forEach(q);var n=function(){var e=Math.min(_.length-1,Math.floor((B()-H)/50));return _[e]}();n&&n.latency!==i.value&&(i.value=n.latency,i.entries=n.entries,r())}))},a=p("event",o,{durationThreshold:null!==(t=n.durationThreshold)&&void 0!==t?t:40});r=v(e,i,j,n.reportAllChanges),a&&(a.observe({type:"first-input",buffered:!0}),h((function(){o(a.takeRecords()),r(!0)})),s((function(){H=B(),_.length=0,x.clear(),i=l("INP"),r=v(e,i,j,n.reportAllChanges)})))})))},G=[2500,4e3],J={},K=function(e,n){n=n||{},S((function(){var t,r=L(),i=l("LCP"),o=function(e){n.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-d(),0),i.entries=[e],t())}))},a=p("largest-contentful-paint",o);if(a){t=v(e,i,G,n.reportAllChanges);var c=g((function(){J[i.id]||(o(a.takeRecords()),a.disconnect(),J[i.id]=!0,t(!0))}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return O(c)}),!0)})),h(c),s((function(r){i=l("LCP"),t=v(e,i,G,n.reportAllChanges),m((function(){i.value=performance.now()-r.timeStamp,J[i.id]=!0,t(!0)}))}))}}))},Q=[800,1800],U=function e(n){document.prerendering?S((function(){return e(n)})):"complete"!==document.readyState?addEventListener("load",(function(){return e(n)}),!0):setTimeout(n,0)},V=function(e,n){n=n||{};var t=l("TTFB"),r=v(e,t,Q,n.reportAllChanges);U((function(){var i=f();i&&(t.value=Math.max(i.responseStart-d(),0),t.entries=[i],r(!0),s((function(){t=l("TTFB",0),(r=v(e,t,Q,n.reportAllChanges))(!0)})))}))},W={passive:!0,capture:!0},X=new Date,Y=function(e,n){i||(i=n,o=e,a=new Date,ee(removeEventListener),Z())},Z=function(){if(o>=0&&o<a-X){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+o};c.forEach((function(n){n(e)})),c=[]}},$=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){Y(e,n),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,W),removeEventListener("pointercancel",r,W)};addEventListener("pointerup",t,W),addEventListener("pointercancel",r,W)}(n,e):Y(n,e)}},ee=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,$,W)}))},ne=[100,300],te=function(e,n){n=n||{},S((function(){var t,r=L(),a=l("FID"),u=function(e){e.startTime<r.firstHiddenTime&&(a.value=e.processingStart-e.startTime,a.entries.push(e),t(!0))},f=function(e){e.forEach(u)},d=p("first-input",f);t=v(e,a,ne,n.reportAllChanges),d&&(h(g((function(){f(d.takeRecords()),d.disconnect()}))),s((function(){var r;a=l("FID"),t=v(e,a,ne,n.reportAllChanges),c=[],o=-1,i=null,ee(addEventListener),r=u,c.push(r),Z()})))}))}}}]);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "main.js": "/public/bundle.js",
3
- "175.bundle.js": "/public/175.bundle.js",
3
+ "237.bundle.js": "/public/237.bundle.js",
4
4
  "inferno_logo.png": "/public/0e0b993fd6ff351f435ff1c2938daf2d.png",
5
5
  "inferno_icon.png": "/public/a5cd39450ab0336db73c5e57228b649d.png"
6
6
  }