factory_bot_instrumentation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +30 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +43 -0
- data/.simplecov +3 -0
- data/.travis.yml +28 -0
- data/Appraisals +15 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +161 -0
- data/LICENSE +21 -0
- data/Makefile +96 -0
- data/README.md +713 -0
- data/Rakefile +8 -0
- data/app/assets/config/factory_bot_instrumentation_manifest.js +2 -0
- data/app/assets/javascripts/factory_bot_instrumentation/application.js +16 -0
- data/app/assets/javascripts/factory_bot_instrumentation/create.js +134 -0
- data/app/assets/javascripts/factory_bot_instrumentation/hooks.js +123 -0
- data/app/assets/javascripts/factory_bot_instrumentation/lib/form.js +66 -0
- data/app/assets/javascripts/factory_bot_instrumentation/lib/utils.js +116 -0
- data/app/assets/stylesheets/factory_bot_instrumentation/application.css +91 -0
- data/app/controllers/factory_bot/instrumentation/application_controller.rb +7 -0
- data/app/controllers/factory_bot/instrumentation/root_controller.rb +113 -0
- data/app/views/factory_bot/instrumentation/application/_config.html.erb +5 -0
- data/app/views/factory_bot/instrumentation/application/_scripts.html.erb +18 -0
- data/app/views/factory_bot/instrumentation/application/_spinner.html.erb +7 -0
- data/app/views/factory_bot/instrumentation/application/_styles.html.erb +20 -0
- data/app/views/factory_bot/instrumentation/root/index.html.erb +31 -0
- data/app/views/factory_bot_instrumentation/_blocks.html.erb +6 -0
- data/app/views/factory_bot_instrumentation/_navigation.html.erb +13 -0
- data/app/views/factory_bot_instrumentation/_scripts.html.erb +5 -0
- data/app/views/factory_bot_instrumentation/_styles.html.erb +5 -0
- data/app/views/layouts/factory_bot/instrumentation/application.html.erb +29 -0
- data/bin/rails +14 -0
- data/config/instrumentation.yml +55 -0
- data/config/routes.rb +8 -0
- data/doc/assets/blocks.png +0 -0
- data/doc/assets/customized.png +0 -0
- data/doc/assets/navigation.png +0 -0
- data/doc/assets/project.svg +68 -0
- data/doc/assets/regular.png +0 -0
- data/docker-compose.yml +8 -0
- data/factory_bot_instrumentation.gemspec +33 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +147 -0
- data/gemfiles/rails_5.0.gemfile +7 -0
- data/gemfiles/rails_5.0.gemfile.lock +154 -0
- data/gemfiles/rails_5.1.gemfile +7 -0
- data/gemfiles/rails_5.1.gemfile.lock +154 -0
- data/gemfiles/rails_5.2.gemfile +7 -0
- data/gemfiles/rails_5.2.gemfile.lock +162 -0
- data/lib/factory_bot/instrumentation/configuration.rb +20 -0
- data/lib/factory_bot/instrumentation/engine.rb +19 -0
- data/lib/factory_bot/instrumentation/version.rb +7 -0
- data/lib/factory_bot_instrumentation.rb +43 -0
- metadata +209 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will
|
3
|
+
* include all the files listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets,
|
6
|
+
* vendor/assets/stylesheets, or any plugin's vendor/assets/stylesheets
|
7
|
+
* directory can be referenced here using a relative path.
|
8
|
+
*
|
9
|
+
* You're free to add application-wide styles to this file and they'll appear
|
10
|
+
* at the bottom of the compiled file so the styles you add here take
|
11
|
+
* precedence over styles defined in any other CSS/SCSS files in this
|
12
|
+
* directory. Styles in this file should be added after the last require_*
|
13
|
+
* statement. It is generally better to create a new file per style scope.
|
14
|
+
*
|
15
|
+
*= require_tree .
|
16
|
+
*= require_self
|
17
|
+
*/
|
18
|
+
|
19
|
+
.row { margin-top: 5em }
|
20
|
+
.result-container { background-color: #f8f8f8 }
|
21
|
+
.hljs { background-color: #fff; }
|
22
|
+
.jumbotron { padding: 1rem }
|
23
|
+
.jumbotron .row { margin-top: 0 }
|
24
|
+
.badge { cursor: pointer }
|
25
|
+
.btn-link:hover, .btn-link:active, .btn-link:focus { text-decoration: none }
|
26
|
+
|
27
|
+
pre {
|
28
|
+
margin-bottom: 0;
|
29
|
+
white-space: pre-wrap;
|
30
|
+
white-space: -moz-pre-wrap;
|
31
|
+
white-space: -pre-wrap;
|
32
|
+
white-space: -o-pre-wrap;
|
33
|
+
word-wrap: break-word;
|
34
|
+
}
|
35
|
+
|
36
|
+
pre + .btn {
|
37
|
+
margin-top: 1rem;
|
38
|
+
}
|
39
|
+
|
40
|
+
.spinner {
|
41
|
+
margin: 100px auto;
|
42
|
+
width: 50px;
|
43
|
+
height: 40px;
|
44
|
+
text-align: center;
|
45
|
+
font-size: 10px;
|
46
|
+
}
|
47
|
+
|
48
|
+
.spinner > div {
|
49
|
+
background-color: #333;
|
50
|
+
height: 100%;
|
51
|
+
width: 6px;
|
52
|
+
display: inline-block;
|
53
|
+
|
54
|
+
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
|
55
|
+
animation: sk-stretchdelay 1.2s infinite ease-in-out;
|
56
|
+
}
|
57
|
+
|
58
|
+
.spinner .rect2 {
|
59
|
+
-webkit-animation-delay: -1.1s;
|
60
|
+
animation-delay: -1.1s;
|
61
|
+
}
|
62
|
+
|
63
|
+
.spinner .rect3 {
|
64
|
+
-webkit-animation-delay: -1.0s;
|
65
|
+
animation-delay: -1.0s;
|
66
|
+
}
|
67
|
+
|
68
|
+
.spinner .rect4 {
|
69
|
+
-webkit-animation-delay: -0.9s;
|
70
|
+
animation-delay: -0.9s;
|
71
|
+
}
|
72
|
+
|
73
|
+
.spinner .rect5 {
|
74
|
+
-webkit-animation-delay: -0.8s;
|
75
|
+
animation-delay: -0.8s;
|
76
|
+
}
|
77
|
+
|
78
|
+
@-webkit-keyframes sk-stretchdelay {
|
79
|
+
0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
|
80
|
+
20% { -webkit-transform: scaleY(1.0) }
|
81
|
+
}
|
82
|
+
|
83
|
+
@keyframes sk-stretchdelay {
|
84
|
+
0%, 40%, 100% {
|
85
|
+
transform: scaleY(0.4);
|
86
|
+
-webkit-transform: scaleY(0.4);
|
87
|
+
} 20% {
|
88
|
+
transform: scaleY(1.0);
|
89
|
+
-webkit-transform: scaleY(1.0);
|
90
|
+
}
|
91
|
+
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FactoryBot::Instrumentation
|
4
|
+
# The Instrumentation engine controller with frontend and API actions.
|
5
|
+
class RootController < ApplicationController
|
6
|
+
# This is required to make API controllers template renderable
|
7
|
+
include ActionView::Layouts
|
8
|
+
|
9
|
+
# Configure the default application layout
|
10
|
+
layout 'factory_bot/instrumentation/application'
|
11
|
+
|
12
|
+
# Show the instrumentation frontend which features the output of configured
|
13
|
+
# dynamic seeds scenarios. The frontend allows humans to generate new seed
|
14
|
+
# data on the fly.
|
15
|
+
def index
|
16
|
+
@instrumentation = instrumentation
|
17
|
+
@scenarios = scenarios
|
18
|
+
@config = FactoryBot::Instrumentation.configuration
|
19
|
+
render :index, layout: true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a new entity with the given factory settings to create on demand
|
23
|
+
# dependencies for your testing needs. You can pass in requests without
|
24
|
+
# authentication in the following JSON format:
|
25
|
+
#
|
26
|
+
# {
|
27
|
+
# "factory": "user",
|
28
|
+
# "traits": ["confirmed"],
|
29
|
+
# "overwrite": {
|
30
|
+
# "first_name": "Bernd",
|
31
|
+
# "last_name": "Müller",
|
32
|
+
# "email": "bernd.mueller@example.com",
|
33
|
+
# "password": "secret"
|
34
|
+
# }
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# The result is the API v1 representation of the created entity.
|
38
|
+
def create
|
39
|
+
# Reload the factories to improve the test development experience
|
40
|
+
FactoryBot.reload
|
41
|
+
# Call the factory construction with the user given parameters
|
42
|
+
entity = FactoryBot.create(*factory_params)
|
43
|
+
# Render the resulting entity as an API v1 representation
|
44
|
+
render plain: entity.to_json, content_type: 'application/json'
|
45
|
+
rescue StandardError => err
|
46
|
+
# Log for local factory debugging and re-raise for canary onwards
|
47
|
+
Rails.logger.error("#{err}\n#{err.backtrace.join("\n")}")
|
48
|
+
raise err
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Parse the given parameters from the request and build
|
54
|
+
# a valid FactoryBot options set.
|
55
|
+
#
|
56
|
+
# @return [Array<Mixed>] the FactoryBot options
|
57
|
+
def factory_params
|
58
|
+
data = params.permit(:factory, traits: [])
|
59
|
+
overwrite = params.to_unsafe_h.fetch(:overwrite, {}).deep_symbolize_keys
|
60
|
+
|
61
|
+
[
|
62
|
+
data.fetch(:factory).to_sym,
|
63
|
+
*data.fetch(:traits, []).map(&:to_sym),
|
64
|
+
**overwrite
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Unfortunately +Rails.configuration.instrumentation+ is only read once and
|
69
|
+
# do not hot-reload on changes. Thats why we read this file manually to get
|
70
|
+
# always a fresh state.
|
71
|
+
#
|
72
|
+
# @return [Hash{String => Mixed}] the instrumentation scenarios
|
73
|
+
def instrumentation
|
74
|
+
config_file = FactoryBot::Instrumentation.configuration.config_file.to_s
|
75
|
+
content = Pathname.new(config_file).read
|
76
|
+
template = ERB.new(content)
|
77
|
+
YAML.load(template.result(binding))[Rails.env]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Map all the instrumentation scenarios into groups and pass them back.
|
81
|
+
#
|
82
|
+
# @return [Hash{String => Array}] the grouped scenarios
|
83
|
+
def scenarios
|
84
|
+
instrumentation['scenarios'].each_with_object({}) do |scenario, memo|
|
85
|
+
group = scenario_group(scenario['name'])
|
86
|
+
scenario['group'] = group
|
87
|
+
memo[group] = [] unless memo.key? group
|
88
|
+
memo[group] << scenario
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Map all the configured scenario groups to a useable hash.
|
93
|
+
#
|
94
|
+
# @return [Hash{Regexp => String}] the group mapping
|
95
|
+
def groups
|
96
|
+
instrumentation['groups'].each_with_object({}) do |(key, val), memo|
|
97
|
+
memo[Regexp.new(Regexp.quote(key))] = val
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Fetch the group name for a given scenario name. This will utilize the
|
102
|
+
# +SCENARIO_GROUPS+ map.
|
103
|
+
#
|
104
|
+
# @param name [String] the scenario name
|
105
|
+
# @return [String] the group name
|
106
|
+
def scenario_group(name)
|
107
|
+
groups.map do |pattern, group_name|
|
108
|
+
next unless pattern.match? name
|
109
|
+
group_name
|
110
|
+
end.compact.first || 'Various'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/async/2.6.1/async.min.js"
|
2
|
+
integrity="sha256-QRRHCc3xM0GNZvTCvi0vm2f9zdOiOptAy6xGq7qN5hI="
|
3
|
+
crossorigin="anonymous"></script>
|
4
|
+
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
|
5
|
+
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
6
|
+
crossorigin="anonymous"></script>
|
7
|
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.bundle.min.js"
|
8
|
+
integrity="sha384-u/bQvRA/1bobcXlcEYpsEdFVK/vJs3+T+nXLsBYJthmdBuavHvAW6UsmqO2Gd/F9"
|
9
|
+
crossorigin="anonymous"></script>
|
10
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"
|
11
|
+
integrity="sha256-/BfiIkHlHoVihZdc6TFuj7MmJ0TWcWsMXkeDFwhi0zw="
|
12
|
+
crossorigin="anonymous"></script>
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/json.min.js"
|
14
|
+
integrity="sha256-KPdGtw3AdDen/v6+9ue/V3m+9C2lpNiuirroLsHrJZM="
|
15
|
+
crossorigin="anonymous"></script>
|
16
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.1/clipboard.js"
|
17
|
+
integrity="sha256-OBi1PBVEbUGcnFd3wGpTEGuz/VKAdU0Zvux5ovdamtI="
|
18
|
+
crossorigin="anonymous"></script>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
|
2
|
+
rel="stylesheet"
|
3
|
+
integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB"
|
4
|
+
crossorigin="anonymous">
|
5
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css"
|
6
|
+
rel="stylesheet"
|
7
|
+
integrity="sha256-3YM6A3pH4QFCl9WbSU8oXF5N6W/2ylvW0o2g+Z6TmLQ="
|
8
|
+
crossorigin="anonymous" />
|
9
|
+
<link href="https://use.fontawesome.com/releases/v5.1.0/css/solid.css"
|
10
|
+
rel="stylesheet"
|
11
|
+
integrity="sha384-TbilV5Lbhlwdyc4RuIV/JhD8NR+BfMrvz4BL5QFa2we1hQu6wvREr3v6XSRfCTRp"
|
12
|
+
crossorigin="anonymous">
|
13
|
+
<link href="https://use.fontawesome.com/releases/v5.1.0/css/regular.css"
|
14
|
+
rel="stylesheet"
|
15
|
+
integrity="sha384-avJt9MoJH2rB4PKRsJRHZv7yiFZn8LrnXuzvmZoD3fh1aL6aM6s0BBcnCvBe6XSD"
|
16
|
+
crossorigin="anonymous">
|
17
|
+
<link href="https://use.fontawesome.com/releases/v5.1.0/css/fontawesome.css"
|
18
|
+
rel="stylesheet"
|
19
|
+
integrity="sha384-ozJwkrqb90Oa3ZNb+yKFW2lToAWYdTiF1vt8JiH5ptTGHTGcN7qdoR1F95e0kYyG"
|
20
|
+
crossorigin="anonymous">
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<div class="row">
|
2
|
+
<div class="col-xl-4 col-lg-12">
|
3
|
+
<form id="generate" class="jumbotron">
|
4
|
+
<div class="form-group">
|
5
|
+
<select class="form-control scenario">
|
6
|
+
<% @scenarios.each do |group, scenarios| %>
|
7
|
+
<optgroup label="<%= group %>">
|
8
|
+
<% scenarios.each_with_index do |scenario, idx| %>
|
9
|
+
<option value="<%= group %>/<%= idx %>">
|
10
|
+
<%= scenario['name'] %>
|
11
|
+
</option>
|
12
|
+
<% end %>
|
13
|
+
</optgroup>
|
14
|
+
<% end %>
|
15
|
+
</select>
|
16
|
+
<small class="form-text text-muted description"></small>
|
17
|
+
</div>
|
18
|
+
<button type="submit" class="btn btn-primary btn-block">Generate</button>
|
19
|
+
</form>
|
20
|
+
<%= render partial: 'factory_bot_instrumentation/blocks' %>
|
21
|
+
</div>
|
22
|
+
<div class="col-xl-8 col-lg-12">
|
23
|
+
<%= render partial: 'spinner' %>
|
24
|
+
<div id="result"
|
25
|
+
class="jumbotron result-container"
|
26
|
+
style="display: none"></div>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
<script>
|
30
|
+
$(() => { (new CreateForm()).bind(); });
|
31
|
+
</script>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<%#
|
2
|
+
You can replace this file at you application to add navigation entries.
|
3
|
+
Create a new file at
|
4
|
+
+app/views/factory_bot_instrumentation/_navigation.html.erb+
|
5
|
+
to make use of this feature.
|
6
|
+
|
7
|
+
<li class="nav-item active">
|
8
|
+
<a class="nav-link" href="#">Home</span></a>
|
9
|
+
</li>
|
10
|
+
<li class="nav-item">
|
11
|
+
<a class="nav-link" href="#">Link</a>
|
12
|
+
</li>
|
13
|
+
%>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<title><%= @config.application_name %> Instrumentation</title>
|
7
|
+
<%= render partial: 'styles' %>
|
8
|
+
<%= stylesheet_link_tag 'factory_bot_instrumentation/application',
|
9
|
+
media: 'all' %>
|
10
|
+
<%= render 'factory_bot_instrumentation/styles' %>
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
|
14
|
+
<a class="navbar-brand" href="#">
|
15
|
+
<%= @config.application_name %> Instrumentation
|
16
|
+
</a>
|
17
|
+
<ul class="navbar-nav mr-auto">
|
18
|
+
<%= render 'factory_bot_instrumentation/navigation' %>
|
19
|
+
</ul>
|
20
|
+
</nav>
|
21
|
+
<%= render partial: 'scripts' %>
|
22
|
+
<%= render partial: 'config' %>
|
23
|
+
<%= javascript_include_tag 'factory_bot_instrumentation/application' %>
|
24
|
+
<%= render 'factory_bot_instrumentation/scripts' %>
|
25
|
+
<main role="main" class="container">
|
26
|
+
<%= yield %>
|
27
|
+
</main>
|
28
|
+
</body>
|
29
|
+
</html>
|
data/bin/rails
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/factory_bot_instrumentation/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
11
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
12
|
+
|
13
|
+
require 'rails/all'
|
14
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Define new dynamic seed scenarios here which can be used on the API
|
2
|
+
# instrumentation frontend.
|
3
|
+
|
4
|
+
default: &default
|
5
|
+
# Each group consists of a key (the pattern to match) and the value (group
|
6
|
+
# name). The patterns are put inside a quoted regex and the first matching
|
7
|
+
# one will be used so the configuration order is important.
|
8
|
+
groups:
|
9
|
+
UX: UX Scenarios
|
10
|
+
user: Users
|
11
|
+
|
12
|
+
# All the scenarios which can be generated.
|
13
|
+
scenarios:
|
14
|
+
- name: Empty user
|
15
|
+
desc: Create a new user without any dependent data.
|
16
|
+
factory: :user
|
17
|
+
traits:
|
18
|
+
- :confirmed
|
19
|
+
overwrite: {}
|
20
|
+
|
21
|
+
- name: User with a single friend
|
22
|
+
desc: Create a new user with a single friend.
|
23
|
+
factory: :user
|
24
|
+
traits:
|
25
|
+
- :confirmed
|
26
|
+
- :with_friend
|
27
|
+
overwrite: {}
|
28
|
+
|
29
|
+
- name: User with a single friend named Bob
|
30
|
+
desc: Create a new user with a single friend whoes name is Bob.
|
31
|
+
factory: :user
|
32
|
+
traits:
|
33
|
+
- :confirmed
|
34
|
+
- :with_friend
|
35
|
+
overwrite:
|
36
|
+
friend_overwrites:
|
37
|
+
first_name: Bob
|
38
|
+
|
39
|
+
- name: User with multiple friends
|
40
|
+
desc: Create a new user with 5 friends.
|
41
|
+
factory: :user
|
42
|
+
traits:
|
43
|
+
- :confirmed
|
44
|
+
- :with_friends
|
45
|
+
overwrite:
|
46
|
+
friends_amount: 5
|
47
|
+
|
48
|
+
test:
|
49
|
+
<<: *default
|
50
|
+
|
51
|
+
development:
|
52
|
+
<<: *default
|
53
|
+
|
54
|
+
production:
|
55
|
+
<<: *default
|