quilt_rails 1.8.0 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +411 -97
- data/app/controllers/quilt/performance_report_controller.rb +16 -0
- data/config/routes.rb +1 -0
- data/lib/quilt_rails.rb +1 -0
- data/lib/quilt_rails/configuration.rb +1 -0
- data/lib/quilt_rails/performance.rb +30 -0
- data/lib/quilt_rails/performance/client.rb +86 -0
- data/lib/quilt_rails/performance/connection.rb +30 -0
- data/lib/quilt_rails/performance/event.rb +74 -0
- data/lib/quilt_rails/performance/event_metadata.rb +26 -0
- data/lib/quilt_rails/performance/navigation.rb +107 -0
- data/lib/quilt_rails/performance/navigation_metadata.rb +29 -0
- data/lib/quilt_rails/performance/report.rb +38 -0
- data/lib/quilt_rails/performance/reportable.rb +12 -0
- data/lib/quilt_rails/react_renderable.rb +3 -0
- data/lib/quilt_rails/version.rb +1 -1
- metadata +26 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 698a173ee3b10092312909bbee61feba9982397e474043ed6a3a77a548b2836c
|
4
|
+
data.tar.gz: 99d5d7844a7d1b1850df281589b5ebfa1b08ac830845f650bacd99722799dc4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2967e5754b5c2525da40d52fd3acc00097fbb6c3011c417c5d2118748880560bf32f7d2262eaa531e324aa53d2c9166a990da87658b418ff8a8803c677581ab7
|
7
|
+
data.tar.gz: 5fe683e1107e708a0a6e3bb169d37761b84b73e8dea0e296d7d469f168a4d783654961c1fb500b663efddbeeaf6834797bc917571c3696d54ff6d8d3f53b5740
|
data/README.md
CHANGED
@@ -1,63 +1,94 @@
|
|
1
1
|
# quilt_rails
|
2
2
|
|
3
|
-
A turn-key solution for integrating
|
3
|
+
A turn-key solution for integrating Quilt client-side libraries into your Rails app, with support for server-side-rendering using [`@shopify/react-server`](https://www.npmjs.com/package/@shopify/react-server), integration with [`@shopify/sewing-kit`](https://www.npmjs.com/package/@shopify/sewing-kit) for building, testing and linting, and front-end performance tracking through [`@shopify/performance`](https://www.npmjs.com/package/@shopify/performance).
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
7
|
+
- [Server-side-rendering](#server-side-rendering)
|
8
|
+
- [Quick start](#server-side-rendering-quick-start)
|
9
|
+
- [Generate Rails boilerplate](#generate-rails-boilerplate)
|
10
|
+
- [Add Ruby dependencies](#add-ruby-dependencies)
|
11
|
+
- [Generate Quilt boilerplate](#generate-quilt-boilerplate)
|
12
|
+
- [Try it out](#try-it-out)
|
13
|
+
- [Manual Install](#manual-installation)
|
14
|
+
- [Install Dependencies](#install-dependencies)
|
15
|
+
- [Setup the Rails app](#setup-the-rails-app)
|
16
|
+
- [Add JavaScript](#add-javascript)
|
17
|
+
- [Run the server](#run-the-server)
|
18
|
+
- [Application Layout](#application-layout)
|
19
|
+
- [Advanced Use](#advanced-use)
|
20
|
+
- [Testing](#testing)
|
21
|
+
- [Interacting with the request and response in React code](#interacting-with-the-request-and-response-in-react-code)
|
22
|
+
- [Dealing with isomorphic state](#dealing-with-isomorphic-state)
|
23
|
+
- [Customizing the node server](#customizing-the-node-server)
|
24
|
+
- [Performance tracking a React app](#performance-tracking-a-react-app)
|
25
|
+
- [Install dependencies](#install-dependencies)
|
26
|
+
- [Setup an endpoint for performance reports](setup-an-endpoint-for-performance-reports)
|
27
|
+
- [Add annotations](#add-annotations)
|
28
|
+
- [Send the report](#send-the-report)
|
29
|
+
- [Verify in development](#verify-in-development)
|
30
|
+
- [Configure StatsD for production](#configure-statsd-for-production)
|
31
|
+
- [API](#api)
|
32
|
+
- [ReactRenderable](#reactrenderable)
|
33
|
+
- [PerformanceReportable](#performanceReportable)
|
34
|
+
- [Engine](#engine)
|
35
|
+
- [Generators](#generators)
|
36
|
+
|
37
|
+
## Server-side-rendering
|
38
|
+
|
39
|
+
### Alpha functionality - do not use in high-traffic production applications
|
40
|
+
|
41
|
+
**Warning:** quilt_rails's server-side-rendering module `ReactRenderable` does not work at scale. Improvements to its architecture are being investigated. In its current state, it can be used for:
|
42
|
+
|
43
|
+
- Workshop applications
|
44
|
+
- Proof of concept applications
|
45
|
+
- Low traffic applications
|
46
|
+
|
47
|
+
For a description of the current architecture's problems, see [this Github comment](https://github.com/Shopify/quilt/issues/1059#issuecomment-539195340).
|
48
|
+
|
49
|
+
The ["decide on a scalable quilt_rails architecture" issue](https://github.com/Shopify/quilt/issues/1100) will track discussion of future architectures.
|
50
|
+
|
51
|
+
To scale up existing quilt_rails applications, skip server-side queries in your components. e.g.:
|
52
|
+
|
53
|
+
```ts
|
54
|
+
useQuery(MyQuery, {
|
55
|
+
skip: typeof document === 'undefined',
|
56
|
+
});
|
57
|
+
```
|
58
|
+
|
59
|
+
### Quick start
|
29
60
|
|
30
61
|
Using the magic of generators, we can spin up a basic app with a few console commands.
|
31
62
|
|
32
|
-
|
63
|
+
#### Generate Rails boilerplate
|
33
64
|
|
34
65
|
`dev init`
|
35
66
|
|
36
67
|
When prompted, choose `rails`. This will generate a basic Rails application scaffold.
|
37
68
|
|
38
|
-
|
69
|
+
#### Add Ruby dependencies
|
39
70
|
|
40
71
|
`bundle add sewing_kit quilt_rails`
|
41
72
|
|
42
73
|
This will install our ruby dependencies and update the project's gemfile.
|
43
74
|
|
44
|
-
|
75
|
+
#### Generate Quilt boilerplate
|
45
76
|
|
46
77
|
`rails generate quilt:install`
|
47
78
|
|
48
79
|
This will install the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine inside of `config/routes.rb`.
|
49
80
|
|
50
|
-
|
81
|
+
#### Try it out
|
51
82
|
|
52
83
|
`dev server`
|
53
84
|
|
54
85
|
Will run the application, starting up both servers and compiling assets.
|
55
86
|
|
56
|
-
|
87
|
+
### Manual installation
|
57
88
|
|
58
89
|
An application can also be setup manually using the following steps.
|
59
90
|
|
60
|
-
|
91
|
+
#### Install dependencies
|
61
92
|
|
62
93
|
```sh
|
63
94
|
# Add core Node dependencies
|
@@ -70,11 +101,11 @@ yarn
|
|
70
101
|
dev up
|
71
102
|
```
|
72
103
|
|
73
|
-
|
104
|
+
#### Setup the Rails app
|
74
105
|
|
75
106
|
There are 2 ways to consume this package.
|
76
107
|
|
77
|
-
|
108
|
+
##### Option 1: Mount the Engine
|
78
109
|
|
79
110
|
Add the engine to `routes.rb`.
|
80
111
|
|
@@ -96,7 +127,7 @@ Rails.application.routes.draw do
|
|
96
127
|
end
|
97
128
|
```
|
98
129
|
|
99
|
-
|
130
|
+
##### Option 2: Add a React controller and routes
|
100
131
|
|
101
132
|
Create a `ReactController` to handle react requests.
|
102
133
|
|
@@ -117,7 +148,7 @@ Add routes to default to the `ReactController`.
|
|
117
148
|
root 'react#index'
|
118
149
|
```
|
119
150
|
|
120
|
-
|
151
|
+
#### Add JavaScript
|
121
152
|
|
122
153
|
`sewing_kit` looks for the top level component of your React app in `app/ui/index`. The component exported from this component (and any imported JS/CSS) will be built into a `main` bundle, and used to render the initial server-rendered markup.
|
123
154
|
|
@@ -135,15 +166,15 @@ function App() {
|
|
135
166
|
export default App;
|
136
167
|
```
|
137
168
|
|
138
|
-
|
169
|
+
#### Run the server
|
139
170
|
|
140
171
|
`dev server`
|
141
172
|
|
142
173
|
Will run the application, starting up both servers and compiling assets.
|
143
174
|
|
144
|
-
|
175
|
+
### Application layout
|
145
176
|
|
146
|
-
|
177
|
+
#### Minimal
|
147
178
|
|
148
179
|
The basic layout for an app using `quilt_rails` and friends will have a `ui` folder nested inside the normal Rails `app` folder, containing at _least_ an index.js file exporting a React component.
|
149
180
|
|
@@ -158,7 +189,7 @@ The basic layout for an app using `quilt_rails` and friends will have a `ui` fol
|
|
158
189
|
└─- react_controller.rb (see above)
|
159
190
|
```
|
160
191
|
|
161
|
-
|
192
|
+
#### Rails and React
|
162
193
|
|
163
194
|
A more complex application will want a more complex layout. The following shows scalable locations for:
|
164
195
|
|
@@ -199,59 +230,9 @@ A more complex application will want a more complex layout. The following shows
|
|
199
230
|
└── Home.{js|ts}
|
200
231
|
```
|
201
232
|
|
202
|
-
|
203
|
-
|
204
|
-
### ReactRenderable
|
205
|
-
|
206
|
-
The `ReactRenderable` mixin is intended to be used in Rails controllers, and provides only the `render_react` method. This method handles proxying to a running `@shopify/react-server`.
|
207
|
-
|
208
|
-
```ruby
|
209
|
-
class ReactController < ApplicationController
|
210
|
-
include Quilt::ReactRenderable
|
211
|
-
|
212
|
-
def index
|
213
|
-
render_react
|
214
|
-
end
|
215
|
-
end
|
216
|
-
```
|
217
|
-
|
218
|
-
### Engine
|
219
|
-
|
220
|
-
`Quilt::Engine` provides a preconfigured controller which consumes `ReactRenderable` and provides an index route which uses it.
|
233
|
+
### Advanced use
|
221
234
|
|
222
|
-
|
223
|
-
# config/routes.rb
|
224
|
-
Rails.application.routes.draw do
|
225
|
-
# ...
|
226
|
-
mount Quilt::Engine, at: '/path/to/react'
|
227
|
-
end
|
228
|
-
```
|
229
|
-
|
230
|
-
### Configuration
|
231
|
-
|
232
|
-
The `configure` method allows customization of the address the service will proxy to for UI rendering.
|
233
|
-
|
234
|
-
```ruby
|
235
|
-
# config/initializers/quilt.rb
|
236
|
-
Quilt.configure do |config|
|
237
|
-
config.react_server_host = "localhost:3000"
|
238
|
-
config.react_server_protocol = 'https'
|
239
|
-
end
|
240
|
-
```
|
241
|
-
|
242
|
-
### Generators
|
243
|
-
|
244
|
-
#### `quilt:install`
|
245
|
-
|
246
|
-
Installs the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine in `config/routes.rb`.
|
247
|
-
|
248
|
-
#### `sewing_kit:install`
|
249
|
-
|
250
|
-
Adds a basic `sewing-kit.config.ts` file.
|
251
|
-
|
252
|
-
## Advanced use
|
253
|
-
|
254
|
-
### Testing
|
235
|
+
#### Testing
|
255
236
|
|
256
237
|
For fast tests with consistent results, test front-end components using the tools provided by sewing-kit instead of Rails integration tests.
|
257
238
|
|
@@ -259,7 +240,7 @@ Use [`sewing-kit test`](https://github.com/Shopify/sewing-kit/blob/master/docs/c
|
|
259
240
|
|
260
241
|
For testing React applications we provide and support [`@shopify/react-testing`](https://github.com/Shopify/quilt/tree/master/packages/react-testing).
|
261
242
|
|
262
|
-
|
243
|
+
##### Example
|
263
244
|
|
264
245
|
Given a component `MyComponent.tsx`
|
265
246
|
|
@@ -286,17 +267,17 @@ describe('MyComponent', () => {
|
|
286
267
|
});
|
287
268
|
```
|
288
269
|
|
289
|
-
|
270
|
+
##### Customizing the test environment
|
290
271
|
|
291
272
|
Often you will want to hook up custom polyfills, global mocks, or other logic that needs to run either before the initialization of the test environment, or once for each test suite.
|
292
273
|
|
293
274
|
By default, sewing-kit will look for such test setup files under `/app/ui/tests`. Check out the [documentation](https://github.com/Shopify/sewing-kit/blob/master/docs/plugins/jest.md#smart-defaults) for more details.
|
294
275
|
|
295
|
-
|
276
|
+
##### Interacting with the request and response in React code
|
296
277
|
|
297
278
|
React-server sets up [@shopify/react-network](https://github.com/Shopify/quilt/blob/master/packages/react-network) automatically, so most interactions with the request or response can be done from inside the React app.
|
298
279
|
|
299
|
-
|
280
|
+
##### Example: getting headers
|
300
281
|
|
301
282
|
```tsx
|
302
283
|
// app/ui/index.tsx
|
@@ -319,7 +300,7 @@ function App() {
|
|
319
300
|
export default App;
|
320
301
|
```
|
321
302
|
|
322
|
-
|
303
|
+
##### Example: redirecting
|
323
304
|
|
324
305
|
```tsx
|
325
306
|
// app/ui/index.tsx
|
@@ -337,13 +318,13 @@ function App() {
|
|
337
318
|
export default App;
|
338
319
|
```
|
339
320
|
|
340
|
-
|
321
|
+
#### Isomorphic state
|
341
322
|
|
342
323
|
With SSR enabled React apps, state must be serialized on the server and deserialized on the client to keep it consistent. When using `@shopify/react-server`, the best tool for this job is [`@shopify/react-html`](https://github.com/Shopify/quilt/tree/master/packages/react-html)'s [`useSerialized`](https://github.com/Shopify/quilt/tree/master/packages/react-html#in-your-application-code) hook.
|
343
324
|
|
344
325
|
`useSerialized` can be used to implement [universal-providers](https://github.com/Shopify/quilt/tree/master/packages/react-universal-provider#what-is-a-universal-provider-), allowing application code to manage what is persisted between the server and client without adding any custom code to client or server entrypoints. We offer some for common use cases such as [CSRF](https://github.com/Shopify/quilt/tree/master/packages/react-csrf-universal-provider), [GraphQL](https://github.com/Shopify/quilt/tree/master/packages/react-graphql-universal-provider), [I18n](https://github.com/Shopify/quilt/tree/master/packages/react-i18n-universal-provider), and the [Shopify App Bridge](https://github.com/Shopify/quilt/tree/master/packages/react-app-bridge-universal-provider).
|
345
326
|
|
346
|
-
|
327
|
+
#### Customizing the node server
|
347
328
|
|
348
329
|
By default, sewing-kit bundles in `@shopify/react-server-webpack-plugin` for `quilt_rails` applications to get apps up and running fast without needing to manually write any node server code. If what it provides is not sufficient, a custom server can be defined by adding a `server.js` or `server.ts` file to the app folder.
|
349
330
|
|
@@ -380,7 +361,7 @@ const app = createServer({
|
|
380
361
|
export default app;
|
381
362
|
```
|
382
363
|
|
383
|
-
|
364
|
+
#### Fixing rejected CSRF tokens for new user sessions
|
384
365
|
|
385
366
|
If a React component calls back to a Rails endpoint (e.g., `/graphql`), Rails may throw a `Can't verify CSRF token authenticity` exception. This stems from the Rails CSRF tokens not persisting until after the first `UiController` call ends.
|
386
367
|
|
@@ -404,3 +385,336 @@ class GraphqlController < ApplicationController
|
|
404
385
|
end
|
405
386
|
end
|
406
387
|
```
|
388
|
+
|
389
|
+
## Performance tracking a React app
|
390
|
+
|
391
|
+
Using [`Quilt::Performance::Reportable`](#performanceReportable) and [@shopify/react-performance](https://www.npmjs.com/package/@shopify/react-performance) it's easy to add performance tracking to apps using[`sewing_kit`](https://www.npmjs.com/package/@shopify/sewing-kit) for client-side-rendering or `quilt_rails` for server-side-rendering.
|
392
|
+
|
393
|
+
### Install dependencies
|
394
|
+
|
395
|
+
1. Install the gem (if your app is not already using `quilt_rails`).
|
396
|
+
|
397
|
+
```bash
|
398
|
+
bundle add quilt_rails
|
399
|
+
```
|
400
|
+
|
401
|
+
2. Install `@shopify/react-performance`, the library we will use to annotate our React application and send performance reports to our server.
|
402
|
+
|
403
|
+
```bash
|
404
|
+
yarn add @shopify/react-performance
|
405
|
+
```
|
406
|
+
|
407
|
+
### Setup an endpoint for performance reports
|
408
|
+
|
409
|
+
If your application is not using `Quilt::Engine`, you will need to manually configure the server-side portion of performance tracking. If it _is_ using the engine, the following will be done automatically.
|
410
|
+
|
411
|
+
1. Add a `PerformanceController` and the corresponding routes to your Rails app.
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
# app/controllers/performance_report_controller.rb
|
415
|
+
|
416
|
+
class PerformanceReportController < ActionController::Base
|
417
|
+
include Quilt::Performance::Reportable
|
418
|
+
protect_from_forgery with: :null_session
|
419
|
+
|
420
|
+
def create
|
421
|
+
process_report
|
422
|
+
|
423
|
+
render json: { result: 'success' }, status: 200
|
424
|
+
rescue ActionController::ParameterMissing => error
|
425
|
+
render json: { error: error.message, status: 422 }
|
426
|
+
end
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
2. Add a route pointing at the controller.
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
# config/routes.rb
|
434
|
+
|
435
|
+
post '/performance_report', to: 'performance_report#create'
|
436
|
+
|
437
|
+
# rest of routes
|
438
|
+
```
|
439
|
+
|
440
|
+
### Add annotations
|
441
|
+
|
442
|
+
Add a [`<PerformanceMark />`](https://github.com/Shopify/quilt/tree/master/packages/react-performance#performancemark) to each of your route-level components.
|
443
|
+
|
444
|
+
```tsx
|
445
|
+
// app/ui/features/Home/Home.tsx
|
446
|
+
import {PerformanceMark} from '@shopify/react-performance';
|
447
|
+
|
448
|
+
export function Home() {
|
449
|
+
return (
|
450
|
+
<div>
|
451
|
+
My Cool Home Page
|
452
|
+
{/* tell the library the page has finished rendering completely */}
|
453
|
+
<PerformanceMark stage="complete" id="Home" />
|
454
|
+
</div>
|
455
|
+
);
|
456
|
+
}
|
457
|
+
```
|
458
|
+
|
459
|
+
### Send the report
|
460
|
+
|
461
|
+
Add a [`usePerformanceReport`](https://github.com/Shopify/quilt/tree/master/packages/react-performance#usePerformanceReport) call to your top-level `<App />` component.
|
462
|
+
|
463
|
+
```tsx
|
464
|
+
// app/ui/foundation/App/App.tsx
|
465
|
+
import {usePerformanceReport} from '@shopify/react-performance';
|
466
|
+
|
467
|
+
export function App() {
|
468
|
+
// send the report to the server
|
469
|
+
usePerformanceReport('/performance_report');
|
470
|
+
|
471
|
+
return <>{/* your app JSX goes here*/}</>;
|
472
|
+
}
|
473
|
+
```
|
474
|
+
|
475
|
+
For more details on how to use the APIs from `@shopify/react-performance` check out its [documentation](https://github.com/Shopify/quilt/tree/master/packages/react-performance).
|
476
|
+
|
477
|
+
### Verify in development
|
478
|
+
|
479
|
+
By default `quilt_rails` will not send metrics in development mode. To verify your app is setup correctly you can check in your network tab when visiting your application and see that POST requests are sent to `/performance_report`, and recieve a `200 OK` response.
|
480
|
+
|
481
|
+
If you want more insight into what distributions _would_ be sent in production, you can use the `on_distribution` callback provided by the library to setup logging.
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
# app/controllers/performance_report_controller.rb
|
485
|
+
|
486
|
+
class PerformanceReportController < ActionController::Base
|
487
|
+
include Quilt::Performance::Reportable
|
488
|
+
protect_from_forgery with: :null_session
|
489
|
+
|
490
|
+
def create
|
491
|
+
# customize process_report's behaviour with a block
|
492
|
+
process_report do |client|
|
493
|
+
client.on_distribution do |name, value, tags|
|
494
|
+
# We log out the details of each distribution that would be sent in production.
|
495
|
+
Rails.logger.debug("Distribution: #{name}, #{value}, #{tags}")
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
render json: { result: 'success' }, status: 200
|
500
|
+
rescue ActionController::ParameterMissing => error
|
501
|
+
render json: { error: error.message, status: 422 }
|
502
|
+
end
|
503
|
+
end
|
504
|
+
```
|
505
|
+
|
506
|
+
Now you can check your Rails console output and verify that metrics are reported as expected.
|
507
|
+
|
508
|
+
### Configure StatsD for production
|
509
|
+
|
510
|
+
> Attention Shopifolk! If using `dev` your `StatsD` endpoint will already be configured for you in production. You should not need to do the following. ✨
|
511
|
+
|
512
|
+
To tell `Quilt::Performance::Reportable` where to send it's distributions, setup the environment variables detailed [documentation](https://github.com/Shopify/statsd-instrument#configuration).
|
513
|
+
|
514
|
+
## API
|
515
|
+
|
516
|
+
### ReactRenderable
|
517
|
+
|
518
|
+
The `ReactRenderable` mixin is intended to be used in Rails controllers, and provides only the `render_react` method. This method handles proxying to a running `@shopify/react-server`.
|
519
|
+
|
520
|
+
```ruby
|
521
|
+
class ReactController < ApplicationController
|
522
|
+
include Quilt::ReactRenderable
|
523
|
+
|
524
|
+
def index
|
525
|
+
render_react
|
526
|
+
end
|
527
|
+
end
|
528
|
+
```
|
529
|
+
|
530
|
+
### Performance
|
531
|
+
|
532
|
+
#### Reportable
|
533
|
+
|
534
|
+
The `Quilt::Performance::Reportable` mixin is intended to be used in Rails controllers, and provides only the `process_report` method. This method handles parsing an incoming report from [@shopify/react-performance's](https://www.npmjs.com/package/@shopify/react-performance) `<PerformanceReport />` component (or a custom report in the same format) and sending it to your application's StatsD endpoint as `distribution`s using [`StatsD-Instrument`](https://rubygems.org/gems/statsd-instrument).
|
535
|
+
|
536
|
+
> **Note** `Quilt::Performance::Reportable` does not require you to use the `React::Renderable` mixin, React-Server, or even any server-side-rendering solution at all. It should work perfectly fine for applications using something like `sewing_kit_script_tag` based client-side-rendering.
|
537
|
+
|
538
|
+
```ruby
|
539
|
+
class PerformanceController < ApplicationController
|
540
|
+
include Quilt::Performance::Reportable
|
541
|
+
|
542
|
+
def create
|
543
|
+
process_report
|
544
|
+
end
|
545
|
+
end
|
546
|
+
```
|
547
|
+
|
548
|
+
The params sent to the controller are expected to be of type `application/json`. Given the following example JSON sent by `@shopify/react-performance`,
|
549
|
+
|
550
|
+
```json
|
551
|
+
{
|
552
|
+
"connection": {
|
553
|
+
"rtt": 100,
|
554
|
+
"downlink": 2,
|
555
|
+
"effectiveType": "3g",
|
556
|
+
"type": "4g"
|
557
|
+
},
|
558
|
+
"navigations": [
|
559
|
+
{
|
560
|
+
"details": {
|
561
|
+
"start": 12312312,
|
562
|
+
"duration": 23924,
|
563
|
+
"target": "/",
|
564
|
+
"events": [
|
565
|
+
{
|
566
|
+
"type": "script",
|
567
|
+
"start": 23123,
|
568
|
+
"duration": 124
|
569
|
+
},
|
570
|
+
{
|
571
|
+
"type": "style",
|
572
|
+
"start": 23,
|
573
|
+
"duration": 14
|
574
|
+
}
|
575
|
+
],
|
576
|
+
"result": 0
|
577
|
+
},
|
578
|
+
"metadata": {
|
579
|
+
"index": 0,
|
580
|
+
"supportsDetailedTime": true,
|
581
|
+
"supportsDetailedEvents": true
|
582
|
+
}
|
583
|
+
}
|
584
|
+
],
|
585
|
+
"events": [
|
586
|
+
{
|
587
|
+
"type": "ttfb",
|
588
|
+
"start": 2,
|
589
|
+
"duration": 1000
|
590
|
+
}
|
591
|
+
]
|
592
|
+
}
|
593
|
+
```
|
594
|
+
|
595
|
+
the above controller would send the following metrics:
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
StatsD.distribution('time_to_first_byte', 2, ['browser_connection_type:3g'])
|
599
|
+
StatsD.distribution('time_to_first_byte', 2, ['browser_connection_type:3g'])
|
600
|
+
StatsD.distribution('navigation_complete', 23924, ['browser_connection_type:3g'])
|
601
|
+
StatsD.distribution('navigation_usable', 23924, ['browser_connection_type:3g'])
|
602
|
+
```
|
603
|
+
|
604
|
+
##### Customizing `process_report` with a block
|
605
|
+
|
606
|
+
The behaviour of `process_report` can be customized by manipulating the `Quilt::Performance::Client` instance yielded into its implicit block parameter.
|
607
|
+
|
608
|
+
```ruby
|
609
|
+
process_report do |client|
|
610
|
+
# client.on_distribution do ....
|
611
|
+
end
|
612
|
+
```
|
613
|
+
|
614
|
+
#### Client
|
615
|
+
|
616
|
+
The `Quilt::Performance::Client` class is yielded into the block parameter for `process_report`, and is the primary API for customizing what metrics are sent for a given POST.
|
617
|
+
|
618
|
+
##### Client#on_distribution
|
619
|
+
|
620
|
+
The `on_distribution` method takes a block which is run for each distribution (including custom ones) sent during `process_report`.
|
621
|
+
|
622
|
+
The provided callback can be used to easily add logging or other side-effects to your measurements.
|
623
|
+
|
624
|
+
```ruby
|
625
|
+
client.on_distribution do |metric_name, value, tags|
|
626
|
+
Rails.logger.debug "#{metric_name}: #{value}, tags: #{tags}"
|
627
|
+
end
|
628
|
+
```
|
629
|
+
|
630
|
+
##### Client#on_navigation
|
631
|
+
|
632
|
+
The `on_navigation` method takes a block which is run once per navigation reported to the performance controller _before_ the default distributions for the navigation are sent.
|
633
|
+
|
634
|
+
The provided callback can be used to add tags to the default `distributions` for a given navigation.
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
client.on_navigation do |navigation, tags|
|
638
|
+
# add tags to be sent with each distribution for this navigation
|
639
|
+
tags[:connection_rtt] = navigation.connection.rtt
|
640
|
+
tags[:connection_type] = navigation.connection.type
|
641
|
+
tags[:navigation_target] = navigation.target
|
642
|
+
end
|
643
|
+
```
|
644
|
+
|
645
|
+
It can also be used to compute and send entirely custom metrics.
|
646
|
+
|
647
|
+
```ruby
|
648
|
+
client.on_navigation do |navigation, tags|
|
649
|
+
# calculate and then send an additional distribution
|
650
|
+
weight = navigation.events_with_size.reduce(0) do |total, event|
|
651
|
+
total + event.size
|
652
|
+
end
|
653
|
+
client.distribution('navigation_total_resource_weight', weight, tags)
|
654
|
+
end
|
655
|
+
```
|
656
|
+
|
657
|
+
##### Client#on_event
|
658
|
+
|
659
|
+
The `on_event` method takes a block which is run once per event reported to the performance controller _before_ the default distributions for the event are sent.
|
660
|
+
|
661
|
+
The provided callback can be used to add tags to the default `distributions` for a given event, or perform other side-effects.
|
662
|
+
|
663
|
+
```ruby
|
664
|
+
client.on_event do |event, tags|
|
665
|
+
# add tags to be sent with each distribution for this event
|
666
|
+
tags[:connection_rtt] = event.connection.rtt
|
667
|
+
tags[:connection_type] = event.connection.type
|
668
|
+
end
|
669
|
+
```
|
670
|
+
|
671
|
+
### Engine
|
672
|
+
|
673
|
+
`Quilt::Engine` provides:
|
674
|
+
|
675
|
+
- a preconfigured `UiController` which consumes `ReactRenderable`
|
676
|
+
- a preconfigured `PerformanceReportController` which consumes `Performance::Reportable`
|
677
|
+
- a `/performance_report` route mapped to `performance_report#index`
|
678
|
+
- a catch-all index route mapped to the `UiController#index`
|
679
|
+
|
680
|
+
```ruby
|
681
|
+
# config/routes.rb
|
682
|
+
Rails.application.routes.draw do
|
683
|
+
# ...
|
684
|
+
mount Quilt::Engine, at: '/my-front-end'
|
685
|
+
end
|
686
|
+
```
|
687
|
+
|
688
|
+
The above is the equivalent of
|
689
|
+
|
690
|
+
```ruby
|
691
|
+
post '/my-front-end/performance_report', to: 'performance_report#create'
|
692
|
+
get '/my-front-end/*path', to: 'ui#index'
|
693
|
+
get '/my-front-end', to: 'ui#index'
|
694
|
+
```
|
695
|
+
|
696
|
+
### Configuration
|
697
|
+
|
698
|
+
The `configure` method allows customization of the address the service will proxy to for UI rendering.
|
699
|
+
|
700
|
+
```ruby
|
701
|
+
# config/initializers/quilt.rb
|
702
|
+
Quilt.configure do |config|
|
703
|
+
config.react_server_host = "localhost:3000"
|
704
|
+
config.react_server_protocol = 'https'
|
705
|
+
end
|
706
|
+
```
|
707
|
+
|
708
|
+
### StatsD environment variables
|
709
|
+
|
710
|
+
The `Performance::Reportable` mixin uses [https://github.com/Shopify/statsd-instrument](StatsD-Instrument) to send distributions. For detailed instructions on configuring where it sends data see [the documentation](https://github.com/Shopify/statsd-instrument#configuration).
|
711
|
+
|
712
|
+
### Generators
|
713
|
+
|
714
|
+
#### `quilt:install`
|
715
|
+
|
716
|
+
Installs the Node dependencies, provide a basic React app (in TypeScript) and mounts the Quilt engine in `config/routes.rb`.
|
717
|
+
|
718
|
+
#### `sewing_kit:install`
|
719
|
+
|
720
|
+
Adds a basic `sewing-kit.config.ts` file.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
class PerformanceReportController < ActionController::Base
|
5
|
+
include Quilt::Performance::Reportable
|
6
|
+
protect_from_forgery with: :null_session
|
7
|
+
|
8
|
+
def create
|
9
|
+
process_report
|
10
|
+
|
11
|
+
render json: { result: 'success' }, status: 200
|
12
|
+
rescue ActionController::ParameterMissing => error
|
13
|
+
render json: { error: error.message, status: 422 }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/config/routes.rb
CHANGED
data/lib/quilt_rails.rb
CHANGED
@@ -7,5 +7,6 @@ require "quilt_rails/engine"
|
|
7
7
|
require "quilt_rails/logger"
|
8
8
|
require "quilt_rails/configuration"
|
9
9
|
require "quilt_rails/react_renderable"
|
10
|
+
require "quilt_rails/performance"
|
10
11
|
require "quilt_rails/trusted_ui_server_csrf_strategy"
|
11
12
|
require "quilt_rails/monkey_patches/active_support_reloader" if Rails.env.development?
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "quilt_rails/performance/event_metadata"
|
4
|
+
require "quilt_rails/performance/event"
|
5
|
+
require "quilt_rails/performance/connection"
|
6
|
+
require "quilt_rails/performance/navigation_metadata"
|
7
|
+
require "quilt_rails/performance/navigation"
|
8
|
+
require "quilt_rails/performance/report"
|
9
|
+
require "quilt_rails/performance/client"
|
10
|
+
require "quilt_rails/performance/reportable"
|
11
|
+
|
12
|
+
module Quilt
|
13
|
+
module Performance
|
14
|
+
LIFECYCLE = {
|
15
|
+
time_to_first_byte: 'time_to_first_byte',
|
16
|
+
time_to_first_contentful_paint: 'time_to_first_contentful_paint',
|
17
|
+
time_to_first_paint: 'time_to_first_paint',
|
18
|
+
dom_content_loaded: 'dom_content_loaded',
|
19
|
+
first_input_delay: 'first_input_delay',
|
20
|
+
load: 'dom_load',
|
21
|
+
}
|
22
|
+
|
23
|
+
NAVIGATION = {
|
24
|
+
complete: 'navigation_complete',
|
25
|
+
usable: 'navigation_usable',
|
26
|
+
download_size: 'navigation_download_size',
|
27
|
+
cache_effectiveness: 'navigation_cache_effectiveness',
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class Client
|
6
|
+
def initialize(report)
|
7
|
+
@report = report
|
8
|
+
@base_tags = {
|
9
|
+
browser_connection_type: report.connection.effective_type,
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
@distribution_callback = proc { |_name, _value, _tags| nil }
|
13
|
+
@event_callback = proc { |_event, _tags| {} }
|
14
|
+
@navigation_callback = proc { |_navigation, _tags| {} }
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.send!(report)
|
18
|
+
client = Client.new(report)
|
19
|
+
|
20
|
+
# Allow the user to customize things
|
21
|
+
if block_given?
|
22
|
+
yield(client)
|
23
|
+
end
|
24
|
+
|
25
|
+
client.send(:process_report!)
|
26
|
+
end
|
27
|
+
|
28
|
+
def distribution(metric_name, value, tag_hash = {})
|
29
|
+
tags = tag_hash.map { |key, hash_value| "#{key}:#{hash_value}" }
|
30
|
+
|
31
|
+
@distribution_callback.call(metric_name, value, tags)
|
32
|
+
StatsD.distribution(metric_name, value, tags) unless Rails.env.dev?
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_navigation(&block)
|
36
|
+
@navigation_callback = block
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_event(&block)
|
40
|
+
@event_callback = block
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_distribution(&block)
|
44
|
+
@distribution_callback = block
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def process_report!
|
50
|
+
report_events
|
51
|
+
report_navigations
|
52
|
+
end
|
53
|
+
|
54
|
+
def report_events
|
55
|
+
@report.events.each do |event|
|
56
|
+
event_tags = @base_tags.dup
|
57
|
+
@event_callback.call(event, event_tags)
|
58
|
+
|
59
|
+
distribution(event.metric_name, event.value, event_tags)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def report_navigations
|
64
|
+
@report.navigations.each do |navigation|
|
65
|
+
tags = @base_tags.dup
|
66
|
+
@navigation_callback.call(navigation, tags)
|
67
|
+
send_default_navigation_distributions(navigation, tags)
|
68
|
+
send_conditional_navigation_distributions(navigation, tags)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def send_default_navigation_distributions(navigation, tags)
|
73
|
+
distribution(NAVIGATION[:complete], navigation.time_to_complete, tags)
|
74
|
+
distribution(NAVIGATION[:usable], navigation.time_to_usable, tags)
|
75
|
+
end
|
76
|
+
|
77
|
+
def send_conditional_navigation_distributions(navigation, tags)
|
78
|
+
size = navigation.total_download_size
|
79
|
+
distribution(NAVIGATION[:download_size], size, tags) unless size.nil?
|
80
|
+
|
81
|
+
effectiveness = navigation.cache_effectiveness
|
82
|
+
distribution(NAVIGATION[:cache_effectiveness], effectiveness, tags) unless effectiveness.nil?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class Connection
|
6
|
+
attr_accessor :downlink
|
7
|
+
attr_accessor :effective_type
|
8
|
+
attr_accessor :rtt
|
9
|
+
attr_accessor :type
|
10
|
+
|
11
|
+
def self.from_params(params)
|
12
|
+
params.transform_keys! { |key| key.underscore.to_sym }
|
13
|
+
|
14
|
+
Connection.new(
|
15
|
+
downlink: params[:downlink],
|
16
|
+
effective_type: params[:effective_type],
|
17
|
+
rtt: params[:rtt],
|
18
|
+
type: params[:type]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(downlink:, effective_type:, rtt:, type:)
|
23
|
+
@downlink = downlink
|
24
|
+
@effective_type = effective_type
|
25
|
+
@rtt = rtt
|
26
|
+
@type = type
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class Event
|
6
|
+
TYPE = {
|
7
|
+
time_to_first_byte: 'ttfb',
|
8
|
+
time_to_first_paint: 'ttfp',
|
9
|
+
time_to_first_contentful_paint: 'ttfcp',
|
10
|
+
dom_content_loaded: 'dcl',
|
11
|
+
first_input_delay: 'fid',
|
12
|
+
load: 'load',
|
13
|
+
long_task: 'longtask',
|
14
|
+
usable: 'usable',
|
15
|
+
graphql: 'graphql',
|
16
|
+
script_download: 'script',
|
17
|
+
style_download: 'style',
|
18
|
+
}
|
19
|
+
|
20
|
+
attr_accessor :type
|
21
|
+
attr_accessor :start
|
22
|
+
attr_accessor :duration
|
23
|
+
attr_accessor :metadata
|
24
|
+
attr_accessor :connection
|
25
|
+
|
26
|
+
def self.from_params(params)
|
27
|
+
params.require([:type, :start, :duration])
|
28
|
+
|
29
|
+
attributes = {
|
30
|
+
type: params[:type],
|
31
|
+
start: params[:start],
|
32
|
+
duration: params[:duration],
|
33
|
+
metadata: nil,
|
34
|
+
}
|
35
|
+
|
36
|
+
if params[:metadata]
|
37
|
+
attributes[:metadata] = EventMetadata.from_params(params[:metadata])
|
38
|
+
end
|
39
|
+
|
40
|
+
Event.new(**attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(type:, start:, duration:, metadata:)
|
44
|
+
@type = type
|
45
|
+
@start = start
|
46
|
+
@duration = duration
|
47
|
+
@metadata = metadata
|
48
|
+
end
|
49
|
+
|
50
|
+
def value
|
51
|
+
raw_value = if type == TYPE[:first_input_delay]
|
52
|
+
duration
|
53
|
+
else
|
54
|
+
start
|
55
|
+
end
|
56
|
+
|
57
|
+
raw_value.round
|
58
|
+
end
|
59
|
+
|
60
|
+
def metric_name
|
61
|
+
type_name = TYPE.key(type)
|
62
|
+
if LIFECYCLE[type_name].nil?
|
63
|
+
type
|
64
|
+
else
|
65
|
+
LIFECYCLE[type_name]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_metadata?
|
70
|
+
!metadata.nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class EventMetadata
|
6
|
+
attr_accessor :name
|
7
|
+
attr_accessor :size
|
8
|
+
|
9
|
+
def self.from_params(params)
|
10
|
+
EventMetadata.new(
|
11
|
+
name: params[:name],
|
12
|
+
size: params[:size],
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(name:, size:)
|
17
|
+
@name = name
|
18
|
+
@size = size
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_size?
|
22
|
+
!size.nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class Navigation
|
6
|
+
attr_accessor :start
|
7
|
+
attr_accessor :time_to_complete
|
8
|
+
attr_accessor :target
|
9
|
+
attr_accessor :events
|
10
|
+
attr_accessor :result
|
11
|
+
attr_accessor :metadata
|
12
|
+
attr_accessor :connection
|
13
|
+
|
14
|
+
def self.from_params(params)
|
15
|
+
params.transform_keys! { |key| key.underscore.to_sym }
|
16
|
+
params.require(:details)
|
17
|
+
|
18
|
+
attributes = {
|
19
|
+
start: params[:details][:start],
|
20
|
+
time_to_complete: params[:details][:duration],
|
21
|
+
target: params[:details][:target],
|
22
|
+
result: params[:details][:result],
|
23
|
+
events: (params[:details][:events] || []).map do |event|
|
24
|
+
Event.from_params(event)
|
25
|
+
end,
|
26
|
+
metadata: NavigationMetadata.from_params(params[:metadata] || {}),
|
27
|
+
}
|
28
|
+
|
29
|
+
Navigation.new(**attributes)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(
|
33
|
+
start:,
|
34
|
+
time_to_complete:,
|
35
|
+
target:,
|
36
|
+
result:,
|
37
|
+
events: [],
|
38
|
+
metadata: {}
|
39
|
+
)
|
40
|
+
@start = start
|
41
|
+
@time_to_complete = time_to_complete
|
42
|
+
@target = target
|
43
|
+
@result = result
|
44
|
+
@events = events
|
45
|
+
@metadata = metadata
|
46
|
+
end
|
47
|
+
|
48
|
+
def events_by_type(target_type)
|
49
|
+
events.select do |event|
|
50
|
+
event.type == target_type
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def resource_events
|
55
|
+
style_events = events_by_type(Event::TYPE[:style_download])
|
56
|
+
script_events = events_by_type(Event::TYPE[:script_download])
|
57
|
+
style_events.concat(script_events)
|
58
|
+
end
|
59
|
+
|
60
|
+
def events_with_size
|
61
|
+
resource_events.select do |event|
|
62
|
+
event.has_metadata? && event.metadata.has_size?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def time_to_usable
|
67
|
+
usable_event = events_by_type(Event::TYPE[:usable]).first
|
68
|
+
return usable_event.start - start unless usable_event.nil?
|
69
|
+
|
70
|
+
time_to_complete
|
71
|
+
end
|
72
|
+
|
73
|
+
def total_download_size
|
74
|
+
events_with_size.reduce(nil) do |total, current|
|
75
|
+
current_size = current.metadata.size
|
76
|
+
return current_size + total unless total.nil?
|
77
|
+
current_size
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def total_duration_by_event_type(type)
|
82
|
+
events = events_by_type(type)
|
83
|
+
|
84
|
+
events.reduce(0) do |total, current_event|
|
85
|
+
total + (current_event.duration || 0)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def cache_effectiveness
|
90
|
+
events = events_with_size
|
91
|
+
|
92
|
+
if events.empty? || events.any? { |event| event.metadata.size.nil? }
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
|
96
|
+
cached_events = events.select do |event|
|
97
|
+
# this is not actually checking the size of an array,
|
98
|
+
# there is no EventMetadata#any? method, so this check is being tripped erroneously.
|
99
|
+
# rubocop:disable Style/ZeroLengthPredicate
|
100
|
+
event.metadata.size == 0
|
101
|
+
# rubocop:enable
|
102
|
+
end
|
103
|
+
cached_events.length / events.length
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class NavigationMetadata
|
6
|
+
attr_accessor :index
|
7
|
+
attr_accessor :supports_detailed_time
|
8
|
+
attr_accessor :supports_detailed_events
|
9
|
+
|
10
|
+
def self.from_params(params = {})
|
11
|
+
NavigationMetadata.new(
|
12
|
+
index: params[:index],
|
13
|
+
supports_detailed_time: params[:supports_detailed_time],
|
14
|
+
supports_detailed_events: params[:supports_detailed_events],
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(index:, supports_detailed_events:, supports_detailed_time:)
|
19
|
+
@index = index
|
20
|
+
@supports_detailed_time = supports_detailed_time
|
21
|
+
@supports_detailed_events = supports_detailed_events
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_size?
|
25
|
+
!size.nil?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quilt
|
4
|
+
module Performance
|
5
|
+
class Report
|
6
|
+
attr_accessor :events
|
7
|
+
attr_accessor :navigations
|
8
|
+
attr_accessor :connection
|
9
|
+
|
10
|
+
def self.from_params(params)
|
11
|
+
params.transform_keys! { |key| key.underscore.to_sym }
|
12
|
+
params.require(:connection)
|
13
|
+
|
14
|
+
connection = Connection.from_params(params[:connection])
|
15
|
+
|
16
|
+
Report.new(
|
17
|
+
connection: connection,
|
18
|
+
navigations: (params[:navigations] || []).map do |navigation|
|
19
|
+
navigation = Navigation.from_params(navigation)
|
20
|
+
navigation.connection = connection
|
21
|
+
navigation
|
22
|
+
end,
|
23
|
+
events: (params[:events] || []).map do |event|
|
24
|
+
event = Event.from_params(event)
|
25
|
+
event.connection = connection
|
26
|
+
event
|
27
|
+
end,
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(events:, navigations:, connection:)
|
32
|
+
@events = events
|
33
|
+
@navigations = navigations
|
34
|
+
@connection = connection
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -47,12 +47,15 @@ module Quilt
|
|
47
47
|
def initialize(url)
|
48
48
|
# rubocop:disable LineLength
|
49
49
|
super "Errno::ECONNREFUSED: Waiting for React server to boot up. If this error presists verify that @shopify/react-server is configured on #{url}"
|
50
|
+
# rubocop:enable LineLength
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
54
|
class DoNotIntegrationTestError < StandardError
|
54
55
|
def initialize
|
56
|
+
# rubocop:disable LineLength
|
55
57
|
super "Do not try to use Rails integration tests on your quilt_rails app. Instead use Jest and @shopify/react-testing to test your React application directly."
|
58
|
+
# rubocop:enable LineLength
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
data/lib/quilt_rails/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quilt_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mathew Allen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.9.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: statsd-instrument
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.7.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rubocop
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -76,6 +90,7 @@ extra_rdoc_files: []
|
|
76
90
|
files:
|
77
91
|
- README.md
|
78
92
|
- Rakefile
|
93
|
+
- app/controllers/quilt/performance_report_controller.rb
|
79
94
|
- app/controllers/quilt/ui_controller.rb
|
80
95
|
- config/routes.rb
|
81
96
|
- lib/generators/quilt/USAGE
|
@@ -91,6 +106,15 @@ files:
|
|
91
106
|
- lib/quilt_rails/engine.rb
|
92
107
|
- lib/quilt_rails/logger.rb
|
93
108
|
- lib/quilt_rails/monkey_patches/active_support_reloader.rb
|
109
|
+
- lib/quilt_rails/performance.rb
|
110
|
+
- lib/quilt_rails/performance/client.rb
|
111
|
+
- lib/quilt_rails/performance/connection.rb
|
112
|
+
- lib/quilt_rails/performance/event.rb
|
113
|
+
- lib/quilt_rails/performance/event_metadata.rb
|
114
|
+
- lib/quilt_rails/performance/navigation.rb
|
115
|
+
- lib/quilt_rails/performance/navigation_metadata.rb
|
116
|
+
- lib/quilt_rails/performance/report.rb
|
117
|
+
- lib/quilt_rails/performance/reportable.rb
|
94
118
|
- lib/quilt_rails/react_renderable.rb
|
95
119
|
- lib/quilt_rails/trusted_ui_server_csrf_strategy.rb
|
96
120
|
- lib/quilt_rails/version.rb
|