Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi-level link expansion for edition links #2605

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/models/link_graph/node_collection_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ def collection
attr_reader :link_graph, :with_drafts, :parent_node

def links_by_link_type
# We don't support nested edition links
return [] if parent_is_edition?

if root?
link_reference.root_links_by_link_type(
content_id: link_graph.root_content_id,
Expand All @@ -27,6 +24,8 @@ def links_by_link_type
else
link_reference.child_links_by_link_type(
content_id: parent_node.content_id,
locale: link_graph.root_locale,
with_drafts: link_graph.with_drafts,
link_types_path: parent_node.link_types_path,
parent_content_ids: parent_node.parent_content_ids,
might_have_own_links: parent_node.might_have_own_links?,
Expand Down
231 changes: 197 additions & 34 deletions app/queries/edition_links.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,38 @@
def self.from(content_id,
locale:,
with_drafts:,
allowed_link_types: nil)
allowed_link_types: nil,
parent_content_ids: [],
next_allowed_link_types_from: nil,
next_allowed_link_types_to: nil)
new(
content_id:,
mode: :from,
locale:,
with_drafts:,
allowed_link_types:,
parent_content_ids:,
next_allowed_link_types_from:,
next_allowed_link_types_to:,
).call
end

def self.to(content_id,
locale:,
with_drafts:,
allowed_link_types: nil)
allowed_link_types: nil,
parent_content_ids: [],
next_allowed_link_types_from: nil,
next_allowed_link_types_to: nil)
new(
content_id:,
mode: :to,
locale:,
with_drafts:,
allowed_link_types:,
parent_content_ids:,
next_allowed_link_types_from:,
next_allowed_link_types_to:,
).call
end

Expand All @@ -34,57 +46,83 @@

private

attr_reader :content_id, :mode, :locale, :with_drafts, :allowed_link_types
attr_reader :content_id,
:mode,
:locale,
:with_drafts,
:allowed_link_types,
:parent_content_ids,
:next_allowed_link_types_from,
:next_allowed_link_types_to

def initialize(
content_id:,
mode:,
locale:,
with_drafts:,
allowed_link_types:
allowed_link_types:,
parent_content_ids: [],
next_allowed_link_types_from: nil,
next_allowed_link_types_to: nil
)
@content_id = content_id
@mode = mode
@locale = locale
@with_drafts = with_drafts
@allowed_link_types = allowed_link_types
@parent_content_ids = parent_content_ids
@next_allowed_link_types_from = next_allowed_link_types_from
@next_allowed_link_types_to = next_allowed_link_types_to
end

def links_results
query = Link.left_joins(edition: :document)
where(query)
.order(link_type: :asc, position: :asc)
.pluck(:link_type, link_field, "documents.locale", "editions.id")
end
condition = {}
# condition[:"documents.locale"] = locale if locale
condition[:link_type] = allowed_link_types if allowed_link_types

def link_field
mode == :from ? :target_content_id : "documents.content_id"
if mode == :from
puts "from"

Check failure on line 84 in app/queries/edition_links.rb

View workflow job for this annotation

GitHub Actions / Lint Ruby / Run RuboCop

Rails/Output: Do not write to stdout. Use Rails's logger if you want to log.
Link
.left_joins(edition: :document)
.left_joins(:link_set)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to join link_set here. If you look at the way this works in root_links_by_link_type, the idea is to make separate queries - one for link set links, and one for edition links.

  def root_links_by_link_type(content_id:, locale:, with_drafts: false)
    direct = own_links(content_id)
    reverse = linked_to(content_id, rules.reverse_links)
    edition = edition_links(content_id, locale, with_drafts)

    reverse.merge(direct).merge(edition)
  end

I assume we'd want to take a similar approach in child_links_by_link_type.

.where("documents.content_id": content_id)
.or(Link.where("link_sets.content_id": content_id))
.where(condition)
.where(draft_condition)
.order(link_type: :asc, position: :asc)
.pluck(*fields)
else
puts "to"

Check failure on line 95 in app/queries/edition_links.rb

View workflow job for this annotation

GitHub Actions / Lint Ruby / Run RuboCop

Rails/Output: Do not write to stdout. Use Rails's logger if you want to log.
links = Link
.left_joins(edition: :document)
.left_joins(:link_set)
.where("links.target_content_id": content_id)
.where(condition)
.where(draft_condition)
.order(link_type: :asc, position: :asc)
.pluck(*fields)
puts content_id

Check failure on line 104 in app/queries/edition_links.rb

View workflow job for this annotation

GitHub Actions / Lint Ruby / Run RuboCop

Rails/Output: Do not write to stdout. Use Rails's logger if you want to log.
puts links.inspect

Check failure on line 105 in app/queries/edition_links.rb

View workflow job for this annotation

GitHub Actions / Lint Ruby / Run RuboCop

Rails/Output: Do not write to stdout. Use Rails's logger if you want to log.
puts Link.left_joins(edition: :document).left_joins(:link_set).where("links.target_content_id": content_id).where(condition).inspect

Check failure on line 106 in app/queries/edition_links.rb

View workflow job for this annotation

GitHub Actions / Lint Ruby / Run RuboCop

Rails/Output: Do not write to stdout. Use Rails's logger if you want to log.
links
end
end

def where(query)
condition = if mode == :from
{ "documents.content_id": content_id }
else
{ target_content_id: content_id }
end
condition[:"documents.locale"] = locale if locale
condition[:link_type] = allowed_link_types if allowed_link_types
query.where(condition).where(draft_condition)
def fields
base_fields = [
:link_type,
mode == :from ? :target_content_id : "documents.content_id",
mode == :from ? :target_content_id : "link_sets.content_id",
"documents.locale",
"editions.id",
]
base_fields << has_own_links_field if check_for_from_children?
base_fields << is_linked_to_field if check_for_to_children?
base_fields
end

def draft_condition
return { editions: { content_store: "live" } } unless with_drafts

Check failure on line 125 in app/queries/edition_links.rb

View workflow job for this annotation

GitHub Actions / Lint Ruby / Run RuboCop

Style/RedundantReturn: Redundant `return` detected. (https://rubystyle.guide#no-explicit-return)

<<-SQL.strip_heredoc
CASE WHEN EXISTS (
SELECT 1 FROM editions AS e
WHERE content_store = 'draft'
AND e.document_id = documents.id
)
THEN editions.content_store = 'draft'
ELSE editions.content_store = 'live'
END
SQL
end

def group_results(results)
Expand All @@ -98,10 +136,135 @@

def result_hash(row)
{
content_id: row[1],
locale: row[2],
edition_id: row[3],
content_id: row[1] || row[2],
locale: row[3],
edition_id: row[4],
has_own_links: has_own_links_result(row),
is_linked_to: is_linked_to_result(row),
}
end

def has_own_links_result(row)
return false unless could_have_from_children?

check_for_from_children? ? row[5] : nil
end

def is_linked_to_result(row)
return false unless could_have_to_children?

check_for_from_children? ? row[6] : row[5]
end

def check_for_from_children?
next_allowed_link_types_from && next_allowed_link_types_from.present?
end

def check_for_to_children?
next_allowed_link_types_to && next_allowed_link_types_to.present?
end

def could_have_from_children?
next_allowed_link_types_from.nil? || next_allowed_link_types_from.present?
end

def could_have_to_children?
next_allowed_link_types_to.nil? || next_allowed_link_types_to.present?
end

def has_own_links_field
if mode == :from
Arel.sql(%{
EXISTS(
SELECT nested_links.id
FROM links AS nested_links
INNER JOIN link_sets AS nested_link_sets
ON nested_link_sets.id = nested_links.link_set_id
WHERE nested_link_sets.content_id = links.target_content_id
#{and_not_parent('nested_links.target_content_id')}
AND (#{allowed_links_condition(next_allowed_link_types_from)})
LIMIT 1
) OR EXISTS(
SELECT nested_links.id
FROM links AS nested_links
INNER JOIN documents AS nested_documents
ON nested_documents.content_id = nested_links.target_content_id
WHERE nested_documents.content_id = links.target_content_id
#{and_not_parent('nested_links.target_content_id')}
AND (#{allowed_links_condition(next_allowed_link_types_from)})
LIMIT 1
)
})
else
Arel.sql(%{
EXISTS(
SELECT nested_links.id
FROM links AS nested_links
INNER JOIN link_sets AS nested_link_sets
ON nested_link_sets.id = nested_links.link_set_id
WHERE nested_links.target_content_id = link_sets.content_id
#{and_not_parent('nested_links.target_content_id')}
AND (#{allowed_links_condition(next_allowed_link_types_from)})
LIMIT 1
) OR EXISTS(
SELECT nested_links.id
FROM links AS nested_links
INNER JOIN documents AS nested_documents
ON nested_documents.content_id = nested_links.target_content_id
WHERE nested_links.target_content_id = documents.content_id
#{and_not_parent('nested_links.target_content_id')}
AND (#{allowed_links_condition(next_allowed_link_types_from)})
LIMIT 1
)
})
end
end

def is_linked_to_field
query = if mode == :from
"nested_links.target_content_id = links.target_content_id"
else
"(nested_links.target_content_id = link_sets.content_id OR nested_links.target_content_id = documents.content_id)"
end

Arel.sql(%{
EXISTS(
SELECT nested_links.id
FROM links AS nested_links
LEFT JOIN link_sets AS nested_link_sets
ON nested_link_sets.id = nested_links.link_set_id
LEFT JOIN documents AS nested_documents
ON nested_documents.content_id = nested_links.target_content_id
WHERE #{query}
#{and_not_parent('nested_link_sets.content_id')}
AND (#{allowed_links_condition(next_allowed_link_types_to)})
LIMIT 1
)
})
end

def and_not_parent(field)
return if parent_content_ids.empty?

quoted = parent_content_ids.map { |c_id| quote(c_id) }

"AND #{field} NOT IN (#{quoted.join(', ')})"
end

def allowed_links_condition(allowed_links)
each_link_type = allowed_links.map do |(link_type, next_links)|
raise "Empty links for #{link_type} on #{content_id}" if next_links.empty?

quoted_next_links = next_links.map { |n| quote(n) }

"(links.link_type = #{quote(link_type)} AND nested_links.link_type IN (#{quoted_next_links.join(', ')}))"
end

each_link_type.join(" OR ")
end

def quote(field)
ActiveRecord::Base.connection.quote(field)
end
end
end
Loading
Loading