slimmer 1.2.5 → 2.0.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 (37) hide show
  1. data/CHANGELOG.md +14 -1
  2. data/lib/slimmer.rb +28 -17
  3. data/lib/slimmer/app.rb +9 -9
  4. data/lib/slimmer/headers.rb +32 -1
  5. data/lib/slimmer/{admin_title_inserter.rb → processors/admin_title_inserter.rb} +1 -1
  6. data/lib/slimmer/{body_class_copier.rb → processors/body_class_copier.rb} +1 -1
  7. data/lib/slimmer/{body_inserter.rb → processors/body_inserter.rb} +1 -1
  8. data/lib/slimmer/{conditional_comment_mover.rb → processors/conditional_comment_mover.rb} +1 -1
  9. data/lib/slimmer/{footer_remover.rb → processors/footer_remover.rb} +1 -1
  10. data/lib/slimmer/{google_analytics_configurator.rb → processors/google_analytics_configurator.rb} +3 -2
  11. data/lib/slimmer/{header_context_inserter.rb → processors/header_context_inserter.rb} +1 -1
  12. data/lib/slimmer/processors/logo_class_inserter.rb +20 -0
  13. data/lib/slimmer/processors/related_items_inserter.rb +20 -0
  14. data/lib/slimmer/{search_path_setter.rb → processors/search_path_setter.rb} +2 -4
  15. data/lib/slimmer/{section_inserter.rb → processors/section_inserter.rb} +1 -1
  16. data/lib/slimmer/{tag_mover.rb → processors/tag_mover.rb} +1 -1
  17. data/lib/slimmer/{title_inserter.rb → processors/title_inserter.rb} +1 -1
  18. data/lib/slimmer/skin.rb +28 -20
  19. data/lib/slimmer/template.rb +1 -1
  20. data/lib/slimmer/test.rb +5 -1
  21. data/lib/slimmer/test_template.rb +7 -1
  22. data/lib/slimmer/version.rb +1 -1
  23. data/test/fixtures/related.raw.html.erb +26 -22
  24. data/test/fixtures/wrapper.html.erb +2 -0
  25. data/test/headers_test.rb +56 -1
  26. data/test/processors/body_inserter_test.rb +4 -4
  27. data/test/{google_analytics_test.rb → processors/google_analytics_test.rb} +31 -0
  28. data/test/processors/header_context_inserter_test.rb +5 -5
  29. data/test/processors/logo_class_inserter_test.rb +55 -0
  30. data/test/processors/related_items_inserter_test.rb +61 -0
  31. data/test/{search_path_setter_test.rb → processors/search_path_setter_test.rb} +0 -0
  32. data/test/processors/section_inserter_test.rb +20 -5
  33. data/test/test_helper.rb +31 -22
  34. data/test/typical_usage_test.rb +101 -70
  35. metadata +60 -70
  36. data/lib/slimmer/related_items_inserter.rb +0 -36
  37. data/lib/slimmer/url_rewriter.rb +0 -40
@@ -1,5 +1,18 @@
1
+ # 2.0.0
2
+
3
+ Backwards-incompatible changes:
4
+
5
+ * Artefact has to be explicitly passed to slimmer.
6
+ * RelatedItemsInserter uses passed artefact instead of calling out to panopticon.
7
+ * Slimmer now strips all X-Slimmer-* HTTP headers from the final response.
8
+
9
+ Other changes
10
+
11
+ * new LogoClassInserter module - adds classes to the `#wrapper` element to control the appearence of the directgov and businesslink logos
12
+ * Rounded Corners!!! (it is 2.0 after all)
13
+
1
14
  # 0.9.0
2
15
 
3
16
  * Moved templates into slimmer rather than using separate static project
4
17
  * Added railtie so that slimmer can be dropped into a rails app without configuration
5
- * Began to write *gasp* tests!
18
+ * Began to write *gasp* tests!
@@ -1,3 +1,8 @@
1
+ #########################################
2
+ ####### Look, Rounded corners.... It's 2.0 #####
3
+ #####################################################
4
+ #######################################################
5
+
1
6
  require 'nokogiri'
2
7
  require 'erb'
3
8
  require 'open-uri'
@@ -5,33 +10,39 @@ require 'plek'
5
10
  require 'null_logger'
6
11
  require 'openssl'
7
12
 
13
+ require 'slimmer/version'
8
14
  require 'slimmer/railtie' if defined? Rails
9
15
 
10
16
  module Slimmer
11
- TEMPLATE_HEADER = 'X-Slimmer-Template'
12
- SKIP_HEADER = 'X-Slimmer-Skip'
13
- SEARCH_PATH_HEADER = 'X-Slimmer-Search-Path'
14
17
 
15
- autoload :Version, 'slimmer/version'
16
18
  autoload :Railtie, 'slimmer/railtie'
17
19
  autoload :Skin, 'slimmer/skin'
18
20
 
19
21
  autoload :Template, 'slimmer/template'
20
22
  autoload :App, 'slimmer/app'
21
- autoload :TitleInserter, 'slimmer/title_inserter'
22
- autoload :AdminTitleInserter, 'slimmer/admin_title_inserter'
23
- autoload :SectionInserter, 'slimmer/section_inserter'
24
- autoload :TagMover, 'slimmer/tag_mover'
25
- autoload :ConditionalCommentMover, 'slimmer/conditional_comment_mover'
26
- autoload :FooterRemover, 'slimmer/footer_remover'
27
- autoload :BodyInserter, 'slimmer/body_inserter'
28
- autoload :BodyClassCopier, 'slimmer/body_class_copier'
29
- autoload :UrlRewriter, 'slimmer/url_rewriter'
30
- autoload :HeaderContextInserter, 'slimmer/header_context_inserter'
31
- autoload :GoogleAnalyticsConfigurator, 'slimmer/google_analytics_configurator'
32
- autoload :RelatedItemsInserter, 'slimmer/related_items_inserter'
33
- autoload :SearchPathSetter, 'slimmer/search_path_setter'
23
+ autoload :Headers, 'slimmer/headers'
24
+
25
+ module Processors
26
+ autoload :AdminTitleInserter, 'slimmer/processors/admin_title_inserter'
27
+ autoload :BodyClassCopier, 'slimmer/processors/body_class_copier'
28
+ autoload :BodyInserter, 'slimmer/processors/body_inserter'
29
+ autoload :ConditionalCommentMover, 'slimmer/processors/conditional_comment_mover'
30
+ autoload :FooterRemover, 'slimmer/processors/footer_remover'
31
+ autoload :GoogleAnalyticsConfigurator, 'slimmer/processors/google_analytics_configurator'
32
+ autoload :HeaderContextInserter, 'slimmer/processors/header_context_inserter'
33
+ autoload :LogoClassInserter, 'slimmer/processors/logo_class_inserter'
34
+ autoload :RelatedItemsInserter, 'slimmer/processors/related_items_inserter'
35
+ autoload :SearchPathSetter, 'slimmer/processors/search_path_setter'
36
+ autoload :SectionInserter, 'slimmer/processors/section_inserter'
37
+ autoload :TagMover, 'slimmer/processors/tag_mover'
38
+ autoload :TitleInserter, 'slimmer/processors/title_inserter'
39
+ end
34
40
 
35
41
  class TemplateNotFoundException < StandardError; end
36
42
  class CouldNotRetrieveTemplate < StandardError; end
37
43
  end
44
+
45
+ #######################################################
46
+ #####################################################
47
+ #################################################
48
+ #########################################
@@ -1,5 +1,3 @@
1
- require "gds_api/exceptions"
2
-
3
1
  module Slimmer
4
2
  class App
5
3
  attr_accessor :logger
@@ -29,10 +27,10 @@ module Slimmer
29
27
  response = Rack::Response.new(body, status, headers)
30
28
 
31
29
  if response_can_be_rewritten?(response) && !skip_slimmer?(env, response)
32
- rewrite_response(env, response)
33
- else
34
- [status, headers, body]
30
+ status, headers, body = rewrite_response(env, response)
35
31
  end
32
+
33
+ [status, strip_slimmer_headers(headers), body]
36
34
  end
37
35
 
38
36
  def response_can_be_rewritten?(response)
@@ -53,7 +51,7 @@ module Slimmer
53
51
  end
54
52
 
55
53
  def skip_slimmer_header?(response)
56
- !!response.headers[SKIP_HEADER]
54
+ !!response.headers[Headers::SKIP_HEADER]
57
55
  end
58
56
 
59
57
  def s(body)
@@ -74,7 +72,7 @@ module Slimmer
74
72
 
75
73
  rewritten_body = case response.status
76
74
  when 200
77
- if response.headers[TEMPLATE_HEADER] == 'admin' || request.path =~ /^\/admin(\/|$)/
75
+ if response.headers[Headers::TEMPLATE_HEADER] == 'admin' || request.path =~ /^\/admin(\/|$)/
78
76
  @skin.admin s(response.body)
79
77
  else
80
78
  @skin.success request, response, s(response.body)
@@ -90,8 +88,10 @@ module Slimmer
90
88
  response.headers['Content-Length'] = content_length(response.body)
91
89
 
92
90
  response.finish
93
- rescue GdsApi::TimedOutException
94
- [503, {"Content-Type" => "text/plain"}, ["GDS API request timed out."]]
91
+ end
92
+
93
+ def strip_slimmer_headers(headers)
94
+ headers.reject {|k, v| k =~ /\A#{Headers::HEADER_PREFIX}/ }
95
95
  end
96
96
  end
97
97
  end
@@ -2,6 +2,8 @@ module Slimmer
2
2
  module Headers
3
3
  InvalidHeader = Class.new(RuntimeError)
4
4
 
5
+ HEADER_PREFIX = "X-Slimmer"
6
+
5
7
  SLIMMER_HEADER_MAPPING = {
6
8
  section: "Section",
7
9
  need_id: "Need-ID",
@@ -12,12 +14,41 @@ module Slimmer
12
14
  skip: "Skip",
13
15
  }
14
16
 
17
+ TEMPLATE_HEADER = "#{HEADER_PREFIX}-Template"
18
+ SKIP_HEADER = "#{HEADER_PREFIX}-Skip"
19
+ SEARCH_PATH_HEADER = "#{HEADER_PREFIX}-Search-Path"
20
+ ARTEFACT_HEADER = "#{HEADER_PREFIX}-Artefact"
21
+
15
22
  def set_slimmer_headers(hash)
16
23
  raise InvalidHeader if (hash.keys - SLIMMER_HEADER_MAPPING.keys).any?
17
24
  SLIMMER_HEADER_MAPPING.each do |hash_key, header_suffix|
18
25
  value = hash[hash_key]
19
- headers["X-Slimmer-#{header_suffix}"] = value.to_s if value
26
+ headers["#{HEADER_PREFIX}-#{header_suffix}"] = value.to_s if value
27
+ end
28
+ end
29
+
30
+ def set_slimmer_artefact(artefact_input)
31
+ if artefact_input.is_a?(Hash) or artefact_input.is_a?(OpenStruct)
32
+ artefact = artefact_input.dup
33
+ elsif artefact_input.respond_to?(:to_hash) # e.g. GdsApi::Response
34
+ artefact = artefact_input.to_hash.dup
35
+ end
36
+
37
+ if artefact.is_a?(Hash)
38
+ # Temporary deletions until the actions are removed from the API.
39
+ # The actions increase the size of the artefact significantly, and will
40
+ # only grow over time.
41
+ #
42
+ # We skip this when given an OpenStruct as they won't have actions etc...
43
+ artefact.delete("actions")
44
+ if artefact["related_items"]
45
+ artefact["related_items"].each do |ri|
46
+ ri["artefact"].delete("actions") if ri["artefact"]
47
+ end
48
+ end
20
49
  end
50
+
51
+ headers[ARTEFACT_HEADER] = artefact.to_json
21
52
  end
22
53
  end
23
54
  end
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class AdminTitleInserter
3
3
  def filter(src,dest)
4
4
  title = src.at_css('#site-title')
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class BodyClassCopier
3
3
  def filter(src, dest)
4
4
  src_body_tag = src.at_css("body")
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class BodyInserter
3
3
  def initialize(source_id='wrapper', destination_id='wrapper')
4
4
  @source_selector = '#' + source_id
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class ConditionalCommentMover
3
3
  def filter(src, dest)
4
4
  src.xpath('//comment()').each do |comment|
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class FooterRemover
3
3
  def filter(src,dest)
4
4
  footer = src.at_css("#footer")
@@ -1,6 +1,6 @@
1
1
  require "json"
2
2
 
3
- module Slimmer
3
+ module Slimmer::Processors
4
4
  class GoogleAnalyticsConfigurator
5
5
  PAGE_LEVEL_EVENT = 3
6
6
  HEADER_MAPPING = {
@@ -29,7 +29,8 @@ module Slimmer
29
29
  def set_custom_var(slot, name, value)
30
30
  return nil unless value
31
31
  value.downcase!
32
- "_gaq.push(#{JSON.dump([ "_setCustomVar", slot, name, value, PAGE_LEVEL_EVENT])});"
32
+ response = "_gaq.push(#{JSON.dump([ "_setCustomVar", slot, name, value, PAGE_LEVEL_EVENT])});\n"
33
+ response + "GOVUK.Analytics.#{name} = \"#{value}\";"
33
34
  end
34
35
  end
35
36
  end
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class HeaderContextInserter
3
3
  def initialize(path='.header-context')
4
4
  @path = path
@@ -0,0 +1,20 @@
1
+ module Slimmer
2
+ module Processors
3
+ class LogoClassInserter
4
+ LOGO_CLASSES = %w(businesslink directgov)
5
+
6
+ def initialize(artefact)
7
+ @artefact = artefact
8
+ end
9
+
10
+ def filter(source, dest)
11
+ return unless @artefact and @artefact["tag_ids"]
12
+ logo_classes = LOGO_CLASSES & @artefact["tag_ids"]
13
+ wrapper = dest.css('#wrapper')
14
+ logo_classes.each do |klass|
15
+ wrapper.add_class(klass)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ class Slimmer::Processors::RelatedItemsInserter
2
+ include ERB::Util
3
+
4
+ def initialize(related_block_template, artefact)
5
+ @related_block_template = related_block_template
6
+ @artefact = artefact
7
+ end
8
+
9
+ def filter(content_document, page_template)
10
+ if content_document.at_css('body.mainstream div#related-items')
11
+ page_template.at_css('body.mainstream div#related-items').replace(related_item_block)
12
+ end
13
+ end
14
+
15
+ def related_item_block
16
+ artefact = @artefact
17
+ html = ERB.new(@related_block_template).result(binding)
18
+ Nokogiri::HTML.fragment(html)
19
+ end
20
+ end
@@ -1,6 +1,4 @@
1
- require 'gds_api/helpers'
2
-
3
- module Slimmer
1
+ module Slimmer::Processors
4
2
  class SearchPathSetter
5
3
  def initialize(response)
6
4
  @response = response
@@ -13,7 +11,7 @@ module Slimmer
13
11
  end
14
12
 
15
13
  def search_scope
16
- @response.headers[SEARCH_PATH_HEADER]
14
+ @response.headers[Slimmer::Headers::SEARCH_PATH_HEADER]
17
15
  end
18
16
  end
19
17
  end
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class SectionInserter
3
3
  def filter(src,dest)
4
4
  meta_name = dest.at_css('meta[name="x-section-name"]')
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class TagMover
3
3
  def filter(src,dest)
4
4
  move_tags(src, dest, 'script', :must_have => ['src'])
@@ -1,4 +1,4 @@
1
- module Slimmer
1
+ module Slimmer::Processors
2
2
  class TitleInserter
3
3
  def filter(src,dest)
4
4
  title = src.at_css('head title')
@@ -92,8 +92,6 @@ module Slimmer
92
92
  logger.debug "Slimmer: Processor #{p} started at #{processor_start_time}"
93
93
  begin
94
94
  p.filter(src,dest)
95
- rescue GdsApi::TimedOutException
96
- raise
97
95
  rescue => e
98
96
  logger.error "Slimmer: Failed while processing #{p}: #{[ e.message, e.backtrace ].flatten.join("\n")}"
99
97
  end
@@ -111,39 +109,49 @@ module Slimmer
111
109
 
112
110
  def admin(body)
113
111
  processors = [
114
- TitleInserter.new(),
115
- TagMover.new(),
116
- AdminTitleInserter.new,
117
- FooterRemover.new,
118
- BodyInserter.new(),
119
- BodyClassCopier.new,
112
+ Processors::TitleInserter.new(),
113
+ Processors::TagMover.new(),
114
+ Processors::AdminTitleInserter.new,
115
+ Processors::FooterRemover.new,
116
+ Processors::BodyInserter.new(),
117
+ Processors::BodyClassCopier.new,
120
118
  ]
121
119
  process(processors, body, template('admin'))
122
120
  end
123
121
 
124
122
  def success(source_request, response, body)
123
+ artefact = artefact_from_header(response)
125
124
  processors = [
126
- TitleInserter.new(),
127
- TagMover.new(),
128
- ConditionalCommentMover.new(),
129
- BodyInserter.new(options[:wrapper_id] || 'wrapper'),
130
- BodyClassCopier.new,
131
- HeaderContextInserter.new(),
132
- SectionInserter.new(),
133
- GoogleAnalyticsConfigurator.new(response),
134
- SearchPathSetter.new(response),
135
- RelatedItemsInserter.new(template('related.raw'), source_request),
125
+ Processors::TitleInserter.new(),
126
+ Processors::TagMover.new(),
127
+ Processors::ConditionalCommentMover.new(),
128
+ Processors::BodyInserter.new(options[:wrapper_id] || 'wrapper'),
129
+ Processors::BodyClassCopier.new,
130
+ Processors::HeaderContextInserter.new(),
131
+ Processors::SectionInserter.new(),
132
+ Processors::GoogleAnalyticsConfigurator.new(response),
133
+ Processors::SearchPathSetter.new(response),
134
+ Processors::RelatedItemsInserter.new(template('related.raw'), artefact),
135
+ Processors::LogoClassInserter.new(artefact),
136
136
  ]
137
137
 
138
- template_name = response.headers[TEMPLATE_HEADER] || 'wrapper'
138
+ template_name = response.headers[Headers::TEMPLATE_HEADER] || 'wrapper'
139
139
  process(processors, body, template(template_name))
140
140
  end
141
141
 
142
142
  def error(template_name, body)
143
143
  processors = [
144
- TitleInserter.new()
144
+ Processors::TitleInserter.new()
145
145
  ]
146
146
  process(processors, body, template(template_name))
147
147
  end
148
+
149
+ def artefact_from_header(response)
150
+ if response.headers.include?(Headers::ARTEFACT_HEADER)
151
+ JSON.parse(response.headers[Headers::ARTEFACT_HEADER])
152
+ else
153
+ nil
154
+ end
155
+ end
148
156
  end
149
157
  end
@@ -7,7 +7,7 @@ module Slimmer
7
7
  module ClassMethods
8
8
  def slimmer_template template_name
9
9
  after_filter do
10
- response.headers[Slimmer::TEMPLATE_HEADER] = template_name.to_s
10
+ response.headers[Slimmer::Headers::TEMPLATE_HEADER] = template_name.to_s
11
11
  end
12
12
  end
13
13
  end
@@ -5,7 +5,11 @@ module Slimmer
5
5
  class Skin
6
6
  def load_template name
7
7
  logger.debug "Slimmer: TEST MODE - Loading fixture template from #{__FILE__}"
8
- Slimmer::TestTemplate::TEMPLATE
8
+ if name == 'related.raw'
9
+ %{<div id="test-related-items"></div>}
10
+ else
11
+ Slimmer::TestTemplate::TEMPLATE
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -15,7 +15,13 @@ module Slimmer::TestTemplate
15
15
  <script src="http://static.preview.alphagov.co.uk/static/customisation-settings.js" defer></script>
16
16
  </head>
17
17
  <body>
18
- <div class="header-context">Header</div>
18
+ <nav role="navigation" class="header-context">
19
+ <ol class="group">
20
+ <li><a href="/">Home</a></li>
21
+ <li><a href="/browse">All sections</a></li>
22
+ </ol>
23
+ </nav>
24
+
19
25
  <div id="wrapper"></div>
20
26
  </body>
21
27
  </html>
@@ -1,3 +1,3 @@
1
1
  module Slimmer
2
- VERSION = '1.2.5'
2
+ VERSION = '2.0.0'
3
3
  end