From 4c8181a8984414a7df41a5d53947344f16097b1a Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 10:57:10 +0700 Subject: [PATCH 01/34] init --- Gemfile.lock | 19 ++++++++++++++++++- lib/toggleable/base.rb | 6 +++++- lib/toggleable/configuration.rb | 1 + lib/toggleable/feature_toggler.rb | 9 +++++++++ lib/toggleable/version.rb | 2 +- toggleable.gemspec | 2 ++ 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 21c6269..10bf3d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,10 @@ PATH remote: . specs: - toggleable (0.1.5) + toggleable (0.1.6) activesupport (>= 4.0.0) + json + rest-client GEM remote: https://rubygems.org/ @@ -20,12 +22,20 @@ GEM concurrent-ruby (1.0.5) diff-lcs (1.3) docile (1.3.1) + domain_name (0.5.20180417) + unf (>= 0.0.5, < 1.0.0) dotenv (2.4.0) + http-cookie (1.0.3) + domain_name (~> 0.5) i18n (1.0.1) concurrent-ruby (~> 1.0) jaro_winkler (1.5.1) json (2.1.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) minitest (5.11.3) + netrc (0.11.0) parallel (1.12.1) parser (2.5.1.0) ast (~> 2.4.0) @@ -33,6 +43,10 @@ GEM rainbow (3.0.0) rake (10.5.0) redis (3.3.5) + rest-client (2.0.2) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) rspec (3.7.0) rspec-core (~> 3.7.0) rspec-expectations (~> 3.7.0) @@ -65,6 +79,9 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.5) unicode-display_width (1.4.0) url (0.3.2) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 96a1151..2d087a0 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -56,7 +56,11 @@ def toggle_key(value, actor) def toggle_active return @_toggle_active if defined?(@_toggle_active) && !read_expired? && Toggleable.configuration.use_memoization @_last_read_at = Time.now.localtime - @_toggle_active = Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) + @_toggle_active = if Toggleable.coniguration.toggle_fallback&.active? + Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) + else + Toggleable::FeatureToggler.instance.get_key(key) + end end def read_expired? diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index dda1678..089d708 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -8,5 +8,6 @@ class Configuration attr_accessor :namespace ## required for prefixing the keys. default: `toggleable`` attr_accessor :logger ## optional, it will not log if not configured. attr_accessor :use_memoization ## set true to use memoization. default: false + attr_accessor :toggle_fallback end end diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 72c9f14..c61001f 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require 'singleton' +require 'rest-client' +require 'json' module Toggleable # Toggleable::FeatureToggler provides an instance to manage all toggleable keys. @@ -17,6 +19,13 @@ def register(key) features << key end + def get_toggle(key) + resource = RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features/status?key=#{key}", + ENV['PALANCA_USERNAME'], ENV['PALANCA_PASSWORD']) + result = JSON.parse(resource.get) + result['data']['status'] + end + def available_features(memoize: Toggleable.configuration.use_memoization) available_features = memoize ? memoized_keys : keys available_features.slice(*features) diff --git a/lib/toggleable/version.rb b/lib/toggleable/version.rb index cbca12f..bb15e71 100644 --- a/lib/toggleable/version.rb +++ b/lib/toggleable/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Toggleable - VERSION = '0.1.5' + VERSION = '0.1.6' end diff --git a/toggleable.gemspec b/toggleable.gemspec index 8d70a87..a587e83 100644 --- a/toggleable.gemspec +++ b/toggleable.gemspec @@ -32,6 +32,8 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_runtime_dependency "activesupport", ">= 4.0.0" + spec.add_runtime_dependency "rest-client" + spec.add_runtime_dependency "json" spec.add_development_dependency "bundler", "~> 1.14" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "simplecov", ">= 0.16.1" From 8effa9933ccdbcd26a672575495e9dccdd347b42 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 12:58:28 +0700 Subject: [PATCH 02/34] fix typo --- lib/toggleable/base.rb | 2 +- lib/toggleable/feature_toggler.rb | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 2d087a0..c00bf22 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -56,7 +56,7 @@ def toggle_key(value, actor) def toggle_active return @_toggle_active if defined?(@_toggle_active) && !read_expired? && Toggleable.configuration.use_memoization @_last_read_at = Time.now.localtime - @_toggle_active = if Toggleable.coniguration.toggle_fallback&.active? + @_toggle_active = if Toggleable.configuration.toggle_fallback&.active? Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) else Toggleable::FeatureToggler.instance.get_key(key) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index c61001f..8b3d8c0 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -20,10 +20,13 @@ def register(key) end def get_toggle(key) - resource = RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features/status?key=#{key}", - ENV['PALANCA_USERNAME'], ENV['PALANCA_PASSWORD']) + resource = RestClient::Resource.new "#{ENV['PALANCA_HOST']}/_internal/toggle_features/status?key=#{key}", ENV['PALANCA_USERNAME'], ENV['PALANCA_PASSWORD'], + timeout: 60, + open_timeout: 2 result = JSON.parse(resource.get) result['data']['status'] + rescue + false end def available_features(memoize: Toggleable.configuration.use_memoization) From 0866963cdbb93177184f83312b06dfa0fbe314d0 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 13:04:55 +0700 Subject: [PATCH 03/34] another typo --- lib/toggleable/feature_toggler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 8b3d8c0..31aef8d 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -19,7 +19,7 @@ def register(key) features << key end - def get_toggle(key) + def get_key(key) resource = RestClient::Resource.new "#{ENV['PALANCA_HOST']}/_internal/toggle_features/status?key=#{key}", ENV['PALANCA_USERNAME'], ENV['PALANCA_PASSWORD'], timeout: 60, open_timeout: 2 From 275b6875acea5f7f42b3a5072500fa5bb99e211b Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 13:14:22 +0700 Subject: [PATCH 04/34] remove rescue to debug --- lib/toggleable/feature_toggler.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 31aef8d..136a991 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -25,8 +25,6 @@ def get_key(key) open_timeout: 2 result = JSON.parse(resource.get) result['data']['status'] - rescue - false end def available_features(memoize: Toggleable.configuration.use_memoization) From 72fb788ec56ea7709b7a51bc3a4db2581c6f9c38 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 15:46:43 +0700 Subject: [PATCH 05/34] add client for mass toggle --- lib/toggleable/feature_toggler.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 136a991..effcf02 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -20,10 +20,9 @@ def register(key) end def get_key(key) - resource = RestClient::Resource.new "#{ENV['PALANCA_HOST']}/_internal/toggle_features/status?key=#{key}", ENV['PALANCA_USERNAME'], ENV['PALANCA_PASSWORD'], - timeout: 60, - open_timeout: 2 - result = JSON.parse(resource.get) + resource = RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features?key=#{key}", + ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + result = resource.get timeout: 5, open_timeout: 1 result['data']['status'] end @@ -34,7 +33,15 @@ def available_features(memoize: Toggleable.configuration.use_memoization) def mass_toggle!(mapping, actor: nil) log_changes(mapping, actor) if Toggleable.configuration.logger - Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) + if Toggleable.configuration.toggle_fallback&.active? + Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) + else + mappings[:actor] = actor + + @resource ||= RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections", + ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + resource.put mappings.to_json, timeout: 5, open_timeout: 1 + end end private From 54c1d5f70b743cbc5473bfb411e374856895610c Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 16:17:33 +0700 Subject: [PATCH 06/34] missed json parse --- lib/toggleable/feature_toggler.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index effcf02..5e278a3 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -23,6 +23,7 @@ def get_key(key) resource = RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features?key=#{key}", ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) result = resource.get timeout: 5, open_timeout: 1 + result = ::JSON.parse(result) result['data']['status'] end From f58ad47bb81dcc9e15a355b9716433e3cf055fe8 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 16:28:03 +0700 Subject: [PATCH 07/34] typo --- lib/toggleable/feature_toggler.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 5e278a3..ae341d2 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -37,11 +37,11 @@ def mass_toggle!(mapping, actor: nil) if Toggleable.configuration.toggle_fallback&.active? Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) else - mappings[:actor] = actor + mapping[:actor] = actor @resource ||= RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections", ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) - resource.put mappings.to_json, timeout: 5, open_timeout: 1 + resource.put mapping.to_json, timeout: 5, open_timeout: 1 end end From 0cc1d915689ebfe4aa0491ace0a6c4a164e82d33 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 13 Jul 2018 16:59:07 +0700 Subject: [PATCH 08/34] fix mass toggle --- lib/toggleable/feature_toggler.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index ae341d2..22445bf 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -37,11 +37,11 @@ def mass_toggle!(mapping, actor: nil) if Toggleable.configuration.toggle_fallback&.active? Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) else - mapping[:actor] = actor + payload = { mappings: mapping, actor: actor }.to_json @resource ||= RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections", ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) - resource.put mapping.to_json, timeout: 5, open_timeout: 1 + @resource.put payload, timeout: 5, open_timeout: 1 end end From b0130d6ceb28cb519f0e15bc82287b63831491d1 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Tue, 17 Jul 2018 18:28:51 +0700 Subject: [PATCH 09/34] retry attempt toggleable --- lib/toggleable/feature_toggler.rb | 56 +++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 22445bf..2bd75ed 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -9,6 +9,8 @@ module Toggleable class FeatureToggler include Singleton + MAX_ATTEMPT = 3 + attr_reader :features def initialize @@ -20,11 +22,26 @@ def register(key) end def get_key(key) - resource = RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features?key=#{key}", - ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) - result = resource.get timeout: 5, open_timeout: 1 - result = ::JSON.parse(result) - result['data']['status'] + response = '' + attempt = 1 + url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features?key=#{key}" + resource = RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + + while response.empty? + begin + response = resource.get timeout: 5, open_timeout: 1 + response = ::JSON.parse(response) + result = response['data']['status'] + rescue StandardError => _e + if attempt >= MAX_ATTEMPT + Toggleable.configuration.logger.error(message: "GET #{key} TIMEOUT") + result = false + break + end + attempt += 1 + end + end + result end def available_features(memoize: Toggleable.configuration.use_memoization) @@ -33,16 +50,27 @@ def available_features(memoize: Toggleable.configuration.use_memoization) end def mass_toggle!(mapping, actor: nil) - log_changes(mapping, actor) if Toggleable.configuration.logger - if Toggleable.configuration.toggle_fallback&.active? - Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) - else - payload = { mappings: mapping, actor: actor }.to_json - - @resource ||= RestClient::Resource.new("#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections", - ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) - @resource.put payload, timeout: 5, open_timeout: 1 + Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) + return if Toggleable.configuration.toggle_fallback&.active? + + response = '' + attempt = 1 + url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections" + payload = { mappings: mapping, actor: actor }.to_json + @resource ||= RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + + while response.empty? + begin + response = @resource.put payload, timeout: 5, open_timeout: 1 + rescue StandardError => e + if attempt >= MAX_ATTEMPT + Toggleable.configuration.logger.error(message: 'MASS TOGGLE TIMEOUT') + raise e + end + attempt += 1 + end end + log_changes(mapping, actor) if Toggleable.configuration.logger end private From 4413d072aadc3ca57bab6ab0d3ae764af92ab49d Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Wed, 18 Jul 2018 11:34:26 +0700 Subject: [PATCH 10/34] add toggle key --- lib/toggleable/base.rb | 9 +++++---- lib/toggleable/configuration.rb | 2 +- lib/toggleable/feature_toggler.rb | 26 +++++++++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index c00bf22..8f7096e 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -49,17 +49,18 @@ def process private def toggle_key(value, actor) - Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) + Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) + Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) if Toggleable.configuration.toggle_client&.active? end def toggle_active return @_toggle_active if defined?(@_toggle_active) && !read_expired? && Toggleable.configuration.use_memoization @_last_read_at = Time.now.localtime - @_toggle_active = if Toggleable.configuration.toggle_fallback&.active? - Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) - else + @_toggle_active = if Toggleable.configuration.toggle_client&.active? Toggleable::FeatureToggler.instance.get_key(key) + else + Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) end end diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index 089d708..339aea5 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -8,6 +8,6 @@ class Configuration attr_accessor :namespace ## required for prefixing the keys. default: `toggleable`` attr_accessor :logger ## optional, it will not log if not configured. attr_accessor :use_memoization ## set true to use memoization. default: false - attr_accessor :toggle_fallback + attr_accessor :toggle_client end end diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 2bd75ed..86f6a25 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -44,19 +44,40 @@ def get_key(key) result end + def toggle_key(key, value, actor) + response = '' + attempt = 1 + url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features" + payload = { key: key, status: value, user_id: actor }.to_json + @resource ||= RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + + while response.empty? + begin + response = @resource.put payload, timeout: 5, open_timeout: 1 + rescue StandardError => e + if attempt >= MAX_ATTEMPT + Toggleable.configuration.logger.error(message: "TOGGLE #{key} TIMEOUT") + raise e + end + attempt += 1 + end + end + end + def available_features(memoize: Toggleable.configuration.use_memoization) available_features = memoize ? memoized_keys : keys available_features.slice(*features) end def mass_toggle!(mapping, actor: nil) + log_changes(mapping, actor) if Toggleable.configuration.logger Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) - return if Toggleable.configuration.toggle_fallback&.active? + return unless Toggleable.configuration.toggle_client&.active? response = '' attempt = 1 url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections" - payload = { mappings: mapping, actor: actor }.to_json + payload = { mappings: mapping, user_id: actor }.to_json @resource ||= RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) while response.empty? @@ -70,7 +91,6 @@ def mass_toggle!(mapping, actor: nil) attempt += 1 end end - log_changes(mapping, actor) if Toggleable.configuration.logger end private From d8619cdfcba8967befd5e52bbbe832a49a345548 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Wed, 18 Jul 2018 12:46:23 +0700 Subject: [PATCH 11/34] add memoization to get key --- lib/toggleable/feature_toggler.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 86f6a25..763d910 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -22,6 +22,10 @@ def register(key) end def get_key(key) + @_toggle_active ||= {} + return @_toggle_active[key] if !@_toggle_active[key].nil? && !read_key_expired?(key) + + @_last_key_read_at[key] = Time.now.localtime response = '' attempt = 1 url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features?key=#{key}" @@ -31,17 +35,17 @@ def get_key(key) begin response = resource.get timeout: 5, open_timeout: 1 response = ::JSON.parse(response) - result = response['data']['status'] + @_toggle_active[key] = response['data']['status'] rescue StandardError => _e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: "GET #{key} TIMEOUT") - result = false + @_toggle_active[key] = false break end attempt += 1 end end - result + @_toggle_active[key] end def toggle_key(key, value, actor) @@ -100,15 +104,20 @@ def keys end def memoized_keys - return @_memoized_keys if defined?(@_memoized_keys) && !read_expired? + return @_memoized_keys if defined?(@_memoized_keys) && !read_all_keys_expired? @_last_read_at = Time.now.localtime @_memoized_keys = Toggleable.configuration.storage.get_all(namespace: Toggleable.configuration.namespace) end - def read_expired? + def read_all_keys_expired? @_last_read_at < Time.now.localtime - Toggleable.configuration.expiration_time end + def read_key_expired?(key) + @_last_key_read_at ||= {} + @_last_key_read_at[key] < Time.now.localtime - Toggleable.configuration.expiration_time + end + def log_changes(mapping, actor) previous_values = available_features mapping.each do |key, val| From 9c6887d2e959b7e8271e5c840691880807b89367 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Wed, 18 Jul 2018 14:30:41 +0700 Subject: [PATCH 12/34] safe constantizing toggle class --- lib/toggleable/base.rb | 4 ++-- lib/toggleable/feature_toggler.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 8f7096e..b53a4b7 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -51,13 +51,13 @@ def process def toggle_key(value, actor) Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) - Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) if Toggleable.configuration.toggle_client&.active? + Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) if Toggleable.configuration.toggle_client&.safe_constantize&.active? end def toggle_active return @_toggle_active if defined?(@_toggle_active) && !read_expired? && Toggleable.configuration.use_memoization @_last_read_at = Time.now.localtime - @_toggle_active = if Toggleable.configuration.toggle_client&.active? + @_toggle_active = if Toggleable.configuration.toggle_client&.safe_constantize&.active? Toggleable::FeatureToggler.instance.get_key(key) else Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 763d910..2021b66 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -2,6 +2,7 @@ require 'singleton' require 'rest-client' +require 'active_support/inflector' require 'json' module Toggleable @@ -76,7 +77,7 @@ def available_features(memoize: Toggleable.configuration.use_memoization) def mass_toggle!(mapping, actor: nil) log_changes(mapping, actor) if Toggleable.configuration.logger Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) - return unless Toggleable.configuration.toggle_client&.active? + return unless Toggleable.configuration.toggle_client&.safe_constantize&.active? response = '' attempt = 1 From bdb857ccfee7aabaa7e8ad32dcf3b01d598e8bea Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Wed, 18 Jul 2018 19:07:06 +0700 Subject: [PATCH 13/34] bug --- lib/toggleable/feature_toggler.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 2021b66..f2704c1 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -24,6 +24,7 @@ def register(key) def get_key(key) @_toggle_active ||= {} + @_last_key_read_at ||= {} return @_toggle_active[key] if !@_toggle_active[key].nil? && !read_key_expired?(key) @_last_key_read_at[key] = Time.now.localtime @@ -59,6 +60,7 @@ def toggle_key(key, value, actor) while response.empty? begin response = @resource.put payload, timeout: 5, open_timeout: 1 + Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) rescue StandardError => e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: "TOGGLE #{key} TIMEOUT") @@ -115,7 +117,6 @@ def read_all_keys_expired? end def read_key_expired?(key) - @_last_key_read_at ||= {} @_last_key_read_at[key] < Time.now.localtime - Toggleable.configuration.expiration_time end From aeaf17e67da513ccd6df8c380dd2d03e4b76520e Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Thu, 19 Jul 2018 15:46:51 +0700 Subject: [PATCH 14/34] remove conditions, minus spec --- lib/toggleable/base.rb | 9 ++------- lib/toggleable/configuration.rb | 1 - lib/toggleable/feature_toggler.rb | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index b53a4b7..0adb675 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -49,19 +49,14 @@ def process private def toggle_key(value, actor) - Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) + Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) - Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) if Toggleable.configuration.toggle_client&.safe_constantize&.active? end def toggle_active return @_toggle_active if defined?(@_toggle_active) && !read_expired? && Toggleable.configuration.use_memoization @_last_read_at = Time.now.localtime - @_toggle_active = if Toggleable.configuration.toggle_client&.safe_constantize&.active? - Toggleable::FeatureToggler.instance.get_key(key) - else - Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) - end + @_toggle_active = Toggleable::FeatureToggler.instance.get_key(key) end def read_expired? diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index 339aea5..dda1678 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -8,6 +8,5 @@ class Configuration attr_accessor :namespace ## required for prefixing the keys. default: `toggleable`` attr_accessor :logger ## optional, it will not log if not configured. attr_accessor :use_memoization ## set true to use memoization. default: false - attr_accessor :toggle_client end end diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index f2704c1..ad82b32 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -78,8 +78,6 @@ def available_features(memoize: Toggleable.configuration.use_memoization) def mass_toggle!(mapping, actor: nil) log_changes(mapping, actor) if Toggleable.configuration.logger - Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) - return unless Toggleable.configuration.toggle_client&.safe_constantize&.active? response = '' attempt = 1 From d43a1dfe1fbcfcb6a0d1f86a677d17786c4d5fad Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Thu, 19 Jul 2018 20:14:58 +0700 Subject: [PATCH 15/34] remove unused callback --- lib/toggleable/base.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 0adb675..5ca4617 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -18,7 +18,8 @@ module Base # it will generate these methods into included class. module ClassMethods def active? - return to_bool(toggle_active.to_s) unless toggle_active.nil? + toggle_status = toggle_active + return to_bool(toggle_status.to_s) unless toggle_status.nil? # Lazily register the key Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) From cd37d7c63d1853ad9e0be918d493ce825991a2d3 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 20 Jul 2018 13:26:28 +0700 Subject: [PATCH 16/34] replace env with configuration --- Gemfile.lock | 14 ++++++++++++- lib/toggleable/configuration.rb | 3 +++ lib/toggleable/feature_toggler.rb | 13 ++++++------ lib/toggleable/version.rb | 2 +- spec/class_initializer.rb | 2 ++ spec/spec_helper.rb | 1 + spec/toggleable/feature_toggler_spec.rb | 14 +++++++------ spec/toggleable/toggleable_spec.rb | 27 ++++++++++++------------- toggleable.gemspec | 1 + 9 files changed, 48 insertions(+), 29 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 10bf3d6..36d3046 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - toggleable (0.1.6) + toggleable (0.1.7) activesupport (>= 4.0.0) json rest-client @@ -14,17 +14,22 @@ GEM i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) ast (2.4.0) codecov (0.1.10) json simplecov url concurrent-ruby (1.0.5) + crack (0.4.3) + safe_yaml (~> 1.0.0) diff-lcs (1.3) docile (1.3.1) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) dotenv (2.4.0) + hashdiff (0.3.7) http-cookie (1.0.3) domain_name (~> 0.5) i18n (1.0.1) @@ -40,6 +45,7 @@ GEM parser (2.5.1.0) ast (~> 2.4.0) powerpack (0.1.2) + public_suffix (3.0.2) rainbow (3.0.0) rake (10.5.0) redis (3.3.5) @@ -71,6 +77,7 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) + safe_yaml (1.0.4) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) @@ -84,6 +91,10 @@ GEM unf_ext (0.0.7.5) unicode-display_width (1.4.0) url (0.3.2) + webmock (3.4.2) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff PLATFORMS ruby @@ -99,6 +110,7 @@ DEPENDENCIES rubocop simplecov (>= 0.16.1) toggleable! + webmock BUNDLED WITH 1.16.1 diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index dda1678..e885b70 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -4,6 +4,9 @@ module Toggleable # Toggleable::Configuration yields the configuration of toggleable. class Configuration attr_accessor :expiration_time ## expiration time for memoization. default: 5 minutes + attr_accessor :palanca_host + attr_accessor :palanca_user + attr_accessor :palanca_password attr_accessor :storage ## storage used. default: memory store attr_accessor :namespace ## required for prefixing the keys. default: `toggleable`` attr_accessor :logger ## optional, it will not log if not configured. diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index ad82b32..cc3f558 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -30,8 +30,8 @@ def get_key(key) @_last_key_read_at[key] = Time.now.localtime response = '' attempt = 1 - url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features?key=#{key}" - resource = RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle_features?key=#{key}" + resource = RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin @@ -53,9 +53,9 @@ def get_key(key) def toggle_key(key, value, actor) response = '' attempt = 1 - url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle_features" payload = { key: key, status: value, user_id: actor }.to_json - @resource ||= RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) + @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin @@ -81,10 +81,9 @@ def mass_toggle!(mapping, actor: nil) response = '' attempt = 1 - url = "#{ENV['PALANCA_HOST']}/_internal/toggle_features/collections" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle_features/collections" payload = { mappings: mapping, user_id: actor }.to_json - @resource ||= RestClient::Resource.new(url, ENV['PALANCA_BASIC_USER'], ENV['PALANCA_BASIC_PASSWORD']) - + @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin response = @resource.put payload, timeout: 5, open_timeout: 1 diff --git a/lib/toggleable/version.rb b/lib/toggleable/version.rb index bb15e71..f390d53 100644 --- a/lib/toggleable/version.rb +++ b/lib/toggleable/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Toggleable - VERSION = '0.1.6' + VERSION = '0.1.7' end diff --git a/spec/class_initializer.rb b/spec/class_initializer.rb index 821db76..4804655 100644 --- a/spec/class_initializer.rb +++ b/spec/class_initializer.rb @@ -24,4 +24,6 @@ def log(key:, value:, actor:) Toggleable.configure do |t| t.logger = logger + t.palanca_host = 'localhost:8027' + t.use_memoization = false end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e05de23..1d430d5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,4 +22,5 @@ SimpleCov::Formatter::HTMLFormatter end +require 'webmock/rspec' require 'class_initializer' diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index 2de57fa..39a88e1 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -28,8 +28,8 @@ } before do - allow(subject).to receive(:keys).and_return(keys) allow(subject).to receive(:features).and_return(['active_key', 'inactive_key']) + allow(subject).to receive(:keys).and_return(keys) end it { expect(subject.available_features).to eq({ 'active_key' => 'true', 'inactive_key' => 'false' }) } @@ -52,8 +52,8 @@ } before do - Toggleable.configuration.storage.mass_set(keys, namespace: Toggleable.configuration.namespace) allow(subject).to receive(:features).and_return(['active_key', 'inactive_key']) + Toggleable.configuration.storage.mass_set(keys, namespace: Toggleable.configuration.namespace) end it do @@ -64,6 +64,10 @@ end end + before do + stub_request(:put, "http://localhost:8027/_internal/toggle_features/collections").to_return(status: 200, body: 'success') + end + describe '#mass_toggle! with memory store' do let(:mapping_before) { { @@ -84,13 +88,12 @@ before do subject.register('key') subject.register('other_key') - subject.mass_toggle!(mapping_before, actor: actor_id) + allow(subject).to receive(:available_features).and_return(mapping_before) end it do expect(Toggleable.configuration.logger).to receive(:log).with(key: 'other_key', value: 'true', actor: actor_id).and_return(true) subject.mass_toggle!(mapping_after, actor: actor_id) - expect(subject.available_features).to include(mapping_after) end end @@ -118,13 +121,12 @@ before do subject.register('key') subject.register('other_key') - subject.mass_toggle!(mapping_before, actor: actor_id) + allow(subject).to receive(:available_features).and_return(mapping_before) end it do expect(Toggleable.configuration.logger).to receive(:log).with(key: 'other_key', value: 'true', actor: actor_id).and_return(true) subject.mass_toggle!(mapping_after, actor: actor_id) - expect(subject.available_features).to include(mapping_after) end end end diff --git a/spec/toggleable/toggleable_spec.rb b/spec/toggleable/toggleable_spec.rb index 5eaa39a..85637a8 100644 --- a/spec/toggleable/toggleable_spec.rb +++ b/spec/toggleable/toggleable_spec.rb @@ -21,15 +21,15 @@ class SampleFeature it { is_expected.to respond_to(:key) } it { is_expected.to respond_to(:description) } + before(:each) do + allow(Toggleable::FeatureToggler.instance).to receive(:toggle_key).and_return(true) + end + describe 'active? before key exist should create the key also' do context 'with memory store' do - it { expect(subject.active?).to be_falsy } - end - - context 'with redis store' do before do allow(subject).to receive(:toggle_active).and_return(nil) - allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) + allow(Toggleable::FeatureToggler.instance).to receive(:get_key).and_return(false) end it { expect(subject.active?).to be_falsy } @@ -53,16 +53,8 @@ def description let(:actor_id) { 1 } context 'activation' do - it do - expect(Toggleable.configuration.logger).to receive(:log).with(key: SampleFeature.key, value: true, actor: actor_id).and_return(true) - subject.activate!(actor: actor_id) - expect(subject.active?).to be_truthy - end - end - - context 'activation with redis' do before do - allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) + allow(Toggleable::FeatureToggler.instance).to receive(:get_key).and_return(true) end it do @@ -73,6 +65,10 @@ def description end context 'deactivation' do + before do + allow(Toggleable::FeatureToggler.instance).to receive(:get_key).and_return(false) + end + it do expect(Toggleable.configuration.logger).to receive(:log).with(key: SampleFeature.key, value: false, actor: actor_id).and_return(true) subject.deactivate!(actor: actor_id) @@ -82,6 +78,7 @@ def description context 'deactivation without namespace' do before do + allow(Toggleable::FeatureToggler.instance).to receive(:get_key).and_return(false) allow(Toggleable.configuration).to receive(:namespace).and_return(nil) end @@ -95,6 +92,7 @@ def description context 'processing class when inactive will do nothing' do before do subject.deactivate! + allow(Toggleable::FeatureToggler.instance).to receive(:get_key).and_return(false) end it do @@ -107,6 +105,7 @@ def description context 'processing class when active' do before do + allow(Toggleable::FeatureToggler.instance).to receive(:get_key).and_return(false) subject.activate! end diff --git a/toggleable.gemspec b/toggleable.gemspec index a587e83..bd75d3a 100644 --- a/toggleable.gemspec +++ b/toggleable.gemspec @@ -40,6 +40,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "redis", "~> 3.0" spec.add_development_dependency "dotenv", ">= 2.4.0" spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "webmock" spec.add_development_dependency "rspec_junit_formatter" spec.add_development_dependency "rubocop" spec.add_development_dependency "codecov" From 092162ae3982436c6b99f7f36c73ba6a1116a3e1 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 20 Jul 2018 21:14:51 +0700 Subject: [PATCH 17/34] minus spec --- lib/toggleable/base.rb | 8 +------- spec/spec_helper.rb | 7 +------ spec/toggleable/toggleable_spec.rb | 10 ---------- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 5ca4617..3110693 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -19,7 +19,7 @@ module Base module ClassMethods def active? toggle_status = toggle_active - return to_bool(toggle_status.to_s) unless toggle_status.nil? + return toggle_status.to_s == 'true' unless toggle_status.nil? # Lazily register the key Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) @@ -63,12 +63,6 @@ def toggle_active def read_expired? @_last_read_at < Time.now.localtime - Toggleable.configuration.expiration_time end - - def to_bool(value) - return true if value =~ /^(true|t|yes|y|1)$/i - return false if value.empty? || value =~ /^(false|f|no|n|0)$/i - raise ArgumentError, "invalid value for Boolean: \"#{value}\"" - end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1d430d5..f6538ee 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,12 +5,7 @@ SimpleCov.start do add_filter '/spec/' -end - -require 'codecov' - -SimpleCov.start do - add_filter '/spec/' + add_filter '/lib/toggleable/storage' end require 'codecov' diff --git a/spec/toggleable/toggleable_spec.rb b/spec/toggleable/toggleable_spec.rb index 85637a8..d8af8c6 100644 --- a/spec/toggleable/toggleable_spec.rb +++ b/spec/toggleable/toggleable_spec.rb @@ -116,15 +116,5 @@ def description expect(subject.active?).to be_falsy end end - - context 'wrong argument type for to bool' do - let(:wrong_args) { 'wrong args' } - - before do - allow(subject).to receive(:toggle_active).and_return(wrong_args) - end - - it { expect { subject.active? }.to raise_error(ArgumentError) } - end end end From d59a179ad1cb920e4d7c1f2587d07a9cdcc465fb Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Sun, 22 Jul 2018 18:42:21 +0700 Subject: [PATCH 18/34] change routes --- lib/toggleable/feature_toggler.rb | 6 +++--- spec/toggleable/feature_toggler_spec.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index cc3f558..39a1e35 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -30,7 +30,7 @@ def get_key(key) @_last_key_read_at[key] = Time.now.localtime response = '' attempt = 1 - url = "#{Toggleable.configuration.palanca_host}/_internal/toggle_features?key=#{key}" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features?key=#{key}" resource = RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? @@ -53,7 +53,7 @@ def get_key(key) def toggle_key(key, value, actor) response = '' attempt = 1 - url = "#{Toggleable.configuration.palanca_host}/_internal/toggle_features" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features" payload = { key: key, status: value, user_id: actor }.to_json @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) @@ -81,7 +81,7 @@ def mass_toggle!(mapping, actor: nil) response = '' attempt = 1 - url = "#{Toggleable.configuration.palanca_host}/_internal/toggle_features/collections" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features/collections" payload = { mappings: mapping, user_id: actor }.to_json @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index 39a88e1..cdf48c9 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -65,7 +65,7 @@ end before do - stub_request(:put, "http://localhost:8027/_internal/toggle_features/collections").to_return(status: 200, body: 'success') + stub_request(:put, "http://localhost:8027/_internal/toggle-features/collections").to_return(status: 200, body: 'success') end describe '#mass_toggle! with memory store' do From 30e63a0e04424d5ca85732d8907e856c275ea0c7 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Mon, 23 Jul 2018 11:57:29 +0700 Subject: [PATCH 19/34] update timeout and gemfile restclient --- Gemfile.lock | 12 +++++------- lib/toggleable/feature_toggler.rb | 6 +++--- toggleable.gemspec | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 36d3046..80b3c45 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,7 +4,7 @@ PATH toggleable (0.1.7) activesupport (>= 4.0.0) json - rest-client + rest-client (= 1.8.0) GEM remote: https://rubygems.org/ @@ -36,9 +36,7 @@ GEM concurrent-ruby (~> 1.0) jaro_winkler (1.5.1) json (2.1.0) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) + mime-types (2.99.3) minitest (5.11.3) netrc (0.11.0) parallel (1.12.1) @@ -49,10 +47,10 @@ GEM rainbow (3.0.0) rake (10.5.0) redis (3.3.5) - rest-client (2.0.2) + rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rspec (3.7.0) rspec-core (~> 3.7.0) rspec-expectations (~> 3.7.0) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 39a1e35..bc0bbd7 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -35,7 +35,7 @@ def get_key(key) while response.empty? begin - response = resource.get timeout: 5, open_timeout: 1 + response = resource.get timeout: 2, open_timeout: 1 response = ::JSON.parse(response) @_toggle_active[key] = response['data']['status'] rescue StandardError => _e @@ -59,7 +59,7 @@ def toggle_key(key, value, actor) while response.empty? begin - response = @resource.put payload, timeout: 5, open_timeout: 1 + response = @resource.put payload, timeout: 2, open_timeout: 1 Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) rescue StandardError => e if attempt >= MAX_ATTEMPT @@ -86,7 +86,7 @@ def mass_toggle!(mapping, actor: nil) @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin - response = @resource.put payload, timeout: 5, open_timeout: 1 + response = @resource.put payload, timeout: 2, open_timeout: 1 rescue StandardError => e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: 'MASS TOGGLE TIMEOUT') diff --git a/toggleable.gemspec b/toggleable.gemspec index bd75d3a..e78624e 100644 --- a/toggleable.gemspec +++ b/toggleable.gemspec @@ -32,7 +32,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_runtime_dependency "activesupport", ">= 4.0.0" - spec.add_runtime_dependency "rest-client" + spec.add_runtime_dependency "rest-client", "1.8.0" spec.add_runtime_dependency "json" spec.add_development_dependency "bundler", "~> 1.14" spec.add_development_dependency "rake", "~> 10.0" From 388471bec553cae1ba45485ad749d8fd09e6d2d1 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Mon, 23 Jul 2018 18:05:14 +0700 Subject: [PATCH 20/34] fix url and add spec --- lib/toggleable/feature_toggler.rb | 2 +- spec/toggleable/feature_toggler_spec.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index bc0bbd7..3804e91 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -30,7 +30,7 @@ def get_key(key) @_last_key_read_at[key] = Time.now.localtime response = '' attempt = 1 - url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features?key=#{key}" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features?feature=#{key}" resource = RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index cdf48c9..d1763f0 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -64,6 +64,21 @@ end end + describe 'get key' do + let(:key) { 'sample_key' } + let(:data) { { status: true } } + let(:response) { { data: data }.to_json } + + context 'successful' do + before do + stub_request(:get, "http://localhost:8027/_internal/toggle-features?feature=#{key}") + .to_return(status: 200, body: response) + end + + it { expect(subject.get_key(key)).to be_truthy } + end + end + before do stub_request(:put, "http://localhost:8027/_internal/toggle-features/collections").to_return(status: 200, body: 'success') end From 6e0189e6a450ff46e0f5e2fd177827a1aa81e3c0 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Fri, 27 Jul 2018 19:32:46 +0700 Subject: [PATCH 21/34] standardize for mothership --- lib/toggleable/base.rb | 1 + lib/toggleable/feature_toggler.rb | 7 +++++-- lib/toggleable/storage/memory_store.rb | 10 +++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 3110693..a4536cf 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -50,6 +50,7 @@ def process private def toggle_key(value, actor) + Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) end diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 3804e91..bdf02e9 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -41,8 +41,7 @@ def get_key(key) rescue StandardError => _e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: "GET #{key} TIMEOUT") - @_toggle_active[key] = false - break + raise e end attempt += 1 end @@ -78,7 +77,11 @@ def available_features(memoize: Toggleable.configuration.use_memoization) def mass_toggle!(mapping, actor: nil) log_changes(mapping, actor) if Toggleable.configuration.logger + Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) + mass_set_palanca!(mapping, actor: actor) + end + def mass_set_palanca!(mapping, actor: nil) response = '' attempt = 1 url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features/collections" diff --git a/lib/toggleable/storage/memory_store.rb b/lib/toggleable/storage/memory_store.rb index 6b55bc4..e7daae7 100644 --- a/lib/toggleable/storage/memory_store.rb +++ b/lib/toggleable/storage/memory_store.rb @@ -8,25 +8,25 @@ class MemoryStore < ActiveSupport::Cache::MemoryStore ## the self you provide must implement these methods ## namespace parameter is optional, only if you provide namespace configuration - def get(key, namespace:) + def get(key, namespace: nil) read(key, namespace: namespace) end - def get_all(namespace:) + def get_all(namespace: nil) read_multi(*keys, namespace: namespace) end - def set(key, value, namespace:) + def set(key, value, namespace: nil) write(key, value, namespace: namespace) end - def set_if_not_exist(key, value, namespace:) + def set_if_not_exist(key, value, namespace: nil) fetch(key, namespace: namespace) do value end end - def mass_set(mappings, namespace:) + def mass_set(mappings, namespace: nil) write_multi(mappings, namespace: namespace) end From 888cae81da4df9252d196b0e200dfb65152c11c0 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Tue, 31 Jul 2018 22:59:29 +0700 Subject: [PATCH 22/34] fix endpoint and lazy update to palanca --- lib/toggleable/base.rb | 3 ++- lib/toggleable/feature_toggler.rb | 6 +++--- spec/toggleable/feature_toggler_spec.rb | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index a4536cf..ea37a6d 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -23,6 +23,7 @@ def active? # Lazily register the key Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) + Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE) DEFAULT_VALUE end @@ -51,7 +52,7 @@ def process def toggle_key(value, actor) Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) - Toggleable::FeatureToggler.instance.toggle_key(key, value, actor) + Toggleable::FeatureToggler.instance.toggle_key(key, value, actor: actor) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) end diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index bdf02e9..9032d57 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -49,7 +49,7 @@ def get_key(key) @_toggle_active[key] end - def toggle_key(key, value, actor) + def toggle_key(key, value, actor: nil) response = '' attempt = 1 url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features" @@ -84,12 +84,12 @@ def mass_toggle!(mapping, actor: nil) def mass_set_palanca!(mapping, actor: nil) response = '' attempt = 1 - url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features/collections" + url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features/bulk-update" payload = { mappings: mapping, user_id: actor }.to_json @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin - response = @resource.put payload, timeout: 2, open_timeout: 1 + response = @resource.post payload, timeout: 2, open_timeout: 1 rescue StandardError => e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: 'MASS TOGGLE TIMEOUT') diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index d1763f0..bb2e2c6 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -80,7 +80,7 @@ end before do - stub_request(:put, "http://localhost:8027/_internal/toggle-features/collections").to_return(status: 200, body: 'success') + stub_request(:post, "http://localhost:8027/_internal/toggle-features/bulk-update").to_return(status: 200, body: 'success') end describe '#mass_toggle! with memory store' do From eab1a828dfcaffb707e855ae58dfc26e97d86510 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Wed, 1 Aug 2018 00:05:12 +0700 Subject: [PATCH 23/34] fix bug --- lib/toggleable/base.rb | 2 +- lib/toggleable/feature_toggler.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index ea37a6d..b1567c0 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -59,7 +59,7 @@ def toggle_key(value, actor) def toggle_active return @_toggle_active if defined?(@_toggle_active) && !read_expired? && Toggleable.configuration.use_memoization @_last_read_at = Time.now.localtime - @_toggle_active = Toggleable::FeatureToggler.instance.get_key(key) + @_toggle_active = Toggleable.configuration.storage.get(key, namespace: Toggleable.configuration.namespace) end def read_expired? diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 9032d57..3148b3f 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -38,7 +38,7 @@ def get_key(key) response = resource.get timeout: 2, open_timeout: 1 response = ::JSON.parse(response) @_toggle_active[key] = response['data']['status'] - rescue StandardError => _e + rescue StandardError => e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: "GET #{key} TIMEOUT") raise e From 5d4bef727dfdd894efa148e948ac1f8641648289 Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Thu, 2 Aug 2018 14:32:13 +0700 Subject: [PATCH 24/34] remove memoization --- lib/toggleable/feature_toggler.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 3148b3f..341b5e0 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -54,11 +54,11 @@ def toggle_key(key, value, actor: nil) attempt = 1 url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features" payload = { key: key, status: value, user_id: actor }.to_json - @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) + resource = RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin - response = @resource.put payload, timeout: 2, open_timeout: 1 + response = resource.put payload, timeout: 2, open_timeout: 1 Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) rescue StandardError => e if attempt >= MAX_ATTEMPT @@ -86,10 +86,10 @@ def mass_set_palanca!(mapping, actor: nil) attempt = 1 url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features/bulk-update" payload = { mappings: mapping, user_id: actor }.to_json - @resource ||= RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) + resource = RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? begin - response = @resource.post payload, timeout: 2, open_timeout: 1 + response = resource.post payload, timeout: 2, open_timeout: 1 rescue StandardError => e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: 'MASS TOGGLE TIMEOUT') From f440c4ad49ff3b50b26c87957404744b3d6d4dbd Mon Sep 17 00:00:00 2001 From: Budi Pangestu Date: Thu, 2 Aug 2018 14:34:14 +0700 Subject: [PATCH 25/34] fix another bug --- lib/toggleable/feature_toggler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 341b5e0..7b09131 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -53,7 +53,7 @@ def toggle_key(key, value, actor: nil) response = '' attempt = 1 url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features" - payload = { key: key, status: value, user_id: actor }.to_json + payload = { feature: key, status: value, user_id: actor }.to_json resource = RestClient::Resource.new(url, Toggleable.configuration.palanca_user, Toggleable.configuration.palanca_password) while response.empty? From 988bd61c415833e6d04d73270393aa50b7ea2ec5 Mon Sep 17 00:00:00 2001 From: budi Date: Mon, 27 Aug 2018 20:14:53 +0700 Subject: [PATCH 26/34] add notify --- lib/toggleable/configuration.rb | 1 + lib/toggleable/feature_toggler.rb | 17 ++++++++++++--- spec/class_initializer.rb | 1 + spec/toggleable/feature_toggler_spec.rb | 29 ++++++------------------- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index e885b70..fbdef4f 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -7,6 +7,7 @@ class Configuration attr_accessor :palanca_host attr_accessor :palanca_user attr_accessor :palanca_password + attr_accessor :notify_endpoint ## optional for notify changes attr_accessor :storage ## storage used. default: memory store attr_accessor :namespace ## required for prefixing the keys. default: `toggleable`` attr_accessor :logger ## optional, it will not log if not configured. diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 7b09131..a252cc0 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: true +# frozen_string_literal: false require 'singleton' require 'rest-client' @@ -121,11 +121,22 @@ def read_key_expired?(key) end def log_changes(mapping, actor) - previous_values = available_features + toggles = '' + values = '' mapping.each do |key, val| - next if previous_values[key].to_s == val.to_s Toggleable.configuration.logger.log(key: key, value: val, actor: actor) + toggles.concat("#{key},") + values.concat("#{val},") end + + notify_changes(toggles[0...-1], values[0...-1]) if Toggleable.configuration.notify_endpoint + end + + def notify_changes(toggles, values) + url = "#{Toggleable.configuration.notify_endpoint}/notify_toggle?keys=#{toggles}&values=#{values}" + RestClient::Resource.new(url).get timeout: 2, open_timeout: 1 + rescue StandardError + nil end end end diff --git a/spec/class_initializer.rb b/spec/class_initializer.rb index 4804655..71b230c 100644 --- a/spec/class_initializer.rb +++ b/spec/class_initializer.rb @@ -25,5 +25,6 @@ def log(key:, value:, actor:) Toggleable.configure do |t| t.logger = logger t.palanca_host = 'localhost:8027' + t.notify_endpoint = 'localhost:5858' t.use_memoization = false end diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index bb2e2c6..d5fbabf 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -81,19 +81,12 @@ before do stub_request(:post, "http://localhost:8027/_internal/toggle-features/bulk-update").to_return(status: 200, body: 'success') + stub_request(:get, "http://localhost:5858/notify_toggle?keys=other_key&values=true").to_return(status: 200, body: 'success') end describe '#mass_toggle! with memory store' do - let(:mapping_before) { - { - 'key' => 'true', - 'other_key' => 'false' - } - } - let(:mapping_after) { { - 'key' => 'true', 'other_key' => 'true' } } @@ -103,7 +96,6 @@ before do subject.register('key') subject.register('other_key') - allow(subject).to receive(:available_features).and_return(mapping_before) end it do @@ -113,30 +105,23 @@ end describe '#mass_toggle! with redis' do - before do - allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) - end - - let(:mapping_before) { - { - 'key' => 'true', - 'other_key' => 'false' - } - } - let(:mapping_after) { { - 'key' => 'true', 'other_key' => 'true' } } let(:actor_id) { 1 } + before do + allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) + allow_any_instance_of(RestClient::Resource).to receive(:get).and_raise(StandardError) + end + + before do subject.register('key') subject.register('other_key') - allow(subject).to receive(:available_features).and_return(mapping_before) end it do From 08c973ed1f480b5a9170748fd699f1b0df2d791d Mon Sep 17 00:00:00 2001 From: budipang Date: Tue, 28 Aug 2018 13:01:09 +0700 Subject: [PATCH 27/34] add notify to toggle base --- lib/toggleable/base.rb | 8 ++++++++ spec/toggleable/toggleable_spec.rb | 2 ++ 2 files changed, 10 insertions(+) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index b1567c0..2cf1e80 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -54,6 +54,7 @@ def toggle_key(value, actor) Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) Toggleable::FeatureToggler.instance.toggle_key(key, value, actor: actor) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) + notify_changes(key, value) if Toggleable.configuration.notify_endpoint end def toggle_active @@ -65,6 +66,13 @@ def toggle_active def read_expired? @_last_read_at < Time.now.localtime - Toggleable.configuration.expiration_time end + + def notify_changes(toggles, values) + url = "#{Toggleable.configuration.notify_endpoint}/notify_toggle?keys=#{toggles}&values=#{values}" + RestClient::Resource.new(url).get timeout: 2, open_timeout: 1 + rescue StandardError + nil + end end end end diff --git a/spec/toggleable/toggleable_spec.rb b/spec/toggleable/toggleable_spec.rb index d8af8c6..496f62e 100644 --- a/spec/toggleable/toggleable_spec.rb +++ b/spec/toggleable/toggleable_spec.rb @@ -23,6 +23,8 @@ class SampleFeature before(:each) do allow(Toggleable::FeatureToggler.instance).to receive(:toggle_key).and_return(true) + stub_request(:get, "http://localhost:5858/notify_toggle?keys=sample_feature&values=true").to_return(status: 200, body: 'success') + stub_request(:get, "http://localhost:5858/notify_toggle?keys=sample_feature&values=false").to_return(status: 200, body: 'success') end describe 'active? before key exist should create the key also' do From 25ce76b3b0e3dbf0c3c5d858bf770347717bbac8 Mon Sep 17 00:00:00 2001 From: budipang Date: Tue, 28 Aug 2018 15:40:23 +0700 Subject: [PATCH 28/34] Revert "add notify to toggle base" This reverts commit 08c973ed1f480b5a9170748fd699f1b0df2d791d. --- lib/toggleable/base.rb | 8 -------- spec/toggleable/toggleable_spec.rb | 2 -- 2 files changed, 10 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 2cf1e80..b1567c0 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -54,7 +54,6 @@ def toggle_key(value, actor) Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) Toggleable::FeatureToggler.instance.toggle_key(key, value, actor: actor) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) - notify_changes(key, value) if Toggleable.configuration.notify_endpoint end def toggle_active @@ -66,13 +65,6 @@ def toggle_active def read_expired? @_last_read_at < Time.now.localtime - Toggleable.configuration.expiration_time end - - def notify_changes(toggles, values) - url = "#{Toggleable.configuration.notify_endpoint}/notify_toggle?keys=#{toggles}&values=#{values}" - RestClient::Resource.new(url).get timeout: 2, open_timeout: 1 - rescue StandardError - nil - end end end end diff --git a/spec/toggleable/toggleable_spec.rb b/spec/toggleable/toggleable_spec.rb index 496f62e..d8af8c6 100644 --- a/spec/toggleable/toggleable_spec.rb +++ b/spec/toggleable/toggleable_spec.rb @@ -23,8 +23,6 @@ class SampleFeature before(:each) do allow(Toggleable::FeatureToggler.instance).to receive(:toggle_key).and_return(true) - stub_request(:get, "http://localhost:5858/notify_toggle?keys=sample_feature&values=true").to_return(status: 200, body: 'success') - stub_request(:get, "http://localhost:5858/notify_toggle?keys=sample_feature&values=false").to_return(status: 200, body: 'success') end describe 'active? before key exist should create the key also' do From 40c010f5ec800c6552eea93f80695e11d5fd1862 Mon Sep 17 00:00:00 2001 From: budipang Date: Tue, 28 Aug 2018 15:40:34 +0700 Subject: [PATCH 29/34] Revert "add notify" This reverts commit 988bd61c415833e6d04d73270393aa50b7ea2ec5. --- lib/toggleable/configuration.rb | 1 - lib/toggleable/feature_toggler.rb | 17 +++------------ spec/class_initializer.rb | 1 - spec/toggleable/feature_toggler_spec.rb | 29 +++++++++++++++++++------ 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index fbdef4f..e885b70 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -7,7 +7,6 @@ class Configuration attr_accessor :palanca_host attr_accessor :palanca_user attr_accessor :palanca_password - attr_accessor :notify_endpoint ## optional for notify changes attr_accessor :storage ## storage used. default: memory store attr_accessor :namespace ## required for prefixing the keys. default: `toggleable`` attr_accessor :logger ## optional, it will not log if not configured. diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index a252cc0..7b09131 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: false +# frozen_string_literal: true require 'singleton' require 'rest-client' @@ -121,22 +121,11 @@ def read_key_expired?(key) end def log_changes(mapping, actor) - toggles = '' - values = '' + previous_values = available_features mapping.each do |key, val| + next if previous_values[key].to_s == val.to_s Toggleable.configuration.logger.log(key: key, value: val, actor: actor) - toggles.concat("#{key},") - values.concat("#{val},") end - - notify_changes(toggles[0...-1], values[0...-1]) if Toggleable.configuration.notify_endpoint - end - - def notify_changes(toggles, values) - url = "#{Toggleable.configuration.notify_endpoint}/notify_toggle?keys=#{toggles}&values=#{values}" - RestClient::Resource.new(url).get timeout: 2, open_timeout: 1 - rescue StandardError - nil end end end diff --git a/spec/class_initializer.rb b/spec/class_initializer.rb index 71b230c..4804655 100644 --- a/spec/class_initializer.rb +++ b/spec/class_initializer.rb @@ -25,6 +25,5 @@ def log(key:, value:, actor:) Toggleable.configure do |t| t.logger = logger t.palanca_host = 'localhost:8027' - t.notify_endpoint = 'localhost:5858' t.use_memoization = false end diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index d5fbabf..bb2e2c6 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -81,12 +81,19 @@ before do stub_request(:post, "http://localhost:8027/_internal/toggle-features/bulk-update").to_return(status: 200, body: 'success') - stub_request(:get, "http://localhost:5858/notify_toggle?keys=other_key&values=true").to_return(status: 200, body: 'success') end describe '#mass_toggle! with memory store' do + let(:mapping_before) { + { + 'key' => 'true', + 'other_key' => 'false' + } + } + let(:mapping_after) { { + 'key' => 'true', 'other_key' => 'true' } } @@ -96,6 +103,7 @@ before do subject.register('key') subject.register('other_key') + allow(subject).to receive(:available_features).and_return(mapping_before) end it do @@ -105,23 +113,30 @@ end describe '#mass_toggle! with redis' do + before do + allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) + end + + let(:mapping_before) { + { + 'key' => 'true', + 'other_key' => 'false' + } + } + let(:mapping_after) { { + 'key' => 'true', 'other_key' => 'true' } } let(:actor_id) { 1 } - before do - allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) - allow_any_instance_of(RestClient::Resource).to receive(:get).and_raise(StandardError) - end - - before do subject.register('key') subject.register('other_key') + allow(subject).to receive(:available_features).and_return(mapping_before) end it do From 9658f30b2219046debca30cd0a50fc3dece0c0db Mon Sep 17 00:00:00 2001 From: budipang Date: Tue, 28 Aug 2018 15:44:16 +0700 Subject: [PATCH 30/34] bump version and remove unnesccesary check --- lib/toggleable/feature_toggler.rb | 2 -- lib/toggleable/version.rb | 2 +- spec/toggleable/feature_toggler_spec.rb | 18 ------------------ 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 7b09131..5843f32 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -121,9 +121,7 @@ def read_key_expired?(key) end def log_changes(mapping, actor) - previous_values = available_features mapping.each do |key, val| - next if previous_values[key].to_s == val.to_s Toggleable.configuration.logger.log(key: key, value: val, actor: actor) end end diff --git a/lib/toggleable/version.rb b/lib/toggleable/version.rb index f390d53..5b2d3ca 100644 --- a/lib/toggleable/version.rb +++ b/lib/toggleable/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Toggleable - VERSION = '0.1.7' + VERSION = '0.1.8' end diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index bb2e2c6..5bd4322 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -84,16 +84,8 @@ end describe '#mass_toggle! with memory store' do - let(:mapping_before) { - { - 'key' => 'true', - 'other_key' => 'false' - } - } - let(:mapping_after) { { - 'key' => 'true', 'other_key' => 'true' } } @@ -103,7 +95,6 @@ before do subject.register('key') subject.register('other_key') - allow(subject).to receive(:available_features).and_return(mapping_before) end it do @@ -117,16 +108,8 @@ allow(Toggleable.configuration).to receive(:storage).and_return(redis_storage) end - let(:mapping_before) { - { - 'key' => 'true', - 'other_key' => 'false' - } - } - let(:mapping_after) { { - 'key' => 'true', 'other_key' => 'true' } } @@ -136,7 +119,6 @@ before do subject.register('key') subject.register('other_key') - allow(subject).to receive(:available_features).and_return(mapping_before) end it do From e50023c6f628e6663f9367aafcdf10de8d06f2db Mon Sep 17 00:00:00 2001 From: budipang Date: Sun, 2 Sep 2018 18:05:21 +0700 Subject: [PATCH 31/34] change notify to email and adjust things --- lib/toggleable/base.rb | 14 +++++++------- lib/toggleable/configuration.rb | 1 + lib/toggleable/feature_toggler.rb | 7 +++---- lib/toggleable/version.rb | 2 +- spec/class_initializer.rb | 1 + spec/toggleable/feature_toggler_spec.rb | 10 ++++++---- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index b1567c0..1a95605 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -23,16 +23,16 @@ def active? # Lazily register the key Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) - Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE) + Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE) if Toggleable.configuration.enable_palanca DEFAULT_VALUE end - def activate!(actor: nil) - toggle_key(true, actor) + def activate!(actor: nil, email: nil) + toggle_key(true, actor, email) end - def deactivate!(actor: nil) - toggle_key(false, actor) + def deactivate!(actor: nil, email: nil) + toggle_key(false, actor, email) end def key @@ -50,10 +50,10 @@ def process private - def toggle_key(value, actor) + def toggle_key(value, actor, email) Toggleable.configuration.storage.set(key, value, namespace: Toggleable.configuration.namespace) - Toggleable::FeatureToggler.instance.toggle_key(key, value, actor: actor) Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) + Toggleable::FeatureToggler.instance.toggle_key(key, value, actor: (email || actor)) if Toggleable.configuration.enable_palanca end def toggle_active diff --git a/lib/toggleable/configuration.rb b/lib/toggleable/configuration.rb index e885b70..c9a1ce9 100644 --- a/lib/toggleable/configuration.rb +++ b/lib/toggleable/configuration.rb @@ -4,6 +4,7 @@ module Toggleable # Toggleable::Configuration yields the configuration of toggleable. class Configuration attr_accessor :expiration_time ## expiration time for memoization. default: 5 minutes + attr_accessor :enable_palanca ## enable palanca api call attr_accessor :palanca_host attr_accessor :palanca_user attr_accessor :palanca_password diff --git a/lib/toggleable/feature_toggler.rb b/lib/toggleable/feature_toggler.rb index 5843f32..bcaa7fc 100644 --- a/lib/toggleable/feature_toggler.rb +++ b/lib/toggleable/feature_toggler.rb @@ -59,7 +59,6 @@ def toggle_key(key, value, actor: nil) while response.empty? begin response = resource.put payload, timeout: 2, open_timeout: 1 - Toggleable.configuration.logger&.log(key: key, value: value, actor: actor) rescue StandardError => e if attempt >= MAX_ATTEMPT Toggleable.configuration.logger.error(message: "TOGGLE #{key} TIMEOUT") @@ -75,13 +74,13 @@ def available_features(memoize: Toggleable.configuration.use_memoization) available_features.slice(*features) end - def mass_toggle!(mapping, actor: nil) + def mass_toggle!(mapping, actor:, email:) log_changes(mapping, actor) if Toggleable.configuration.logger Toggleable.configuration.storage.mass_set(mapping, namespace: Toggleable.configuration.namespace) - mass_set_palanca!(mapping, actor: actor) + mass_set_palanca!(mapping, actor: email) if Toggleable.configuration.enable_palanca end - def mass_set_palanca!(mapping, actor: nil) + def mass_set_palanca!(mapping, actor:) response = '' attempt = 1 url = "#{Toggleable.configuration.palanca_host}/_internal/toggle-features/bulk-update" diff --git a/lib/toggleable/version.rb b/lib/toggleable/version.rb index 5b2d3ca..34548c8 100644 --- a/lib/toggleable/version.rb +++ b/lib/toggleable/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Toggleable - VERSION = '0.1.8' + VERSION = '0.1.9' end diff --git a/spec/class_initializer.rb b/spec/class_initializer.rb index 4804655..3fd03f9 100644 --- a/spec/class_initializer.rb +++ b/spec/class_initializer.rb @@ -26,4 +26,5 @@ def log(key:, value:, actor:) t.logger = logger t.palanca_host = 'localhost:8027' t.use_memoization = false + t.enable_palanca = true end diff --git a/spec/toggleable/feature_toggler_spec.rb b/spec/toggleable/feature_toggler_spec.rb index 5bd4322..d64fc78 100644 --- a/spec/toggleable/feature_toggler_spec.rb +++ b/spec/toggleable/feature_toggler_spec.rb @@ -90,7 +90,8 @@ } } - let(:actor_id) { 1 } + let(:actor_id) { 1 } + let(:actor_email) { 'admin@toggle.com' } before do subject.register('key') @@ -99,7 +100,7 @@ it do expect(Toggleable.configuration.logger).to receive(:log).with(key: 'other_key', value: 'true', actor: actor_id).and_return(true) - subject.mass_toggle!(mapping_after, actor: actor_id) + subject.mass_toggle!(mapping_after, actor: actor_id, email: actor_email) end end @@ -114,7 +115,8 @@ } } - let(:actor_id) { 1 } + let(:actor_id) { 1 } + let(:actor_email) { 'admin@toggle.com' } before do subject.register('key') @@ -123,7 +125,7 @@ it do expect(Toggleable.configuration.logger).to receive(:log).with(key: 'other_key', value: 'true', actor: actor_id).and_return(true) - subject.mass_toggle!(mapping_after, actor: actor_id) + subject.mass_toggle!(mapping_after, actor: actor_id, email: actor_email) end end end From f8112d9f66770870da63b76a541e661fa47d493f Mon Sep 17 00:00:00 2001 From: budipang Date: Sun, 2 Sep 2018 18:45:31 +0700 Subject: [PATCH 32/34] add info on notifying initializtion key --- lib/toggleable/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 1a95605..7fed0a9 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -23,7 +23,7 @@ def active? # Lazily register the key Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) - Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE) if Toggleable.configuration.enable_palanca + Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE, 'key initialization') if Toggleable.configuration.enable_palanca DEFAULT_VALUE end From a703c5c41e562d24c511ea8568323dfeeb1641e4 Mon Sep 17 00:00:00 2001 From: budipang Date: Sun, 2 Sep 2018 18:56:41 +0700 Subject: [PATCH 33/34] typo --- lib/toggleable/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 7fed0a9..386ff50 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -23,7 +23,7 @@ def active? # Lazily register the key Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) - Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE, 'key initialization') if Toggleable.configuration.enable_palanca + Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE, actor: 'key initialization') if Toggleable.configuration.enable_palanca DEFAULT_VALUE end From 5eb2ec9b2750a3c214154b82dbbfe6b9a49e259a Mon Sep 17 00:00:00 2001 From: budipang Date: Fri, 7 Sep 2018 11:38:05 +0700 Subject: [PATCH 34/34] fix possibly race condidtion --- lib/toggleable/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/toggleable/base.rb b/lib/toggleable/base.rb index 386ff50..0e62628 100644 --- a/lib/toggleable/base.rb +++ b/lib/toggleable/base.rb @@ -22,8 +22,8 @@ def active? return toggle_status.to_s == 'true' unless toggle_status.nil? # Lazily register the key - Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) - Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE, actor: 'key initialization') if Toggleable.configuration.enable_palanca + set_status = Toggleable.configuration.storage.set_if_not_exist(key, DEFAULT_VALUE, namespace: Toggleable.configuration.namespace) + Toggleable::FeatureToggler.instance.toggle_key(key, DEFAULT_VALUE, actor: 'key initialization') if Toggleable.configuration.enable_palanca && set_status DEFAULT_VALUE end