rollout_service 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f4eb23b9f1860c997270a8133701f70fbf8dc7ec
4
+ data.tar.gz: 9998ef0936cc0b713ca92d44c78ae4b0bf57624b
5
+ SHA512:
6
+ metadata.gz: 95b8179d34b583a77879ec2dd229522e2eea0d4bfac04b63ad8dc514577b9ba9432370a83a6bc6077a3c29da5f6cd64503fd4af9304d19563a40cb15a303f599
7
+ data.tar.gz: fab775be9f45b368a6f7cf0fb632295dd12200641b6feacca2ad5494c5ebd971f864dd9a2b13a54973bd2919d7f2965de1e106534a8001a3cdc632411b15dc8c
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .idea/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in testttt.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,79 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rollout_service (0.1.0)
5
+ active_attr (~> 0.9.0)
6
+ activesupport (~> 5.0)
7
+ grape
8
+ grape-entity (~> 0.5.0)
9
+ redis
10
+ require_all
11
+ rollout
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ active_attr (0.9.0)
17
+ activemodel (>= 3.0.2, < 5.1)
18
+ activesupport (>= 3.0.2, < 5.1)
19
+ activemodel (5.0.7)
20
+ activesupport (= 5.0.7)
21
+ activesupport (5.0.7)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 0.7, < 2)
24
+ minitest (~> 5.1)
25
+ tzinfo (~> 1.1)
26
+ axiom-types (0.1.1)
27
+ descendants_tracker (~> 0.0.4)
28
+ ice_nine (~> 0.11.0)
29
+ thread_safe (~> 0.3, >= 0.3.1)
30
+ builder (3.2.3)
31
+ coercible (1.0.0)
32
+ descendants_tracker (~> 0.0.1)
33
+ concurrent-ruby (1.0.5)
34
+ descendants_tracker (0.0.4)
35
+ thread_safe (~> 0.3, >= 0.3.1)
36
+ equalizer (0.0.11)
37
+ grape (1.0.3)
38
+ activesupport
39
+ builder
40
+ mustermann-grape (~> 1.0.0)
41
+ rack (>= 1.3.0)
42
+ rack-accept
43
+ virtus (>= 1.0.0)
44
+ grape-entity (0.5.2)
45
+ multi_json (>= 1.3.2)
46
+ i18n (1.0.1)
47
+ concurrent-ruby (~> 1.0)
48
+ ice_nine (0.11.2)
49
+ minitest (5.11.3)
50
+ multi_json (1.13.1)
51
+ mustermann (1.0.2)
52
+ mustermann-grape (1.0.0)
53
+ mustermann (~> 1.0.0)
54
+ rack (2.0.5)
55
+ rack-accept (0.4.5)
56
+ rack (>= 0.4)
57
+ rake (10.5.0)
58
+ redis (4.0.1)
59
+ require_all (2.0.0)
60
+ rollout (2.4.3)
61
+ thread_safe (0.3.6)
62
+ tzinfo (1.2.5)
63
+ thread_safe (~> 0.1)
64
+ virtus (1.0.5)
65
+ axiom-types (~> 0.1)
66
+ coercible (~> 1.0)
67
+ descendants_tracker (~> 0.0, >= 0.0.3)
68
+ equalizer (~> 0.0, >= 0.0.9)
69
+
70
+ PLATFORMS
71
+ ruby
72
+
73
+ DEPENDENCIES
74
+ bundler (~> 1.13)
75
+ rake (~> 10.0)
76
+ rollout_service!
77
+
78
+ BUNDLED WITH
79
+ 1.16.2
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # Rollout-Service
2
+ **A Grape service that expose rollout gem via RESTful endpoints**
3
+
4
+ This service expose RESTfull endpoints that allows you to perform CRUD operation on [rollout](https://github.com/fetlife/rollout) gem.
5
+
6
+ This service works great with [Rollout-Dashboard](https://github.com/fiverr/rollout_dashboard) - a beautiful user interface for rollout gem)
7
+
8
+ ## End-Points Documentation:
9
+
10
+ | Description | END POINT |
11
+ | ------------- | ------------- |
12
+ | Get all features | GET /api/v1/features |
13
+ | Get specific feature by name | GET /api/v1/features/:feature_name |
14
+ | Get specific feature by name | GET /api/v1/features/:feature_name |
15
+ | Check if feature is active | GET /api/v1/features/:feature_name/:user_id/active |
16
+ | Create a new feature | POST /api/v1/features/:feature_name |
17
+ | Partially update existing feature | PATCH /api/v1/features/:feature_name |
18
+ | Delete a feature | DELETE /api/v1/features/:feature_name |
19
+
20
+
21
+ # FAQ
22
+
23
+ # How to set redis configuration?
24
+ Edit `./config/redis.yml`
25
+
26
+ ## How to start the service?
27
+ run `bundle exec rackup -p :port`
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,119 @@
1
+ module RolloutService
2
+ module API
3
+ class Features < Grape::API
4
+
5
+ get '/' do
6
+ features = Config.rollout.features
7
+ features.map! do|feature|
8
+ Models::Feature.find(feature)
9
+ end
10
+
11
+ RestfulModels::Response.represent(data: features)
12
+ end
13
+
14
+
15
+ route_param :feature_name do
16
+ params do
17
+ requires :feature_name, type: Models::Feature
18
+ end
19
+ get '/' do
20
+ feature = params[:feature_name]
21
+
22
+ if feature.valid?
23
+ RestfulModels::Response.represent(data: feature)
24
+ else
25
+ status 500
26
+ RestfulModels::Response.represent(message: 'Error, feature is not valid')
27
+ end
28
+ end
29
+
30
+ params do
31
+ requires :user_id, type: Integer, desc: 'The user ID'
32
+ requires :feature_name, type: Models::Feature
33
+ end
34
+ get '/:user_id/active' do
35
+ user_id = params[:user_id].to_i
36
+ feature = params[:feature_name]
37
+
38
+ is_active = feature.active?(user_id)
39
+
40
+ RestfulModels::Response.represent(data: { active: is_active })
41
+ end
42
+
43
+ params do
44
+ requires :feature_name, type: Models::Feature
45
+ end
46
+ delete '/' do
47
+ feature = params[:feature_name]
48
+ feature.delete
49
+ ''
50
+ end
51
+
52
+ params do
53
+ requires :description, type: String, desc: 'The feature description'
54
+ requires :feature_name, type: String
55
+ end
56
+ post '/' do
57
+ feature_name = params[:feature_name]
58
+ error! 'Feature is already exist!' if Models::Feature.exist?(feature_name)
59
+
60
+ options = {
61
+ name: feature_name,
62
+ percentage: params[:percentage].to_i,
63
+ description: params[:description],
64
+ author: current_user.name,
65
+ author_mail: current_user.email,
66
+ created_at: Time.current
67
+ }
68
+
69
+ feature = Models::Feature.new(options)
70
+
71
+ begin
72
+ feature.save!
73
+ Models::Feature.set_users_to_feature(feature, params[:users])
74
+ RestfulModels::Response.represent(
75
+ message: 'Feature created successfully!',
76
+ data: feature
77
+ )
78
+ rescue => e
79
+ status 500
80
+ RestfulModels::Response.represent(message: "An error has been occurred.\r\n #{e}")
81
+ end
82
+ end
83
+
84
+ params do
85
+ requires :feature_name, type: Models::Feature
86
+ end
87
+ patch '/' do
88
+ feature = params[:feature_name]
89
+
90
+ options = {
91
+ percentage: params[:percentage].to_i,
92
+ description: params[:description],
93
+ created_at: Time.current
94
+ }
95
+
96
+ # if the feature for some reason had no author, the current user become
97
+ if feature.author.blank?
98
+ options[:author] = current_user.name
99
+ options[:author_mail] = current_user.email
100
+ end
101
+
102
+ feature.assign_attributes(options)
103
+
104
+ begin
105
+ feature.save!
106
+ Models::Feature.set_users_to_feature(feature, params[:users])
107
+ RestfulModels::Response.represent(
108
+ message: 'Feature updated successfully!',
109
+ data: feature
110
+ )
111
+ rescue => e
112
+ status 500
113
+ RestfulModels::Response.represent(message: "An error has been occurred.\r\n #{e}")
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,14 @@
1
+ module RolloutService
2
+ module Config
3
+ extend self
4
+ attr_accessor :rollout, :redis
5
+
6
+ def configure
7
+ yield self
8
+ raise 'You must provide a redis instance' if redis.blank?
9
+
10
+ self.rollout = Rollout.new(redis, use_sets: true)
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,107 @@
1
+ module RolloutService
2
+ module Models
3
+ class Feature
4
+ include ActiveAttr::Model
5
+
6
+ MAX_HISTORY_RECORDS = 50
7
+
8
+ attribute :name, type: String
9
+ attribute :description, type: String
10
+ attribute :percentage, type: Integer, default: 0
11
+ attribute :author, type: String
12
+ attribute :author_mail, type: String
13
+ attribute :created_at, type: Date
14
+ attribute :history, default: []
15
+ attribute :users, default: []
16
+
17
+ validates :name,
18
+ :description,
19
+ :percentage,
20
+ :created_at,
21
+ :author,
22
+ :author_mail,
23
+ presence: true
24
+
25
+ def self.parse(name)
26
+ instance = find(name)
27
+ raise 'Feature is not exist' if instance.nil?
28
+ instance
29
+ end
30
+
31
+ def self.find(name)
32
+ return nil unless exist?(name)
33
+
34
+ feature = Config::rollout.get(name)
35
+ feature_data = feature.data.deep_symbolize_keys!
36
+
37
+ feature_data.merge!({
38
+ name: feature.name,
39
+ percentage: feature.percentage,
40
+ users: feature.users
41
+ })
42
+
43
+ feature_data.delete_if {|key, _| !self.method_defined?(key)}
44
+
45
+ self.new(feature_data)
46
+ end
47
+
48
+ def self.exist?(name)
49
+ features = Config::rollout.features
50
+ features.include?(name.to_sym)
51
+ end
52
+
53
+ def self.set_users_to_feature(rollout, users)
54
+ return if users.nil? || rollout.nil?
55
+ users = users.to_a
56
+
57
+ current_active_users = rollout.users
58
+ users_to_remove = current_active_users - users
59
+ Config::rollout.deactivate_users(rollout.name ,users_to_remove)
60
+ Config::rollout.activate_users(rollout.name ,users)
61
+ rollout.users = users
62
+ users
63
+ end
64
+
65
+ def save!
66
+ set_history_attribute
67
+ raise 'Feature is not valid!' unless self.valid?
68
+
69
+ Config::rollout.activate_percentage(self.name, self.percentage)
70
+
71
+ feature_data = {
72
+ history: self.history,
73
+ description: self.description,
74
+ author: self.author,
75
+ author_mail: self.author_mail,
76
+ created_at: self.created_at
77
+ }
78
+
79
+ feature_data.delete_if { |_, value| value.blank? }
80
+ Config::rollout.set_feature_data(self.name, feature_data)
81
+ end
82
+
83
+ def active?(user_id)
84
+ Config::rollout.active?(self.name, user_id)
85
+ end
86
+
87
+ def delete
88
+ Config::rollout.delete(self.name)
89
+ end
90
+
91
+ private
92
+
93
+ def set_history_attribute
94
+ last_record = history.last
95
+ return if last_record.present? && last_record[:percentage] == self.percentage
96
+
97
+ self.history << {
98
+ author: self.author,
99
+ author_mail: self.author_mail,
100
+ percentage: self.percentage,
101
+ updated_at: Time.current
102
+ }
103
+ self.history = self.history.last(MAX_HISTORY_RECORDS)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,12 @@
1
+ module RolloutService
2
+ class User
3
+ attr_reader :name, :email
4
+
5
+ def initialize(env)
6
+ user = env['User-Details'] || ''
7
+ name, email = user.split(':')
8
+ @name = name || 'Anonymous'
9
+ @email = email || 'anonymous@anonymous.con'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ module RolloutService
2
+ module RestfulModels
3
+ class Response < Grape::Entity
4
+ expose :data, unless: Proc.new {|field| field.blank?}
5
+ expose :message, if: ->(response, _) { response[:data].blank? }
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module RolloutService
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ require 'rollout_service/version'
2
+ require 'require_all'
3
+ require 'grape'
4
+ require 'grape-entity'
5
+ require 'rollout'
6
+ require 'redis'
7
+ require 'active_support'
8
+ require 'active_attr'
9
+
10
+ require_rel 'rollout_service/config'
11
+ require_rel 'rollout_service/restful_models'
12
+ require_rel 'rollout_service/models'
13
+ require_rel 'rollout_service/api'
14
+
15
+ module RolloutService
16
+ class Service < Grape::API
17
+ format :json
18
+
19
+ helpers do
20
+ def current_user
21
+ @current_user ||= User.new(env)
22
+ end
23
+ end
24
+
25
+ resource(:features) { mount API::Features }
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rollout_service/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rollout_service"
8
+ spec.version = RolloutService::VERSION
9
+ spec.authors = ["Fiverr"]
10
+ spec.email = ["dev@fiverr.com"]
11
+ spec.summary = "This gem exposes rollout gem API"
12
+ spec.homepage = "https://www.fiverr.com"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ end
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency 'grape', '~> 1'
20
+ spec.add_dependency 'grape-entity', '~> 0.5'
21
+ spec.add_dependency 'rollout', '~> 2.4'
22
+ spec.add_dependency 'require_all', '~> 2'
23
+ spec.add_dependency 'activesupport', '~> 5'
24
+ spec.add_dependency 'active_attr', '~> 0.9'
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.13"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rollout_service
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Fiverr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: grape
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: grape-entity
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rollout
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: require_all
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: active_attr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.13'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.13'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ description:
126
+ email:
127
+ - dev@fiverr.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - Gemfile
134
+ - Gemfile.lock
135
+ - README.md
136
+ - Rakefile
137
+ - lib/rollout_service.rb
138
+ - lib/rollout_service/api/feature_api.rb
139
+ - lib/rollout_service/config/config.rb
140
+ - lib/rollout_service/models/feature.rb
141
+ - lib/rollout_service/models/user.rb
142
+ - lib/rollout_service/restful_models/response.rb
143
+ - lib/rollout_service/version.rb
144
+ - rollout_service.gemspec
145
+ homepage: https://www.fiverr.com
146
+ licenses: []
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.5.2
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: This gem exposes rollout gem API
168
+ test_files: []