jekyll-algolia 1.0.0 → 1.6.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +51 -30
  3. data/README.md +69 -27
  4. data/lib/errors/invalid_credentials.txt +12 -0
  5. data/lib/errors/invalid_index_name.txt +9 -0
  6. data/lib/errors/missing_api_key.txt +15 -0
  7. data/lib/errors/missing_application_id.txt +11 -0
  8. data/lib/errors/missing_index_name.txt +18 -0
  9. data/lib/errors/no_records_found.txt +14 -0
  10. data/lib/errors/record_too_big.txt +27 -0
  11. data/lib/errors/record_too_big_api.txt +10 -0
  12. data/lib/errors/settings_manually_edited.txt +17 -0
  13. data/lib/errors/too_many_records.txt +14 -0
  14. data/lib/errors/unknown_application_id.txt +16 -0
  15. data/lib/errors/unknown_settings.txt +12 -0
  16. data/lib/jekyll-algolia.rb +45 -60
  17. data/lib/jekyll/algolia/configurator.rb +137 -44
  18. data/lib/jekyll/algolia/error_handler.rb +36 -48
  19. data/lib/jekyll/algolia/extractor.rb +16 -6
  20. data/lib/jekyll/algolia/file_browser.rb +161 -68
  21. data/lib/jekyll/algolia/hooks.rb +18 -6
  22. data/lib/jekyll/algolia/indexer.rb +283 -145
  23. data/lib/jekyll/algolia/logger.rb +39 -8
  24. data/lib/jekyll/algolia/overwrites/githubpages-configuration.rb +32 -0
  25. data/lib/jekyll/algolia/overwrites/jekyll-algolia-site.rb +151 -0
  26. data/lib/jekyll/algolia/overwrites/jekyll-document.rb +13 -0
  27. data/lib/jekyll/algolia/overwrites/jekyll-paginate-pager.rb +20 -0
  28. data/lib/jekyll/algolia/overwrites/jekyll-tags-link.rb +33 -0
  29. data/lib/jekyll/algolia/progress_bar.rb +27 -0
  30. data/lib/jekyll/algolia/shrinker.rb +112 -0
  31. data/lib/jekyll/algolia/utils.rb +118 -2
  32. data/lib/jekyll/algolia/version.rb +1 -1
  33. data/lib/jekyll/commands/algolia.rb +3 -14
  34. metadata +75 -31
  35. data/errors/invalid_credentials.txt +0 -10
  36. data/errors/invalid_credentials_for_tmp_index.txt +0 -17
  37. data/errors/invalid_index_name.txt +0 -11
  38. data/errors/missing_api_key.txt +0 -17
  39. data/errors/missing_application_id.txt +0 -12
  40. data/errors/missing_index_name.txt +0 -19
  41. data/errors/no_records_found.txt +0 -20
  42. data/errors/record_too_big.txt +0 -25
  43. data/errors/unknown_application_id.txt +0 -20
  44. data/errors/unknown_settings.txt +0 -15
@@ -0,0 +1,12 @@
1
+ E:[✗ Error] Unknown setting
2
+ E:
3
+ E:The jekyll-algolia plugin could not correctly configure your index as some of the settings passed are not recognized.
4
+ W:
5
+ W:It seems that one of the custom index settings you defined was not recognized by the API and was rejected:
6
+ W:  {setting_name}: {setting_value}
7
+ I:
8
+ I:Make sure the setting name and value are correct. You can find a list of all the available settings with their documentation in our API reference:
9
+ I:  https://www.algolia.com/doc/api-reference/api-parameters/
10
+ I:Or specifically for this setting:
11
+ I:  https://www.algolia.com/doc/api-reference/api-parameters/{setting_name}/
12
+ I:
@@ -1,22 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'jekyll/commands/algolia'
4
+ require 'date'
4
5
 
5
6
  module Jekyll
6
7
  # Requirable file, loading all dependencies.
7
8
  # Methods here are called by the main `jekyll algolia` command
8
9
  module Algolia
9
- require 'jekyll/algolia/version'
10
- require 'jekyll/algolia/utils'
11
- require 'jekyll/algolia/hooks'
12
10
  require 'jekyll/algolia/configurator'
13
- require 'jekyll/algolia/logger'
14
11
  require 'jekyll/algolia/error_handler'
15
- require 'jekyll/algolia/file_browser'
16
12
  require 'jekyll/algolia/extractor'
13
+ require 'jekyll/algolia/file_browser'
14
+ require 'jekyll/algolia/hooks'
17
15
  require 'jekyll/algolia/indexer'
16
+ require 'jekyll/algolia/logger'
17
+ require 'jekyll/algolia/progress_bar'
18
+ require 'jekyll/algolia/shrinker'
19
+ require 'jekyll/algolia/utils'
20
+ require 'jekyll/algolia/version'
18
21
 
19
- @config = {}
22
+ MissingCredentialsError = Class.new(StandardError)
20
23
 
21
24
  # Public: Init the Algolia module
22
25
  #
@@ -26,14 +29,46 @@ module Jekyll
26
29
  # The gist of the plugin works by instanciating a Jekyll site,
27
30
  # monkey-patching its `write` method and building it.
28
31
  def self.init(config = {})
29
- @config = config
30
- @site = Jekyll::Algolia::Site.new(@config)
32
+ # Monkey patch Jekyll and external plugins
33
+ load_overwrites
34
+
35
+ config = Configurator.init(config).config
36
+ @site = Jekyll::Algolia::Site.new(config)
37
+
38
+ unless Configurator.assert_valid_credentials
39
+ raise(
40
+ MissingCredentialsError,
41
+ "One or more credentials were not found for site at: #{@site.source}"
42
+ )
43
+ end
44
+
45
+ Configurator.warn_of_deprecated_options
31
46
 
32
- exit 1 unless Configurator.assert_valid_credentials
47
+ if Configurator.dry_run?
48
+ Logger.log('W:==== THIS IS A DRY RUN ====')
49
+ Logger.log('W: - No records will be pushed to your index')
50
+ Logger.log('W: - No settings will be updated on your index')
51
+ end
33
52
 
34
53
  self
35
54
  end
36
55
 
56
+ # Public: Monkey patch Jekyll and external plugins so they don't interfere
57
+ # with our plugin
58
+ #
59
+ # Note: This is only loaded when running `jekyll algolia` so should not have
60
+ # any impact on regular builds
61
+ def self.load_overwrites
62
+ require 'jekyll/algolia/overwrites/githubpages-configuration'
63
+ require 'jekyll/algolia/overwrites/jekyll-algolia-site'
64
+ require 'jekyll/algolia/overwrites/jekyll-document'
65
+ require 'jekyll/algolia/overwrites/jekyll-paginate-pager'
66
+ require 'jekyll/algolia/overwrites/jekyll-tags-link'
67
+
68
+ # Register our own tags to overwrite the default tags
69
+ Liquid::Template.register_tag('link', JekyllAlgoliaLink)
70
+ end
71
+
37
72
  # Public: Run the main Algolia module
38
73
  #
39
74
  # Actually "process" the site, which will acts just like a regular `jekyll
@@ -43,65 +78,15 @@ module Jekyll
43
78
  # Note: The internal list of files to be processed will only be created when
44
79
  # calling .process
45
80
  def self.run
81
+ Logger.log('I:Processing site...')
46
82
  @site.process
47
83
  end
48
84
 
49
- # Public: Get access to the Jekyll config
50
- #
51
- # All other classes will need access to this config, so we make it publicly
52
- # accessible
53
- def self.config
54
- @config
55
- end
56
-
57
85
  # Public: Get access to the Jekyll site
58
86
  #
59
87
  # Tests will need access to the inner Jekyll website so we expose it here
60
88
  def self.site
61
89
  @site
62
90
  end
63
-
64
- # A Jekyll::Site subclass that overrides #write from the parent class to
65
- # create JSON records out of rendered documents and push those records
66
- # to Algolia instead of writing files to disk.
67
- class Site < Jekyll::Site
68
- def write
69
- if Configurator.dry_run?
70
- Logger.log('W:==== THIS IS A DRY RUN ====')
71
- Logger.log('W: - No records will be pushed to your index')
72
- Logger.log('W: - No settings will be updated on your index')
73
- end
74
-
75
- records = []
76
- files = []
77
- each_site_file do |file|
78
- # Skip files that should not be indexed
79
- is_indexable = FileBrowser.indexable?(file)
80
- unless is_indexable
81
- Logger.verbose("W:Skipping #{file.path}")
82
- next
83
- end
84
-
85
- path = FileBrowser.path_from_root(file)
86
- Logger.verbose("I:Extracting records from #{path}")
87
- file_records = Extractor.run(file)
88
-
89
- files << file
90
- records += file_records
91
- end
92
-
93
- # Applying the user hook on the whole list of records
94
- records = Hooks.apply_all(records)
95
-
96
- # Adding a unique objectID to each record
97
- records.map! do |record|
98
- Extractor.add_unique_object_id(record)
99
- end
100
-
101
- Logger.verbose("I:Found #{files.length} files")
102
-
103
- Indexer.run(records)
104
- end
105
- end
106
91
  end
107
92
  end
@@ -6,64 +6,93 @@ module Jekyll
6
6
  module Configurator
7
7
  include Jekyll::Algolia
8
8
 
9
+ @config = {}
10
+
9
11
  # Algolia default values
10
12
  ALGOLIA_DEFAULTS = {
11
13
  'extensions_to_index' => nil,
12
14
  'files_to_exclude' => nil,
13
15
  'nodes_to_index' => 'p',
14
16
  'indexing_batch_size' => 1000,
15
- 'indexing_mode' => 'diff',
17
+ 'max_record_size' => 10_000,
16
18
  'settings' => {
17
- 'distinct' => true,
18
- 'attributeForDistinct' => 'url',
19
- 'attributesForFaceting' => %w[
20
- searchable(tags)
21
- searchable(type)
22
- searchable(title)
19
+ # Searchable attributes
20
+ 'searchableAttributes' => %w[
21
+ title
22
+ headings
23
+ unordered(content)
24
+ collection,categories,tags
23
25
  ],
26
+ # Custom Ranking
24
27
  'customRanking' => [
25
28
  'desc(date)',
26
- 'desc(weight.heading)',
27
- 'asc(weight.position)'
29
+ 'desc(custom_ranking.heading)',
30
+ 'asc(custom_ranking.position)'
28
31
  ],
29
- 'highlightPreTag' => '<em class="ais-Highlight">',
30
- 'highlightPostTag' => '</em>',
31
- 'searchableAttributes' => %w[
32
- title
33
- hierarchy.lvl0
34
- hierarchy.lvl1
35
- hierarchy.lvl2
36
- hierarchy.lvl3
37
- hierarchy.lvl4
38
- hierarchy.lvl5
39
- unordered(content)
40
- collection,unordered(categories),unordered(tags)
32
+ 'unretrievableAttributes' => [
33
+ 'custom_ranking'
41
34
  ],
42
- # We want to allow highlight in more keys than what we search on
35
+ # Highlight
43
36
  'attributesToHighlight' => %w[
44
37
  title
45
- hierarchy.lvl0
46
- hierarchy.lvl1
47
- hierarchy.lvl2
48
- hierarchy.lvl3
49
- hierarchy.lvl4
50
- hierarchy.lvl5
38
+ headings
51
39
  content
52
40
  html
53
41
  collection
54
42
  categories
55
43
  tags
44
+ ],
45
+ 'highlightPreTag' => '<em class="ais-Highlight">',
46
+ 'highlightPostTag' => '</em>',
47
+ # Snippet
48
+ 'attributesToSnippet' => %w[
49
+ content:55
50
+ ],
51
+ 'snippetEllipsisText' => '…',
52
+ # Distinct
53
+ 'distinct' => true,
54
+ 'attributeForDistinct' => 'url',
55
+ # Faceting
56
+ 'attributesForFaceting' => %w[
57
+ type
58
+ searchable(collection)
59
+ searchable(categories)
60
+ searchable(tags)
61
+ searchable(title)
56
62
  ]
57
63
  }
58
64
  }.freeze
59
65
 
66
+ # Public: Init the configurator with the Jekyll config
67
+ #
68
+ # config - The config passed by the `jekyll algolia` command. Default to
69
+ # the default Jekyll config
70
+ def self.init(config = nil)
71
+ # Use the default Jekyll configuration if none specified. Silence the
72
+ # warning about no config set
73
+ Logger.silent { config = Jekyll.configuration } if config.nil?
74
+
75
+ @config = config
76
+
77
+ @config = disable_other_plugins(@config)
78
+
79
+ self
80
+ end
81
+
82
+ # Public: Access to the global configuration object
83
+ #
84
+ # This is a method around @config so we can mock it in the tests
85
+ def self.config
86
+ @config
87
+ end
88
+
60
89
  # Public: Get the value of a specific Jekyll configuration option
61
90
  #
62
91
  # key - Key to read
63
92
  #
64
93
  # Returns the value of this configuration option, nil otherwise
65
94
  def self.get(key)
66
- Jekyll::Algolia.config[key]
95
+ config[key]
67
96
  end
68
97
 
69
98
  # Public: Get the value of a specific Algolia configuration option, or
@@ -74,7 +103,7 @@ module Jekyll
74
103
  # Returns the value of this option, or the default value
75
104
  def self.algolia(key)
76
105
  config = get('algolia') || {}
77
- value = config[key] || ALGOLIA_DEFAULTS[key]
106
+ value = config[key].nil? ? ALGOLIA_DEFAULTS[key] : config[key]
78
107
 
79
108
  # No value found but we have a method to define the default value
80
109
  if value.nil? && respond_to?("default_#{key}")
@@ -120,27 +149,22 @@ module Jekyll
120
149
  ENV['ALGOLIA_INDEX_NAME'] || algolia('index_name')
121
150
  end
122
151
 
152
+ # Public: Return the name of the index used to store the object ids
153
+ def self.index_object_ids_name
154
+ "#{index_name}_object_ids"
155
+ end
156
+
123
157
  # Public: Get the index settings
124
158
  #
125
159
  # This will be a merge of default settings and the one defined in the
126
160
  # _config.yml file
127
161
  def self.settings
162
+ return {} if algolia('settings') == false
163
+
128
164
  user_settings = algolia('settings') || {}
129
165
  ALGOLIA_DEFAULTS['settings'].merge(user_settings)
130
166
  end
131
167
 
132
- # Public: Return the current indexing mode
133
- #
134
- # Default mode is `diff`, but users can configure their own by updating
135
- # the `indexing_mode` config in _config.yml. The only other authorized
136
- # value is `atomic`. If an unrecognized mode is defined, it defaults to
137
- # `diff`.
138
- def self.indexing_mode
139
- mode = algolia('indexing_mode') || ALGOLIA_DEFAULTS['indexing_mode']
140
- return 'diff' unless %w[diff atomic].include?(mode)
141
- mode
142
- end
143
-
144
168
  # Public: Check that all credentials are set
145
169
  #
146
170
  # Returns true if everything is ok, false otherwise. Will display helpful
@@ -162,7 +186,8 @@ module Jekyll
162
186
  # Markdown files can have many different extensions. We keep the one
163
187
  # defined in the Jekyll config
164
188
  def self.default_extensions_to_index
165
- ['html'] + get('markdown_ext').split(',')
189
+ markdown_ext = get('markdown_ext') || ''
190
+ ['html'] + markdown_ext.split(',')
166
191
  end
167
192
 
168
193
  # Public: Setting a default value to ignore index.html/index.md files in
@@ -175,7 +200,7 @@ module Jekyll
175
200
  # User can still add it by manually specifying a `files_to_exclude` to an
176
201
  # empty array
177
202
  def self.default_files_to_exclude
178
- algolia('extensions_to_index').map do |extension|
203
+ extensions_to_index.map do |extension|
179
204
  "index.#{extension}"
180
205
  end
181
206
  end
@@ -186,6 +211,7 @@ module Jekyll
186
211
  def self.verbose?
187
212
  value = get('verbose')
188
213
  return true if value == true
214
+
189
215
  false
190
216
  end
191
217
 
@@ -195,8 +221,75 @@ module Jekyll
195
221
  def self.dry_run?
196
222
  value = get('dry_run')
197
223
  return true if value == true
224
+
225
+ false
226
+ end
227
+
228
+ # Public: Returns true if the command should always update the settings
229
+ #
230
+ # When set to true, the index settings will always be updated, no matter
231
+ # if they've been modified or not
232
+ def self.force_settings?
233
+ value = get('force_settings')
234
+ return true if value == true
235
+
198
236
  false
199
237
  end
238
+
239
+ # Public: Returns a list of extensions to index
240
+ #
241
+ # Will use default values or read the algolia.extensions_to_index key.
242
+ # Accepts both an array or a comma-separated list
243
+ def self.extensions_to_index
244
+ extensions = algolia('extensions_to_index')
245
+ return [] if extensions.nil?
246
+
247
+ extensions = extensions.split(',') if extensions.is_a? String
248
+ extensions
249
+ end
250
+
251
+ # Public: Disable features from other Jekyll plugins that might interfere
252
+ # with the indexing
253
+ # Note that if other jekyll plugins are defined as part of the
254
+ # :jekyll_plugins group in the Gemfile, we might be able to override them
255
+ # using .load_overwrites in jekyll-algolia.rb.
256
+ # If they are simply required in Gemfile, then we might need to revert
257
+ # their values to nil values from here
258
+ def self.disable_other_plugins(config)
259
+ # Disable archive pages from jekyll-archives
260
+ config.delete('jekyll-archives')
261
+
262
+ # Disable pagination from jekyll-paginate
263
+ config.delete('paginate')
264
+
265
+ # Disable pagination for jekyll-paginate-v2
266
+ config['pagination'] = {} unless config['pagination'].is_a?(Hash)
267
+ config['pagination']['enabled'] = false
268
+
269
+ # Disable autopages for jekyll-paginate-v2
270
+ config['autopages'] = {} unless config['autopages'].is_a?(Hash)
271
+ config['autopages']['enabled'] = false
272
+
273
+ # Disable tags from jekyll-tagging
274
+ config.delete('tag_page_dir')
275
+ config.delete('tag_page_layout')
276
+
277
+ config
278
+ end
279
+
280
+ # Public: Check for any deprecated config option and warn the user
281
+ def self.warn_of_deprecated_options
282
+ # indexing_mode is no longer used
283
+ return if algolia('indexing_mode').nil?
284
+
285
+ # rubocop:disable Metrics/LineLength
286
+ Logger.log('I:')
287
+ Logger.log('W:[jekyll-algolia] You are using the algolia.indexing_mode option which has been deprecated in v1.1')
288
+ Logger.log('I: Indexing is now always using an atomic diff algorithm.')
289
+ Logger.log('I: This option is no longer necessary, you can remove it from your _config.yml')
290
+ Logger.log('I:')
291
+ # rubocop:enable Metrics/LineLength
292
+ end
200
293
  end
201
294
  end
202
295
  end
@@ -18,7 +18,6 @@ module Jekyll
18
18
  # happened to the display
19
19
  def self.stop(error, context = {})
20
20
  Logger.verbose("E:[jekyll-algolia] Raw error: #{error}")
21
- Logger.verbose("E:[jekyll-algolia] Context: #{context}")
22
21
 
23
22
  identified_error = identify(error, context)
24
23
 
@@ -47,10 +46,10 @@ module Jekyll
47
46
  def self.identify(error, context = {})
48
47
  known_errors = %w[
49
48
  unknown_application_id
50
- invalid_credentials_for_tmp_index
51
49
  invalid_credentials
52
- record_too_big
53
- unknown_settings
50
+ record_too_big_api
51
+ too_many_records
52
+ unknown_setting
54
53
  invalid_index_name
55
54
  ]
56
55
 
@@ -58,6 +57,7 @@ module Jekyll
58
57
  known_errors.each do |potential_error|
59
58
  error_check = send("#{potential_error}?", error, context)
60
59
  next if error_check == false
60
+
61
61
  return {
62
62
  name: potential_error,
63
63
  details: error_check
@@ -156,29 +156,6 @@ module Jekyll
156
156
  { 'application_id' => app_id }
157
157
  end
158
158
 
159
- # Public: Check if credentials specifically can't access the _tmp index
160
- #
161
- # _context - Not used
162
- #
163
- # If the error happens on a _tmp folder, it might mean that the key does
164
- # not have access to the _tmp indices and the error message will reflect
165
- # that.
166
- def self.invalid_credentials_for_tmp_index?(error, _context = {})
167
- details = error_hash(error.message)
168
-
169
- index_name_tmp = details['index_name']
170
- if details['message'] != 'Index not allowed with this API key' ||
171
- index_name_tmp !~ /_tmp$/
172
- return false
173
- end
174
-
175
- {
176
- 'application_id' => Configurator.application_id,
177
- 'index_name' => Configurator.index_name,
178
- 'index_name_tmp' => index_name_tmp
179
- }
180
- end
181
-
182
159
  # Public: Check if the credentials are working
183
160
  #
184
161
  # _context - Not used
@@ -186,45 +163,41 @@ module Jekyll
186
163
  # Application ID and API key submitted don't match any credentials known
187
164
  def self.invalid_credentials?(error, _context = {})
188
165
  details = error_hash(error.message)
166
+ return false if details == false
189
167
 
190
168
  if details['message'] != 'Invalid Application-ID or API key'
191
169
  return false
192
170
  end
193
171
 
194
172
  {
195
- 'application_id' => details['application_id']
173
+ 'application_id' => details['application_id'],
174
+ 'index_name' => Configurator.index_name,
175
+ 'index_object_ids_name' => Configurator.index_object_ids_name
196
176
  }
197
177
  end
198
178
 
199
179
  # Public: Check if the sent records are not too big
200
180
  #
201
- # context[:records] - list of records to push
181
+ # context[:records] - list of records sent in the batch
202
182
  #
203
- # Records cannot weight more that 10Kb. If we're getting this error it
204
- # means that one of the records is too big, so we'll try to give
205
- # informations about it so the user can debug it.
206
- def self.record_too_big?(error, context = {})
183
+ # One of the sent record is too big and has been rejected by the API. This
184
+ # should not happen as we proactively check for record size before pushing
185
+ # them. If it still happens it means that the value set in max_record_size
186
+ # is not matching the value in the plan.
187
+ def self.record_too_big_api?(error, _context = {})
207
188
  details = error_hash(error.message)
189
+ return false if details == false
208
190
 
209
191
  message = details['message']
210
192
  return false if message !~ /^Record .* is too big .*/
211
193
 
212
- # Getting the record size
213
- size, = /.*size=(.*) bytes.*/.match(message).captures
214
- size = Filesize.from("#{size} B").pretty
215
- object_id = details['objectID']
216
-
217
- # Getting record details
218
- record = Utils.find_by_key(context[:records], :objectID, object_id)
194
+ record_size, = /.*size=(.*) bytes.*/.match(message).captures
195
+ record_size_readable = Filesize.from("#{record_size}B").to_s('Kb')
196
+ max_record_size = Configurator.algolia('max_record_size')
219
197
 
220
198
  {
221
- 'object_id' => object_id,
222
- 'object_title' => record[:title],
223
- 'object_url' => record[:url],
224
- 'object_hint' => record[:content][0..100],
225
- 'nodes_to_index' => Configurator.algolia('nodes_to_index'),
226
- 'size' => size,
227
- 'size_limit' => '10 Kb'
199
+ 'record_size' => record_size_readable,
200
+ 'max_record_size' => max_record_size
228
201
  }
229
202
  end
230
203
 
@@ -235,8 +208,9 @@ module Jekyll
235
208
  # The API will block any call that tries to update a setting value that is
236
209
  # not available. We'll tell the user which one so they can fix their
237
210
  # issue.
238
- def self.unknown_settings?(error, context = {})
211
+ def self.unknown_setting?(error, context = {})
239
212
  details = error_hash(error.message)
213
+ return false if details == false
240
214
 
241
215
  message = details['message']
242
216
  return false if message !~ /^Invalid object attributes.*/
@@ -257,6 +231,7 @@ module Jekyll
257
231
  # Some characters are forbidden in index names
258
232
  def self.invalid_index_name?(error, _context = {})
259
233
  details = error_hash(error.message)
234
+ return false if details == false
260
235
 
261
236
  message = details['message']
262
237
  return false if message !~ /^indexName is not valid.*/
@@ -265,6 +240,19 @@ module Jekyll
265
240
  'index_name' => Configurator.index_name
266
241
  }
267
242
  end
243
+
244
+ # Public: Check if the application has too many records
245
+ #
246
+ # We're trying to push too many records and it goes over quota
247
+ def self.too_many_records?(error, _context = {})
248
+ details = error_hash(error.message)
249
+ return false if details == false
250
+
251
+ message = details['message']
252
+ return false if message !~ /^Record quota exceeded.*/
253
+
254
+ {}
255
+ end
268
256
  end
269
257
  end
270
258
  end