flipside 0.2.2 → 0.3.1
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +1 -1
- data/lib/flipside/feature_presenter.rb +2 -39
- data/lib/flipside/importmap.rb +26 -0
- data/lib/flipside/public/index.js +0 -2
- data/lib/flipside/version.rb +1 -1
- data/lib/flipside/views/_datetime_modal.erb +1 -1
- data/lib/flipside/views/_feature_item.erb +8 -4
- data/lib/flipside/views/_toggle_button.erb +9 -9
- data/lib/flipside/views/feature_entities.erb +4 -4
- data/lib/flipside/views/feature_roles.erb +4 -4
- data/lib/flipside/views/index.erb +6 -4
- data/lib/flipside/views/layout.erb +2 -10
- data/lib/flipside/views/show.erb +7 -7
- data/lib/flipside/web.rb +110 -90
- data/lib/flipside.rb +9 -8
- metadata +46 -4
- data/lib/flipside/public/toggle_controller.js +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4893265e134d048226b4a512076453e5fec25f7a842a5a8c8fffd2b8333e5b07
|
4
|
+
data.tar.gz: 313e1f9c0269d0418f8bc3a91f36bd92fde267491a08000c774c3597200f5853
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89efbf2244f81b2662f835129097d04d09193897b74f70939c45ef8063e0172c34c6b161779afc0519d5e0b4cb2bf5c92d27b0a2562bae97053fc86cf5d61805
|
7
|
+
data.tar.gz: 399217a46520038a4bb73e2d67fd36e499f445ef74a035a1fefb7a2650c95e5d392de68d182340cd42c5cb64d404c59c64f3d43a9a64d9b6aef56463f5231a44
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -114,7 +114,7 @@ Flipside.enabled? "MyFeature", user1, user2 # => true, enabled for at least one.
|
|
114
114
|
|
115
115
|
|
116
116
|
## UI
|
117
|
-
Flipside comes with a
|
117
|
+
Flipside comes with a Roda web ui to mange feature flags. To mount this roda app in Rails add the following to your routes.rb file.
|
118
118
|
```ruby
|
119
119
|
mount Flipside::Web, at: '/flipside'
|
120
120
|
```
|
@@ -2,49 +2,12 @@ require 'forwardable'
|
|
2
2
|
|
3
3
|
class FeaturePresenter
|
4
4
|
extend Forwardable
|
5
|
-
attr_reader :feature
|
5
|
+
attr_reader :feature
|
6
6
|
|
7
7
|
def_delegators :@feature, :name, :description, :enabled, :entities, :roles
|
8
8
|
|
9
|
-
def initialize(feature
|
9
|
+
def initialize(feature)
|
10
10
|
@feature = feature
|
11
|
-
@base_path = base_path
|
12
|
-
end
|
13
|
-
|
14
|
-
def href
|
15
|
-
File.join(base_path, "feature", ERB::Util.url_encode(name))
|
16
|
-
end
|
17
|
-
|
18
|
-
def toggle_path
|
19
|
-
File.join(href, "toggle")
|
20
|
-
end
|
21
|
-
|
22
|
-
def back_path
|
23
|
-
base_path
|
24
|
-
end
|
25
|
-
|
26
|
-
def entities_path
|
27
|
-
File.join(href, "entities")
|
28
|
-
end
|
29
|
-
|
30
|
-
def add_entity_path
|
31
|
-
File.join(href, "add_entity")
|
32
|
-
end
|
33
|
-
|
34
|
-
def remove_entity_path
|
35
|
-
File.join(href, "remove_entity")
|
36
|
-
end
|
37
|
-
|
38
|
-
def roles_path
|
39
|
-
File.join(href, "roles")
|
40
|
-
end
|
41
|
-
|
42
|
-
def add_role_path
|
43
|
-
File.join(href, "add_role")
|
44
|
-
end
|
45
|
-
|
46
|
-
def remove_role_path
|
47
|
-
File.join(href, "remove_role")
|
48
11
|
end
|
49
12
|
|
50
13
|
def status
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Flipside
|
2
|
+
module Importmap
|
3
|
+
LIBRARIES = {
|
4
|
+
"@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus/dist/stimulus.js",
|
5
|
+
}.freeze
|
6
|
+
|
7
|
+
def importmap_tags
|
8
|
+
JSON.generate({imports:})
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def imports
|
14
|
+
LIBRARIES.merge(controllers)
|
15
|
+
end
|
16
|
+
|
17
|
+
def controllers
|
18
|
+
pattern = File.join(__dir__, "public/*_controller.js")
|
19
|
+
|
20
|
+
Dir.glob(pattern).to_h do |controller|
|
21
|
+
name = File.basename(controller, ".js")
|
22
|
+
[name, public_path("#{name}.js")]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
import { Application } from "@hotwired/stimulus"
|
2
|
-
import ToggleController from "toggle_controller"
|
3
2
|
import SearchController from "search_controller"
|
4
3
|
import ModalController from "modal_controller"
|
5
4
|
import InlineEditController from "inline_edit_controller"
|
6
5
|
|
7
6
|
const application = Application.start()
|
8
|
-
application.register("toggle", ToggleController)
|
9
7
|
application.register("search", SearchController)
|
10
8
|
application.register("modal", ModalController)
|
11
9
|
application.register("inline-edit", InlineEditController)
|
data/lib/flipside/version.rb
CHANGED
@@ -16,7 +16,7 @@
|
|
16
16
|
</button>
|
17
17
|
</div>
|
18
18
|
<div class="mt-4">
|
19
|
-
<form action="<%= feature
|
19
|
+
<form action="<%= feature_path(feature) %>" method="post">
|
20
20
|
<input type="hidden" name="_method" value="put" />
|
21
21
|
<%# <input type="hidden" name="feature_name" value="<%= feature.name %1>" /> %>
|
22
22
|
<label class="font-bold text-slate-800">Update <%= attribute %></label>
|
@@ -1,6 +1,10 @@
|
|
1
|
-
<div class="p-4 grid grid-cols-12 gap-4
|
2
|
-
<div class="col-span-
|
3
|
-
|
1
|
+
<div class="p-4 grid grid-cols-12 gap-4 items-center text-slate-300">
|
2
|
+
<div class="col-span-10">
|
3
|
+
<a href=<%= feature_path(feature) %> class="grid grid-cols-9 cursor-pointer hover:text-slate-400">
|
4
|
+
<div class="col-span-3 text-left text-xl"><%= feature.name %></div>
|
5
|
+
<div class="col-span-6 text-center text-lg"><%= feature.description&.capitalize %></div>
|
6
|
+
</a>
|
7
|
+
</div>
|
4
8
|
<div class="col-span-1 text-right text-lg font-normal">
|
5
9
|
<span
|
6
10
|
class="inline-block py-1 px-2 min-w-20 text-center rounded-lg <%= feature.status_color %>"
|
@@ -9,6 +13,6 @@
|
|
9
13
|
</span>
|
10
14
|
</div>
|
11
15
|
<div class="text-right">
|
12
|
-
<%=
|
16
|
+
<%= render :_toggle_button, locals: {feature:} %>
|
13
17
|
</div>
|
14
18
|
</div>
|
@@ -1,12 +1,12 @@
|
|
1
1
|
<% color = feature.enabled ? "bg-blue-700" : "bg-gray-200" %>
|
2
2
|
<% translate = feature.enabled ? "translate-x-5" : "translate-x-0" %>
|
3
|
+
<%# FIXME: How to prevent clicking the submit button from propageting? E.g. link navigation on index page. %>
|
3
4
|
|
4
|
-
<
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
</button>
|
5
|
+
<form action="<%= feature_path(feature, "toggle") %>" method="post">
|
6
|
+
<input type="hidden" name="_method" value="put" />
|
7
|
+
<button type="submit"
|
8
|
+
class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent <%= color %>"
|
9
|
+
>
|
10
|
+
<span aria-hidden="true" class="pointer-events-none inline-block size-5 <%= translate %> transform rounded-full bg-white shadow"></span>
|
11
|
+
</button>
|
12
|
+
</form>
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<div class="m-12">
|
2
|
-
<a href="<%= feature
|
2
|
+
<a href="<%= feature_path(feature) %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
|
3
3
|
<div class="mt-12 w-1/3 m-auto bg-gray-800 text-slate-400 p-8 rounded-lg shadow-lg">
|
4
4
|
<h1 class="text-2xl font-bold mb-6">Entities for <span class="font-extrabold text-slate-300"><%= feature.name %></span></h1>
|
5
5
|
<h2 class="text-xl font-bold">Add entity</h2>
|
6
|
-
<form action="<%= feature
|
7
|
-
<div data-controller="search" data-search-url-value="
|
6
|
+
<form action="<%= feature_path(feature, "add_entity") %>" method="post">
|
7
|
+
<div data-controller="search" data-search-url-value="<%= search_entity_path %>" class="w-full flex p-0 gap-2 items-center">
|
8
8
|
<select
|
9
9
|
name="class_name"
|
10
10
|
data-search-target="param"
|
@@ -54,7 +54,7 @@
|
|
54
54
|
<%= Flipside.display_entity(entity.flippable) %> (<%= entity.flippable_type %>)
|
55
55
|
</div>
|
56
56
|
<div>
|
57
|
-
<form action="<%= feature
|
57
|
+
<form action="<%= feature_path(feature, "remove_entity") %>" method="post">
|
58
58
|
<input type="hidden" name="entity_id" value="<%= entity.id %>" />
|
59
59
|
<button type="submit" class="p-2 bg-gray-300 text-gray-700 font-semibold rounded hover:bg-gray-400">Remove</button>
|
60
60
|
</form>
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<div class="m-12">
|
2
|
-
<a href="<%= feature
|
2
|
+
<a href="<%= feature_path(feature) %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
|
3
3
|
<div class="mt-12 w-1/3 m-auto bg-gray-800 text-slate-400 p-8 rounded-lg shadow-lg">
|
4
4
|
<h1 class="text-2xl font-bold mb-6">Roles for <span class="font-extrabold text-slate-300"><%= feature.name %></span></h1>
|
5
5
|
<h2 class="text-xl font-bold">Add Role</h2>
|
6
|
-
<form action="<%= feature
|
7
|
-
<div data-controller="search" data-search-url-value="
|
6
|
+
<form action="<%= feature_path(feature, "add_role") %>" method="post">
|
7
|
+
<div data-controller="search" data-search-url-value="<%= search_role_path %>" class="w-full flex p-0 gap-2 items-center">
|
8
8
|
<select
|
9
9
|
name="class_name"
|
10
10
|
data-search-target="param"
|
@@ -54,7 +54,7 @@
|
|
54
54
|
<%= "#{role.class_name}##{role.method}" %>
|
55
55
|
</div>
|
56
56
|
<div>
|
57
|
-
<form action="<%= feature
|
57
|
+
<form action="<%= feature_path(feature, "remove_role") %>" method="post">
|
58
58
|
<input type="hidden" name="role_id" value="<%= role.id %>" />
|
59
59
|
<button type="submit" class="p-2 bg-gray-300 text-gray-700 font-semibold rounded hover:bg-gray-400">Remove</button>
|
60
60
|
</form>
|
@@ -7,11 +7,13 @@
|
|
7
7
|
|
8
8
|
<div class="mt-12 m-auto">
|
9
9
|
<ul>
|
10
|
+
<li class="hidden only:block text-center">
|
11
|
+
<p>No feature added yet!</p><p>Create a new feature from the console by typing:</p>
|
12
|
+
<code class="inline-block p-1 text-green-700 bg-slate-900">Flipside.create(name: "my_feature", description: "Some decription...")</code>
|
13
|
+
</li>
|
10
14
|
<% features.each do |feature| %>
|
11
|
-
<li class="border-b border-slate-500
|
12
|
-
|
13
|
-
<%= erb :_feature_item, locals: {feature:} %>
|
14
|
-
</a>
|
15
|
+
<li class="border-b border-slate-500">
|
16
|
+
<%= render :_feature_item, locals: {feature:} %>
|
15
17
|
</li>
|
16
18
|
<% end %>
|
17
19
|
</ul>
|
@@ -5,15 +5,7 @@
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
6
|
<script src="https://cdn.tailwindcss.com"></script>
|
7
7
|
<script type="importmap">
|
8
|
-
|
9
|
-
"imports": {
|
10
|
-
"@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus/dist/stimulus.js",
|
11
|
-
"toggle_controller": "/flipside/toggle_controller.js",
|
12
|
-
"search_controller": "/flipside/search_controller.js",
|
13
|
-
"modal_controller": "/flipside/modal_controller.js",
|
14
|
-
"inline_edit_controller": "/flipside/inline_edit_controller.js"
|
15
|
-
}
|
16
|
-
}
|
8
|
+
<%= importmap_tags %>
|
17
9
|
</script>
|
18
10
|
</head>
|
19
11
|
<body class="w-full h-full bg-slate-600">
|
@@ -21,6 +13,6 @@
|
|
21
13
|
<%= yield %>
|
22
14
|
</div>
|
23
15
|
|
24
|
-
<script src="
|
16
|
+
<script src="<%= public_path("index.js") %>" type="module" ></script>
|
25
17
|
</body>
|
26
18
|
</html>
|
data/lib/flipside/views/show.erb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="m-12">
|
2
|
-
<a href="<%=
|
2
|
+
<a href="<%= base_path %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
|
3
3
|
<div class="mt-12 w-1/2 m-auto bg-gray-800 text-slate-400 p-8 rounded-lg shadow-lg">
|
4
4
|
<h1 class="text-2xl font-bold mb-6">Feature Details</h1>
|
5
5
|
<div class="mt-4 grid grid-cols-1 gap-4">
|
@@ -18,7 +18,7 @@
|
|
18
18
|
Edit
|
19
19
|
</button>
|
20
20
|
</div>
|
21
|
-
<form action="<%= feature
|
21
|
+
<form action="<%= feature_path(feature) %>" method="post" class="hidden ml-4 grow flex gap-2" data-inline-edit-target="edit">
|
22
22
|
<input type="hidden" name="_method" value="put" />
|
23
23
|
<input
|
24
24
|
type="text"
|
@@ -37,18 +37,18 @@
|
|
37
37
|
</div>
|
38
38
|
<div class="mt-2 flex justify-between items-center">
|
39
39
|
<span class="font-semibold">Enabled for all:</span>
|
40
|
-
<span><%=
|
40
|
+
<span><%= render :_toggle_button, locals: {feature:} %></span>
|
41
41
|
</div>
|
42
42
|
<div class="mt-2 flex justify-between items-center">
|
43
43
|
<span class="font-semibold">Active From:</span>
|
44
44
|
<span>
|
45
|
-
<%=
|
45
|
+
<%= render :_datetime_modal, locals: {feature:, attribute: :activated_at} %>
|
46
46
|
</span>
|
47
47
|
</div>
|
48
48
|
<div class="mt-2 flex justify-between items-center">
|
49
49
|
<span class="font-semibold">Active Until:</span>
|
50
50
|
<span>
|
51
|
-
<%=
|
51
|
+
<%= render :_datetime_modal, locals: {feature:, attribute: :deactivated_at} %>
|
52
52
|
</span>
|
53
53
|
</div>
|
54
54
|
<div class="mt-2 grid grid-cols-3 items-center">
|
@@ -56,7 +56,7 @@
|
|
56
56
|
<span class="text-center"><%= feature.entity_count_str %></span>
|
57
57
|
<span class="text-right text-xl text-slate-400">
|
58
58
|
<a
|
59
|
-
href="<%= feature
|
59
|
+
href="<%= feature_path(feature, "entities") %>"
|
60
60
|
class="px-2 py-1 rounded-lg bg-blue-900 border border-blue-950 hover:bg-blue-800" >
|
61
61
|
Edit
|
62
62
|
</a>
|
@@ -67,7 +67,7 @@
|
|
67
67
|
<span class="text-center"><%= feature.role_count_str %></span>
|
68
68
|
<span class="text-right text-xl text-slate-400">
|
69
69
|
<a
|
70
|
-
href="<%= feature
|
70
|
+
href="<%= feature_path(feature, "roles") %>"
|
71
71
|
class="px-2 py-1 rounded-lg bg-blue-900 border border-blue-950 hover:bg-blue-800" >
|
72
72
|
Edit
|
73
73
|
</a>
|
data/lib/flipside/web.rb
CHANGED
@@ -1,105 +1,125 @@
|
|
1
|
-
require
|
1
|
+
require 'roda'
|
2
2
|
require "uri"
|
3
3
|
require "flipside/feature_presenter"
|
4
|
+
require "flipside/importmap"
|
5
|
+
require "rack/method_override"
|
4
6
|
|
5
7
|
module Flipside
|
6
|
-
class Web <
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
8
|
+
class Web < Roda
|
9
|
+
include Flipside::Importmap
|
10
|
+
use Rack::MethodOverride
|
11
|
+
|
12
|
+
opts[:add_script_name] = true
|
13
|
+
|
14
|
+
plugin :r
|
15
|
+
plugin :json
|
16
|
+
plugin :halt
|
17
|
+
plugin :path
|
18
|
+
plugin :all_verbs
|
19
|
+
plugin :unescape_path
|
20
|
+
plugin :render, views: File.expand_path("views", __dir__)
|
21
|
+
plugin :public,
|
22
|
+
root: File.expand_path("public", __dir__),
|
23
|
+
headers: {"Cache-Control" => "public, max-age=14400"}
|
24
|
+
|
25
|
+
path(:public) { |name| "/#{name}" }
|
26
|
+
path(:base, "/")
|
27
|
+
path(:feature) do |feature, *segments, **query|
|
28
|
+
path = "/feature/#{ERB::Util.url_encode(feature.name)}"
|
29
|
+
path = "#{path}/#{segments.join("/")}" if segments.any?
|
30
|
+
path = "#{path}?#{query.map { |k,v| "#{k}=#{v}" }.join("&")}" if query.keys.any?
|
31
|
+
path
|
32
|
+
end
|
33
|
+
path(:search_entity, "/search_entity")
|
34
|
+
path(:search_role, "/search_role")
|
35
|
+
|
36
|
+
def load_feature(name)
|
37
|
+
Flipside::Feature.find_by!(name:)
|
38
|
+
rescue
|
39
|
+
r.halt 404, view(:not_found)
|
21
40
|
end
|
22
41
|
|
23
|
-
|
24
|
-
|
42
|
+
route do |r|
|
43
|
+
r.public
|
25
44
|
|
26
|
-
|
27
|
-
|
45
|
+
r.root do
|
46
|
+
features = Flipside::Feature.order(:name).map do |feature|
|
47
|
+
FeaturePresenter.new(feature)
|
48
|
+
end
|
28
49
|
|
29
|
-
|
30
|
-
content_type :text
|
31
|
-
|
32
|
-
if feature.update(enabled: !feature.enabled)
|
33
|
-
204
|
34
|
-
else
|
35
|
-
[422, "Failed to update feature"]
|
50
|
+
view :index, locals: {features:}
|
36
51
|
end
|
37
|
-
end
|
38
52
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
53
|
+
r.on "feature", String do |name|
|
54
|
+
feature = load_feature(name)
|
55
|
+
|
56
|
+
r.is do
|
57
|
+
r.get do
|
58
|
+
view(:show, locals: { feature: FeaturePresenter.new(feature) })
|
59
|
+
end
|
60
|
+
|
61
|
+
r.put do
|
62
|
+
kwargs = r.params.slice("description", "activated_at", "deactivated_at")
|
63
|
+
feature.update(**kwargs)
|
64
|
+
r.redirect r.path, 303
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
r.put "toggle" do
|
69
|
+
if feature.update(enabled: !feature.enabled)
|
70
|
+
referer = r.env["HTTP_REFERER"]
|
71
|
+
r.redirect (referer || feature_path(feature)), 303
|
72
|
+
else
|
73
|
+
response.status = 422
|
74
|
+
"Failed to update feature"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
r.get "entities" do
|
79
|
+
view(:feature_entities, locals: { feature: FeaturePresenter.new(feature) })
|
80
|
+
end
|
81
|
+
|
82
|
+
r.post "add_entity" do
|
83
|
+
class_name, identifier = r.params.values_at("class_name", "identifier")
|
84
|
+
entity = Flipside.find_entity(class_name:, identifier:)
|
85
|
+
Flipside.add_entity(feature:, entity:)
|
86
|
+
r.redirect feature_path(feature, "entities"), 303
|
87
|
+
end
|
88
|
+
|
89
|
+
r.post "remove_entity" do
|
90
|
+
Flipside.remove_entity(feature:, entity_id: r.params["entity_id"])
|
91
|
+
r.redirect feature_path(feature, "entities"), 303
|
92
|
+
end
|
93
|
+
|
94
|
+
r.get "roles" do
|
95
|
+
view(:feature_roles, locals: { feature: FeaturePresenter.new(feature) })
|
96
|
+
end
|
97
|
+
|
98
|
+
r.post "add_role" do
|
99
|
+
class_name, method_name = r.params.values_at("class_name", "identifier")
|
100
|
+
Flipside.add_role(name:, class_name:, method_name:)
|
101
|
+
r.redirect feature_path(feature, "roles"), 303
|
102
|
+
end
|
103
|
+
|
104
|
+
r.post "remove_role" do
|
105
|
+
Flipside.remove_role(name:, role_id: r.params["role_id"])
|
106
|
+
r.redirect feature_path(feature, "roles"), 303
|
107
|
+
end
|
108
|
+
end
|
94
109
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
110
|
+
r.get "search_entity" do
|
111
|
+
class_name = r.params["class_name"]
|
112
|
+
query = URI.decode_www_form_component(r.params["q"])
|
113
|
+
result = Flipside.search_entity(class_name:, query:)
|
114
|
+
view(:_search_result, locals: { result:, class_name:, query: })
|
115
|
+
end
|
100
116
|
|
101
|
-
|
102
|
-
|
117
|
+
r.get "search_role" do
|
118
|
+
class_name = r.params["class_name"]
|
119
|
+
query = URI.decode_www_form_component(r.params["q"])
|
120
|
+
result = Flipside.search_role(class_name:, query:)
|
121
|
+
view(:_search_result, locals: { result:, class_name:, query: })
|
122
|
+
end
|
103
123
|
end
|
104
124
|
end
|
105
125
|
end
|
data/lib/flipside.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record"
|
3
4
|
require "flipside/version"
|
4
5
|
require "flipside/web"
|
5
6
|
require "flipside/config/settings"
|
@@ -38,23 +39,23 @@ module Flipside
|
|
38
39
|
feature.update(enabled: true)
|
39
40
|
end
|
40
41
|
|
41
|
-
def add_entity(
|
42
|
-
feature
|
42
|
+
def add_entity(entity:, feature: nil, name: nil)
|
43
|
+
feature ||= find_by!(name:)
|
43
44
|
Entity.find_or_create_by(feature:, flippable: entity)
|
44
45
|
end
|
45
46
|
|
46
|
-
def remove_entity(
|
47
|
-
feature
|
47
|
+
def remove_entity(entity_id:, feature: nil, name: nil)
|
48
|
+
feature ||= find_by!(name:)
|
48
49
|
feature.entities.find_by(id: entity_id)&.destroy
|
49
50
|
end
|
50
51
|
|
51
|
-
def add_role(
|
52
|
-
feature
|
52
|
+
def add_role(class_name:, method_name:, feature: nil, name: nil)
|
53
|
+
feature ||= find_by!(name:)
|
53
54
|
Role.find_or_create_by(feature:, class_name:, method: method_name)
|
54
55
|
end
|
55
56
|
|
56
|
-
def remove_role(
|
57
|
-
feature
|
57
|
+
def remove_role(role_id:, feature: nil, name: nil)
|
58
|
+
feature ||= find_by!(name:)
|
58
59
|
feature.roles.find_by(id: role_id)&.destroy
|
59
60
|
end
|
60
61
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipside
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sammy Henningsson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -25,7 +25,21 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '6.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: roda
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: tilt
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
@@ -52,6 +66,20 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rackup
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.2'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: rspec
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +108,20 @@ dependencies:
|
|
80
108
|
- - ">="
|
81
109
|
- !ruby/object:Gem::Version
|
82
110
|
version: '2.1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: puma
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '6.5'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '6.5'
|
83
125
|
description: Create simple feature toggles.
|
84
126
|
email:
|
85
127
|
- sammy.henningsson@hey.com
|
@@ -105,11 +147,11 @@ files:
|
|
105
147
|
- lib/flipside/config/roles.rb
|
106
148
|
- lib/flipside/config/settings.rb
|
107
149
|
- lib/flipside/feature_presenter.rb
|
150
|
+
- lib/flipside/importmap.rb
|
108
151
|
- lib/flipside/public/index.js
|
109
152
|
- lib/flipside/public/inline_edit_controller.js
|
110
153
|
- lib/flipside/public/modal_controller.js
|
111
154
|
- lib/flipside/public/search_controller.js
|
112
|
-
- lib/flipside/public/toggle_controller.js
|
113
155
|
- lib/flipside/search_result.rb
|
114
156
|
- lib/flipside/version.rb
|
115
157
|
- lib/flipside/views/_datetime_modal.erb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
import { Controller } from "@hotwired/stimulus";
|
2
|
-
|
3
|
-
export default class ToggleController extends Controller {
|
4
|
-
static targets = ["switch"]
|
5
|
-
static values = {
|
6
|
-
url: String,
|
7
|
-
enabled: Boolean
|
8
|
-
}
|
9
|
-
|
10
|
-
async switch(event) {
|
11
|
-
try {
|
12
|
-
const data = {enable: !this.enabledValue}
|
13
|
-
const response = await fetch(this.urlValue, {
|
14
|
-
method: "PUT",
|
15
|
-
headers: {"Content-Type": "application/json"},
|
16
|
-
body: JSON.stringify(data)
|
17
|
-
})
|
18
|
-
|
19
|
-
if (response.ok) {
|
20
|
-
location.reload()
|
21
|
-
} else {
|
22
|
-
const text = await response.text()
|
23
|
-
console.error("Failed to update:", text)
|
24
|
-
}
|
25
|
-
} catch (error) {
|
26
|
-
console.error("Error during the PUT request:", error)
|
27
|
-
}
|
28
|
-
}
|
29
|
-
}
|