Class: Banzai::Filter::References::MilestoneReferenceFilter

Inherits:
AbstractReferenceFilter show all
Defined in:
lib/banzai/filter/references/milestone_reference_filter.rb

Overview

HTML filter that replaces milestone references with links.

Constant Summary

Constants included from Concerns::TimeoutFilterHandler

Concerns::TimeoutFilterHandler::COMPLEX_MARKDOWN_MESSAGE, Concerns::TimeoutFilterHandler::RENDER_TIMEOUT, Concerns::TimeoutFilterHandler::SANITIZATION_RENDER_TIMEOUT

Constants included from Concerns::PipelineTimingCheck

Concerns::PipelineTimingCheck::MAX_PIPELINE_SECONDS

Constants inherited from ReferenceFilter

ReferenceFilter::REFERENCE_TYPE_ATTRIBUTE, ReferenceFilter::REFERENCE_TYPE_DATA_ATTRIBUTE_NAME

Constants included from Concerns::TextReplacer

Concerns::TextReplacer::REFERENCE_PLACEHOLDER, Concerns::TextReplacer::REFERENCE_PLACEHOLDER_PATTERN

Instance Method Summary collapse

Methods inherited from AbstractReferenceFilter

#call, #find_object_cached, #find_object_from_link, #find_object_from_link_cached, #from_ref_cached, #identifier, #initialize, #object_link_content_html_extras, #object_link_filter, #parent_type, #symbol_from_match_data, #url_for_object_cached, #wrap_link

Methods included from CrossProjectReference

#parent_from_ref

Methods included from Concerns::TimeoutFilterHandler

#call

Methods included from Concerns::PipelineTimingCheck

#call, #exceeded_pipeline_max?

Methods inherited from ReferenceFilter

call, #call, #call_and_update_nodes, #each_node, #group, #initialize, #nodes, #nodes?, #object_class, #project

Methods included from Concerns::TextReplacer

#replace_references_in_text_with_html

Methods included from Concerns::HtmlWriter

#write_opening_tag

Methods included from Concerns::OutputSafety

#escape_once

Methods included from RequestStoreReferenceCache

#cached_call, #get_or_set_cache

Constructor Details

This class inherits a constructor from Banzai::Filter::References::AbstractReferenceFilter

Instance Method Details

#data_attributes_for(original, parent, object, link_content: false, link_reference: false) ⇒ Object



153
154
155
156
157
158
159
160
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 153

def data_attributes_for(original, parent, object, link_content: false, link_reference: false)
  object_parent = object.resource_parent

  return super unless object_parent.is_a?(Group)
  return super if object_parent.id == parent.id

  super.merge({ group: object_parent.id, namespace: object_parent.id, project: nil })
end

#find_milestones(parent, find_by_iid = false, absolute_path: false) ⇒ Object



100
101
102
103
104
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 100

def find_milestones(parent, find_by_iid = false, absolute_path: false)
  finder_params = milestone_finder_params(parent, find_by_iid, absolute_path)

  MilestonesFinder.new(finder_params).execute
end

#find_object(parent_object, id) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 37

def find_object(parent_object, id)
  key = reference_cache.records_per_parent[parent_object].keys.find do |k|
    k[:milestone_iid] == id[:milestone_iid] || k[:milestone_name] == id[:milestone_name]
  end

  reference_cache.records_per_parent[parent_object][key] if key
end

#group_and_ancestors_ids(parent, absolute_path) ⇒ Object



116
117
118
119
120
121
122
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 116

def group_and_ancestors_ids(parent, absolute_path)
  if group_context?(parent)
    absolute_path ? parent.id : parent.self_and_ancestors.select(:id)
  elsif project_context?(parent)
    absolute_path ? nil : parent.group&.self_and_ancestors&.select(:id)
  end
end

#group_context?(parent) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 80

def group_context?(parent)
  parent.is_a?(Group)
end

#milestone_finder_params(parent, find_by_iid, absolute_path) ⇒ Object



106
107
108
109
110
111
112
113
114
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 106

def milestone_finder_params(parent, find_by_iid, absolute_path)
  { order: nil, state: 'all' }.tap do |params|
    params[:project_ids] = parent.id if project_context?(parent)

    # We don't support IID lookups because IIDs can clash between
    # group/project milestones and group/subgroup milestones.
    params[:group_ids] = group_and_ancestors_ids(parent, absolute_path) unless find_by_iid
  end
end


130
131
132
133
134
135
136
137
138
139
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 130

def object_link_content_html(object, matches)
  html = super
  reference = object.project&.to_reference_base(project)

  i = doc.document.create_element('i')
  i.content = "in #{reference}"
  html += " #{i.to_html}" if reference.present?

  html
end


141
142
143
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 141

def object_link_title(object, matches)
  nil
end

#parentObject



145
146
147
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 145

def parent
  project || group
end

#parent_records(parent, ids) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 11

def parent_records(parent, ids)
  return Milestone.none unless valid_context?(parent)

  relation = []

  # We need to handle relative and absolute paths separately
  milestones_absolute_indexed = ids.group_by { |id| id[:absolute_path] }
  milestones_absolute_indexed.each do |absolute_path, fitered_ids|
    milestone_iids = fitered_ids&.pluck(:milestone_iid)&.compact

    if milestone_iids.present?
      relation << find_milestones(parent, true, absolute_path: absolute_path).iid_in(milestone_iids)
    end

    milestone_names = fitered_ids&.pluck(:milestone_name)&.compact
    if milestone_names.present?
      relation << find_milestones(parent, false, absolute_path: absolute_path).where(name: milestone_names)
    end
  end

  relation.compact!
  return Milestone.none if relation.all?(Milestone.none)

  Milestone.from_union(relation).includes(:project, :group)
end

#parse_symbol(symbol, match_data) ⇒ Object

Transform a symbol extracted from the text to a meaningful value

This method has the contract that if a string ref refers to a record record, then ‘parse_symbol(ref) == record_identifier(record)`.

This contract is slightly broken here, as we only have either the milestone_iid or the milestone_name, but not both. But below, we have both pieces of information. But it’s accounted for in find_object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 53

def parse_symbol(symbol, match_data)
  absolute_path = !!match_data&.named_captures&.fetch('absolute_path')

  if symbol
    # when parsing links, there is no `match_data[:milestone_iid]`, but `symbol`
    # holds the iid
    { milestone_iid: symbol.to_i, milestone_name: nil, absolute_path: absolute_path }
  else
    {
      milestone_iid: match_data[:milestone_iid]&.to_i,
      milestone_name: match_data[:milestone_name]&.tr('"', ''),
      absolute_path: absolute_path
    }
  end
end

#project_context?(parent) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 84

def project_context?(parent)
  parent.is_a?(Project)
end

#record_identifier(record) ⇒ Object

This method has the contract that if a string ref refers to a record record, then ‘class.parse_symbol(ref) == record_identifier(record)`. See note in parse_symbol above



72
73
74
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 72

def record_identifier(record)
  { milestone_iid: record.iid, milestone_name: record.name }
end

#references_in(text, pattern = Milestone.reference_pattern) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 88

def references_in(text, pattern = Milestone.reference_pattern)
  # We'll handle here the references that follow the `reference_pattern`.
  # Other patterns (for example, the link pattern) are handled by the
  # default implementation.
  return super(text, pattern) if pattern != Milestone.reference_pattern

  replace_references_in_text_with_html(text.gsub(pattern)) do |match_data|
    ident = identifier(match_data)
    yield match_data[0], ident, match_data[:project], match_data[:namespace], match_data
  end
end

#requires_unescaping?Boolean

Returns:

  • (Boolean)


149
150
151
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 149

def requires_unescaping?
  true
end

#url_for_object(milestone, project) ⇒ Object



124
125
126
127
128
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 124

def url_for_object(milestone, project)
  Gitlab::Routing
    .url_helpers
    .milestone_url(milestone, only_path: context[:only_path])
end

#valid_context?(parent) ⇒ Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/banzai/filter/references/milestone_reference_filter.rb', line 76

def valid_context?(parent)
  group_context?(parent) || project_context?(parent)
end