bullet_train 1.1.7 → 1.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 398b4fbeb7a7498078dfce7f74bdb6518763246d4dac6186fe751cbbafc6ddeb
4
- data.tar.gz: 5b98cfa4d24635d9a2290637ef2ae4a16614943c59dad22669032c23f4f3b1b1
3
+ metadata.gz: 861c6f60ec10ff83389d2ac53d5a702dd230db869b890b6e46f84896fecb8b7b
4
+ data.tar.gz: 55d51e53583ef3e2bdd179f821e7a48920483acea55adb818a736ca411106a70
5
5
  SHA512:
6
- metadata.gz: 84b8c473cdf360347dd498dd3ae3721ae0b776587e6f93da18b289c59ed470d7795a47fa7ee73c214d2800a636ead41ee8666ce9bdd4d22364385e0075c91a02
7
- data.tar.gz: f7e34ab6d0112a3cddf5a3ad18e4c740c9fecdd9b46ccb24ad34cac6aadab0269aece837c3abcf03738558aef4e86ee0d190dad0ce42632e0dbdab01f437d08f
6
+ metadata.gz: a5ddb5c77373a119154c1f1fd1993b9f89723dd0239f4e71402e3c7399100f38734edbd0bb9768d2545e607d98281da486ab7ac6c2d55e3b06da902eceebc4a3
7
+ data.tar.gz: 47337cee5019ad52c09e325cb9ef035b24d59da94083337a817cc45342ea0c0feac586e115ac793b3bd802e20e1b985aecd479b603dbed2b1765dbaf2638333a
@@ -1,5 +1,6 @@
1
1
  module Account::Teams::ControllerBase
2
2
  extend ActiveSupport::Concern
3
+ extend Controllers::Base
3
4
 
4
5
  included do
5
6
  load_and_authorize_resource :team, class: "Team", prepend: true,
@@ -21,7 +22,9 @@ module Account::Teams::ControllerBase
21
22
 
22
23
  private
23
24
 
24
- include strong_parameters_from_api
25
+ if defined?(Api::V1::ApplicationController)
26
+ include strong_parameters_from_api
27
+ end
25
28
  end
26
29
 
27
30
  # GET /teams
@@ -3,8 +3,8 @@ module DocumentationSupport
3
3
 
4
4
  def docs
5
5
  target = params[:page].presence || "index"
6
- files = `find -L tmp/gems/*/docs | grep \.md`.lines.map(&:chomp).sort
7
- @file = files.detect { |file| file.include?(target) }
6
+ all_paths = ([Rails.root.to_s] + `bundle show --paths`.lines.map(&:chomp))
7
+ @path = all_paths.map { |path| path + "/docs/#{target}.md" }.detect { |path| File.exist?(path) }
8
8
  render :docs, layout: "docs"
9
9
  end
10
10
  end
@@ -6,6 +6,7 @@ import ClipboardController from './clipboard_controller'
6
6
  import FormController from './form_controller'
7
7
  import MobileMenuController from './mobile_menu_controller'
8
8
  import TextToggleController from './text_toggle_controller'
9
+ import SelectAllController from './select_all_controller'
9
10
 
10
11
  export const controllerDefinitions = [
11
12
  [BulkActionFormController, 'bulk_action_form_controller.js'],
@@ -14,6 +15,7 @@ export const controllerDefinitions = [
14
15
  [FormController, 'form_controller.js'],
15
16
  [MobileMenuController, 'mobile_menu_controller.js'],
16
17
  [TextToggleController, 'text_toggle_controller.js'],
18
+ [SelectAllController, 'select_all_controller.js'],
17
19
  ].map(function(d) {
18
20
  const key = d[1]
19
21
  const controller = d[0]
@@ -0,0 +1,82 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = [ "checkbox", "toggleCheckbox", "toggleLabel", "wrapper" ]
5
+ static classes = [ "unavailable" ]
6
+
7
+ connect() {
8
+ this.enableSelectAll()
9
+ }
10
+
11
+ enableSelectAll() {
12
+ if (!this.hasWrapperTarget) { return }
13
+ if (!this.hasUnavailableClass) { return }
14
+
15
+ this.wrapperTarget.classList.remove(this.unavailableClass)
16
+ this.updateToggle()
17
+ }
18
+
19
+ selectAllOrNone(event) {
20
+ event.preventDefault()
21
+ event.stopPropagation()
22
+ if (this.allSelected) {
23
+ this.selectNone()
24
+ } else {
25
+ this.selectAll()
26
+ }
27
+ this.updateToggle()
28
+ this.dispatch('toggled')
29
+ }
30
+
31
+ selectAll() {
32
+ this.checkboxTargets.forEach(checkbox => {
33
+ checkbox.checked = true
34
+ })
35
+ }
36
+
37
+ selectNone() {
38
+ this.checkboxTargets.forEach(checkbox => {
39
+ checkbox.checked = false
40
+ })
41
+ }
42
+
43
+ updateToggle() {
44
+ let checkbox = this.toggleCheckboxTarget
45
+ let useAlternateLabel = false
46
+
47
+ if (this.allSelected) {
48
+ if (checkbox) {
49
+ checkbox.checked = true
50
+ checkbox.indeterminate = false
51
+ }
52
+ useAlternateLabel = true
53
+ } else if (this.selectedValues.length > 0) {
54
+ if (checkbox) {
55
+ checkbox.indeterminate = true
56
+ }
57
+ } else {
58
+ if (checkbox) {
59
+ checkbox.checked = false
60
+ checkbox.indeterminate = false
61
+ }
62
+ }
63
+
64
+ if (this.hasToggleLabelTarget) {
65
+ this.toggleLabelTarget.dispatchEvent(new CustomEvent(`${this.identifier}:toggle-select-all-label`, { detail: { useAlternate: useAlternateLabel }} ))
66
+ }
67
+ }
68
+
69
+ get selectedValues() {
70
+ let values = []
71
+ this.checkboxTargets.forEach(checkbox => {
72
+ if (checkbox.checked) {
73
+ values.push(checkbox.value)
74
+ }
75
+ })
76
+ return values
77
+ }
78
+
79
+ get allSelected() {
80
+ return this.selectedValues.length === this.checkboxTargets.length
81
+ }
82
+ }
@@ -1,6 +1,6 @@
1
- <% @body = markdown(File.read(Rails.root.to_s + "/#{@file}").gsub('.md)', ')')) %>
1
+ <% @body = markdown(File.read(@path).gsub('.md)', ')')) %>
2
2
 
3
- <% if @file == "tmp/gems/bullet_train/docs/index.md" %>
3
+ <% if @path.include?("docs/index.md") %>
4
4
  <% header, groups = @body.split("<h2>", 2) %>
5
5
  <%= header.html_safe %>
6
6
 
@@ -6,38 +6,34 @@ en:
6
6
  bullet_train-api:
7
7
  git: "bullet-train-co/bullet_train-api"
8
8
  bullet_train-fields:
9
- git: "bullet-train-co/bullet_train-fields"
9
+ git: "bullet-train-co/bullet_train-core"
10
10
  npm: "@bullet-train/fields"
11
11
  bullet_train-has_uuid:
12
- git: "bullet-train-co/bullet_train-has_uuid"
12
+ git: "bullet-train-co/bullet_train-core"
13
13
  bullet_train-incoming_webhooks:
14
- git: "bullet-train-co/bullet_train-incoming_webhooks"
14
+ git: "bullet-train-co/bullet_train-core"
15
15
  bullet_train-integrations:
16
- git: "bullet-train-co/bullet_train-integrations"
16
+ git: "bullet-train-co/bullet_train-core"
17
17
  bullet_train-integrations-stripe:
18
- git: "bullet-train-co/bullet_train-base-integrations-stripe"
18
+ git: "bullet-train-co/bullet_train-core"
19
19
  bullet_train-obfuscates_id:
20
- git: "bullet-train-co/bullet_train-obfuscates_id"
20
+ git: "bullet-train-co/bullet_train-core"
21
21
  bullet_train-outgoing_webhooks:
22
22
  git: "bullet-train-co/bullet_train-outgoing_webhooks"
23
- bullet_train-outgoing_webhooks-core:
24
- git: "bullet-train-co/bullet_train-outgoing_webhooks-core"
25
23
  bullet_train-scope_questions:
26
- git: "bullet-train-co/bullet_train-scope_questions"
24
+ git: "bullet-train-co/bullet_train-core"
27
25
  bullet_train-scope_validator:
28
- git: "bullet-train-co/bullet_train-scope_validator"
26
+ git: "bullet-train-co/bullet_train-core"
29
27
  bullet_train-sortable:
30
- git: "bullet-train-co/bullet_train-sortable"
28
+ git: "bullet-train-co/bullet_train-core"
31
29
  npm: "@bullet-train/bullet-train-sortable"
32
30
  bullet_train-super_scaffolding:
33
31
  git: "bullet-train-co/bullet_train-super_scaffolding"
34
32
  bullet_train-super_load_and_authorize_resource:
35
- git: "bullet-train-co/bullet_train-super_load_and_authorize_resource"
33
+ git: "bullet-train-co/bullet_train-core"
36
34
  bullet_train-themes:
37
- git: "bullet-train-co/bullet_train-themes"
38
- bullet_train-themes-base:
39
- git: "bullet-train-co/bullet_train-themes-base"
35
+ git: "bullet-train-co/bullet_train-core"
40
36
  bullet_train-themes-light:
41
- git: "bullet-train-co/bullet_train-themes-light"
37
+ git: "bullet-train-co/bullet_train-core"
42
38
  bullet_train-themes-tailwind_css:
43
- git: "bullet-train-co/bullet_train-themes-tailwind_css"
39
+ git: "bullet-train-co/bullet_train-core"
@@ -0,0 +1,148 @@
1
+ # Bullet Train Usage Limits
2
+
3
+ Bullet Train provides a holistic method for defining model-based usage limits in your Rails application.
4
+
5
+ ## Installation
6
+
7
+ ### 1. Purchase Bullet Train Pro
8
+
9
+ First, [purchase Bullet Train Pro](https://buy.stripe.com/aEU7vc4dBfHtfO89AV). Once you've completed this process, you'll be issued a private token for the Bullet Train Pro package server. The process is currently completed manually, so you may have to way a little to receive your keys.
10
+
11
+ ### 2. Install the Package
12
+
13
+ ### 2.1. Add the Private Ruby Gems
14
+
15
+ You'll need to specify both Ruby gems in your `Gemfile`, since we have to specify a private source for both:
16
+
17
+ ```ruby
18
+ source "https://YOUR_TOKEN_HERE@gem.fury.io/bullettrain" do
19
+ gem "bullet_train-billing"
20
+ gem "bullet_train-billing-stripe" # Or whichever billing provider you're using.
21
+ gem "bullet_train-billing-usage"
22
+ end
23
+ ```
24
+
25
+ ### 2.2. Bundle Install
26
+
27
+ ```
28
+ bundle install
29
+ ```
30
+
31
+ ### 2.3. Copy Database Migrations
32
+
33
+ Use the following two commands on your shell to copy the required migrations into your local project:
34
+
35
+ ```
36
+ cp `bundle show --paths | grep bullet_train-billing | sort | head -n 1`/db/migrate/* db/migrate
37
+ cp `bundle show --paths | grep bullet_train-billing-stripe | sort | head -n 1`/db/migrate/* db/migrate
38
+ ```
39
+
40
+ Note this is different than how many Rails engines ask you to install migrations. This is intentional, as we want to maintain the original timestamps associated with these migrations.
41
+
42
+ ### 2.4. Run Migrations
43
+
44
+ ```
45
+ rake db:migrate
46
+ ```
47
+
48
+ ## Configuration
49
+ Usage limit configuration piggybacks on your [product definitions](/docs/billing/stripe.md) in `config/models/billing/products.yml`. It may help to make reference to the [default product definitions in the Bullet Train starter repository](https://github.com/bullet-train-co/bullet_train/blob/main/config/models/billing/products.yml).
50
+
51
+ ## Basic Usage Limits
52
+ All limit definitions are organized by product, then by model, and finally by _verb_. For example, you can define the number of projects a team is allowed to have on a basic plan like so:
53
+
54
+ ```yaml
55
+ basic:
56
+ prices:
57
+ # ...
58
+ limits:
59
+ projects:
60
+ have:
61
+ count: 3
62
+ enforcement: hard
63
+ upgradable: true
64
+ ```
65
+
66
+ It's important to note that `have` is a special verb and represents the simple `count` of a given model on a `Team`. All _other_ verbs will be interpreted as time-based usage limits.
67
+
68
+ ### Options
69
+ - `enforcement` can be `hard` or `soft`.
70
+ - When a `hard` limit is hit, the create form will be disabled.
71
+ - When a `soft` limit is hit, users are simply notified, but can continue to surpass the limit.
72
+ - `upgradable` indicates whether or not a user should be prompted to upgrade when they hit this limit.
73
+
74
+ ### Excluding Records from `have` Usage Limits
75
+ All models have an overridable `billable` scope that includes all records by default. You can override this scope to ensure that certain records are filtered out from consideration when calculating usage limits. For example, we do the following on `Membership` to exclude removed team members from contributing to any limitation put on the number of team members, like so:
76
+
77
+ ```ruby
78
+ scope :billable, -> { current_and_invited }
79
+ ```
80
+
81
+ ## Time-Based Usage Limits
82
+
83
+ ### Configuring Limits
84
+ In addition to simple `have` usage limits, you can specify other types of usage limits by defining other verbs. For example, you can limit the number of blog posts that can be published in a 3-day period on the free plan like this:
85
+
86
+ ```yaml
87
+ free:
88
+ limits:
89
+ blogs/posts:
90
+ publish:
91
+ count: 1
92
+ duration: 3
93
+ interval: days
94
+ enforcement: hard
95
+ ```
96
+
97
+ - `count` is how many times something can happen.
98
+ - `duration` and `interval` represent the time period we'll track for, e.g. "3 days" in this case.
99
+ - Valid options for `interval` are anything you can append to an integer, e.g. `minutes`, `hours`, `days`, `weeks`, `months`, etc., both plural and singular.
100
+
101
+ ### Tracking Usage
102
+ For these custom verbs, it's important to also instrument the application for tracking when these actions have taken place. For example:
103
+
104
+ ```ruby
105
+ class Blogs::Post < ApplicationRecord
106
+ # ...
107
+
108
+ def publish!
109
+ update(published_at: Time.zone.now)
110
+ track_billing_usage(:published)
111
+ end
112
+ end
113
+ ```
114
+
115
+ If you'd like to increment the usage count by more than one, you can pass a quantity like `count: 5` to this call.
116
+
117
+ ### Cycling Trackers Regularly
118
+ We include a Rake task you'll need to run on a regular basis in order to cycle the usage trackers that are created at a `Team` level. By default, you should probably run this every five minutes:
119
+
120
+ ```
121
+ rake billing:cycle_usage_trackers
122
+ ```
123
+
124
+ ## Checking Usage Limits
125
+
126
+ ### Checking Basic Limits
127
+ For basic `have` limits, forms generated by Super Scaffolding will be automatically disabled when a `hard` limit has been hit. Index views will also alert users to a limit being hit or broken for both `hard` and `soft` limits.
128
+
129
+ > If your Bullet Train application scaffolding predates this feature, you can reference the newest Tangible Things [index template](https://github.com/bullet-train-co/bullet_train-super_scaffolding/blob/main/app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb) and [form template](https://github.com/bullet-train-co/bullet_train-super_scaffolding/blob/main/app/views/account/scaffolding/completely_concrete/tangible_things/_form.html.erb) to see how we're using the `shared/limits/index` and `shared/limits/form` partials to present and enforce usage limits, and copy this usage in your own views.
130
+
131
+ ### Checking Time-Based Limits
132
+ To make decisions based on or enforce time-based limits in your views and controllers, you can use the `current_limits` helper like this:
133
+
134
+ ```
135
+ current_limits.can?(:publish, Blogs::Post)
136
+ ```
137
+
138
+ (You can also pass quantities like `count: 5` as an option.)
139
+
140
+ #### Presenting an Error
141
+
142
+ If you're checking on this in a controller before taking an action and you want to present an error to the user based on their usage, you can redirect with this special flash error message key:
143
+
144
+ ```
145
+ flash[:error] = :create_limit
146
+ redirect_to [:account, @post]
147
+ ```
148
+ > TODO This technically works but needs to be redone. Too limited.
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.1.7"
2
+ VERSION = "1.1.10"
3
3
  end
data/lib/colorizer.rb CHANGED
@@ -21,7 +21,7 @@ module Colorizer
21
21
  r = l
22
22
  g = l
23
23
  b = l
24
- v = l <= 0.5 ? (l * (1.0 + sl)) : (l + sl - l * sl)
24
+ v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl)
25
25
  if v > 0
26
26
  m = l + l - v
27
27
  sv = (v - m) / v
@@ -97,17 +97,18 @@ namespace :bullet_train do
97
97
 
98
98
  if gem
99
99
  details = framework_packages[gem]
100
+ package = details[:git].split("/").last
100
101
 
101
102
  puts "OK! Let's work on `#{gem}` together!".green
102
103
  puts ""
103
104
 
104
- if File.exist?("local/#{gem}")
105
- puts "We found the repository in `local/#{gem}`. We will try to use what's already there.".yellow
105
+ if File.exist?("local/#{package}")
106
+ puts "We found the repository in `local/#{package}`. We will try to use what's already there.".yellow
106
107
  puts ""
107
108
 
108
109
  # Adding these flags enables us to execute git commands in the gem from our starter repo.
109
- work_tree_flag = "--work-tree=local/#{gem}"
110
- git_dir_flag = "--git-dir=local/#{gem}/.git"
110
+ work_tree_flag = "--work-tree=local/#{package}"
111
+ git_dir_flag = "--git-dir=local/#{package}/.git"
111
112
 
112
113
  git_status = `git #{work_tree_flag} #{git_dir_flag} status`
113
114
  unless git_status.match?("nothing to commit, working tree clean")
@@ -119,7 +120,7 @@ namespace :bullet_train do
119
120
  current_branch = `git #{work_tree_flag} #{git_dir_flag} branch`.split("\n").select { |branch_name| branch_name.match?(/^\*\s/) }.pop.gsub(/^\*\s/, "")
120
121
  unless current_branch == "main"
121
122
  puts "Previously on #{current_branch}.".blue
122
- puts "Switching local/#{gem} to main branch.".blue
123
+ puts "Switching local/#{package} to main branch.".blue
123
124
  stream("git #{work_tree_flag} #{git_dir_flag} checkout main")
124
125
  end
125
126
 
@@ -127,7 +128,7 @@ namespace :bullet_train do
127
128
  stream("git #{work_tree_flag} #{git_dir_flag} pull origin main")
128
129
  else
129
130
  # Use https:// URLs when using this task in Gitpod.
130
- stream "git clone #{`whoami`.chomp == "gitpod" ? "https://github.com/" : "git@github.com:"}#{details[:git]}.git local/#{gem}"
131
+ stream "git clone #{(`whoami`.chomp == "gitpod") ? "https://github.com/" : "git@github.com:"}#{details[:git]}.git local/#{package}"
131
132
  end
132
133
 
133
134
  stream("git #{work_tree_flag} #{git_dir_flag} fetch")
@@ -141,24 +142,27 @@ namespace :bullet_train do
141
142
  stream("git #{work_tree_flag} #{git_dir_flag} checkout #{input}")
142
143
  end
143
144
 
145
+ glob = if package == "bullet_train-core"
146
+ ", glob: \"#{gem}/#{gem}.gemspec\""
147
+ end
148
+
144
149
  puts ""
145
150
  puts "Now we'll try to link up that repository in the `Gemfile`.".blue
146
- if `cat Gemfile | grep "gem \\\"#{gem}\\\", path: \\\"local/#{gem}\\\""`.chomp.present?
151
+ if `cat Gemfile | grep "gem \\\"#{gem}\\\", path: \\\"local/#{package}\\\""`.chomp.present?
147
152
  puts "This gem is already linked to a checked out copy in `local` in the `Gemfile`.".green
148
153
  elsif `cat Gemfile | grep "gem \\\"#{gem}\\\","`.chomp.present?
149
154
  puts "This gem already has some sort of alternative source configured in the `Gemfile`.".yellow
150
155
  puts "We can't do anything with this. Sorry! We'll proceed, but you have to link this package yourself.".red
151
156
  elsif `cat Gemfile | grep "gem \\\"#{gem}\\\""`.chomp.present?
152
157
  puts "This gem is directly present in the `Gemfile`, so we'll update that line.".green
153
-
154
158
  text = File.read("Gemfile")
155
- new_contents = text.gsub(/gem "#{gem}"/, "gem \"#{gem}\", path: \"local/#{gem}\"")
159
+ new_contents = text.gsub(/gem "#{gem}"/, "gem \"#{gem}\", path: \"local/#{package}\"#{glob}")
156
160
  File.open("Gemfile", "w") { |file| file.puts new_contents }
157
161
  else
158
162
  puts "This gem isn't directly present in the `Gemfile`, so we'll add it temporarily.".green
159
163
  File.open("Gemfile", "a+") { |file|
160
164
  file.puts
161
- file.puts "gem \"#{gem}\", path: \"local/#{gem}\" # Added by \`bin/develop\`."
165
+ file.puts "gem \"#{gem}\", path: \"local/#{package}\"#{glob} # Added by `bin/develop`."
162
166
  }
163
167
  end
164
168
 
@@ -172,7 +176,7 @@ namespace :bullet_train do
172
176
 
173
177
  puts ""
174
178
  puts "OK, we're opening that package in your IDE, `#{ENV["IDE"] || "code"}`. (You can configure this with `export IDE=whatever`.)".blue
175
- `#{ENV["IDE"] || "code"} local/#{gem}`
179
+ `#{ENV["IDE"] || "code"} local/#{package}`
176
180
 
177
181
  puts ""
178
182
  if details[:npm]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.7
4
+ version: 1.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-18 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: bullet_train-routes
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: devise
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -475,6 +489,7 @@ files:
475
489
  - app/javascript/controllers/form_controller.js
476
490
  - app/javascript/controllers/index.js
477
491
  - app/javascript/controllers/mobile_menu_controller.js
492
+ - app/javascript/controllers/select_all_controller.js
478
493
  - app/javascript/controllers/text_toggle_controller.js
479
494
  - app/javascript/electron/index.js
480
495
  - app/javascript/index.js
@@ -611,6 +626,7 @@ files:
611
626
  - docs/action-models.md
612
627
  - docs/authentication.md
613
628
  - docs/billing/stripe.md
629
+ - docs/billing/usage.md
614
630
  - docs/desktop.md
615
631
  - docs/field-partials.md
616
632
  - docs/field-partials/buttons.md