vinistock / sail Goto Github PK
View Code? Open in Web Editor NEWSail is a lightweight Rails engine that brings an admin panel for managing configuration settings on a live Rails app
License: Other
Sail is a lightweight Rails engine that brings an admin panel for managing configuration settings on a live Rails app
License: Other
The caching in setting view results in multiple copies of the same settging
I think the reason is in Setting.paginated method
sail/app/models/sail/setting.rb
Lines 35 to 39 in 86203b0
The cache must rely on the entity id to generate the cache key but the id is not present in paginated result.
Hiya :) I've been thinking about ways to make the settings page less busy - once you fill it up with cards there's a lot of detail on there to take in at a glance.
One thing that I thought might help was if the settings card SAVE buttons were all in a disabled greyed-out state initially, only changing to active eye-catching tangerine when that particular setting has been changed.
Relatedly, I'd be interested in having the option to show/hide different parts of the card UI, maybe as a set of config options... or even a way to override the card template? For instance, I don't think the end-users of my CMS will care about whether a setting is a 'boolean', a 'float', etc, so that label is one detail I would hide by default - again to reduce the overall complexity of the settings page.
Hi; I've got this in routes.rb, and I haven't overridden any settings so far:
mount Sail::Engine, at: '/admin/settings'
When I try to load http://localhost:3000/admin/settings from a test I get this backtrace:
TypeError:
can't cast Rack::Session::SessionId
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:388:in `commit_session'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:268:in `context'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:260:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/railties-6.1.0/lib/rails/rack/logger.rb:37:in `call_app'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/railties-6.1.0/lib/rails/rack/logger.rb:26:in `block in call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/railties-6.1.0/lib/rails/rack/logger.rb:26:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/ahoy_matey-3.1.0/lib/ahoy/engine.rb:22:in `call_with_quiet_ahoy'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/request_store-1.5.0/lib/request_store/middleware.rb:19:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-2.2.3/lib/rack/method_override.rb:24:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-2.2.3/lib/rack/runtime.rb:22:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-2.2.3/lib/rack/sendfile.rb:110:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/railties-6.1.0/lib/rails/engine.rb:539:in `call'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-test-1.1.0/lib/rack/mock_session.rb:29:in `request'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb:266:in `process_request'
# /home/denny/.rvm/gems/ruby-3.0.0/gems/rack-test-1.1.0/lib/rack/test.rb:119:in `request'
# ./spec/requests/admin/sail_settings_spec.rb:20:in `block (3 levels) in <top (required)>'
I'm a bit out of my depth here, but I was wondering if it was because I'm using ActiveRecord::SessionStore and you're using ActionDispatch::Session::CookieStore ?
If I hit the URL with a browser, I get a longer backtrace in the logs that mentions both...
Started GET "/admin/settings" for ::1 at 2021-01-04 20:39:22 +0000
(0.5ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
TypeError (can't cast Rack::Session::SessionId):
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/quoting.rb:246:in `_type_cast'
activerecord (6.1.0) lib/active_record/connection_adapters/postgresql/quoting.rb:162:in `_type_cast'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/quoting.rb:43:in `type_cast'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/quoting.rb:205:in `block in type_casted_binds'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/quoting.rb:203:in `map'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/quoting.rb:203:in `type_casted_binds'
activerecord (6.1.0) lib/active_record/connection_adapters/postgresql_adapter.rb:689:in `exec_cache'
activerecord (6.1.0) lib/active_record/connection_adapters/postgresql_adapter.rb:657:in `execute_and_clear'
activerecord (6.1.0) lib/active_record/connection_adapters/postgresql/database_statements.rb:53:in `exec_query'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/database_statements.rb:536:in `select_prepared'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/database_statements.rb:67:in `select_all'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/query_cache.rb:101:in `block in select_all'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/query_cache.rb:118:in `block in cache_sql'
activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize'
activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt'
activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'
activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'
activesupport (6.1.0) lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/query_cache.rb:109:in `cache_sql'
activerecord (6.1.0) lib/active_record/connection_adapters/abstract/query_cache.rb:101:in `select_all'
activerecord (6.1.0) lib/active_record/querying.rb:47:in `find_by_sql'
activerecord (6.1.0) lib/active_record/relation.rb:850:in `block in exec_queries'
activerecord (6.1.0) lib/active_record/relation.rb:868:in `skip_query_cache_if_necessary'
activerecord (6.1.0) lib/active_record/relation.rb:835:in `exec_queries'
activerecord (6.1.0) lib/active_record/relation.rb:638:in `load'
activerecord (6.1.0) lib/active_record/relation.rb:249:in `records'
activerecord (6.1.0) lib/active_record/relation.rb:244:in `to_ary'
activerecord (6.1.0) lib/active_record/relation/finder_methods.rb:553:in `find_nth_with_limit'
activerecord (6.1.0) lib/active_record/relation/finder_methods.rb:538:in `find_nth'
activerecord (6.1.0) lib/active_record/relation/finder_methods.rb:122:in `first'
activerecord-session_store (f188efbc49a5) lib/active_record/session_store/session.rb:51:in `find_by_session_id'
activerecord-session_store (f188efbc49a5) lib/active_record/session_store/session.rb:28:in `find_by_session_id'
activerecord-session_store (f188efbc49a5) lib/action_dispatch/session/active_record_store.rb:120:in `block in get_session_model'
activesupport (6.1.0) lib/active_support/logger_silence.rb:18:in `block in silence'
activesupport (6.1.0) lib/active_support/logger_thread_safe_level.rb:52:in `log_at'
activesupport (6.1.0) lib/active_support/logger_silence.rb:18:in `silence'
activesupport (6.1.0) lib/active_support/logger.rb:64:in `block (3 levels) in broadcast'
activesupport (6.1.0) lib/active_support/logger_silence.rb:18:in `block in silence'
activesupport (6.1.0) lib/active_support/logger_thread_safe_level.rb:52:in `log_at'
activesupport (6.1.0) lib/active_support/logger_silence.rb:18:in `silence'
activesupport (6.1.0) lib/active_support/logger.rb:62:in `block (2 levels) in broadcast'
activerecord-session_store (f188efbc49a5) lib/action_dispatch/session/active_record_store.rb:119:in `get_session_model'
activerecord-session_store (f188efbc49a5) lib/action_dispatch/session/active_record_store.rb:136:in `find_session'
rack (2.2.3) lib/rack/session/abstract/id.rb:314:in `load_session'
actionpack (6.1.0) lib/action_dispatch/middleware/session/abstract_store.rb:45:in `block in load_session'
actionpack (6.1.0) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `stale_session_check!'
actionpack (6.1.0) lib/action_dispatch/middleware/session/abstract_store.rb:45:in `load_session'
actionpack (6.1.0) lib/action_dispatch/request/session.rb:234:in `load!'
actionpack (6.1.0) lib/action_dispatch/request/session.rb:226:in `load_for_read!'
actionpack (6.1.0) lib/action_dispatch/request/session.rb:144:in `to_hash'
actionpack (6.1.0) lib/action_dispatch/request/session.rb:217:in `merge!'
actionpack (6.1.0) lib/action_dispatch/request/session.rb:217:in `merge!'
actionpack (6.1.0) lib/action_dispatch/request/session.rb:19:in `create'
actionpack (6.1.0) lib/action_dispatch/middleware/session/abstract_store.rb:71:in `prepare_session'
rack (2.2.3) lib/rack/session/abstract/id.rb:265:in `context'
rack (2.2.3) lib/rack/session/abstract/id.rb:260:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/cookies.rb:689:in `call'
railties (6.1.0) lib/rails/engine.rb:539:in `call'
railties (6.1.0) lib/rails/railtie.rb:207:in `public_send'
railties (6.1.0) lib/rails/railtie.rb:207:in `method_missing'
actionpack (6.1.0) lib/action_dispatch/routing/mapper.rb:20:in `block in <class:Constraints>'
actionpack (6.1.0) lib/action_dispatch/routing/mapper.rb:49:in `serve'
actionpack (6.1.0) lib/action_dispatch/journey/router.rb:50:in `block in serve'
actionpack (6.1.0) lib/action_dispatch/journey/router.rb:32:in `each'
actionpack (6.1.0) lib/action_dispatch/journey/router.rb:32:in `serve'
actionpack (6.1.0) lib/action_dispatch/routing/route_set.rb:842:in `call'
warden (1.2.9) lib/warden/manager.rb:36:in `block in call'
warden (1.2.9) lib/warden/manager.rb:34:in `catch'
warden (1.2.9) lib/warden/manager.rb:34:in `call'
rack (2.2.3) lib/rack/tempfile_reaper.rb:15:in `call'
rack (2.2.3) lib/rack/etag.rb:27:in `call'
rack (2.2.3) lib/rack/conditional_get.rb:27:in `call'
rack (2.2.3) lib/rack/head.rb:12:in `call'
actionpack (6.1.0) lib/action_dispatch/http/permissions_policy.rb:22:in `call'
actionpack (6.1.0) lib/action_dispatch/http/content_security_policy.rb:18:in `call'
rack (2.2.3) lib/rack/session/abstract/id.rb:266:in `context'
rack (2.2.3) lib/rack/session/abstract/id.rb:260:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/cookies.rb:689:in `call'
activerecord (6.1.0) lib/active_record/migration.rb:601:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
activesupport (6.1.0) lib/active_support/callbacks.rb:98:in `run_callbacks'
actionpack (6.1.0) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/actionable_exceptions.rb:18:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/debug_exceptions.rb:29:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
railties (6.1.0) lib/rails/rack/logger.rb:37:in `call_app'
railties (6.1.0) lib/rails/rack/logger.rb:26:in `block in call'
activesupport (6.1.0) lib/active_support/tagged_logging.rb:99:in `block in tagged'
activesupport (6.1.0) lib/active_support/tagged_logging.rb:37:in `tagged'
activesupport (6.1.0) lib/active_support/tagged_logging.rb:99:in `tagged'
railties (6.1.0) lib/rails/rack/logger.rb:26:in `call'
ahoy_matey (3.1.0) lib/ahoy/engine.rb:22:in `call_with_quiet_ahoy'
sprockets-rails (3.2.2) lib/sprockets/rails/quiet_assets.rb:13:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
request_store (1.5.0) lib/request_store/middleware.rb:19:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/request_id.rb:26:in `call'
rack (2.2.3) lib/rack/method_override.rb:24:in `call'
rack (2.2.3) lib/rack/runtime.rb:22:in `call'
activesupport (6.1.0) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/executor.rb:14:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/static.rb:24:in `call'
rack (2.2.3) lib/rack/sendfile.rb:110:in `call'
actionpack (6.1.0) lib/action_dispatch/middleware/host_authorization.rb:98:in `call'
webpacker (5.2.1) lib/webpacker/dev_server_proxy.rb:25:in `perform_request'
rack-proxy (0.6.5) lib/rack/proxy.rb:57:in `call'
railties (6.1.0) lib/rails/engine.rb:539:in `call'
puma (5.1.1) lib/puma/configuration.rb:246:in `call'
puma (5.1.1) lib/puma/request.rb:76:in `block in handle_request'
puma (5.1.1) lib/puma/thread_pool.rb:337:in `with_force_shutdown'
puma (5.1.1) lib/puma/request.rb:75:in `handle_request'
puma (5.1.1) lib/puma/server.rb:431:in `process_client'
puma (5.1.1) lib/puma/thread_pool.rb:145:in `block in spawn_thread'
Let me know if I'm doing something stupid :) or if there's any useful info I can provide otherwise.
Hello @vinistock
After installing the latest version of sail gem 1.5.0
, I am facing lot of problems now on my Rails 4 application.
rails console
I am unable to create any setting. Following is the error ๐2.2.2 :001 > Sail::Setting.create(name: :my_setting, cast_type: :integer, description: 'A very important setting', value: '15')
SyntaxError: /Users/rohan/.rvm/gems/ruby-2.2.2/gems/sail-1.5.0/app/models/sail/setting.rb:62: syntax error, unexpected '.'
...ere(name: throttled_by).first&.throttle?
... ^
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:457:in `load'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:457:in `block in load_file'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:647:in `new_constants_in'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:456:in `load_file'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:354:in `require_or_load'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:494:in `load_missing_constant'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:184:in `const_missing'
from (irb):1
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/railties-4.2.4/lib/rails/commands/console.rb:110:in `start'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/railties-4.2.4/lib/rails/commands/console.rb:9:in `start'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/railties-4.2.4/lib/rails/commands/commands_tasks.rb:68:in `console'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/railties-4.2.4/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
from /Users/rohan/.rvm/gems/ruby-2.2.2/gems/railties-4.2.4/lib/rails/commands.rb:17:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
v1.5.0
and now when I navigate to http://localhost:3000/sail
then I face following errors ๐Can you guide me if I am missing any step?
Hello! I have created 1 test setting in sail.yml and loaded it.
description: Name of the portal
value: monestro
cast_type: string
group: portal_settings
But now when I try to open the dashboard I get the error mentioned in header.
the gem version is 3.0.1, mounted like this:
constraints subdomain: ['backoffice', 'staging-backoffice'] do
โฆ
constraints(Clearance::Constraints::SignedIn.new { |user| user.staff? }) do
โฆ
namespace :admin do
mount Sail::Engine => '/sail'
end
end
end
Really seems I am not doing anything fancy, so I don't understand why constant resolution for "Sail::Types" does not work
For some reason, Sail assets are not loaded correctly in Rails 4 applications. Issue #23 contains screenshots of such problem and further description.
Hello, could you please explain what purpose does the caching serve if in order to retrieve the value of the setting the query is still being made?
Talking about get method in Sail::Setting model
def self.get(name)
Sail.instrumenter.increment_usage_of(name)
setting = Setting.for_value_by_name(name).first
return if setting.nil?
if setting.should_not_cache?
setting.safe_cast
else
Rails.cache.fetch("setting_get_#{name}", expires_in: Sail.configuration.cache_life_span) do
setting.safe_cast
end
end
end
Doesn't this defeat purpose of caching?
Thank you for an awesome gem and sorry if this is a stupid question and I just miss something :)
Hi @vinistock Seems we have an issue with class name generated due to migration step for sail configuration settings.
When we run the step rails g sail my_desired_migration_name
and let's say we name our migration as for_sail
i.e. rails g sail for_sail
then it generates following migration file.
class CreateSailSettings < ActiveRecord::Migration
def change
create_table :sail_settings do |t|
t.string :name, null: false
t.text :description
t.string :value, null: false
t.integer :cast_type, null: false, limit: 1
t.timestamps
t.index ["name"], name: "index_settings_on_name", unique: true
end
end
end
But when we run rake db:migrate
then this throws an error as
NameError: uninitialized constant ForSail
Here the class name is CreateSailSettings
which should be ideally ForSail
in this case. I had to manually modify the class name i.e. top line in the migration to be class ForSail < ActiveRecord::Migration
and then the migrations started working.
We are planning to use Sail to allow support/QA team make some configurable changes easily. Some of those configs need to be set for specific case/time and then reset/unset it. The problem is, in order to unset cofig value, we need to remove the config and the team need to remember the exact config name to set it again.
We want to make values nullable, so that we can keep the attributes without any values. They will return nil
if accessed. Add Unset
& Reset to default
to revert the values.
We are making the above mentioned changes inorder to support our use cases, let me know if they seems like good addition to Sail.
Here:
sail/app/controllers/sail/settings_controller.rb
Lines 19 to 25 in b30a302
The controller should not be manipulating values for the model to save.
This makes writing test difficult, moreover the validation in model layer is enough to only store value as 'true' or 'false'.
This method could be avoided or moved to some better place
When using a YAML file to populate the database with settings, Sail adds a hook in the after initialize period. For some reason, this is breaking in webrick.
Issue #23 contains some more information.
Some specs flaky specs are breaking way too often in travis. They need to be adapted so that they don't break so regularly.
โณ /Users/plbelt/.rvm/gems/ruby-2.6.0@learning_office/bundler/gems/sail-f88cbb636bcd/app/controllers/sail/settings_controller.rb:18```
The issue is an ambiguous `updated_at` column created by the nested-selected when using `fresh_when(@settings)` ... when `@settings = Setting.by_query...` already includes that column.
The idea of Sail is to be a centralized source of live configurations for Rails applications. More setting types can make the configurations simpler and require less application code to be written.
Propose new types of settings that could be useful having in Sail. Your proposal can either be a written idea as a comment in this issue or as a pull request implementing the new type.
Existing types can be found in the readme.
running on rails 5.2.2 / ruby 2.6.1
rake sail:create_config_file causes NameError: uninitialized constant Sail::Setting
rake sail:load_defaults causes NameError: uninitialized constant Sail::Setting
any clues appreciated?
thanks
Sail needs to provide the ability of reading settings' values via javascript. Therefore, we need a show action in settings controller to return settings information as JSON.
{ "value": true }
. It should only accept JSON format as well.Depending on how critical changing a setting in a live application may be, other admins or groups might want to be notified of such changes.
Therefore, it can be useful to have a notification system that will push messages to tools such as Slack, Hipchat or Microsoft teams to warn specific channels or people about changes made in their production settings.
Investigate what is needed to create an abstract notification system that can push messages to multiple services (somewhat similar to what active_job does for using different queues). This abstraction can be made into a new gem for generic purposes.
Ideally, a Sail user would only need the following to receive the messages:
config.notifier = { service: :slack, groups: %i[feature_flags] }
)gem "slack-ruby-client"
This will probably require writing wrapper classes for each service. Additionally, define which services Sail will support on the first release of this new feature.
It would be interesting to have a cron setting type. The definitions for this new type are as follows
Will work very similar to the string cast type, but must only keep crons (validations required for before saving to the database).
When returning the setting value, if DateTime.now.utc matches the cron, return true. Otherwise, return false.
The "Monitor mode" button in the navbar is supposed to be aligned with the button "Guide". However, in Safari "Monitor mode" is below "Guide" by a few pixels.
Both buttons should be vertically centered within the navbar.
Query param needs to be preserved when switching pages, otherwise its impossible to actually dig through search results.
The to_prepare
in engine.rb
method uses an implicit block to set the auth lambda, which isn't allowed in Ruby 3.x (and is flagged by rubocop in 2.7.2, I think).
To Reproduce
Set config.dashboard_auth_lambda
in a Ruby 3.x application and try to run it; it exits with the following error:
/home/denny/code/sail/lib/sail/engine.rb:54:in `new': tried to create Proc object without a block (ArgumentError)
from /home/denny/code/sail/lib/sail/engine.rb:54:in `to_prepare'
from /home/denny/code/sail/lib/sail/engine.rb:33:in `block in <class:Engine>'
[...]
When I try to complete any action in the settings dashboard, the action fails and I get a strong params error in my logs:
ActionController::UnpermittedParameters (found unpermitted parameters: :_method, :locale):
I'll push up a PR for this in a few minutes, assuming it's as simple as it sounds ๐ ๐ค๐ผ
A lot of information in the dashboard is really only useful for editing settings. If someone desires to simply put the dashboard on a big screen and keep it as a monitor, the view can be much simpler with bigger fonts to make it extra simple to spot the values of each setting. Also, more settings can fit the same page.
Add a button to the nav bar to enter monitor mode. Monitor mode will only include the name and value of each setting in their cards, use bigger fonts and increase the number of settings per page. No setting management mechanisms, like inputs, buttons or labels, should be visible in the monitor mode.
As always, feedback and thoughts on the new feature are much appreciated.
It would be nice if Sail provided the appropriate types to be used by applications that have a GraphQL based backend. I'm not sure if defining the types inside the engine is enough or if any code would be required in the main app.
Some possibilities that can be explored:
At the moment, Sail does not work with an application created using the --api option. This is due to missing middleware and libraries that are not included in the API mode.
It would be great if Sail could resolve the missing requirements on its own and work out of the box for API only applications since they can also benefit from configurations.
A/B test flags are useful for validating features live. Sail should support A/B testing out of the box.
When an ab_test setting has its value as 'true' in the database, the get method should randomly return true or false. This can easily be achieve using
[true, false].sample
When an ab_test setting has its value as 'false' in the database, the get method should always return false (Boolean).
In summary, they are basically a Boolean setting, but when the value is 'true', it will randomize between true or false.
value_is_true_or_false
_setting.html.erb
partial, make sure ab_test types are displayed exactly like BooleansDescribe the bug
Hey guys ๐ . Recently we've migrated from using ActiveRecord Session Store (https://github.com/rails/activerecord-session_store) to redis_store (https://github.com/redis-store/redis-actionpack). After this change our Sail Dashboard stopped working:
When debugging from within Sails:
[1] pry(#<Sail::SettingsController>)> session.options
=> #<ActionDispatch::Request::Session::Options:0x00007fa5b36098b0
@by=
#<ActionDispatch::Session::RedisStore:0x00007fa593577a48
@app=#<ActionDispatch::Routing::RouteSet:0x00007fa571addf18>,
@conn=
#<Redis::Rack::Connection:0x00007fa5935771b0
@options=
{:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :redis_server=>nil},
@pool=nil,
@pooled=false,
@store=#<Redis client v4.6.0 for redis://0.0.0.0:6379/0>>,
@cookie_only=true,
@default_options=
{:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :redis_server=>nil},
@key="_session_id",
@mutex=#<Thread::Mutex:0x00007fa5935771d8>,
@same_site=nil>,
@delegate=
{:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :redis_server=>nil}>
What I've noticed - the session key ("_session_id") looks like kind of a default key - in our configuration we are using different key name, so when we would try to fetch sesssion.id we'll get nil, because parent app stores session under different key.
When I've updated this line
Line 42 in 7da7c74
if Rails.application.config.session_store.nil?
config.middleware.use ActionDispatch::Session::CookieStore
end
Everything works as expected - dashboard loads, and when debugging and checking whats inside session object:
=> #<ActionDispatch::Request::Session::Options:0x00007fb952e3a720
@by=
#<ActionDispatch::Session::RedisStore:0x00007fb9e2a5fa98
@app=
#<ActionDispatch::ContentSecurityPolicy::Middleware:0x00007fb9e2a5fcc8
@app=
#<Rack::Head:0x00007fb9e2a5fed0
@app=
#<Rack::ConditionalGet:0x00007fb9e2a5ff48
@app=
#<Rack::ETag:0x00007fb9e2a54008
@app=
#<Rack::TempfileReaper:0x00007fb9e2a54058
@app=
#<Warden::Manager:0x00007fb9e2a54170
@app=
#<ApolloUploadServer::Middleware:0x00007fb9e2a541c0
@app=
#<Warden::JWTAuth::Middleware:0x00007fb9e2a54210
@app=#<Bullet::Rack:0x00007fb9e2a54260 @app=#<ActionDispatch::Routing::RouteSet:0x00007fb984297210>>>>,
@config=
{:default_scope=>:account,
:scope_defaults=>{},
:default_strategies=>
{:account=>[:jwt, :database_authenticatable],
...},
:intercept_401=>false,
:failure_app=>#<Devise::Delegator:0x00007fb98468e3f0>}>>,
@cache_control="max-age=0, private, must-revalidate",
@no_cache_control="no-cache">>>>,
@conn=
#<Redis::Rack::Connection:0x00007fb9e2a5f368
@options=
{:path=>"/",
:domain=>nil,
:expire_after=>1 minute,
:secure=>false,
:httponly=>true,
:defer=>false,
:renew=>false,
:redis_server=>["redis://0.0.0.0:6379/0/session"],
:servers=>["redis://0.0.0.0:6379/0/session"],
:threadsafe=>true},
@pool=nil,
@pooled=false,
@store=#<Redis client v4.6.0 for redis://0.0.0.0:6379/0>>,
@cookie_only=true,
@default_options=
{:path=>"/",
:domain=>nil,
:expire_after=>1 minute,
:secure=>false,
:httponly=>true,
:defer=>false,
:renew=>false,
:redis_server=>["redis://0.0.0.0:6379/0/session"],
:servers=>["redis://0.0.0.0:6379/0/session"],
:threadsafe=>true},
@key="_hub_session",
@mutex=#<Thread::Mutex:0x00007fb9e2a5f390>,
@same_site=nil>,
@delegate=
{:path=>"/",
:domain=>nil,
:expire_after=>1 minute,
:secure=>false,
:httponly=>true,
:defer=>false,
:renew=>false,
:redis_server=>["redis://0.0.0.0:6379/0/session"],
:servers=>["redis://0.0.0.0:6379/0/session"],
:threadsafe=>true,
:id=>"7f9d96ee441b7b418f5f2573263fa03c"}>
[7] pry(#<Sail::SettingsController>)>
So here I can see way more details and options, also the key matches the one we've configured ("_hub_session"). Looks like when we are adding a middleware in after_initialize block it somehow overrides some of the configs from the base/parent app ๐ค .
Environment
Gemfile
source 'https://rubygems.org'
ruby '2.7.6'
gem 'rails', '~> 6.0.0'
gem 'redis-actionpack'
gem 'sail'
...
To Reproduce
Steps to reproduce the behavior:
Install 'redis-actionpack' gem,
create app/initializers/session_store.rb
opts = {
key: '_hub_session',
servers: ['redis_url'],
expire_after: 1.hour,
threadsafe: true,
secure: !(Rails.env.development? || Rails.env.test?)
}
Rails.application.config.session_store :redis_store, opts
routes:
...
authenticate :user, ->(u) { u.admin? } do
mount Sail::Engine => '/sail'
...
Login and visit localhost:3000/sail
Expected behavior
User should see a Sail Dashboard
Actual behavior
User sees an error page.
Eventually, it can be useful to save the entire state of the configuration for an application. For instance, if testing the effects of combinations of numerous settings, switching each individual setting can be a time consuming manual task.
Profiles will allow saving the current state of all settings. Switching between profiles will also be available. This can also benefit automated test suites to explore different scenarios by saving profiles with many different setting combinations.
Target version is 3.0.0 (will require extra migrations and therefore will include breaking changes).
By clicking a cog button in the dashboard, I want a menu to show up allowing me to save, delete, edit and switch between profiles easily.
As a test engineer, I want be able to switch profiles via JSON API.
Please feel free to provide feedback or suggestions as these are very helpful when designing new features (thumbs up or down included).
Describe the bug
ActionController::Base#expire_fragmen
t allow to pass regex but it doesn't work with redis cache story
Line 87 in fb15198
To Reproduce
Steps to reproduce the behavior:
USAGES_UNTIL_CACHE_EXPIRE
by usingSail.get(flag_name)
Expected behavior
Exception shouldn't raise
Actual behavior
Exception ArgumentError: Only Redis glob strings are supported: /name: "flag_name"/
is raised
Often, the people responsible for managing configurations in production like environments are not technical or do not engage directly with coding.
Therefore, it is unlikely that any non-technical admin will look for the gem's README or Wiki for support on its usage. Having a quick reference guide can make sure users will make the most out of Sail.
Add a "Guide" link to the nav bar on the right that will display a modal containing a quick reference guide on how to use the dashboard. The guide should include all the following information in a concise and clear manner:
As always, feedback and thoughts on the new feature are much appreciated.
Hi @vinistock
While installing sail
in my newer Rails 6.0 application, I faced migration issues after step rails g sail:install
. The errors while running rails db:migrate
is
StandardError: An error has occurred, this and all later migrations canceled:
Directly inheriting from ActiveRecord::Migration is not supported. Please specify the Rails release the migration was written for:
class CreateSailSettings < ActiveRecord::Migration[4.2]
...
...
...
...
I fixed it on my local by just inheriting it using ActiveRecord::Migration[6.0]
instead of ActiveRecord::Migration
. i.e. I edited both the migration files for CreateSailSettings
class and CreateSailProfiles
class and inherited them from ActiveRecord::Migration[6.0]
. This now allows me to run the migrations successfully.
Should we start working on to make it Rails 6.0 compatible? ๐
BTW, last year we both collaborated on your sail
gem for one of the features and few fixes.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.