Code Monkey home page Code Monkey logo

sail's People

Contributors

andyrosenberg avatar asko-ohmann avatar chaadow avatar denny avatar dependabot-preview[bot] avatar dependabot[bot] avatar dersnek avatar johnthethird avatar oleg-kiviljov avatar rohandaxini avatar vikasnautiyal avatar vinistock avatar zvlex avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sail's Issues

Caching a setting view fragment doesn't work because pagination doesn't return setting id

The caching in setting view results in multiple copies of the same settging

<% cache setting, expires_in: Sail.configuration.cache_life_span do %>

I think the reason is in Setting.paginated method

scope :paginated, lambda { |page, per_page|
select(:name, :description, :group, :value, :cast_type, :updated_at)
.offset(page.to_i * per_page)
.limit(per_page)
}

The cache must rely on the entity id to generate the cache key but the id is not present in paginated result.

Reducing visual complexity: Disable save buttons until setting is changed? Hide type label?

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.

Problem with sessions

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.

Unable to use Sail v1.5.0

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.

  1. From 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>'
  1. I recreated migrations and installed sail from scratch on my application for v1.5.0 and now when I navigate to http://localhost:3000/sail then I face following errors ๐Ÿ‘‡

screen shot 2018-12-09 at 6 27 37 pm

Can you guide me if I am missing any step?

NameError: uninitialized constant Sail::Types when visiting the dashboard

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

Why cache if the db is still being hit?

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 :)

Migration issue

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.

Make Values Nullable

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.

Avoid using SettingsController#value

Here:

def value
if params[:cast_type] == Sail::ConstantCollection::BOOLEAN
params[:value] == Sail::ConstantCollection::ON
else
params[:value]
end
end

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

Fix build stability issues

Some specs flaky specs are breaking way too often in travis. They need to be adapted so that they don't break so regularly.

  1. spec/features/managing_profiles_feature_spec.rb:71 can't find profiles modal
  2. spec/features/quick_guide_feature_spec.rb:8 can't find quick guide
  3. spec/features/sorting_settings_feature_spec.rb:37 can't find sorting menu

Invalid SQL generated in index

  โ†ณ /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.

Propose new setting types

Description

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.

Request

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.

Create show action for settings

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.

Steps

  • Add show to the settings resources in routes.rb
  • Create show action in settings controller. It should use the name parameter to find the setting and return its value in a JSON like { "value": true }. It should only accept JSON format as well.
  • Don't forget to write the specs in settings_controller_spec.rb

Add a notification mechanism for setting updates

Context

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.

Request

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:

  • Pick which service they want in their Sail configure block (e.g.: config.notifier = { service: :slack, groups: %i[feature_flags] })
  • Add the gem responsible for the notifier service to their gemfile (e.g.: gem "slack-ruby-client"
  • Make the needed credentials to publish messages accessible to Sail

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.

Create cron cast type

It would be interesting to have a cron setting type. The definitions for this new type are as follows

Expected behavior

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.

Steps

  • Add cast type cron to the Setting class
  • In the Setting.get method, use the fugit gem to check if the saved cron matches DateTime.now.utc
  • Add before_save validation to check if string is a valid cron (validation should only run for cron cast_type settings)

Monitor mode button misplaced for Safari

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.

Deprecated syntax for implementing auth lambda fails in Ruby 3.x

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>'
  [...]

Unpermitted params

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 ๐Ÿ™‚ ๐Ÿคž๐Ÿผ

Add a monitor mode for the dashboard

Context

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.

Request

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.

Feedback

As always, feedback and thoughts on the new feature are much appreciated.

Add support for graphql

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:

  1. Figure out a way of defining all that is needed from within the engine
  2. Provide the types and add documentation on how to add them to the GraphQL endpoints so that developers know how to use it
  3. Create a generator that will install Sail types in the main application

Make Sail work with Rails API mode

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.

Create A/B test cast type

A/B test flags are useful for validating features live. Sail should support A/B testing out of the box.

Expected behavior

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.

Steps

  • Add ab_test to cast types in Setting class
  • In Setting.get, add logic to randomize true or false or always return false depending on setting value
  • Make sure ab_test settings are validated with value_is_true_or_false
  • In the _setting.html.erb partial, make sure ab_test types are displayed exactly like Booleans

Issues with sessions when using `redis_store` and custom session key names

Describe 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:

Zrzut ekranu 2022-04-28 o 10 00 48

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

config.middleware.use Rails.application.config.session_store || ActionDispatch::Session::CookieStore
to

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.

Option to make pagination more compact

Currently if you have lot of settings the pagination takes too much space.

Here is an example of the issue:

U9RTAEvA

Probably would be better to have the pagination in format < 1,2,3,4,5 ... > if the page number exceeds certain threshold.

Implement profiles

Context

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).

Feature

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.

Feedback and suggestions

Please feel free to provide feedback or suggestions as these are very helpful when designing new features (thumbs up or down included).

Redis cache store is unsupported

Describe the bug
ActionController::Base#expire_fragment allow to pass regex but it doesn't work with redis cache story

ActionController::Base.new.expire_fragment(/name: "#{setting_name}"/)

To Reproduce
Steps to reproduce the behavior:

  1. Add flag
  2. Use redis as rails cache store
  3. Read flag more than USAGES_UNTIL_CACHE_EXPIRE by using
Sail.get(flag_name)

Expected behavior
Exception shouldn't raise

Actual behavior
Exception ArgumentError: Only Redis glob strings are supported: /name: "flag_name"/ is raised

Add a quick reference guide to the dashboard

Context

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.

Request

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:

  • The list of all groups
  • The list of all cast types (not all available, but all the ones used in settings)
  • Information on what the labels mean
  • Information on how to use the search
  • Information on how to use profiles
  • Information on what the relevancy score is

Feedback

As always, feedback and thoughts on the new feature are much appreciated.

Rails 6.0 compatibility issues in migrations

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.