flagsmith 2.0.0 → 3.0.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +4 -1
  4. data/.rubocop_todo.yml +0 -1
  5. data/.ruby-version +1 -0
  6. data/Gemfile.lock +35 -6
  7. data/LICENCE +4 -5
  8. data/README.md +8 -64
  9. data/Rakefile +5 -1
  10. data/example/.env.development +5 -0
  11. data/example/.env.test +4 -0
  12. data/example/.gitignore +5 -0
  13. data/example/.hanamirc +3 -0
  14. data/example/.rspec +2 -0
  15. data/example/Gemfile +25 -0
  16. data/example/Gemfile.lock +269 -0
  17. data/example/README.md +29 -0
  18. data/example/Rakefile +9 -0
  19. data/example/apps/web/application.rb +162 -0
  20. data/example/apps/web/config/routes.rb +1 -0
  21. data/example/apps/web/controllers/.gitkeep +0 -0
  22. data/example/apps/web/controllers/home/index.rb +32 -0
  23. data/example/apps/web/templates/application.html.slim +7 -0
  24. data/example/apps/web/templates/home/index.html.slim +10 -0
  25. data/example/apps/web/views/application_layout.rb +7 -0
  26. data/example/apps/web/views/home/index.rb +29 -0
  27. data/example/config/boot.rb +2 -0
  28. data/example/config/environment.rb +17 -0
  29. data/example/config/initializers/.gitkeep +0 -0
  30. data/example/config/initializers/flagsmith.rb +9 -0
  31. data/example/config/puma.rb +15 -0
  32. data/example/config.ru +3 -0
  33. data/example/spec/example/entities/.gitkeep +0 -0
  34. data/example/spec/example/mailers/.gitkeep +0 -0
  35. data/example/spec/example/repositories/.gitkeep +0 -0
  36. data/example/spec/features_helper.rb +12 -0
  37. data/example/spec/spec_helper.rb +103 -0
  38. data/example/spec/support/.gitkeep +0 -0
  39. data/example/spec/support/capybara.rb +8 -0
  40. data/example/spec/web/controllers/.gitkeep +0 -0
  41. data/example/spec/web/controllers/home/index_spec.rb +9 -0
  42. data/example/spec/web/features/.gitkeep +0 -0
  43. data/example/spec/web/views/application_layout_spec.rb +10 -0
  44. data/example/spec/web/views/home/index_spec.rb +10 -0
  45. data/lib/flagsmith/engine/core.rb +88 -0
  46. data/lib/flagsmith/engine/environments/models.rb +61 -0
  47. data/lib/flagsmith/engine/features/models.rb +173 -0
  48. data/lib/flagsmith/engine/identities/models.rb +115 -0
  49. data/lib/flagsmith/engine/organisations/models.rb +28 -0
  50. data/lib/flagsmith/engine/projects/models.rb +31 -0
  51. data/lib/flagsmith/engine/segments/constants.rb +41 -0
  52. data/lib/flagsmith/engine/segments/evaluator.rb +68 -0
  53. data/lib/flagsmith/engine/segments/models.rb +121 -0
  54. data/lib/flagsmith/engine/utils/hash_func.rb +34 -0
  55. data/lib/flagsmith/hash_slice.rb +12 -0
  56. data/lib/flagsmith/sdk/analytics_processor.rb +39 -0
  57. data/lib/flagsmith/sdk/api_client.rb +47 -0
  58. data/lib/flagsmith/sdk/config.rb +91 -0
  59. data/lib/flagsmith/sdk/errors.rb +9 -0
  60. data/lib/flagsmith/sdk/instance_methods.rb +137 -0
  61. data/lib/flagsmith/sdk/intervals.rb +24 -0
  62. data/lib/flagsmith/sdk/models/flag.rb +62 -0
  63. data/lib/flagsmith/sdk/models/flags/collection.rb +105 -0
  64. data/lib/flagsmith/sdk/pooling_manager.rb +31 -0
  65. data/lib/flagsmith/version.rb +5 -0
  66. data/lib/flagsmith.rb +79 -101
  67. metadata +104 -6
  68. data/.gitignore +0 -57
  69. data/flagsmith.gemspec +0 -22
@@ -0,0 +1,162 @@
1
+ require 'hanami/helpers'
2
+ require 'hanami/assets'
3
+
4
+ require_relative '../../../lib/flagsmith'
5
+
6
+ module Web
7
+ class Application < Hanami::Application
8
+ configure do
9
+ root __dir__
10
+
11
+ load_paths << %w[
12
+ controllers
13
+ views
14
+ ]
15
+
16
+ routes 'config/routes'
17
+
18
+ layout :application # It will load Web::Views::ApplicationLayout
19
+
20
+ templates 'templates'
21
+
22
+ ##
23
+ # ASSETS
24
+ #
25
+ assets do
26
+ # In order to skip JavaScript compression comment the following line
27
+ javascript_compressor :builtin
28
+
29
+ # In order to skip stylesheet compression comment the following line
30
+ stylesheet_compressor :builtin
31
+ end
32
+
33
+ ##
34
+ # SECURITY
35
+ #
36
+
37
+ # X-Frame-Options is a HTTP header supported by modern browsers.
38
+ # It determines if a web page can or cannot be included via <frame> and
39
+ # <iframe> tags by untrusted domains.
40
+ #
41
+ # Web applications can send this header to prevent Clickjacking attacks.
42
+ #
43
+ # Read more at:
44
+ #
45
+ # * https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options
46
+ # * https://www.owasp.org/index.php/Clickjacking
47
+ #
48
+ security.x_frame_options 'DENY'
49
+
50
+ # X-Content-Type-Options prevents browsers from interpreting files as
51
+ # something else than declared by the content type in the HTTP headers.
52
+ #
53
+ # Read more at:
54
+ #
55
+ # * https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#X-Content-Type-Options
56
+ # * https://msdn.microsoft.com/en-us/library/gg622941%28v=vs.85%29.aspx
57
+ # * https://blogs.msdn.microsoft.com/ie/2008/09/02/ie8-security-part-vi-beta-2-update
58
+ #
59
+ security.x_content_type_options 'nosniff'
60
+
61
+ # X-XSS-Protection is a HTTP header to determine the behavior of the
62
+ # browser in case an XSS attack is detected.
63
+ #
64
+ # Read more at:
65
+ #
66
+ # * https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
67
+ # * https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#X-XSS-Protection
68
+ #
69
+ security.x_xss_protection '1; mode=block'
70
+
71
+ # Content-Security-Policy (CSP) is a HTTP header supported by modern
72
+ # browsers. It determines trusted sources of execution for dynamic
73
+ # contents (JavaScript) or other web related assets: stylesheets, images,
74
+ # fonts, plugins, etc.
75
+ #
76
+ # Web applications can send this header to mitigate Cross Site Scripting
77
+ # (XSS) attacks.
78
+ #
79
+ # The default value allows images, scripts, AJAX, fonts and CSS from the
80
+ # same origin, and does not allow any other resources to load (eg object,
81
+ # frame, media, etc).
82
+ #
83
+ # Inline JavaScript is NOT allowed. To enable it, please use:
84
+ # "script-src 'unsafe-inline'".
85
+ #
86
+ # Content Security Policy introduction:
87
+ #
88
+ # * http://www.html5rocks.com/en/tutorials/security/content-security-policy/
89
+ # * https://www.owasp.org/index.php/Content_Security_Policy
90
+ # * https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
91
+ #
92
+ # Inline and eval JavaScript risks:
93
+ #
94
+ # * http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
95
+ # * http://www.html5rocks.com/en/tutorials/security/content-security-policy/#eval-too
96
+ #
97
+ # Content Security Policy usage:
98
+ #
99
+ # * http://content-security-policy.com/
100
+ # * https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy
101
+ #
102
+ # Content Security Policy references:
103
+ #
104
+ # * https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
105
+ #
106
+ security.content_security_policy %(
107
+ form-action 'self';
108
+ frame-ancestors 'self';
109
+ base-uri 'self';
110
+ default-src 'none';
111
+ script-src 'self';
112
+ connect-src 'self';
113
+ img-src 'self' https: data:;
114
+ style-src 'self' 'unsafe-inline' https:;
115
+ font-src 'self';
116
+ object-src 'none';
117
+ plugin-types application/pdf;
118
+ child-src 'self';
119
+ frame-src 'self';
120
+ media-src 'self'
121
+ )
122
+
123
+ ##
124
+ # FRAMEWORKS
125
+ #
126
+
127
+ # Configure the code that will yield each time Web::Action is included
128
+ # This is useful for sharing common functionality
129
+ #
130
+ # See: http://www.rubydoc.info/gems/hanami-controller#Configuration
131
+ controller.prepare do
132
+ # include MyAuthentication # included in all the actions
133
+ # before :authenticate! # run an authentication before callback
134
+ end
135
+
136
+ # Configure the code that will yield each time Web::View is included
137
+ # This is useful for sharing common functionality
138
+ #
139
+ # See: http://www.rubydoc.info/gems/hanami-view#Configuration
140
+ view.prepare do
141
+ include Hanami::Helpers
142
+ include Web::Assets::Helpers
143
+ end
144
+ end
145
+
146
+ ##
147
+ # DEVELOPMENT
148
+ #
149
+ configure :development do
150
+ # Don't handle exceptions, render the stack trace
151
+ handle_exceptions false
152
+ end
153
+
154
+ ##
155
+ # TEST
156
+ #
157
+ configure :test do
158
+ # Don't handle exceptions, render the stack trace
159
+ handle_exceptions false
160
+ end
161
+ end
162
+ end
@@ -0,0 +1 @@
1
+ get '/', to: 'home#index'
File without changes
@@ -0,0 +1,32 @@
1
+ module Web
2
+ module Controllers
3
+ module Home
4
+ class Index
5
+ include Web::Action
6
+
7
+ expose :identifier, :show_button, :button_color
8
+
9
+ def call(params)
10
+ @identifier = params.get(:flagsmith, :identifier)
11
+
12
+ if @identifier.nil? || @identifier.blank?
13
+ # Get the default flags for the current environment
14
+ flags = $flagsmith.get_environment_flags
15
+ @show_button = flags.is_feature_enabled("secret_button")
16
+ @button_data = JSON.parse(flags.get_feature_value("secret_button"))["colour"]
17
+ else
18
+ trait_key = params.get(:flagsmith, :trait_key)
19
+ trait_value = params.get(:flagsmith, :trait_value)
20
+ traits = trait_key.nil? ? nil : { trait_key: trait_value }
21
+
22
+ # Get the flags for an identity, including the provided trait which will be
23
+ # persisted to the API for future requests.
24
+ identity_flags = $flagsmith.get_identity_flags(identifier, traits)
25
+ @show_button = identity_flags.is_feature_enabled('secret_button')
26
+ @button_color = JSON.parse(identity_flags.get_feature_value('secret_button'))['colour']
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title Flagsmith Example
5
+ body
6
+ main[style="max-width: 768px; margin: auto; text-align: center"]
7
+ = yield
@@ -0,0 +1,10 @@
1
+ p
2
+ ' Hello,
3
+ = (identifier.nil? || identifier.blank?) ? 'World' : identifier
4
+
5
+ = if show_button
6
+ button[style="background-color: #{button_color}"] A secret button
7
+
8
+ p
9
+
10
+ = form
@@ -0,0 +1,7 @@
1
+ module Web
2
+ module Views
3
+ class ApplicationLayout
4
+ include Web::Layout
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Web
2
+ module Views
3
+ module Home
4
+ class Index
5
+ include Web::View
6
+
7
+ def form
8
+ form_for :flagsmith, '/', method: :get do
9
+ h3 'Identify as a User'
10
+ div style: 'margin-bottom: 1em;' do
11
+ label :identifier
12
+ text_field :identifier
13
+ end
14
+ p '... with an optional user trait'
15
+ div style: 'margin-bottom: 1em;' do
16
+ label :trait_key
17
+ text_field :trait_key
18
+ end
19
+ div style: 'margin-bottom: 1em;' do
20
+ label :trait_value
21
+ text_field :trait_value
22
+ end
23
+ div { submit 'Identify!' }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,2 @@
1
+ require_relative './environment'
2
+ Hanami.boot
@@ -0,0 +1,17 @@
1
+ require 'bundler/setup'
2
+ require 'hanami/setup'
3
+ require 'hanami/model'
4
+ require_relative '../apps/web/application'
5
+
6
+ Hanami.configure do
7
+ mount Web::Application, at: '/'
8
+
9
+ model do
10
+ adapter :sql, ENV.fetch('DATABASE_URL')
11
+ end
12
+
13
+ environment :development do
14
+ # See: https://guides.hanamirb.org/projects/logging
15
+ logger level: :debug
16
+ end
17
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ $flagsmith = Flagsmith::Client.new(
2
+ enable_local_evaluation: true,
3
+ environment_refresh_interval_seconds: 60,
4
+ default_flag_handler: lambda { |feature_name|
5
+ Flagsmith::Flag.new(
6
+ feature_name: feature_name, enabled: false, value: {}.to_json, feature_id: nil
7
+ )
8
+ }
9
+ )
@@ -0,0 +1,15 @@
1
+ require_relative './environment'
2
+ workers 1
3
+
4
+ threads_count = 1
5
+ threads threads_count, threads_count
6
+
7
+ preload_app!
8
+
9
+ rackup DefaultRackup
10
+ port 2300
11
+ environment 'development'
12
+
13
+ on_worker_boot do
14
+ Hanami.boot
15
+ end
data/example/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'config/environment'
2
+
3
+ run Hanami.app
File without changes
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ # Require this file for feature tests
2
+ require_relative './spec_helper'
3
+
4
+ require 'capybara'
5
+ require 'capybara/rspec'
6
+
7
+ RSpec.configure do |config|
8
+ config.include RSpec::FeatureExampleGroup
9
+
10
+ config.include Capybara::DSL, feature: true
11
+ config.include Capybara::RSpecMatchers, feature: true
12
+ end
@@ -0,0 +1,103 @@
1
+ # Require this file for unit tests
2
+ ENV['HANAMI_ENV'] ||= 'test'
3
+
4
+ require_relative '../config/environment'
5
+ Hanami.boot
6
+ Hanami::Utils.require!("#{__dir__}/support")
7
+
8
+ # This file was generated by the `rspec --init` command. Conventionally, all
9
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
10
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
11
+ # this file to always be loaded, without a need to explicitly require it in any
12
+ # files.
13
+ #
14
+ # Given that it is always loaded, you are encouraged to keep this file as
15
+ # light-weight as possible. Requiring heavyweight dependencies from this file
16
+ # will add to the boot time of your test suite on EVERY test run, even for an
17
+ # individual file that may not need all of that loaded. Instead, consider making
18
+ # a separate helper file that requires the additional dependencies and performs
19
+ # the additional setup, and require it from the spec files that actually need
20
+ # it.
21
+ #
22
+ # The `.rspec` file also contains a few flags that are not defaults but that
23
+ # users commonly want.
24
+ #
25
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
26
+ RSpec.configure do |config|
27
+ # rspec-expectations config goes here. You can use an alternate
28
+ # assertion/expectation library such as wrong or the stdlib/minitest
29
+ # assertions if you prefer.
30
+ config.expect_with :rspec do |expectations|
31
+ # This option will default to `true` in RSpec 4. It makes the `description`
32
+ # and `failure_message` of custom matchers include text for helper methods
33
+ # defined using `chain`, e.g.:
34
+ # be_bigger_than(2).and_smaller_than(4).description
35
+ # # => "be bigger than 2 and smaller than 4"
36
+ # ...rather than:
37
+ # # => "be bigger than 2"
38
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
39
+ end
40
+
41
+ # rspec-mocks config goes here. You can use an alternate test double
42
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
43
+ config.mock_with :rspec do |mocks|
44
+ # Prevents you from mocking or stubbing a method that does not exist on
45
+ # a real object. This is generally recommended, and will default to
46
+ # `true` in RSpec 4.
47
+ mocks.verify_partial_doubles = true
48
+ end
49
+
50
+ # The settings below are suggested to provide a good initial experience
51
+ # with RSpec, but feel free to customize to your heart's content.
52
+ =begin
53
+ # These two settings work together to allow you to limit a spec run
54
+ # to individual examples or groups you care about by tagging them with
55
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
56
+ # get run.
57
+ config.filter_run :focus
58
+ config.run_all_when_everything_filtered = true
59
+
60
+ # Allows RSpec to persist some state between runs in order to support
61
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
62
+ # you configure your source control system to ignore this file.
63
+ config.example_status_persistence_file_path = "spec/examples.txt"
64
+
65
+ # Limits the available syntax to the non-monkey patched syntax that is
66
+ # recommended. For more details, see:
67
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
68
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
69
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
70
+ config.disable_monkey_patching!
71
+
72
+ # This setting enables warnings. It's recommended, but in many cases may
73
+ # be too noisy due to issues in dependencies.
74
+ config.warnings = false
75
+
76
+ # Many RSpec users commonly either run the entire suite or an individual
77
+ # file, and it's useful to allow more verbose output when running an
78
+ # individual spec file.
79
+ if config.files_to_run.one?
80
+ # Use the documentation formatter for detailed output,
81
+ # unless a formatter has already been configured
82
+ # (e.g. via a command-line flag).
83
+ config.default_formatter = 'doc'
84
+ end
85
+
86
+ # Print the 10 slowest examples and example groups at the
87
+ # end of the spec run, to help surface which specs are running
88
+ # particularly slow.
89
+ config.profile_examples = 10
90
+
91
+ # Run specs in random order to surface order dependencies. If you find an
92
+ # order dependency and want to debug it, you can fix the order by providing
93
+ # the seed, which is printed after each run.
94
+ # --seed 1234
95
+ config.order = :random
96
+
97
+ # Seed global randomization in this process using the `--seed` CLI option.
98
+ # Setting this allows you to use `--seed` to deterministically reproduce
99
+ # test failures related to randomization by passing the same `--seed` value
100
+ # as the one that triggered the failure.
101
+ Kernel.srand config.seed
102
+ =end
103
+ end
File without changes
@@ -0,0 +1,8 @@
1
+ module RSpec
2
+ module FeatureExampleGroup
3
+ def self.included(group)
4
+ group.metadata[:type] = :feature
5
+ Capybara.app = Hanami.app
6
+ end
7
+ end
8
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ RSpec.describe Web::Controllers::Home::Index, type: :action do
2
+ let(:action) { described_class.new }
3
+ let(:params) { Hash[] }
4
+
5
+ it 'is successful' do
6
+ response = action.call(params)
7
+ expect(response[0]).to eq 200
8
+ end
9
+ end
File without changes
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Web::Views::ApplicationLayout, type: :view do
4
+ let(:layout) { Web::Views::ApplicationLayout.new({ format: :html }, "contents") }
5
+ let(:rendered) { layout.render }
6
+
7
+ it 'contains application name' do
8
+ expect(rendered).to include('Web')
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ RSpec.describe Web::Views::Home::Index, type: :view do
2
+ let(:exposures) { Hash[format: :html] }
3
+ let(:template) { Hanami::View::Template.new('apps/web/templates/home/index.html.slim') }
4
+ let(:view) { described_class.new(template, exposures) }
5
+ let(:rendered) { view.render }
6
+
7
+ it 'exposes #format' do
8
+ expect(view.format).to eq exposures.fetch(:format)
9
+ end
10
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'semantic'
4
+ require 'securerandom'
5
+
6
+ require_relative 'environments/models'
7
+ require_relative 'features/models'
8
+ require_relative 'identities/models'
9
+ require_relative 'organisations/models'
10
+ require_relative 'projects/models'
11
+ require_relative 'segments/evaluator'
12
+ require_relative 'segments/models'
13
+ require_relative 'utils/hash_func'
14
+
15
+ module Flagsmith
16
+ module Engine
17
+ # Flags engine methods
18
+ module Core
19
+ include Flagsmith::Engine::Segments::Evaluator
20
+
21
+ def get_identity_feature_state(environment, identity, feature_name, override_traits = nil)
22
+ feature_states = get_identity_feature_states_dict(environment, identity, override_traits).values
23
+
24
+ feature_state = feature_states.find { |f| f.feature.name == feature_name }
25
+
26
+ raise Flagsmith::FeatureStateNotFound, 'Feature State Not Found' if feature_state.nil?
27
+
28
+ feature_state
29
+ end
30
+
31
+ def get_identity_feature_states(environment, identity, override_traits = nil)
32
+ feature_states = get_identity_feature_states_dict(environment, identity, override_traits).values
33
+
34
+ return feature_states.select(&:enabled?) if environment.project.hide_disabled_flags
35
+
36
+ feature_states
37
+ end
38
+
39
+ def get_environment_feature_state(environment, feature_name)
40
+ features_state = environment.feature_states.find { |f| f.feature.name == feature_name }
41
+
42
+ raise Flagsmith::FeatureStateNotFound, 'Feature State Not Found' if features_state.nil?
43
+
44
+ features_state
45
+ end
46
+
47
+ def get_environment_feature_states(environment)
48
+ return environment.feature_states.select(&:enabled?) if environment.project.hide_disabled_flags
49
+
50
+ environment.feature_states
51
+ end
52
+
53
+ private
54
+
55
+ def get_identity_feature_states_dict(environment, identity, override_traits = nil)
56
+ # Get feature states from the environment
57
+ feature_states = {}
58
+ override = ->(fs) { feature_states[fs.feature.id] = fs }
59
+ environment.feature_states.each(&override)
60
+
61
+ override_by_matching_segments(environment, identity, override_traits) do |fs|
62
+ override.call(fs) unless higher_segment_priority?(feature_states, fs)
63
+ end
64
+
65
+ # Override with any feature states defined directly the identity
66
+ identity.identity_features.each(&override)
67
+ feature_states
68
+ end
69
+
70
+ # Override with any feature states defined by matching segments
71
+ def override_by_matching_segments(environment, identity, override_traits)
72
+ identity_segments = get_identity_segments(environment, identity, override_traits)
73
+ identity_segments.each do |matching_segment|
74
+ matching_segment.feature_states.each do |feature_state|
75
+ yield feature_state if block_given?
76
+ end
77
+ end
78
+ end
79
+
80
+ def higher_segment_priority?(collection, feature_state)
81
+ collection.key?(feature_state.feature.id) &&
82
+ collection[feature_state.feature.id].higher_segment_priority?(
83
+ feature_state
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flagsmith
4
+ module Engine
5
+ # EnvironmentModel
6
+ class Environment
7
+ attr_reader :id, :api_key
8
+ attr_accessor :project, :feature_states, :amplitude_config, :segment_config,
9
+ :mixpanel_config, :heap_config
10
+
11
+ def initialize(id:, api_key:, project:, feature_states: [])
12
+ @id = id
13
+ @api_key = api_key
14
+ @project = project
15
+ @feature_states = feature_states
16
+ end
17
+
18
+ class << self
19
+ def build(json)
20
+ project = Flagsmith::Engine::Project.build(json[:project])
21
+ feature_states = json[:feature_states].map do |fs|
22
+ Flagsmith::Engine::FeatureState.build(fs)
23
+ end
24
+
25
+ new(**json.slice(:id, :api_key).merge(project: project, feature_states: feature_states))
26
+ end
27
+ end
28
+ end
29
+
30
+ module Environments
31
+ # EnvironmentApiKeyModel
32
+ class ApiKey
33
+ attr_reader :id, :key, :created_at, :name, :client_api_key
34
+ attr_accessor :expires_at, :active
35
+
36
+ def initialize(params = {})
37
+ @id = params.fetch(:id)
38
+ @key = params.fetch(:key)
39
+ @name = params.fetch(:name)
40
+ @client_api_key = params.fetch(:client_api_key)
41
+ @created_at = params.fetch(:created_at, Time.now)
42
+ @expires_at = params.fetch(:expires_at, nil)
43
+ @active = params.fetch(:active, true)
44
+ end
45
+
46
+ def valid?
47
+ active && (!expires_at || expires_at > Time.now)
48
+ end
49
+
50
+ class << self
51
+ def build(json)
52
+ attributes = json.slice(:id, :key, :name, :client_api_key, :active)
53
+ attributes = attributes.merge(expires_at: Date.parse(json[:created_at])) unless json[:created_at].nil?
54
+ attributes = attributes.merge(expires_at: Date.parse(json[:expires_at])) unless json[:expires_at].nil?
55
+ new(**attributes)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end