lightningff 0.0.2 → 0.0.4

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: 88d8c73c6bac9eb684d13681f741c8c470388aaa6f704d3f7ea4acbd570d810f
4
- data.tar.gz: 9519b22659caf09331f314d99d452040788ddfeb07aaa928e803d7cdb3098673
3
+ metadata.gz: 9d5d292f26ff307055c719d05e47726617b98ad9fe3d1c3c88a2c5842064cda4
4
+ data.tar.gz: a80724414799257c295c954b3ae6d7151764203636e8edbad59ea0f9ad27eb98
5
5
  SHA512:
6
- metadata.gz: fa2041ef7c649e5516f977e03a4a3cc1f1df35a0c735e6263985a99728c0635612ab6bbe0bb1975693ca621d4272b46a72795d351b75ce64311190c31391fb14
7
- data.tar.gz: 66852cdcdeb487ced36b21ed29c93e352c99277d8df384a211f12f9afe6214a165a87dbd819ddc939589e01242931056c6a4d4741c9f11ca86e7ea02beadb774
6
+ metadata.gz: dfdcf7ba3d009b2bc2b25bbdb6d35759448873edb39cf177fbbfa1a8bfc8ce359772ba4e5ed4aaceadbe38bf87804a8c6cf7664e926616a0df61f7c18d3458ab
7
+ data.tar.gz: 7073968c53537f49b35ee82f6f289f770d8ee36468a6186cdb0ac5b066f79d64f1f936cd1904def43838e861826e76397a9807854c572ac166b0bebbf411e4c3
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # ⚡️ Lightning ![Tests](https://github.com/LightningFF/lightning/actions/workflows/run_test.yml/badge.svg) [![Gem Version](https://badge.fury.io/rb/lightningff.svg)](https://badge.fury.io/rb/lightningff)
1
+ # ⚡️ Lightning
2
+
3
+ ![Tests](https://github.com/LightningFF/lightning/actions/workflows/run_test.yml/badge.svg) [![Gem Version](https://badge.fury.io/rb/lightningff.svg)](https://badge.fury.io/rb/lightningff)
4
+
2
5
  An end-to-end feature flagging system that can be setup in <1 minute.
3
6
 
4
7
  Lightning is a rails gem you can install into your Rails application to get both console and UI access to manage feature flags. Lightning saves you time to avoid building an in-house solution.
@@ -11,6 +14,19 @@ gem 'lightningff', require: 'lightning'
11
14
  ```
12
15
  and run `bundle install`. _Note: You might need to run `bundle update` to resolve any incompatible issues with rails._
13
16
 
17
+ #### Script Installation
18
+
19
+ Run the following script to install the entire feature flag functionality without manual setup for your models (i.e. User, Workspace)
20
+ ```ruby
21
+ rails lightning:install User Workspace
22
+ ```
23
+ If you want more control over the setup, run the command below and check out [Manual Installation](#manual-installation) and skip the migrations part
24
+ ```ruby
25
+ rails lightning:install
26
+ ```
27
+
28
+ #### Manual Installation
29
+
14
30
  Set up feature flag migrations by running the following lines
15
31
  ```bash
16
32
  bin/rails lightning:install:migrations
@@ -28,9 +44,25 @@ class User < ApplicationRecord
28
44
  end
29
45
  ```
30
46
 
31
- To check feature availability for entity: `Lightning::Feature.enabled?(user, <feature_key>)`
47
+ ## Usage
32
48
 
33
- ### UI setup
49
+ ```ruby
50
+ user = User.create(name: 'Dummy user')
51
+
52
+ # Check if feature is enabled for entity
53
+ Lightning.enabled?(user, 'homepage_v2')
54
+
55
+ # Opt in an entity
56
+ Lightning.opt_in('homepage_v2', user)
57
+
58
+ # Opt out an entity
59
+ Lightning.opt_out('homepage_v2', user)
60
+
61
+ # List entities opted in to a feature
62
+ Lightning.opt_ins('hompage_v2')
63
+ ```
64
+
65
+ ## UI setup
34
66
 
35
67
  Mount engine in `routes.rb` file
36
68
  ```ruby
@@ -39,31 +71,36 @@ Rails.application.routes.draw do
39
71
  end
40
72
  ```
41
73
 
42
- ## Rails Console API Usage
74
+ ## API
75
+
76
+ **Create feature (state is disabled)**
43
77
 
44
78
  ```ruby
45
- ### Feature Management
46
- # Create feature (state is disabled)
47
79
  Lightning.create!('homepage_v2', 'New homepage with better logo')
48
- # Get feature by key
80
+ ```
81
+
82
+ **Find a feature by key**
83
+
84
+ ```ruby
49
85
  Lightning.get('homepage_v2')
50
- # List all features
86
+ ```
87
+
88
+ **List all features**
89
+
90
+ ```ruby
51
91
  Lightning.list
52
- # Update feature state/description
53
- Lightning.update('homepage_v2', {state: 'enabled_per_entity', description: 'Homepage with new nav'})
54
- # Delete feature
55
- Lightning.delete('homepage_v2')
92
+ ```
56
93
 
57
- ### Feature Permissions Management
58
- u = User.create(name: 'Dummy user')
59
- # Add entity to feature
60
- Lightning.enable_entity('homepage_v2', u)
61
- # List entities for feature
62
- Lightning.entities
63
- # Remove entity to feature
64
- Lightning.remove_entity('homepage_v2', u)
65
- # Check if feature is enabled for entity
66
- Lightning.enabled?(u, 'homepage_v2')
94
+ **Update feature state/description**
95
+
96
+ ```ruby
97
+ Lightning.update('homepage_v2', { state: 'enabled_per_entity', description: 'Homepage with new nav' })
98
+ ```
99
+
100
+ **Delete feature**
101
+
102
+ ```ruby
103
+ Lightning.delete('homepage_v2')
67
104
  ```
68
105
 
69
106
  ## Advanced Configuration
@@ -19,19 +19,28 @@ html {
19
19
  }
20
20
 
21
21
  .fw-monospace {
22
- font-family: monospace;
22
+ font-family: "Courier New", Courier, monospace;
23
23
  }
24
24
 
25
- .text-underline-on-hover {
25
+ .text-decoration-underline-on-hover {
26
26
  text-decoration: none;
27
27
  }
28
28
 
29
- .text-underline-on-hover:hover {
29
+ .text-decoration-underline-on-hover:hover {
30
30
  text-decoration: underline;
31
31
  }
32
32
 
33
+ .fs-7 {
34
+ font-size: 14px;
35
+ }
36
+
37
+ .fs-8 {
38
+ font-size: 12px;
39
+ }
40
+
33
41
  .lightning-logo {
34
42
  font-size: 18px;
35
43
  font-weight: bolder;
36
44
  letter-spacing: -0.5px;
45
+ text-decoration: none;
37
46
  }
@@ -5,14 +5,18 @@ module Lightning
5
5
 
6
6
  def create
7
7
  @feature = Feature.find(params[:feature_id])
8
- @feature_opt_in = @feature.feature_opt_ins.new
9
- @feature_opt_in.entity_id = params[:feature_opt_in][:entity_id]
10
- @feature_opt_in.entity_type = params[:feature_opt_in][:entity_type]
11
- if @feature_opt_in.save
12
- flash[:notice] = "Permissions has been created!"
13
- else
14
- flash[:notice] = "FAILED TO SAVE PERMISSIONS! INVALID ENTITY ID!"
8
+ entity_ids = params[:feature_opt_in][:entity_id].split(",")
9
+ entity_class = params[:feature_opt_in][:entity_type].constantize
10
+
11
+ entity_ids.each do |entity_id|
12
+ entity = entity_class.find(entity_id)
13
+ @feature.feature_opt_ins.find_or_create_by!(entity_id: entity.id, entity_type: entity_class.to_s)
15
14
  end
15
+
16
+ flash[:notice] = "Permissions has been created!"
17
+ redirect_to @feature
18
+ rescue => e
19
+ flash[:notice] = "Failed to opt in some entities: #{e.message}"
16
20
  redirect_to @feature
17
21
  end
18
22
 
@@ -24,6 +28,7 @@ module Lightning
24
28
  end
25
29
 
26
30
  private
31
+
27
32
  def feature_opt_ins_params
28
33
  params.require(:feature_opt_in).permit(:entity_id, :entity_type)
29
34
  end
@@ -4,26 +4,21 @@ module Lightning
4
4
  class FeaturesController < ApplicationController
5
5
  before_action :set_feature, only: [:show, :edit, :update, :destroy]
6
6
 
7
- # GET /features
8
7
  def index
9
8
  @features = Feature.all
10
9
  end
11
- #
12
- # GET /features/1
10
+
13
11
  def show
14
12
  @flaggable_entities = Lightning.flaggable_entities
15
13
  end
16
14
 
17
- # GET /features/new
18
15
  def new
19
16
  @feature = Feature.new
20
17
  end
21
18
 
22
- # GET /feature/1/edit
23
19
  def edit
24
20
  end
25
21
 
26
- # POST /features
27
22
  def create
28
23
  @feature = Feature.new(feature_params)
29
24
 
@@ -34,7 +29,6 @@ module Lightning
34
29
  end
35
30
  end
36
31
 
37
- # PATCH/PUT /features/1
38
32
  def update
39
33
  if @feature.update(feature_params)
40
34
  redirect_to @feature, notice: 'Feature was successfully updated.'
@@ -43,19 +37,17 @@ module Lightning
43
37
  end
44
38
  end
45
39
 
46
- # DELETE /features/1
47
40
  def destroy
48
41
  @feature.destroy
49
42
  redirect_to features_url, notice: 'Feature was successfully destroyed.'
50
43
  end
51
44
 
52
45
  private
53
- # Use callbacks to share common setup or constraints between actions.
46
+
54
47
  def set_feature
55
48
  @feature = Feature.find(params[:id])
56
49
  end
57
50
 
58
- # Only allow a list of trusted parameters through.
59
51
  def feature_params
60
52
  params.require(:feature).permit(:key, :description, :state)
61
53
  end
@@ -1,7 +1,11 @@
1
1
  module Lightning
2
2
  module Flaggable
3
3
  def lightning_display
4
- "#{self.id} - #{self.name} (#{self.class.to_s})"
4
+ if self.respond_to?(:name)
5
+ "#{self.id} - #{self.name}"
6
+ else
7
+ self.id
8
+ end
5
9
  end
6
10
  end
7
11
  end
@@ -7,7 +7,16 @@ module Lightning
7
7
  belongs_to :entity, polymorphic: true
8
8
  before_validation :set_entity
9
9
 
10
+ def entity_name
11
+ if entity.respond_to?(:lightning_display)
12
+ entity.lightning_display
13
+ else
14
+ entity.id
15
+ end
16
+ end
17
+
10
18
  private
19
+
11
20
  def set_entity
12
21
  self.entity = self.entity_type.constantize.find_by_id(self.entity_id)
13
22
  end
@@ -4,11 +4,11 @@
4
4
  <title>Lightning — Feature Flags</title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
-
8
7
  <%= stylesheet_link_tag "lightning/application", media: "all" %>
8
+
9
9
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
10
11
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
11
-
12
12
  </head>
13
13
 
14
14
  <body>
@@ -0,0 +1,14 @@
1
+ <div class="bg-light border-bottom p-3 mb-3">
2
+ <div class="container d-flex justify-content-between align-items-center">
3
+ <%= link_to '⚡️ Lightning', features_path, class: 'lightning-logo text-reset' %>
4
+
5
+ <div class="d-flex align-items-center gap-3">
6
+ <%= link_to "https://github.com/LightningFF/lightning", class: "text-decoration-underline-on-hover text-muted fs-5", target: "_blank" do %>
7
+ <i class="bi-github"></i>
8
+ <% end %>
9
+ <% if show_new_feature_button %>
10
+ <%= link_to 'New Feature', new_feature_path, class: "btn btn-primary btn-sm" %>
11
+ <% end %>
12
+ </div>
13
+ </div>
14
+ </div>
@@ -1,37 +1,49 @@
1
- <%= form_with(model: feature) do |form| %>
2
- <% if feature.errors.any? %>
3
- <div id="error_explanation">
4
- <h2><%= pluralize(feature.errors.count, "error") %> prohibited this feature from being saved:</h2>
1
+ <%= form_with(model: feature, class: "d-grid") do |form| %>
2
+ <div class="col-4">
3
+ <% if feature.errors.any? %>
4
+ <div id="error_explanation">
5
+ <h2><%= pluralize(feature.errors.count, "error") %> prohibited this feature from being saved:</h2>
5
6
 
6
- <ul>
7
- <% feature.errors.each do |error| %>
8
- <li><%= error.full_message %></li>
9
- <% end %>
10
- </ul>
11
- </div>
12
- <% end %>
13
-
14
- <div class="field">
15
- <%= form.label :key %>
16
- <%= form.text_field :key %>
17
- </div>
7
+ <ul>
8
+ <% feature.errors.each do |error| %>
9
+ <li><%= error.full_message %></li>
10
+ <% end %>
11
+ </ul>
12
+ </div>
13
+ <% end %>
18
14
 
19
- <div class="field">
20
- <%= form.label :description %>
21
- <%= form.text_area :description %>
22
- </div>
15
+ <% if @feature.new_record? %>
16
+ <div class="mb-2">
17
+ <%= form.label :key, "Name", class: "form-label" %>
18
+ <%= form.text_field :key, class: "form-control" %>
19
+ </div>
20
+ <% end %>
23
21
 
24
- <div class="field">
25
- <%= form.label :state %>
26
- <%= form.select :state, Lightning::Feature.states.keys %>
27
- </div>
22
+ <div class="mb-2">
23
+ <%= form.label :description, class: "form-label" %>
24
+ <%= form.text_area :description, class: "form-control" %>
25
+ </div>
28
26
 
29
- <!-- <div class="field">-->
30
- <%#= form.label :globally_enabled %>
31
- <%#= form.text_area :globally_enabled %>
32
- <!-- </div>-->
27
+ <div class="mb-2">
28
+ <% if @feature.new_record? %>
29
+ <span class="text-muted">After saving your feature flag, you can enable it.</span>
30
+ <% else %>
31
+ <%= form.label :state, class: "form-label" %>
32
+ <%= form.select :state, [
33
+ ["Globally disabled", "disabled"],
34
+ ["Globally enabled (regardless of opt-ins)", "enabled_globally"],
35
+ ["Enabled only for opt-ins", "enabled_per_entity"],
36
+ ], {}, { class: "form-select" } %>
37
+ <% end %>
38
+ </div>
33
39
 
34
- <div class="actions">
35
- <%= form.submit %>
40
+ <div>
41
+ <%= form.submit 'Save', class: "btn btn-primary" %>
42
+ <% if @feature.new_record? %>
43
+ <%= link_to 'Cancel', features_path, class: "btn btn-light" %>
44
+ <% else %>
45
+ <%= link_to 'Cancel', feature_path(feature), class: "btn btn-light" %>
46
+ <% end%>
47
+ </div>
36
48
  </div>
37
- <% end %>
49
+ <% end %>
@@ -1,7 +1,15 @@
1
- <h1>Editing Feature</h1>
1
+ <%= render partial: "lightning/header", locals: { show_new_feature_button: false } %>
2
2
 
3
- <%= render 'form', feature: @feature %>
4
- <br/>
3
+ <div class="container">
4
+ <div class="d-grid align-items-center gap-1 d-md-block border-bottom pb-2 mb-4">
5
+ <%= link_to feature_path(@feature), class: 'btn btn-light btn-sm' do %>
6
+ <i class="bi-arrow-left-short"></i> <%= @feature.key %>
7
+ <% end %>
8
+ </div>
5
9
 
6
- <%= link_to 'Show', @feature %> |
7
- <%= link_to 'Back', features_path %>
10
+ <h1 class="fw-monospace fw-bold">
11
+ <%= @feature.key %>
12
+ </h1>
13
+
14
+ <%= render 'form', feature: @feature %>
15
+ </div>
@@ -1,31 +1,34 @@
1
+ <%= render partial: "lightning/header", locals: { show_new_feature_button: true } %>
2
+
1
3
  <div class="container">
2
4
  <p id="notice"><%= notice %></p>
3
5
 
4
- <div class="d-flex justify-content-between align-items-center">
5
- <div class="lightning-logo">⚡️ Lightning</div>
6
- <%= link_to '+ New Feature', new_feature_path, class: "btn btn-primary" %>
7
- </div>
8
-
9
- <table class="table mt-5">
10
- <tbody>
11
- <% @features.each do |feature| %>
12
- <tr>
13
- <td>
14
- <% if feature.enabled_per_entity? %>
15
- <span class="badge bg-primary"><%= pluralize feature.feature_opt_ins.count, "opt in" %></span>
16
- <% elsif feature.enabled_globally? %>
17
- <span class="badge bg-success">On</span>
18
- <% else %>
19
- <span class="badge bg-secondary">Off</span>
20
- <% end %>
21
- </td>
22
- <td>
23
- <%= link_to feature.key, feature, class: "fw-monospace fw-bold text-underline-on-hover text-reset" %>
24
- </td>
25
- <td><%= feature.description %></td>
26
- <td><%= feature.created_at.to_formatted_s(:short) %></td>
27
- </tr>
28
- <% end %>
29
- </tbody>
30
- </table>
6
+ <%if @features.empty? %>
7
+ <p class="lead">
8
+ ⛳️ No features created yet.
9
+ </p>
10
+ <% else %>
11
+ <table class="table">
12
+ <tbody>
13
+ <% @features.each do |feature| %>
14
+ <tr>
15
+ <td>
16
+ <% if feature.enabled_per_entity? %>
17
+ <span class="badge bg-primary"><%= pluralize feature.feature_opt_ins.count, "opt in" %></span>
18
+ <% elsif feature.enabled_globally? %>
19
+ <span class="badge bg-success">On</span>
20
+ <% else %>
21
+ <span class="badge bg-secondary">Off</span>
22
+ <% end %>
23
+ </td>
24
+ <td>
25
+ <%= link_to feature.key, feature, class: "fw-monospace fw-bold text-decoration-underline-on-hover text-reset" %>
26
+ </td>
27
+ <td><%= feature.description %></td>
28
+ <td><%= feature.created_at.to_formatted_s(:short) %></td>
29
+ </tr>
30
+ <% end %>
31
+ </tbody>
32
+ </table>
33
+ <% end %>
31
34
  </div>
@@ -1,6 +1,15 @@
1
- <h1>New Feature</h1>
1
+ <%= render partial: "lightning/header", locals: { show_new_feature_button: false } %>
2
2
 
3
- <%= render 'form', feature: @feature %>
4
- <br/>
3
+ <div class="container">
4
+ <div class="d-grid align-items-center gap-1 d-md-block border-bottom pb-2 mb-4">
5
+ <%= link_to features_path, class: 'btn btn-light btn-sm' do %>
6
+ <i class="bi-arrow-left-short"></i> All features
7
+ <% end %>
8
+ </div>
5
9
 
6
- <%= link_to 'Back', features_path %>
10
+ <h3 class="fw-bold">
11
+ New Feature
12
+ </h3>
13
+
14
+ <%= render 'form', feature: @feature %>
15
+ </div>
@@ -1,39 +1,99 @@
1
- <p id="notice"><%= notice %></p>
1
+ <%= render partial: "lightning/header", locals: { show_new_feature_button: false } %>
2
2
 
3
- <td><%= link_to 'Edit', edit_feature_path(@feature) %></td>
4
- <td><%= button_to 'Destroy', feature_path(@feature), method: :delete, data: { confirm: 'Are you sure?' } %></td>
3
+ <div class="container">
4
+ <p id="notice"><%= notice %></p>
5
5
 
6
- <p>
7
- <strong>Key:</strong>
8
- <%= @feature.key %>
9
- </p>
6
+ <div class="d-grid align-items-center gap-1 d-md-block border-bottom pb-2 mb-4">
7
+ <%= link_to features_path, class: 'btn btn-light btn-sm' do %>
8
+ <i class="bi-arrow-left-short"></i> All features
9
+ <% end %>
10
+ <span class="text-light px-2">|</span>
11
+ <%= link_to 'Edit', edit_feature_path(@feature), class: "btn btn-light btn-sm" %>
12
+ <div class="btn-group">
13
+ <button type="button" class="btn btn-light btn-sm dropdown-toggle ms-2" data-bs-toggle="dropdown" aria-expanded="false">
14
+ More
15
+ </button>
16
+ <ul class="dropdown-menu">
17
+ <li>
18
+ <%= button_to 'Delete', feature_path(@feature), class: "dropdown-item", method: :delete, data: { confirm: 'Are you sure?' } %>
19
+ </li>
20
+ </ul>
21
+ </div>
22
+ </div>
10
23
 
11
- <p>
12
- <strong>Description:</strong>
13
- <%= @feature.description %>
14
- </p>
24
+ <h1 class="fw-monospace fw-bold mb-1">
25
+ <%= @feature.key %>
26
+ </h1>
27
+ <div class="fs-6 mb-2">
28
+ <%= @feature.description %>
29
+ </div>
30
+ <div class="d-flex align-items-baseline gap-2">
31
+ <div class="fs-5">
32
+ <% if @feature.enabled_per_entity? %>
33
+ <span class="badge bg-primary">
34
+ <%= pluralize @feature.feature_opt_ins.count, "opt in" %>
35
+ </span>
36
+ <% elsif @feature.enabled_globally? %>
37
+ <span class="badge bg-success">Globally enabled</span>
38
+ <% else %>
39
+ <span class="badge bg-secondary">Off</span>
40
+ <% end %>
41
+ </div>
42
+ <div class="text-muted">•</div>
43
+ <div class="text-muted fs-8">
44
+ Created <%= @feature.created_at.to_formatted_s(:short) %>
45
+ </div>
46
+ <div class="text-muted">•</div>
47
+ <div class="text-muted fs-8">
48
+ Last updated <%= @feature.updated_at.to_formatted_s(:short) %>
49
+ </div>
50
+ </div>
15
51
 
16
- <p>
17
- <strong>State:</strong>
18
- <%= @feature.state %>
19
- </p>
52
+ <div class="mt-5">
53
+ <%= form_with model: [@feature, @feature.feature_opt_ins.build] do |form| %>
54
+ <div>
55
+ <label class="form-label">Enable feature for entities</label>
56
+ </div>
57
+ <div class="row g-3">
58
+ <div class="col-auto">
59
+ <%=
60
+ form.select :entity_type,
61
+ options_for_select(@flaggable_entities.map { |entity| [ entity.to_s, entity.to_s] }),
62
+ {},
63
+ { class: "form-select" }
64
+ %>
65
+ </div>
66
+ <div class="col-4">
67
+ <%= form.text_field :entity_id, placeholder: 'comma separated entity ids', class: 'form-control' %>
68
+ </div>
69
+ <div class="col-auto">
70
+ <%= form.submit 'Save', class: "btn btn-primary" %>
71
+ </div>
72
+ </div>
73
+ <% end %>
74
+ </div>
20
75
 
76
+ <div class="mt-5">
77
+ <h3>
78
+ Opt Ins
79
+ <small class="text-muted">(<%= @feature.feature_opt_ins.count %>)</small>
80
+ </h3>
21
81
 
22
- <p>
23
- <strong>Created:</strong>
24
- <%= @feature.created_at.to_formatted_s(:long) %>
25
- </p>
26
-
27
- <p>
28
- <strong>Updated:</strong>
29
- <%= @feature.updated_at.to_formatted_s(:long) %>
30
- </p>
31
-
32
-
33
- <h3>Opt In Entities</h3>
34
- <%= render @feature.feature_opt_ins %>
35
- <%= render "lightning/feature_opt_ins/form" %>
36
- <br/>
37
-
38
- <%= link_to 'Edit', edit_feature_path(@feature) %> |
39
- <%= link_to 'Back', features_path %>
82
+ <% if @feature.feature_opt_ins.reject(&:new_record?).empty? %>
83
+ <p class="lead text-muted">👌 No entities opted in yet.</p>
84
+ <% else %>
85
+ <table class="table table-sm">
86
+ <% @feature.feature_opt_ins.each do |feature_opt_in| %>
87
+ <% next if feature_opt_in.new_record? %>
88
+ <tbody>
89
+ <tr>
90
+ <td><code><%= feature_opt_in.entity.class %></code><td>
91
+ <td><%= feature_opt_in.entity_name %></td>
92
+ <td><%= button_to 'Opt out', [feature_opt_in.feature, feature_opt_in], class: "btn btn-light btn-sm", method: :delete, data: { confirm: 'Are you sure?' } %></td>
93
+ </tr>
94
+ </tbody>
95
+ <% end %>
96
+ </table>
97
+ <% end %>
98
+ </div>
99
+ </div>
@@ -1,4 +1,4 @@
1
- class CreateFeatureFlagFramework < ActiveRecord::Migration[6.1]
1
+ class CreateFeatureFlagFramework < ActiveRecord::Migration[5.0]
2
2
  def change
3
3
  create_table :lightning_features do |t|
4
4
  t.string :key, index: { unique: true }, null: false
@@ -17,5 +17,6 @@ class CreateFeatureFlagFramework < ActiveRecord::Migration[6.1]
17
17
  end
18
18
 
19
19
  add_index :lightning_feature_opt_ins, [:entity_id, :entity_type]
20
+ add_index :lightning_feature_opt_ins, [:feature_id, :entity_id, :entity_type], unique: true, name: 'opt_in_index'
20
21
  end
21
22
  end
data/lib/lightning.rb CHANGED
@@ -12,6 +12,6 @@ module Lightning
12
12
  end
13
13
 
14
14
  class << self
15
- delegate :create!, :get, :list, :update, :delete, :entities, :enable_entity, :remove_entity, :enabled?, to: Api
15
+ delegate :create!, :get, :list, :update, :delete, :opt_ins, :opt_in, :opt_out, :enabled?, to: Api
16
16
  end
17
17
  end
data/lib/lightning/api.rb CHANGED
@@ -28,29 +28,22 @@ module Lightning
28
28
  get(key).destroy
29
29
  end
30
30
 
31
- def self.entities(key)
31
+ def self.opt_ins(key)
32
32
  get(key).feature_opt_ins.all.map(&:entity)
33
33
  end
34
34
 
35
- def self.enable_entity(key, entity)
36
- permissioned_entity = get(key).feature_opt_ins.new
37
- permissioned_entity.entity_id = entity.id
38
- permissioned_entity.entity_type = entity.class.to_s
39
- permissioned_entity.save!
35
+ def self.opt_in(key, entity)
36
+ get(key).feature_opt_ins.find_or_create_by!(entity_id: entity.id, entity_type: entity.class.to_s)
40
37
  end
41
38
 
42
- def self.remove_entity(key, entity)
39
+ def self.opt_out(key, entity)
43
40
  get(key).feature_opt_ins.find_by(entity_id: entity.id, entity_type: entity.class.to_s)&.destroy
44
41
  rescue ActiveRecord::RecordNotFound
45
42
  raise ::Lightning::Errors::EntityNotFound, "Could not find entity with id #{entity.id} and type #{entity.class.to_s}"
46
43
  end
47
44
 
48
- def self.enabled?(entity, feature_key)
49
- joined_table = Feature.left_outer_joins(:feature_opt_ins)
50
- joined_table
51
- .where(key: feature_key, state: :enabled_per_entity, feature_opt_ins: { entity_id: entity.id, entity_type: entity.class.to_s })
52
- .or(joined_table.where(key: feature_key, state: :enabled_globally))
53
- .exists?
45
+ def self.enabled?(feature_key, entity)
46
+ Feature.where(key: feature_key).left_outer_joins(:feature_opt_ins).where("state = ? OR (state = ? AND lightning_feature_opt_ins.entity_id = ? AND lightning_feature_opt_ins.entity_type = ?)", Feature.states[:enabled_globally], Feature.states[:enabled_per_entity], entity.id, entity.class.to_s).exists?
54
47
  end
55
48
  end
56
49
  end
@@ -3,7 +3,7 @@ module Lightning
3
3
  isolate_namespace Lightning
4
4
 
5
5
  initializer "lightning.assets.precompile" do |app|
6
- app.config.assets.precompile += %w( lightning/application.css )
6
+ app.config.assets.precompile += %w(lightning/application.css lightning/favicon.png)
7
7
  end
8
8
 
9
9
  config.generators do |g|
@@ -1,3 +1,3 @@
1
1
  module Lightning
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.4'
3
3
  end
@@ -3,15 +3,13 @@ namespace :lightning do
3
3
  task :install do
4
4
  _, *args = ARGV
5
5
 
6
-
7
- # Task goes here
8
- puts 'Copying migrations from engines...'
6
+ puts '️⚡️ Copying migrations'
9
7
  `bin/rails lightning:install:migrations`
10
8
 
11
- puts 'Running engine migrations...'
9
+ puts '⚡️ Running migrations'
12
10
  `bin/rails db:migrate SCOPE=lightning`
13
11
 
14
- puts 'Creating initializers script'
12
+ puts '⚡️ Creating lightning initializer'
15
13
  `echo "Lightning.flaggable_entities = [#{args.map{ |a| '\"' + a + '\"'}.join(", ")}]" > config/initializers/lightning.rb`
16
14
 
17
15
  args.each do |model|
@@ -43,9 +41,7 @@ namespace :lightning do
43
41
  end
44
42
 
45
43
 
46
- puts 'Successfully ran setup script'
47
-
48
- # Don't run the tasks in the arguments
44
+ puts '⚡️ Lightning setup complete'
49
45
  exit
50
46
  end
51
47
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightningff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruthwick Pathireddy
@@ -9,28 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-05-27 00:00:00.000000000 Z
12
+ date: 2021-06-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
19
- - !ruby/object:Gem::Version
20
- version: 6.1.3
21
18
  - - ">="
22
19
  - !ruby/object:Gem::Version
23
- version: 6.1.3.2
20
+ version: 5.0.0
24
21
  type: :runtime
25
22
  prerelease: false
26
23
  version_requirements: !ruby/object:Gem::Requirement
27
24
  requirements:
28
- - - "~>"
29
- - !ruby/object:Gem::Version
30
- version: 6.1.3
31
25
  - - ">="
32
26
  - !ruby/object:Gem::Version
33
- version: 6.1.3.2
27
+ version: 5.0.0
34
28
  description: With Lightning, you can set up an end-to-end highly customizable feature
35
29
  flagging system in <1 minute. It provides console and UI support for feature flag
36
30
  management.
@@ -45,6 +39,7 @@ files:
45
39
  - README.md
46
40
  - Rakefile
47
41
  - app/assets/config/lightning_manifest.js
42
+ - app/assets/images/lightning/favicon.png
48
43
  - app/assets/stylesheets/lightning/application.css
49
44
  - app/controllers/lightning/application_controller.rb
50
45
  - app/controllers/lightning/feature_opt_ins_controller.rb
@@ -57,8 +52,7 @@ files:
57
52
  - app/models/lightning/feature.rb
58
53
  - app/models/lightning/feature_opt_in.rb
59
54
  - app/views/layouts/lightning/application.html.erb
60
- - app/views/lightning/feature_opt_ins/_feature_opt_in.html.erb
61
- - app/views/lightning/feature_opt_ins/_form.html.erb
55
+ - app/views/lightning/_header.html.erb
62
56
  - app/views/lightning/features/_form.html.erb
63
57
  - app/views/lightning/features/edit.html.erb
64
58
  - app/views/lightning/features/index.html.erb
@@ -1,3 +0,0 @@
1
- <div>
2
- <%= feature_opt_in.entity.lightning_display %>
3
- <%= button_to 'Destroy', [feature_opt_in.feature, feature_opt_in], method: :delete, data: { confirm: 'Are you sure?' } %></div>
@@ -1,12 +0,0 @@
1
- <h3>Permission Entities</h3>
2
- <%= form_with model: [@feature, @feature.feature_opt_ins.build] do |form| %>
3
- <p>
4
- <%= form.label :entity_type, 'Select Entity Type' %><br>
5
- <%= form.select :entity_type, options_for_select(@flaggable_entities.map {|u| [ u.to_s, u.to_s] }) %>
6
- </p>
7
- <p>
8
- <%= form.label :entity_id, 'Entity Id' %><br>
9
- <%= form.text_field :entity_id %>
10
- </p>
11
- <%= form.submit 'Add entity to feature' %>
12
- <% end %>