Module: Banzai::Renderer
- Defined in:
- lib/banzai/renderer.rb
Constant Summary collapse
- USER_CONTENT_ID_PREFIX =
'user-content-'
- HTML_PIPELINE_SUBSCRIPTION =
'call_filter.html_pipeline'
Class Method Summary collapse
-
.cache_collection_render(texts_and_contexts) ⇒ Object
Perform multiple render from an Array of Markdown String into an Array of HTML-safe String of HTML.
- .cacheless_render(text, context = {}) ⇒ Object
-
.cacheless_render_field(object, field, context = {}) ⇒ Object
Same as
render_field
, but without consulting or updating the cache field. - .color_for_duration(duration, min: 1, max: 2) ⇒ Object
- .formatted_duration(duration) ⇒ Object
- .full_cache_key(cache_key, pipeline_name) ⇒ Object
-
.full_cache_multi_key(cache_key, pipeline_name) ⇒ Object
To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key.
-
.instrument_filters ⇒ Object
this is built specifically for outputting debug timing/information for the Banzai pipeline.
-
.post_process(html, context) ⇒ Object
Perform post-processing on an HTML String.
-
.render(text, context = {}) ⇒ Object
Convert a Markdown String into an HTML-safe String of HTML.
-
.render_field(object, field, context = {}) ⇒ Object
Convert a Markdown-containing field on an object into an HTML-safe String of HTML.
- .render_result(text, context = {}) ⇒ Object
Class Method Details
.cache_collection_render(texts_and_contexts) ⇒ Object
Perform multiple render from an Array of Markdown String into an Array of HTML-safe String of HTML.
The redis cache is completely obviated if we receive a ‘:rendered` key in the context, as it is assumed the item has been pre-rendered somewhere else and there is no need to cache it.
If no ‘:rendered` key is present in the context, as the rendered Markdown String can be already cached, read all the data from the cache using Rails.cache.read_multi operation. If the Markdown String is not in the cache or it’s not cacheable (no cache_key entry is provided in the context) the Markdown String is rendered and stored in the cache so the next render call gets the rendered HTML-safe String from the cache.
For further explanation see #render method comments.
texts_and_contexts - An Array of Hashes that contains the Markdown String (:text)
an passed to our HTML Pipeline (:context)
If on the :context you specify a :cache_key entry will be used to retrieve it and cache the result of rendering the Markdown String.
Returns an Array containing HTML-safe String instances.
Example:
texts_and_contexts
=> [{ text: '### Hello',
context: { cache_key: [note, :note] } }]
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/banzai/renderer.rb', line 84 def self.cache_collection_render(texts_and_contexts) items_collection = texts_and_contexts.each do |item| context = item[:context] if context.key?(:rendered) item[:rendered] = context.delete(:rendered) else # If the attribute didn't come in pre-rendered, let's prepare it for caching it in redis cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline]) item[:cache_key] = cache_key if cache_key end end cacheable_items, non_cacheable_items = items_collection.group_by do |item| if item.key?(:rendered) # We're not really doing anything here as these don't need any processing, but leaving it just in case # as they could have a cache_key and we don't want them to be re-rendered :rendered elsif item.key?(:cache_key) :cacheable else :non_cacheable end end.values_at(:cacheable, :non_cacheable) items_in_cache = [] items_not_in_cache = [] if cacheable_items.present? items_in_cache = Rails.cache.read_multi(*cacheable_items.map { |item| item[:cache_key] }) items_not_in_cache = cacheable_items.reject do |item| item[:rendered] = items_in_cache[item[:cache_key]] items_in_cache.key?(item[:cache_key]) end end (items_not_in_cache + Array.wrap(non_cacheable_items)).each do |item| item[:rendered] = render(item[:text], item[:context]) Rails.cache.write(item[:cache_key], item[:rendered]) if item[:cache_key] end items_collection.map { |item| item[:rendered] } end |
.cacheless_render(text, context = {}) ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/banzai/renderer.rb', line 162 def self.cacheless_render(text, context = {}) return text.to_s unless text.present? result = render_result(text, context) output = result[:output] if output.respond_to?(:to_html) output.to_html else output.to_s end end |
.cacheless_render_field(object, field, context = {}) ⇒ Object
Same as render_field
, but without consulting or updating the cache field
49 50 51 52 53 54 |
# File 'lib/banzai/renderer.rb', line 49 def self.cacheless_render_field(object, field, context = {}) text = object.__send__(field) # rubocop:disable GitlabSecurity/PublicSend context = context.reverse_merge(object.banzai_render_context(field)) if object.respond_to?(:banzai_render_context) cacheless_render(text, context) end |
.color_for_duration(duration, min: 1, max: 2) ⇒ Object
231 232 233 234 235 236 237 238 239 |
# File 'lib/banzai/renderer.rb', line 231 def self.color_for_duration(duration, min: 1, max: 2) if duration < min :green elsif duration >= min && duration < max :orange else :red end end |
.formatted_duration(duration) ⇒ Object
226 227 228 229 |
# File 'lib/banzai/renderer.rb', line 226 def self.formatted_duration(duration) color = color_for_duration(duration) Rainbow.new.wrap(format('%5f_s', duration)).color(color) end |
.full_cache_key(cache_key, pipeline_name) ⇒ Object
175 176 177 178 179 180 181 182 183 |
# File 'lib/banzai/renderer.rb', line 175 def self.full_cache_key(cache_key, pipeline_name) return unless cache_key [ "banzai", *cache_key, pipeline_name || :full, Gitlab::MarkdownCache.latest_cached_markdown_version(local_version: nil) ] end |
.full_cache_multi_key(cache_key, pipeline_name) ⇒ Object
To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key. Other option will be to generate stringified keys on our side and don’t delegate to Rails.cache.expanded_key method.
188 189 190 191 192 |
# File 'lib/banzai/renderer.rb', line 188 def self.full_cache_multi_key(cache_key, pipeline_name) return unless cache_key Rails.cache.__send__(:expanded_key, full_cache_key(cache_key, pipeline_name)) # rubocop:disable GitlabSecurity/PublicSend end |
.instrument_filters ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/banzai/renderer.rb', line 198 def self.instrument_filters service = ActiveSupport::Notifications HTML::Pipeline.default_instrumentation_service = service service.monotonic_subscribe(HTML_PIPELINE_SUBSCRIPTION) do |_event, start, ending, _transaction_id, payload| duration = ending - start payload[:result][:pipeline_timing] = payload[:result][:pipeline_timing].to_f + duration if payload[:context][:debug] || payload[:context][:debug_timing] duration_str = formatted_duration(duration) pipeline_timing_str = formatted_duration(payload[:result][:pipeline_timing]) filter_name = payload[:filter].delete_prefix('Banzai::Filter::') pipeline_name = payload[:pipeline].delete_prefix('Banzai::Pipeline::') logger = Logger.new($stdout) logger.debug "#{duration_str} (#{pipeline_timing_str}): #{filter_name} [#{pipeline_name}]" if payload[:context][:debug] logger.debug(payload) end end end yield ensure service.unsubscribe(HTML_PIPELINE_SUBSCRIPTION) if service end |
.post_process(html, context) ⇒ Object
Perform post-processing on an HTML String
This method is used to perform state-dependent changes to a String of HTML, such as removing references that the current user doesn’t have permission to make (‘ReferenceRedactorFilter`).
html - String to process context - Hash of options to customize output
:pipeline - Symbol pipeline type - for context transform only, defaults to :full
:project - Project
:user - User object
:post_process_pipeline - pipeline to use for post_processing - defaults to PostProcessPipeline
Returns an HTML-safe String
149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/banzai/renderer.rb', line 149 def self.post_process(html, context) context = Pipeline[context[:pipeline]].transform_context(context) # Use a passed class for the pipeline or default to PostProcessPipeline pipeline = context.delete(:post_process_pipeline) || ::Banzai::Pipeline::PostProcessPipeline if context[:xhtml] pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) else pipeline.to_html(html, context) end.html_safe end |
.render(text, context = {}) ⇒ Object
Convert a Markdown String into an HTML-safe String of HTML
Note that while the returned HTML will have been sanitized of dangerous HTML, it may post a risk of information leakage if it’s not also passed through ‘post_process`.
Also note that the returned String is always HTML, not XHTML. Views requiring XHTML, such as Atom feeds, need to call ‘post_process` on the result, providing the appropriate `pipeline` option.
text - Markdown String context - Hash of context options passed to our HTML Pipeline
Returns an HTML-safe String
22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/banzai/renderer.rb', line 22 def self.render(text, context = {}) cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) if cache_key Rails.cache.fetch(cache_key) do cacheless_render(text, context) end else cacheless_render(text, context) end end |
.render_field(object, field, context = {}) ⇒ Object
Convert a Markdown-containing field on an object into an HTML-safe String of HTML. This method is analogous to calling render(object.field), but it can cache the rendered HTML in the object, rather than Redis.
38 39 40 41 42 43 44 45 46 |
# File 'lib/banzai/renderer.rb', line 38 def self.render_field(object, field, context = {}) unless object.respond_to?(:cached_markdown_fields) return cacheless_render_field(object, field, context) end object.refresh_markdown_cache! unless object.cached_html_up_to_date?(field) object.cached_html_for(field) end |