factory_bot_instrumentation 0.1.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +30 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +43 -0
  6. data/.simplecov +3 -0
  7. data/.travis.yml +28 -0
  8. data/Appraisals +15 -0
  9. data/CHANGELOG.md +3 -0
  10. data/Gemfile +8 -0
  11. data/Gemfile.lock +161 -0
  12. data/LICENSE +21 -0
  13. data/Makefile +96 -0
  14. data/README.md +713 -0
  15. data/Rakefile +8 -0
  16. data/app/assets/config/factory_bot_instrumentation_manifest.js +2 -0
  17. data/app/assets/javascripts/factory_bot_instrumentation/application.js +16 -0
  18. data/app/assets/javascripts/factory_bot_instrumentation/create.js +134 -0
  19. data/app/assets/javascripts/factory_bot_instrumentation/hooks.js +123 -0
  20. data/app/assets/javascripts/factory_bot_instrumentation/lib/form.js +66 -0
  21. data/app/assets/javascripts/factory_bot_instrumentation/lib/utils.js +116 -0
  22. data/app/assets/stylesheets/factory_bot_instrumentation/application.css +91 -0
  23. data/app/controllers/factory_bot/instrumentation/application_controller.rb +7 -0
  24. data/app/controllers/factory_bot/instrumentation/root_controller.rb +113 -0
  25. data/app/views/factory_bot/instrumentation/application/_config.html.erb +5 -0
  26. data/app/views/factory_bot/instrumentation/application/_scripts.html.erb +18 -0
  27. data/app/views/factory_bot/instrumentation/application/_spinner.html.erb +7 -0
  28. data/app/views/factory_bot/instrumentation/application/_styles.html.erb +20 -0
  29. data/app/views/factory_bot/instrumentation/root/index.html.erb +31 -0
  30. data/app/views/factory_bot_instrumentation/_blocks.html.erb +6 -0
  31. data/app/views/factory_bot_instrumentation/_navigation.html.erb +13 -0
  32. data/app/views/factory_bot_instrumentation/_scripts.html.erb +5 -0
  33. data/app/views/factory_bot_instrumentation/_styles.html.erb +5 -0
  34. data/app/views/layouts/factory_bot/instrumentation/application.html.erb +29 -0
  35. data/bin/rails +14 -0
  36. data/config/instrumentation.yml +55 -0
  37. data/config/routes.rb +8 -0
  38. data/doc/assets/blocks.png +0 -0
  39. data/doc/assets/customized.png +0 -0
  40. data/doc/assets/navigation.png +0 -0
  41. data/doc/assets/project.svg +68 -0
  42. data/doc/assets/regular.png +0 -0
  43. data/docker-compose.yml +8 -0
  44. data/factory_bot_instrumentation.gemspec +33 -0
  45. data/gemfiles/rails_4.gemfile +7 -0
  46. data/gemfiles/rails_4.gemfile.lock +147 -0
  47. data/gemfiles/rails_5.0.gemfile +7 -0
  48. data/gemfiles/rails_5.0.gemfile.lock +154 -0
  49. data/gemfiles/rails_5.1.gemfile +7 -0
  50. data/gemfiles/rails_5.1.gemfile.lock +154 -0
  51. data/gemfiles/rails_5.2.gemfile +7 -0
  52. data/gemfiles/rails_5.2.gemfile.lock +162 -0
  53. data/lib/factory_bot/instrumentation/configuration.rb +20 -0
  54. data/lib/factory_bot/instrumentation/engine.rb +19 -0
  55. data/lib/factory_bot/instrumentation/version.rb +7 -0
  56. data/lib/factory_bot_instrumentation.rb +43 -0
  57. metadata +209 -0
data/README.md ADDED
@@ -0,0 +1,713 @@
1
+ ![factory_bot_instrumentation](doc/assets/project.svg)
2
+
3
+ [![Build Status](https://travis-ci.com/hausgold/factory_bot_instrumentation.svg?token=4XcyqxxmkyBSSV3wWRt7&branch=master)](https://travis-ci.com/hausgold/factory_bot_instrumentation)
4
+ [![Gem Version](https://badge.fury.io/rb/factory_bot_instrumentation.svg)](https://badge.fury.io/rb/factory_bot_instrumentation)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/bcf9d9c56e55f4d79747/maintainability)](https://codeclimate.com/repos/5c35e98c8e9b03333000021e/maintainability)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/bcf9d9c56e55f4d79747/test_coverage)](https://codeclimate.com/repos/5c35e98c8e9b03333000021e/test_coverage)
7
+ [![API docs](https://img.shields.io/badge/docs-API-blue.svg)](https://www.rubydoc.info/gems/factory_bot_instrumentation)
8
+
9
+ This project is dedicated to provide an API and frontend to
10
+ [factory_bot](https://github.com/thoughtbot/factory_bot) factories to generate
11
+ test data on demand. With the help of this gem your testers are able to
12
+ interact easily with the entities of your application by using predefined use
13
+ cases.
14
+
15
+ - [Installation](#installation)
16
+ - [Getting started](#getting-started)
17
+ - [Usage](#usage)
18
+ - [Configuration](#configuration)
19
+ - [Instrumentation](#instrumentation)
20
+ - [Routes](#routes)
21
+ - [Authentication](#authentication)
22
+ - [Global settings](#global-settings)
23
+ - [API](#api)
24
+ - [Request](#request)
25
+ - [CORS](#cors)
26
+ - [Response](#response)
27
+ - [Hot reloading](#hot-reloading)
28
+ - [Frontend](#frontend)
29
+ - [Custom hooks](#custom-hooks)
30
+ - [preCreate](#precreate)
31
+ - [postCreate](#postcreate)
32
+ - [preCreateResult](#precreateresult)
33
+ - [postCreateResult](#postcreateresult)
34
+ - [preCreateError](#precreateerror)
35
+ - [postCreateError](#postcreateerror)
36
+ - [Navigation](#navigation)
37
+ - [Additional blocks](#additional-blocks)
38
+ - [Custom scripts](#custom-scripts)
39
+ - [Custom styles](#custom-styles)
40
+ - [Development](#development)
41
+ - [Contributing](#contributing)
42
+
43
+ ## Installation
44
+
45
+ Add this line to your application's Gemfile:
46
+
47
+ ```ruby
48
+ gem 'factory_bot_instrumentation'
49
+ ```
50
+
51
+ And then execute:
52
+
53
+ ```bash
54
+ $ bundle
55
+ ```
56
+
57
+ Or install it yourself as:
58
+
59
+ ```bash
60
+ $ gem install factory_bot_instrumentation
61
+ ```
62
+
63
+ ## Getting started
64
+
65
+ Say you have a complex application with lots of factories which are
66
+ interconnected and you want to test some fixed scenarios from another
67
+ application test suite (eg. end-to-end testing). Then you need to prepare some
68
+ seeds on each involved application before the test suite starts. Now your test
69
+ suite deletes some entities inside a regular test to verify the frontend
70
+ application is working as expected. Whoop. A failure happens on the frontend
71
+ and a lot of tests fail due to the root cause that an entity was not
72
+ deleted/recreated.
73
+
74
+ A better solution whould be a dynamic seed generation per test case. So an
75
+ entity may be deleted in an isolated manner, due to a random seed. Thats even
76
+ better than looking for a statically seeded entity on a list for example,
77
+ because you can now just focus on the single entry which was dynamically
78
+ generated for your insolated test case.
79
+
80
+ Another use case for dynamic seeds are explorative testers. They could benefit
81
+ from the entities your factories are already generating. But on canary, or
82
+ production like environments they are not able to access an Rails console to
83
+ trigger the factory_bot factories. Thats where `factory_bot_instrumentation`
84
+ comes in.
85
+
86
+ ## Usage
87
+
88
+ Lets start with a common factory_bot factory. Say your application handles user
89
+ accounts and a single user may have multiple friends with a self reference.
90
+ (the association does not matter) Than the factory could look like this:
91
+
92
+ ```ruby
93
+ FactoryBot.define do
94
+ factory :user do
95
+ first_name { FFaker::NameDE.first_name }
96
+ last_name { FFaker::NameDE.last_name }
97
+
98
+ transient do
99
+ friend_traits { [] }
100
+ friend_overwrites { {} }
101
+ friends_amount { 2 }
102
+ end
103
+
104
+ trait :confirmed do
105
+ after(:create, &:confirm!)
106
+ end
107
+
108
+ trait :with_friend do
109
+ after(:create) do |user, elevator|
110
+ FactoryBot.create(:lead,
111
+ *elevator.lead_traits.map(&:to_sym),
112
+ friends: [user],
113
+ **elevator.friend_overwrites)
114
+ end
115
+ end
116
+
117
+ trait :with_friends do
118
+ after(:create) do |user, elevator|
119
+ FactoryBot.create_list(:user,
120
+ elevator.leads_amount,
121
+ *elevator.lead_traits.map(&:to_sym),
122
+ friends: [user],
123
+ **elevator.friend_overwrites)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ ```
129
+
130
+ At your specs you would use it somehow like this:
131
+
132
+ ```ruby
133
+ let(:user) { create :user }
134
+ let(:user_with_single_friend) { create :user, :with_friend }
135
+ let(:user_with_single_friend_bob) do
136
+ create :user,
137
+ :with_friend,
138
+ friend_overwrites: { first_name: 'Bob' }
139
+ end
140
+ let(:user_with_many_friends) { create :user, :with_friends, friends_amount: 5 }
141
+ ```
142
+
143
+ With the Instrumentation engine you allow external users to trigger your
144
+ factories the same way via an API (HTTP request) or with preconfigured
145
+ scenarios via an easy to use frontend. Thats it.
146
+
147
+ ### Configuration
148
+
149
+ #### Instrumentation
150
+
151
+ The Instrumentation engine works with preconfigured scenarios on the frontend
152
+ as well as adhoc requests via the API. By default it requires a
153
+ `config/instrumentation.yml` inside your application where all scenarios are
154
+ defined. You can use the following example as a starting point to your
155
+ configuration.
156
+
157
+ ```yaml
158
+ # Define new dynamic seed scenarios here which can be used on the API
159
+ # instrumentation frontend.
160
+
161
+ default: &default
162
+ # Each group consists of a key (the pattern to match) and the value (group
163
+ # name). The patterns are put inside a quoted regex and the first matching
164
+ # one will be used so the configuration order is important.
165
+ groups:
166
+ UX: UX Scenarios
167
+ user: Users
168
+
169
+ # All the scenarios which can be generated.
170
+ scenarios:
171
+ - name: Empty user
172
+ desc: Create a new user without any dependent data.
173
+ factory: :user
174
+ traits:
175
+ - :confirmed
176
+ overwrite: {}
177
+
178
+ - name: User with a single friend
179
+ desc: Create a new user with a single friend.
180
+ factory: :user
181
+ traits:
182
+ - :confirmed
183
+ - :with_friend
184
+ overwrite: {}
185
+
186
+ - name: User with a single friend named Bob
187
+ desc: Create a new user with a single friend whoes name is Bob.
188
+ factory: :user
189
+ traits:
190
+ - :confirmed
191
+ - :with_friend
192
+ overwrite:
193
+ friend_overwrites:
194
+ first_name: Bob
195
+
196
+ - name: User with multiple friends
197
+ desc: Create a new user with 5 friends.
198
+ factory: :user
199
+ traits:
200
+ - :confirmed
201
+ - :with_friends
202
+ overwrite:
203
+ friends_amount: 5
204
+
205
+ test:
206
+ <<: *default
207
+
208
+ development:
209
+ <<: *default
210
+
211
+ production:
212
+ <<: *default
213
+ ```
214
+
215
+ #### Routes
216
+
217
+ You can mount the Instrumentation engine (API and frontend) easily into your
218
+ Rails application the following way:
219
+
220
+ ```ruby
221
+ Rails.application.routes.draw do
222
+ mount FactoryBot::Instrumentation::Engine => '/instrumentation'
223
+ end
224
+ ```
225
+
226
+ In cases you want to enhance the functionality under the same namespace, you
227
+ could mount the Instrumentation engine like this:
228
+
229
+ ```ruby
230
+ Rails.application.routes.draw do
231
+ namespace :instrumentation do
232
+ mount FactoryBot::Instrumentation::Engine => '/'
233
+ resource :authentication, only: :create
234
+ end
235
+ end
236
+ ```
237
+
238
+ The `Instrumentation::Authentication` controller must be implemented by your
239
+ application. The file
240
+ `app/controllers/instrumentation/authentications_controller.rb` could look like
241
+ this:
242
+
243
+ ```ruby
244
+ class Instrumentation::AuthenticationsController < ActionController::API
245
+ # Generate a new web app authentication URL for the given email address.
246
+ # This endpoint creates new login URLs which are valid for 30 minutes.
247
+ def create
248
+ render json: { url: url }
249
+ end
250
+
251
+ private
252
+
253
+ def url
254
+ User.find_by(email: params.permit(:email).fetch(:email)).auth_url
255
+ end
256
+ end
257
+ ```
258
+
259
+ #### Authentication
260
+
261
+ By default the Instrumentation engine comes without authentication at all to
262
+ ease the integration. But as you can imagine the Instrumentation engine opens
263
+ up some risky possibilities to your application. This is fine for a canary or
264
+ development environment, but not for a production environment.
265
+
266
+ There is currently only one way to secure the Instrumentation engine. You can
267
+ completly disable it on your production environment by reconfiguring your
268
+ routes like this:
269
+
270
+ ```ruby
271
+ Rails.application.routes.draw do
272
+ unless Rails.env.production?
273
+ mount FactoryBot::Instrumentation::Engine => '/instrumentation'
274
+ end
275
+ end
276
+ ```
277
+
278
+ Another option would be an HTTP basic authentication on this routes, but this
279
+ is not yet implemented.
280
+
281
+ #### Global settings
282
+
283
+ Beside the configurations from above comes some gem settings you can tweak. The
284
+ best place for this would be an initializer at your Rails application. (eg.
285
+ `config/initializers/factory_bot_instrumentation.rb`) Here comes an example
286
+ with inline documentation of the settings.
287
+
288
+ ```ruby
289
+ FactoryBot::Instrumentation.configure do |conf|
290
+ # You can set a fixed application name here,
291
+ # defaults to your Rails application name in a titlized version
292
+ conf.application_name = 'User API'
293
+ # The instrumentation configuration file path we should use,
294
+ # defaults to config/instrumentation.yml
295
+ conf.config_file = 'config/scenarios.yml'
296
+ end
297
+ ```
298
+
299
+ ### API
300
+
301
+ The Instrumentation engine comes with a single API endpoint which allows you to
302
+ trigger your factory_bot factories with traits and overwrites. Thats just as
303
+ simple as it sounds.
304
+
305
+ The endpoint is at the same path as you mounted the engine. Say you mounted the
306
+ engine at `/instrumentation`, then you can send an `POST` request to this path.
307
+
308
+ #### Request
309
+
310
+ A sample request body looks like this:
311
+
312
+ ```json
313
+ {
314
+ "factory": "user",
315
+ "traits": ["confirmed"],
316
+ "overwrite": {
317
+ "first_name": "Bernd",
318
+ "last_name": "Müller",
319
+ "email": "bernd.mueller@example.com",
320
+ "password": "secret"
321
+ }
322
+ }
323
+ ```
324
+
325
+ When sending requests to this endpoint make sure to send the correct
326
+ accept/content-type headers. (`Accept: application/json`, `Content-Type:
327
+ application/json`)
328
+
329
+ #### CORS
330
+
331
+ In case you want to deal with this endpoint from a different frontend
332
+ application via XHR calls (AJAX) you need to set the CORS headers for your
333
+ application. See [rack-cors](https://github.com/cyu/rack-cors) - and the
334
+ following naive example:
335
+
336
+ ```ruby
337
+ Rails.application.config.middleware.insert_before 0, Rack::Cors do
338
+ allow do
339
+ origins '*'
340
+ resource '*',
341
+ headers: :any,
342
+ methods: %i[get post put patch delete options head]
343
+ end
344
+ end
345
+ ```
346
+
347
+ #### Response
348
+
349
+ The response of this endpoint is always the generated entity as JSON
350
+ representation. (Just like this: `FactoryBot.create(:user).to_json`)
351
+
352
+ #### Hot reloading
353
+
354
+ As you configure your scenarios and enhance your factory_bot factories you
355
+ don't have to reboot your application to get the new configuration read or the
356
+ factory code reloaded manually. Each time you reload the Instrumentation
357
+ frontend on your browser, the configuration file is reread. The same is true
358
+ for API requests - the factories are reloaded before each request.
359
+
360
+ ### Frontend
361
+
362
+ * [Regular Instrumentation Frontend](doc/assets/regular.png)
363
+ * [Fully customized Instrumentation Frontend](doc/assets/customized.png)
364
+
365
+ #### Custom hooks
366
+
367
+ You can define some custom hooks to enhance the functionality. With the help
368
+ of the following hooks you are able to customize the outputs, perform
369
+ additional HTTP requests or anything you like.
370
+
371
+ All the hooks are designed to passthrough a payload. They receive this
372
+ payload as the first argument, and a callback function to signal the end of
373
+ the hook. You MUST pass the payload as second parameter to the callback, or
374
+ pass an error object as first argument. You can modify the payload as you
375
+ wish, eg. adding some data from subsequent requests.
376
+
377
+ Example hooks:
378
+
379
+ ```javascript
380
+ // Error case
381
+ window.hooks.postCreate.push((payload, cb) => {
382
+ cb({ error: true});
383
+ });
384
+
385
+ // Happy case
386
+ window.hooks.postCreate.push((payload, cb) => {
387
+ cb(null, Object.assign(payload, { additional: { data: true } }));
388
+ });
389
+ ```
390
+
391
+ Mind the fact that you can define multiple custom functions per hook type.
392
+ They are executed after each other in a waterfall like flow. The order of
393
+ the hooks array is therefore essential.
394
+
395
+ The best place to put your custom hooks is the `_scripts.html.erb` partial. See
396
+ the [Custom scripts](#custom-scripts) section below. Here comes a sample:
397
+
398
+ ```html
399
+ <script>
400
+ window.hooks.postCreate.push((payload, cb) => {
401
+ // Perform your request
402
+ window.utils.request({
403
+ data: JSON.stringify({ email: payload.email }),
404
+ url: '<%= main_app.instrumentation_authentication_path %>',
405
+ }, (err, result) => {
406
+ if (err) { return cb(err); }
407
+ if (!result.url) { return cb(data); }
408
+ cb(null, Object.assign(payload, { auth: result }));
409
+ });
410
+ });
411
+
412
+ window.hooks.preCreateResult.push((options, cb) => {
413
+ // Append some text to the alert message
414
+ options.alert += ` Some additional alert content.`;
415
+ // Or just overwrite the whole alert content
416
+ options.alert = 'Special alert!';
417
+ // Create a custom card and add it to the accordion
418
+ options.cards.push(window.utils.card({
419
+ id: 'auth',
420
+ icon: 'fa-key',
421
+ title: 'Authentication',
422
+ body: `
423
+ <pre id="auth-data">${options.payload.auth}</pre>
424
+ ${window.utils.clipboardButton('auth-data')}
425
+ `
426
+ }));
427
+ // Don't forget to call the callback function
428
+ cb(null, options);
429
+ });
430
+ </script>
431
+ ```
432
+
433
+ To access your named application routes, you have to use the `main_app` helper.
434
+ So a regular `<%= root_path %>` becomes `<%= main_app.root_path %>` while
435
+ adding some custom scripts/styles/blocks.
436
+
437
+ ##### preCreate
438
+
439
+ With the help of the `perCreate` hooks you can manipulate the create
440
+ request parameters. Think of an additional handling which reads an
441
+ overwrite form or a kind of trait checkboxes to customize the factory
442
+ call. The `payload` looks like this:
443
+
444
+ ```javascript
445
+ {
446
+ factory: 'user',
447
+ traits: ['confirmed'],
448
+ overwrite: { password: 'secret' }
449
+ }
450
+ ```
451
+
452
+ ##### postCreate
453
+
454
+ The `postCreate` hook allows you to perform subsequent requests to fetch
455
+ additional data. Think of a user instrumentation where you want to request
456
+ a one time token for this user. This token can be added to the payload and
457
+ can be shown with the help of the `preCreateResult` hook. The payload
458
+ contains the request parameters and the response body from the
459
+ instrumentation request. Here comes an example `payload`:
460
+
461
+ ```javascript
462
+ {
463
+ request: { factory: 'user', /* [..] */ },
464
+ response: { /* [..] */ }
465
+ }
466
+ ```
467
+
468
+ ##### preCreateResult
469
+
470
+ With the help of the `preCreateResult` hook you can customize the output
471
+ of the result. You could also perform some subsequent requests or some UI
472
+ preparations. You can access the output options and the runtime payload
473
+ with all its data and make modifications to them. This hook is triggered
474
+ before the result is rendered. A sample payload comes here:
475
+
476
+ ```javascript
477
+ {
478
+ alert: 'Your alert text.',
479
+ output: 'Formatted response',
480
+ payload: { request: { /* [..] */ }, response: { /* [..] */ } },
481
+ cards: [
482
+ `The details accordion card,
483
+ you can add more, remove the details card
484
+ or reorder them`
485
+ ],
486
+ openCard: '#details', // Open a custom card, or none
487
+ pre: 'Additinal HTML content before the alert.',
488
+ post: 'Additinal HTML content after the formatted response output.'
489
+ }
490
+ ```
491
+
492
+ ##### postCreateResult
493
+
494
+ In case you want to perform some logic after the result is rendered, you
495
+ can use the `postCreateResult` hook. You can access the output options and
496
+ the runtime payload with all its data, but changes to them won't take
497
+ effect. The `payload` looks like this:
498
+
499
+ ```javascript
500
+ {
501
+ alert: 'Your alert text.',
502
+ output: 'Formatted response',
503
+ payload: { request: { /* [..] */ }, response: { /* [..] */ } },
504
+ cards: [
505
+ `The details accordion card,
506
+ you can add more, remove the details card
507
+ or reorder them`
508
+ ],
509
+ openCard: '#details', // Open a custom card, or none
510
+ pre: 'Additinal HTML content before the alert.',
511
+ post: 'Additinal HTML content after the formatted response output.'
512
+ }
513
+ ```
514
+
515
+ ##### preCreateError
516
+
517
+ With the help of the `preCreateError` hook you can customize the output of
518
+ the error. Furthermore you can perform some subsequent requests or
519
+ whatever comes to your mind. You can access the output options and the
520
+ runtime payload with all its data and make modifications to them. This
521
+ hook is triggered before the error is rendered. A sample payload comes
522
+ here:
523
+
524
+ ```javascript
525
+ {
526
+ alert: 'Your alert text.',
527
+ output: 'Formatted response',
528
+ payload: { request: { /* [..] */ }, response: { /* [..] */ } },
529
+ pre: 'Additinal HTML content before the alert.',
530
+ post: 'Additinal HTML content after the formatted response output.'
531
+ }
532
+ ```
533
+
534
+ ##### postCreateError
535
+
536
+ In case you want to perform some magic after an error occured, you can use
537
+ the `postCreateError` hook. You can access the output options and the
538
+ runtime payload with all its data, but changes to them won't take effect
539
+ because this hook is triggered after the error is rendered. The `payload`
540
+ looks like this:
541
+
542
+ ```javascript
543
+ {
544
+ alert: 'Your alert text.',
545
+ output: 'Formatted response',
546
+ payload: { request: { /* [..] */ }, response: { /* [..] */ } },
547
+ pre: 'Additinal HTML content before the alert.',
548
+ post: 'Additinal HTML content after the formatted response output.'
549
+ }
550
+ ```
551
+
552
+ #### Navigation
553
+
554
+ ![Customized navigation](doc/assets/navigation.png)
555
+
556
+ You can customize the navigation of the Instrumentation frontend by creating
557
+ the `app/views/factory_bot_instrumentation/_navigation.html.erb` inside your
558
+ application. This could be useful to create additional links to documentations
559
+ (or maybe an inline documentation page), some custom instrumentation actions,
560
+ etc. Here comes a sample `_navigation.html.erb`:
561
+
562
+ ```html
563
+ <li class="nav-item active">
564
+ <a class="nav-link" href="#">Home</span></a>
565
+ </li>
566
+ <li class="nav-item">
567
+ <a class="nav-link" href="#">Link</a>
568
+ </li>
569
+ ```
570
+
571
+ #### Additional blocks
572
+
573
+ ![Customized blocks](doc/assets/blocks.png)
574
+
575
+ In some cases you want to add additional functionality to the Instrumentation
576
+ frontend like the feature to login random users, or trigger special behaviour
577
+ of your application. This is done by custom blocks which provide an easy way to
578
+ enhance the frontend. Just create a new
579
+ `app/views/factory_bot_instrumentation/_blocks.html.erb` inside your
580
+ application. In case you have multiple custom blocks it comes in handy to split
581
+ each block into its own partial. Therefore you could create a subdirectory like
582
+ `app/views/factory_bot_instrumentation/blocks/` and place a `_example.html.erb`
583
+ file into it. The `_blocks.html.erb` can than include the partials this way:
584
+
585
+ ```html
586
+ <%= render partial: 'factory_bot_instrumentation/blocks/example' %>
587
+ ```
588
+
589
+ An example block could look like this:
590
+
591
+ ```html
592
+ <form id="authenticate-email" class="jumbotron">
593
+ <div class="form-group">
594
+ <input type="email" class="form-control email"
595
+ placeholder="Enter email">
596
+ <small id="help" class="form-text text-muted">
597
+ This will generate a new direct authentication link for the
598
+ specified user. You do not need to know the actual password to
599
+ test the user account.
600
+ </small>
601
+ </div>
602
+ <button id="login" type="submit" class="btn btn-primary btn-block">
603
+ Login by email
604
+ </button>
605
+ </form>
606
+ <script>
607
+ $(() => {
608
+ const scope = '#authenticate-email';
609
+ const form = new Form(scope);
610
+
611
+ form.email = $(`${scope} .email`);
612
+
613
+ form.bind((event) => {
614
+ async.waterfall([
615
+ Utils.pushWaterfallPayload({ email: form.email.val() }),
616
+ (payload, cb) => {
617
+ // Perform your request (See: utils for a request helper)
618
+ }
619
+ ], (err, result) => {
620
+ if (err) { return form.showError(err, err.responseText || err); }
621
+ form.showResult(result, result.response);
622
+ });
623
+ });
624
+
625
+ form.errorContent = function(payload, output, cb)
626
+ {
627
+ cb(null, `
628
+ <div class="alert alert-danger" role="alert">
629
+ An unexpected error occured.
630
+ </div>
631
+ <pre id="data">${output}</pre>
632
+ ${window.utils.clipboardButton('data')}
633
+ `);
634
+ };
635
+
636
+ form.resultContent = function(payload, output, cb)
637
+ {
638
+ let alertPayload = {
639
+ email: payload.email,
640
+ url: payload.response.url
641
+ };
642
+ cb(null, `
643
+ <div class="alert alert-success" role="alert">
644
+ <a href="${payload.response.url}">Login now</a> to a
645
+ ${payload.email} session.
646
+ </div>
647
+ <pre id="data">${output}</pre>
648
+ ${window.utils.clipboardButton('data')}
649
+ `);
650
+ };
651
+ });
652
+ </script>
653
+ ```
654
+
655
+ #### Custom scripts
656
+
657
+ You can also include some custom scripts which could load additional libraries,
658
+ or add some custom library code. Just create a
659
+ `app/views/factory_bot_instrumentation/_scripts.html.erb` inside your
660
+ application. And fill in your content. Example file:
661
+
662
+ ```html
663
+ <script>
664
+ window.lib = Lib = {};
665
+ Lib.alert = () => alert('This is a test.');
666
+ </script>
667
+ ```
668
+
669
+ See
670
+ [utils.js](app/assets/javascripts/factory_bot_instrumentation/lib/utils.js),
671
+ and [form.js](app/assets/javascripts/factory_bot_instrumentation/lib/form.js)
672
+ for some helpers you can use at your custom hooks or custom scripts.
673
+
674
+ #### Custom styles
675
+
676
+ Next to scripts you can place some custom styles. This can be very helpful for
677
+ custom functionality like blocks or complete custom Instrumentation pages. Just
678
+ create `app/views/factory_bot_instrumentation/_styles.html.erb` inside your
679
+ application. The file could look like this:
680
+
681
+ ```hmtl
682
+ <link rel="stylesheet"
683
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/dark.min.css"
684
+ integrity="sha256-GVo4WKmO61/tVmRyEKLvRm2Nnq7mdFCaOim/9HbNpaM="
685
+ crossorigin="anonymous" />
686
+ <style>
687
+ pre {
688
+ margin-bottom: 0;
689
+ white-space: pre-wrap;
690
+ white-space: -moz-pre-wrap;
691
+ white-space: -pre-wrap;
692
+ white-space: -o-pre-wrap;
693
+ word-wrap: break-word;
694
+ }
695
+ </style>
696
+ ```
697
+
698
+ ## Development
699
+
700
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
701
+ `bundle exec rake spec` to run the tests. You can also run `bin/console` for an
702
+ interactive prompt that will allow you to experiment.
703
+
704
+ To install this gem onto your local machine, run `bundle exec rake install`. To
705
+ release a new version, update the version number in `version.rb`, and then run
706
+ `bundle exec rake release`, which will create a git tag for the version, push
707
+ git commits and tags, and push the `.gem` file to
708
+ [rubygems.org](https://rubygems.org).
709
+
710
+ ## Contributing
711
+
712
+ Bug reports and pull requests are welcome on GitHub at
713
+ https://github.com/hausgold/factory_bot_instrumentation.