contentful_middleman 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +0 -2
- data/README.md +61 -5
- data/contentful_middleman.gemspec +1 -1
- data/lib/contentful_middleman/core.rb +17 -5
- data/lib/contentful_middleman/helpers.rb +13 -0
- data/lib/contentful_middleman/tools/preview_proxy.rb +131 -0
- data/lib/contentful_middleman/version.rb +1 -1
- data/lib/contentful_middleman/webhook_handler.rb +1 -1
- data/spec/contentful_middleman/core_spec.rb +10 -11
- data/spec/contentful_middleman/helpers_spec.rb +17 -0
- data/spec/contentful_middleman/tools/preview_proxy_spec.rb +368 -0
- data/spec/fixtures/vcr_fixtures/helpers/preview.yml +211 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper.yml +93 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/asset.yml +104 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/asset_2.yml +104 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/assets.yml +91 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/assets_2.yml +91 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/entries.yml +121 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/entries_2.yml +96 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/entry.yml +106 -0
- data/spec/fixtures/vcr_fixtures/tools/preview_helper/entry_2.yml +119 -0
- data/spec/spec_helper.rb +7 -1
- metadata +28 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db0206f855bb8bfb8c037a5aabc0c0d1b668db0a
|
4
|
+
data.tar.gz: bfc915bd5b1d17c43f8c339fda06af0fa594417a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5661015434c5c51b32aeab1c1c735eaee5cc41f053c2e618e15bde6ab874743af5877ab594fcdad6659b2602cdf0c56bab443368398efae8a3c9c63cfc68f005
|
7
|
+
data.tar.gz: 0a23c10c9ee693e9c3a2f1f8dcf28232c3025ebf0528c055d7e0a9841bcd6448b0ce169a43b8937af36ff654e442f0cee9fdd7885f7688b9ef931c2ece593715
|
data/.travis.yml
CHANGED
@@ -6,7 +6,13 @@ rvm:
|
|
6
6
|
|
7
7
|
script: "bundle exec rake test"
|
8
8
|
|
9
|
+
before_install: gem install bundler -v 1.10.6
|
10
|
+
|
9
11
|
env: TEST=true JRUBY_OPTS='--2.0'
|
10
12
|
notifications:
|
11
13
|
slack:
|
12
14
|
secure: MMWxqKMT4m2UhZ+Ix4wgCs1nLvu9hUFCbyV/qJWmIFif7V4GUKXi6h9krMqGqBAd3YV3pP/InPlw3QoKBUGIAIr5GzDpSnU5ACv7E61v548qViEvPBqsfhRHha2M164rUHn32tpejJnIizVUyND/hIzNviIrab+G8uOaZXUtB5I=
|
15
|
+
|
16
|
+
matrix:
|
17
|
+
allow_failures:
|
18
|
+
- rvm: jruby
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Change Log
|
2
2
|
## Unreleased
|
3
3
|
|
4
|
+
## 1.3.1
|
5
|
+
### Fixed
|
6
|
+
* Middleman not loading extension due to `@app` reassignment
|
7
|
+
|
8
|
+
## 1.3.0 [YANKED]
|
9
|
+
### Added
|
10
|
+
* Added PreviewProxy for caching Preview API results
|
11
|
+
* Added `#with_preview` view helper for Preview API calls in real-time with a small cache
|
12
|
+
* Added `:webhook_controller` to extension options to be able to customize the Webhook Handler
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
* Moved Webhook Integration to `before_server` hook to avoid multiple Webhook server instances running
|
16
|
+
|
4
17
|
## 1.2.0
|
5
18
|
### Added
|
6
19
|
* Include `:all_entries` option to `activate` block for getting over 1000 entries[#45](https://github.com/contentful-labs/contentful_middleman/issues/45)
|
data/Gemfile
CHANGED
@@ -7,11 +7,9 @@ gem "middleman-core", '~> 3.4'
|
|
7
7
|
# Specify your gem's dependencies in contentful_middleman.gemspec
|
8
8
|
gemspec
|
9
9
|
|
10
|
-
gem "rake"
|
11
10
|
gem "yard"
|
12
11
|
|
13
12
|
# Test tools
|
14
|
-
gem "rspec"
|
15
13
|
gem "simplecov"
|
16
14
|
|
17
15
|
gem "cane", :platforms => [:mri_19, :mri_20], :require => false
|
data/README.md
CHANGED
@@ -6,6 +6,11 @@ Contentful Middleman is a [Middleman](http://middlemanapp.com/) extension to use
|
|
6
6
|
|
7
7
|
Experience the power of Middleman while staying sane as a developer by letting end-users edit content in a web-based interface.
|
8
8
|
|
9
|
+
> The main release works for Middleman v3 - The most used version
|
10
|
+
>
|
11
|
+
> If you are using Middleman v4, consider trying out our [experimental branch](https://github.com/contentful/contentful_middleman/tree/dl/upgrade-to-v4).
|
12
|
+
> Feel free to communicate with us if you are experiencing any bugs.
|
13
|
+
|
9
14
|
|
10
15
|
## Installation
|
11
16
|
Add the following line to the Gemfile of your Middleman project:
|
@@ -54,6 +59,7 @@ use_preview_api | Boolean to toggle the used API. Set it to `false` to use `
|
|
54
59
|
all_entries | Boolean to toggle multiple requests to the API for getting over 1000 entries. This uses a naive approach and can get rate limited. When using this, have in mind adding an `order` in your `:cda_query` . Default order is `order: 'sys.createdAt'`
|
55
60
|
rebuild_on_webhook | Boolean to toggle Webhook server. Server will run in port 5678, and will be expecting to receive Contentful Webhook calls on `/receive`
|
56
61
|
webhook_timeout | Integer (in seconds) for wait time after Webhook received for rebuilding. Only used if `:rebuild_on_webhook` is true. Defaults to 300 seconds
|
62
|
+
webhook_controller | Class for handling Webhook response, defaults to `::ContentfulMiddleman::WebhookHandler`
|
57
63
|
|
58
64
|
You can activate the extension multiple times to import entries from different spaces.
|
59
65
|
|
@@ -183,7 +189,7 @@ data into your templates.
|
|
183
189
|
Consider that we have data stored under `data/partners/partner`. Then in our templates we could use that data like
|
184
190
|
this:
|
185
191
|
|
186
|
-
```
|
192
|
+
```erb
|
187
193
|
<h1>Partners</h1>
|
188
194
|
<ol>
|
189
195
|
<% data.partners.partner.each do |id, partner| %>
|
@@ -213,7 +219,7 @@ Then you have the following methods of accessing locales:
|
|
213
219
|
|
214
220
|
You can access your localized fields by fetching the locale directly from the data
|
215
221
|
|
216
|
-
```
|
222
|
+
```erb
|
217
223
|
<h1>Partners</h1>
|
218
224
|
<ol>
|
219
225
|
<% data.partners.partner.each do |id, partner| %>
|
@@ -226,7 +232,7 @@ You can access your localized fields by fetching the locale directly from the da
|
|
226
232
|
|
227
233
|
You can also map an specific locale for all entry fields using `localize_entry`
|
228
234
|
|
229
|
-
```
|
235
|
+
```erb
|
230
236
|
<h1>Partners</h1>
|
231
237
|
<ol>
|
232
238
|
<% data.partners.partner.each do |id, partner| %>
|
@@ -240,7 +246,7 @@ You can also map an specific locale for all entry fields using `localize_entry`
|
|
240
246
|
|
241
247
|
The `localize` helper will map an specific locale to a field of your entry
|
242
248
|
|
243
|
-
```
|
249
|
+
```erb
|
244
250
|
<h1>Partners</h1>
|
245
251
|
<ol>
|
246
252
|
<% data.partners.partner.each do |id, partner| %>
|
@@ -257,7 +263,7 @@ Or, you can use `localize_value` or `localize_array` if you want more granularit
|
|
257
263
|
> This method is discouraged, as `localize` achieves the same goal and is a field-type
|
258
264
|
agnostic wrapper of these methods.
|
259
265
|
|
260
|
-
```
|
266
|
+
```erb
|
261
267
|
<h1>Partners</h1>
|
262
268
|
<ol>
|
263
269
|
<% data.partners.partner.each do |id, partner| %>
|
@@ -272,3 +278,53 @@ If your fields are not localized, the value of the field will be returned.
|
|
272
278
|
In case of the field being localized but no value being set for a given entry, it will use
|
273
279
|
a fallback locale, by default is `en-US` but can be specified as an additional
|
274
280
|
parameter in all the mentioned calls.
|
281
|
+
|
282
|
+
### Preview API Helper
|
283
|
+
|
284
|
+
You can use the `#with_preview` helper to try your Preview API content without having to
|
285
|
+
generate the entire `data` structures.
|
286
|
+
|
287
|
+
This generates a new Preview Contentful Client and has a cache that will store your objects
|
288
|
+
in memory until they are considered to need refresh.
|
289
|
+
|
290
|
+
It can be used like a Contentful Client:
|
291
|
+
|
292
|
+
```erb
|
293
|
+
<% with_preview(space: 'cfexampleapi', access_token: 'b4c0n73n7fu1') do |preview| %>
|
294
|
+
<% entry = preview.entry('nyancat') %>
|
295
|
+
|
296
|
+
<p>Name: <%= entry.name %></p>
|
297
|
+
<% end %>
|
298
|
+
```
|
299
|
+
|
300
|
+
If you want to clear the cache to force a refresh:
|
301
|
+
|
302
|
+
```erb
|
303
|
+
<% with_preview(space: 'cfexampleapi', access_token: 'b4c0n73n7fu1') do |preview| %>
|
304
|
+
<% preview.clear_cache %>
|
305
|
+
<% end %>
|
306
|
+
```
|
307
|
+
|
308
|
+
#### Caching Rules
|
309
|
+
|
310
|
+
* Every preview client will be cached by Space/Access Token combination
|
311
|
+
* Only `entry`, `entries`, `asset` and `assets` will be cached
|
312
|
+
* Every call will be cached by it's query parameters and ID (if ID is applicable)
|
313
|
+
* Each call will be considered, by default, stale after 3 tries or 2 hours
|
314
|
+
* Cache can be cleared by calling `#clear_cache`, this applies per preview client
|
315
|
+
|
316
|
+
#### Caching Configuration
|
317
|
+
|
318
|
+
You can configure `:tries` and `:expires_in` in the `#with_preview` call like this:
|
319
|
+
|
320
|
+
```erb
|
321
|
+
<% with_preview(
|
322
|
+
space: 'cfexampleapi',
|
323
|
+
access_token: 'b4c0n73n7fu1',
|
324
|
+
tries: 20, # Set Tries to 20 before stale
|
325
|
+
expires_in: ContentfulMiddleman::Tools::PreviewProxy.minutes(5) # Set Expiration to 5 minutes
|
326
|
+
) do |preview| %>
|
327
|
+
<!-- do your stuff -->
|
328
|
+
<% end %>
|
329
|
+
```
|
330
|
+
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
# The version of middleman-core your extension depends on
|
22
|
-
s.add_dependency("middleman-core", ["~> 3.
|
22
|
+
s.add_dependency("middleman-core", ["~> 3.4"])
|
23
23
|
|
24
24
|
# Additional dependencies
|
25
25
|
s.add_dependency("contentful", '~> 0.8')
|
@@ -40,9 +40,23 @@ module ContentfulMiddleman
|
|
40
40
|
option :webhook_timeout, 300,
|
41
41
|
"Wait time before rebuild after receiving a Webhook call"
|
42
42
|
|
43
|
+
option :webhook_controller, ::ContentfulMiddleman::WebhookHandler,
|
44
|
+
"Controller for managing Webhook callbacks"
|
45
|
+
|
43
46
|
|
44
47
|
helpers ContentfulMiddleman::Helpers
|
45
48
|
|
49
|
+
attr_reader :middleman_app
|
50
|
+
def initialize(app, options_hash = {}, &block)
|
51
|
+
super
|
52
|
+
@middleman_app = app
|
53
|
+
|
54
|
+
this = self # Hack due to context change
|
55
|
+
app.before_server do
|
56
|
+
this.webhook_options
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
46
60
|
#
|
47
61
|
# Middleman hooks
|
48
62
|
#
|
@@ -50,8 +64,10 @@ module ContentfulMiddleman
|
|
50
64
|
massage_options
|
51
65
|
|
52
66
|
ContentfulMiddleman.instances << (ContentfulMiddleman::Instance.new self)
|
67
|
+
end
|
53
68
|
|
54
|
-
|
69
|
+
def webhook_options
|
70
|
+
::ContentfulMiddleman::WebhookHandler.start(options) if options.rebuild_on_webhook
|
55
71
|
end
|
56
72
|
|
57
73
|
private
|
@@ -84,9 +100,5 @@ module ContentfulMiddleman
|
|
84
100
|
|
85
101
|
options.content_types = new_content_types_options
|
86
102
|
end
|
87
|
-
|
88
|
-
def webhook_options
|
89
|
-
::ContentfulMiddleman::WebhookHandler.start(options) if options.rebuild_on_webhook
|
90
|
-
end
|
91
103
|
end
|
92
104
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'contentful_middleman/tools/preview_proxy'
|
2
|
+
|
1
3
|
module ContentfulMiddleman
|
2
4
|
module Helpers
|
3
5
|
def contentful_instances
|
@@ -32,5 +34,16 @@ module ContentfulMiddleman
|
|
32
34
|
end
|
33
35
|
value
|
34
36
|
end
|
37
|
+
|
38
|
+
def with_preview(space: '', access_token: '', tries: 3, expires_in: ContentfulMiddleman::Tools::PreviewProxy.hours(2), &block)
|
39
|
+
preview_client = ContentfulMiddleman::Tools::PreviewProxy.instance(
|
40
|
+
space: space,
|
41
|
+
access_token: access_token,
|
42
|
+
tries: tries,
|
43
|
+
expires_in: expires_in
|
44
|
+
)
|
45
|
+
|
46
|
+
block.call(preview_client)
|
47
|
+
end
|
35
48
|
end
|
36
49
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'contentful'
|
2
|
+
|
3
|
+
module ContentfulMiddleman
|
4
|
+
module Tools
|
5
|
+
class PreviewProxy < ::Contentful::Client
|
6
|
+
@@instances = []
|
7
|
+
def self.instance(space: '', access_token: '', tries: 3, expires_in: hours(2))
|
8
|
+
possible_instance = @@instances.detect { |i| i[:space] == space && i[:access_token] == access_token }
|
9
|
+
if possible_instance.nil?
|
10
|
+
preview_client = PreviewProxy.new(space: space, access_token: access_token, tries: tries, expires_in: expires_in)
|
11
|
+
@@instances << {space: space, access_token: access_token, instance: preview_client}
|
12
|
+
else
|
13
|
+
preview_client = possible_instance[:instance]
|
14
|
+
end
|
15
|
+
|
16
|
+
preview_client
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.days(amount)
|
20
|
+
amount
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.hours(amount)
|
24
|
+
amount / 24.0
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.minutes(amount)
|
28
|
+
hours(amount) / 60.0
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.seconds(amount)
|
32
|
+
minutes(amount) / 60.0
|
33
|
+
end
|
34
|
+
|
35
|
+
CACHE_MAPPINGS = {
|
36
|
+
entries: {
|
37
|
+
cache: :cached_entry_collection
|
38
|
+
},
|
39
|
+
assets: {
|
40
|
+
cache: :cached_asset_collection
|
41
|
+
},
|
42
|
+
entry: {
|
43
|
+
cache: :cached_entries
|
44
|
+
},
|
45
|
+
asset: {
|
46
|
+
cache: :cached_assets
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
def initialize(space: '', access_token: '', tries: 3, expires_in: self.class.hours(2))
|
51
|
+
super(
|
52
|
+
space: space,
|
53
|
+
access_token: access_token,
|
54
|
+
dynamic_entries: :auto,
|
55
|
+
preview: true
|
56
|
+
)
|
57
|
+
|
58
|
+
@cache_tries = tries
|
59
|
+
@expires_in = expires_in
|
60
|
+
|
61
|
+
clear_cache
|
62
|
+
end
|
63
|
+
|
64
|
+
def entries(query = {})
|
65
|
+
cache(:entries, ->(query, id) { super(query) }, query)
|
66
|
+
end
|
67
|
+
|
68
|
+
def entry(id, query = {})
|
69
|
+
cache(:entry, ->(query, id) { super(id, query) }, query, id)
|
70
|
+
end
|
71
|
+
|
72
|
+
def assets(query = {})
|
73
|
+
cache(:assets, ->(query, id) { super(query) }, query)
|
74
|
+
end
|
75
|
+
|
76
|
+
def asset(id, query = {})
|
77
|
+
cache(:asset, ->(query, id) { super(id, query) }, query, id)
|
78
|
+
end
|
79
|
+
|
80
|
+
def clear_cache
|
81
|
+
@cached_entry_collection = {}
|
82
|
+
@cached_asset_collection = {}
|
83
|
+
|
84
|
+
@cached_entries = {}
|
85
|
+
@cached_assets = {}
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def cache(name, super_call, query = {}, id = '')
|
91
|
+
mapping = CACHE_MAPPINGS[name]
|
92
|
+
|
93
|
+
if should_fetch_from_api?(name, query: query, id: id)
|
94
|
+
instance_variable_get("@#{mapping[:cache]}")[cache_key(name, query, id)] ||= {}
|
95
|
+
instance_variable_get("@#{mapping[:cache]}")[cache_key(name, query, id)][:tries] = 0
|
96
|
+
instance_variable_get("@#{mapping[:cache]}")[cache_key(name, query, id)][:expires] = DateTime.now + @expires_in
|
97
|
+
|
98
|
+
new_resources = super_call.call(query, id)
|
99
|
+
instance_variable_get("@#{mapping[:cache]}")[cache_key(name, query, id)][:data] = new_resources
|
100
|
+
return new_resources
|
101
|
+
end
|
102
|
+
|
103
|
+
instance_variable_get("@#{mapping[:cache]}")[cache_key(name, query, id)][:tries] += 1
|
104
|
+
instance_variable_get("@#{mapping[:cache]}")[cache_key(name, query, id)][:data]
|
105
|
+
end
|
106
|
+
|
107
|
+
def cache_key(name, query = {}, id = '')
|
108
|
+
mapping = CACHE_MAPPINGS[name]
|
109
|
+
|
110
|
+
Marshal.dump(collection?(name) ? query : query.merge(cache_id: id))
|
111
|
+
end
|
112
|
+
|
113
|
+
def collection?(name)
|
114
|
+
name.to_s.end_with?('s')
|
115
|
+
end
|
116
|
+
|
117
|
+
def should_fetch_from_api?(name, query: {}, id: '')
|
118
|
+
mapping = CACHE_MAPPINGS[name]
|
119
|
+
|
120
|
+
cache = instance_variable_get("@#{mapping[:cache]}")
|
121
|
+
key = cache_key(name, query, id)
|
122
|
+
|
123
|
+
return true unless cache.key?(key)
|
124
|
+
return true if cache[key][:tries] >= @cache_tries
|
125
|
+
return true if cache[key][:expires] <= DateTime.now
|
126
|
+
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -14,7 +14,7 @@ module ContentfulMiddleman
|
|
14
14
|
config[:endpoints] = [{
|
15
15
|
endpoint: '/receive',
|
16
16
|
timeout: options.webhook_timeout,
|
17
|
-
controller:
|
17
|
+
controller: options.webhook_controller
|
18
18
|
}]
|
19
19
|
logger = Logger.new(STDOUT)
|
20
20
|
logger.level = Logger::INFO
|
@@ -68,21 +68,20 @@ describe ContentfulMiddleman::Core do
|
|
68
68
|
|
69
69
|
expect(ContentfulMiddleman.instances.size).to eq(1)
|
70
70
|
end
|
71
|
+
end
|
72
|
+
describe 'webhook handler' do
|
73
|
+
it 'does not get called if rebuild_on_webhook is false' do
|
74
|
+
expect(ContentfulMiddleman::WebhookHandler).not_to receive(:start)
|
71
75
|
|
72
|
-
|
73
|
-
|
74
|
-
expect(ContentfulMiddleman::WebhookHandler).not_to receive(:start)
|
75
|
-
|
76
|
-
subject.after_configuration
|
77
|
-
end
|
76
|
+
subject.middleman_app.run_hook(:before_server)
|
77
|
+
end
|
78
78
|
|
79
|
-
|
80
|
-
|
79
|
+
it 'gets called if rebuild_on_webhook is true' do
|
80
|
+
options.rebuild_on_webhook = true
|
81
81
|
|
82
|
-
|
82
|
+
expect(ContentfulMiddleman::WebhookHandler).to receive(:start)
|
83
83
|
|
84
|
-
|
85
|
-
end
|
84
|
+
subject.middleman_app.run_hook(:before_server)
|
86
85
|
end
|
87
86
|
end
|
88
87
|
end
|
@@ -95,5 +95,22 @@ describe ContentfulMiddleman::Helpers do
|
|
95
95
|
})
|
96
96
|
end
|
97
97
|
end
|
98
|
+
|
99
|
+
describe 'preview helpers' do
|
100
|
+
describe '#with_preview' do
|
101
|
+
it 'creates a preview client' do
|
102
|
+
vcr('helpers/preview') {
|
103
|
+
subject.with_preview(space: 'cfexampleapi', access_token: 'b4c0n73n7fu1') do |preview|
|
104
|
+
expect(preview).to be_a ::Contentful::Client
|
105
|
+
expect(preview).to be_a ::ContentfulMiddleman::Tools::PreviewProxy
|
106
|
+
|
107
|
+
preview_entries = preview.entries
|
108
|
+
expect(preview_entries.size).to eq 11
|
109
|
+
expect(preview_entries).to be_a ::Contentful::Array
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
98
115
|
end
|
99
116
|
end
|