rollout-ui 0.10.1 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fe93ad84dab349410b8d45fd548837b3c7057db163916ae51af5415f0b6a9bd
4
- data.tar.gz: 6b451b50cc1e907a63e94976c103800f26fa6366705ed34e4a31d2faad585507
3
+ metadata.gz: 30014dd8f03ca67cb9bd7152adb431746e9d1ec399201e2a51abb4f3dedab1a4
4
+ data.tar.gz: bfa5ade0416b04978bc47f461513dcb857e88a2aa8838a140564a12fe9101a8f
5
5
  SHA512:
6
- metadata.gz: '09ad9b11897744854625b4654e7d2a5c543492ac945ad6b91e5359b1c6aad0bab6ba0927781588f4c8b3c16e221597f625cf9acc4ca1e6c208c260d20a6e6ee8'
7
- data.tar.gz: 837fa191cde919c0a88f881cc31fe300311026acf7559410f3f23bdc9ffd27c5d6f9644955337d64019c96e2642343837569d8da31648706bdf7a80d411b9bbb
6
+ metadata.gz: bf00bea7e987f4569fff37e268e48dccaf5b30c9cf62e545d8276ab66f426da578fc454e5ab1fd2b1db065a58284400ea72e4a0b8cd3eac088715c0f4d50a041
7
+ data.tar.gz: cea39dbe21f1a32be247c019e0fe7ca3073344ab918c4deb9ca7fedc80230b852b0d4e5ac745e1b4b776d41d5352030f25f41c98e70fd7a07479de4d034d67ca
@@ -7,8 +7,7 @@ on:
7
7
 
8
8
  jobs:
9
9
  release:
10
- # setup-ruby has no build of the .ruby-version Ruby (3.4.7) for ubuntu-26.04.
11
- runs-on: ubuntu-24.04
10
+ runs-on: ubuntu-26.04
12
11
  permissions:
13
12
  contents: write
14
13
  id-token: write
@@ -8,8 +8,7 @@ on:
8
8
 
9
9
  jobs:
10
10
  test:
11
- # setup-ruby has no build of the .ruby-version Ruby (3.4.7) for ubuntu-26.04.
12
- runs-on: ubuntu-24.04
11
+ runs-on: ubuntu-26.04
13
12
  services:
14
13
  redis:
15
14
  image: redis:7-alpine
data/.mise.toml CHANGED
@@ -1,2 +1,2 @@
1
1
  [tools]
2
- ruby = "3.4.7"
2
+ ruby = "3.4.11"
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.7
1
+ 3.4.11
data/README.md CHANGED
@@ -139,6 +139,31 @@ and check Remove for visible rows. **Update before following page links** to
139
139
  avoid discarding unsaved form changes. Enrollment pagination remains available
140
140
  when nickname lookup fails; unknown IDs are never silently dropped.
141
141
 
142
+ ## Missing definitions
143
+
144
+ The overview and feature page mark stored features that have no definition in
145
+ the current application as **Missing definition**. This is informational: it
146
+ does not change targeting, editing, or deletion, and a missing definition is not
147
+ proof that a feature is unused.
148
+
149
+ With Rollout's `definitions` API, the UI checks `Rollout.definitions` when the
150
+ definitions directory exists. Older Rollout versions, a missing directory, or
151
+ `Rollout.definitions_path = nil` leave checking disabled.
152
+
153
+ Applications that also register features in Ruby can supply their complete list
154
+ of defined names instead:
155
+
156
+ ```ruby
157
+ Rollout::UI.configure do
158
+ defined_features { MyApplication.defined_feature_names }
159
+ end
160
+ ```
161
+
162
+ The callback returns string or symbol names. Returning `nil` disables checking;
163
+ an empty list marks every stored feature as missing. Definitions are read once
164
+ per HTML request, so changes appear on the next request. Loading errors are not
165
+ treated as missing definitions. JSON responses are unchanged.
166
+
142
167
  ## Concurrent edits
143
168
 
144
169
  Feature forms include a `feature_version` fingerprint of the editable state:
@@ -6,6 +6,7 @@ module Rollout::UI
6
6
  class Config
7
7
  KEYS = %i[
8
8
  instance
9
+ defined_features
9
10
  actor
10
11
  actor_url
11
12
  timestamp_format
@@ -34,6 +34,10 @@ module Rollout::UI
34
34
  config.defined?(:user_search) && config.defined?(:user_lookup)
35
35
  end
36
36
 
37
+ def missing_definition?(feature_name)
38
+ @defined_features && !@defined_features.include?(feature_name.to_s)
39
+ end
40
+
37
41
  def user_search_min_length
38
42
  config.get(:user_search_min_length).to_i.clamp(1, USER_SEARCH_MAX_LENGTH)
39
43
  end
@@ -1,5 +1,5 @@
1
1
  class Rollout
2
2
  module UI
3
- VERSION = "0.10.1"
3
+ VERSION = "0.11.0"
4
4
  end
5
5
  end
@@ -11,6 +11,9 @@
11
11
  <table class="text-sm w-100 text-left w-full">
12
12
  <thead class="font-semibold text-gray-500 border-b border-gray-200">
13
13
  <th class="pb-2 pr-3">Name</th>
14
+ <% if @defined_features %>
15
+ <th class="w-10 px-3" aria-label="Definition"></th>
16
+ <% end %>
14
17
  <th class="pb-2 pr-3">Percentage</th>
15
18
  <th class="pb-2 pr-3">Groups</th>
16
19
  <th class="pb-2 pr-3">Users</th>
@@ -25,9 +28,21 @@
25
28
  <% feature = @rollout.get(feature_name) %>
26
29
  <tr class="border-b border-gray-200">
27
30
  <td class="py-2">
28
- <a class="text-blue-600 hover:text-blue-700 hover:underline" href="<%= h(feature_path(feature.name)) %>"><%= h(feature_name) %></a>
31
+ <a class="text-blue-600 hover:text-blue-700 hover:underline whitespace-no-wrap" href="<%= h(feature_path(feature.name)) %>"><%= h(feature_name) %></a>
29
32
  <div class="text-gray-500 text-xs"><%= h(feature.data['description']) %></div>
30
33
  </td>
34
+ <% if @defined_features %>
35
+ <td class="py-2 px-3">
36
+ <% if missing_definition?(feature_name) %>
37
+ <div class="flex justify-center">
38
+ <span class="definition-warning relative inline-flex text-yellow-800" tabindex="0" role="img" aria-label="Missing definition">
39
+ <%= erb :"features/partials/warning_icon", layout: false %>
40
+ <span class="definition-tooltip hidden absolute right-0 px-3 border border-yellow-700 bg-yellow-100 text-xs text-center leading-normal whitespace-no-wrap rounded-sm" aria-hidden="true">Missing definition</span>
41
+ </span>
42
+ </div>
43
+ <% end %>
44
+ </td>
45
+ <% end %>
31
46
  <td class="py-2 whitespace-no-wrap">
32
47
  <% if feature.percentage == 0 %>
33
48
  <span class="text-gray-400"><%= h(feature.percentage) %>%</span>
@@ -0,0 +1,3 @@
1
+ <svg class="w-4 h-4 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
2
+ <path d="M1.267 15H14.74c.942 0 1.548-.962 1.122-1.773L9.126 1.674c-.465-.899-1.794-.899-2.258 0L.132 13.227C-.281 14.038.325 15 1.267 15m7.794-2.685c0 .6-.451 1.061-1.097 1.061s-1.096-.462-1.096-1.061v-.025c0-.6.451-1.062 1.096-1.062.646 0 1.097.462 1.097 1.062zm-1.69-7.806h1.264c.349 0 .555.288.517.65l-.555 4.696c-.04.337-.271.55-.594.55s-.555-.213-.594-.55l-.555-4.696c-.038-.362.169-.65.517-.65"/>
3
+ </svg>
@@ -4,6 +4,15 @@
4
4
  <pre class="text-red-500"><%= h(params[:error]) %></pre>
5
5
  <% end %>
6
6
  <h2 class="font-semibold text-xl text-gray-500 pt-12"><%= h(@feature.name) %></h2>
7
+ <% if missing_definition?(@feature.name) %>
8
+ <section class="mt-4 p-4 max-w-3xl flex items-center border border-yellow-700 bg-yellow-100 text-yellow-800 text-sm rounded-sm" aria-labelledby="missing-definition-heading">
9
+ <div class="mr-4 flex-shrink-0"><%= erb :"features/partials/warning_icon", layout: false %></div>
10
+ <div>
11
+ <h3 id="missing-definition-heading" class="font-semibold">Missing definition</h3>
12
+ <p class="mt-1 leading-relaxed">This feature has no definition in the current application.</p>
13
+ </div>
14
+ </section>
15
+ <% end %>
7
16
 
8
17
  <div class="w-8 h-1 bg-gray-300 my-10"></div>
9
18
  <main class="p-6 bg-gray-100 max-w-3xl w-full text-sm rounded-sm">
@@ -25,6 +25,13 @@
25
25
  .dark .bg-white { background-color: #333333; }
26
26
  .dark .border-gray-300 { border-color: #555555; }
27
27
  .dark .hover\:bg-gray-800:hover { background-color: #4a4a4a; }
28
+ .dark .bg-yellow-100 { background-color: #352b16; }
29
+ .dark .border-yellow-700 { border-color: #975a16; }
30
+ .dark .text-yellow-800 { color: #fbd38d; }
31
+ .definition-tooltip { bottom: 100%; z-index: 1; padding-top: 10px; padding-bottom: 6px; }
32
+ .definition-warning:hover .definition-tooltip,
33
+ .definition-warning:focus .definition-tooltip { display: block; }
34
+ .definition-warning:focus { outline: 2px solid currentColor; outline-offset: 2px; }
28
35
  </style>
29
36
  </head>
30
37
  <body>
@@ -2,6 +2,7 @@ require "erb"
2
2
  require "json"
3
3
  require "rack"
4
4
  require "rollout"
5
+ require "set"
5
6
 
6
7
  require "rollout/ui/version"
7
8
  require "rollout/ui/config"
@@ -98,6 +99,7 @@ module Rollout::UI
98
99
  end
99
100
  )
100
101
  else
102
+ load_defined_features
101
103
  @history_events = @rollout.respond_to?(:logging) ? @rollout.logging.global_events.reverse : []
102
104
  load_history_user_nicknames(@history_events)
103
105
  render_view(:'features/index')
@@ -120,6 +122,7 @@ module Rollout::UI
120
122
  if json_request?
121
123
  json(feature_to_hash(@feature))
122
124
  else
125
+ load_defined_features
123
126
  load_selected_users
124
127
  @history_events = @rollout.respond_to?(:logging) ? @rollout.logging.events(@feature.name).reverse : []
125
128
  load_history_user_nicknames(@history_events)
@@ -127,6 +130,17 @@ module Rollout::UI
127
130
  end
128
131
  end
129
132
 
133
+ def load_defined_features
134
+ names = if config.defined?(:defined_features)
135
+ config.get(:defined_features)
136
+ elsif Rollout.respond_to?(:definitions)
137
+ path = Rollout.definitions_path
138
+ Rollout.definitions.keys if path && File.directory?(path)
139
+ end
140
+
141
+ @defined_features = names.map(&:to_s).to_set unless names.nil?
142
+ end
143
+
130
144
  def search_users
131
145
  return not_found unless user_selector_enabled?
132
146
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rollout-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - FetLife
@@ -238,6 +238,7 @@ files:
238
238
  - lib/rollout/ui/views/features/partials/event_log.erb
239
239
  - lib/rollout/ui/views/features/partials/user_pagination.erb
240
240
  - lib/rollout/ui/views/features/partials/user_selector.erb
241
+ - lib/rollout/ui/views/features/partials/warning_icon.erb
241
242
  - lib/rollout/ui/views/features/show.erb
242
243
  - lib/rollout/ui/views/layout.erb
243
244
  - lib/rollout/ui/web.rb