inferno_core 0.4.43 → 0.4.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) 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/test.rb +4 -4
  24. data/lib/inferno/entities/test_group.rb +2 -2
  25. data/lib/inferno/entities/test_suite.rb +2 -24
  26. data/lib/inferno/exceptions.rb +6 -0
  27. data/lib/inferno/ext/fhir_models.rb +3 -3
  28. data/lib/inferno/jobs.rb +4 -4
  29. data/lib/inferno/public/237.bundle.js +1 -0
  30. data/lib/inferno/public/assets.json +1 -1
  31. data/lib/inferno/public/bundle.js +42 -63
  32. data/lib/inferno/public/bundle.js.LICENSE.txt +5 -33
  33. data/lib/inferno/repositories/repository.rb +1 -1
  34. data/lib/inferno/result_summarizer.rb +1 -1
  35. data/lib/inferno/test_runner.rb +2 -2
  36. data/lib/inferno/utils/middleware/request_logger.rb +1 -1
  37. data/lib/inferno/utils/persist_inputs.rb +30 -0
  38. data/lib/inferno/utils/preset_template_generator.rb +1 -1
  39. data/lib/inferno/utils/verify_runnable.rb +15 -0
  40. data/lib/inferno/version.rb +1 -1
  41. data/spec/factories/result.rb +4 -0
  42. data/spec/fixtures/basic_test_group.rb +2 -1
  43. data/spec/fixtures/run_as_group_test_group.rb +11 -0
  44. metadata +24 -170
@@ -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
@@ -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,11 @@ 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
116
122
  end
117
123
  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
  }