factory_bot_instrumentation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.