optimize work package fetching for scheduling

pull/10349/head
ulferts 3 years ago
parent 491928a3fd
commit fe3ff0eb82
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 19
      app/models/work_packages/scopes/for_scheduling.rb
  2. 26
      app/services/work_packages/schedule_dependency.rb
  3. 16
      spec/models/work_packages/scopes/for_scheduling_spec.rb

@ -118,26 +118,29 @@ module WorkPackages::Scopes
def scheduling_paths_sql(work_packages)
values = work_packages.map do |wp|
::OpenProject::SqlSanitization
.sanitize "(:id, false)",
.sanitize "(:id, false, false)",
id: wp.id
end.join(', ')
<<~SQL.squish
to_schedule (id, manually) AS (
SELECT * FROM (VALUES#{values}) AS t(id, manually)
SELECT * FROM (VALUES#{values}) AS t(id, manually, hierarchy_up)
UNION
SELECT
relations.from_id id,
(related_work_packages.schedule_manually OR COALESCE(descendants.manually, false)) manually
(related_work_packages.schedule_manually OR COALESCE(descendants.manually, false)) manually,
relations.hierarchy_up
FROM
to_schedule
JOIN LATERAL
(
SELECT
from_id,
to_id
to_id,
false hierarchy_up
FROM
relations
WHERE NOT to_schedule.manually
@ -149,14 +152,14 @@ module WorkPackages::Scopes
THEN work_package_hierarchies.descendant_id
ELSE work_package_hierarchies.ancestor_id
END from_id,
to_schedule.id to_id
to_schedule.id to_id,
work_package_hierarchies.descendant_id = to_schedule.id hierarchy_up
FROM
work_package_hierarchies
WHERE
NOT to_schedule.manually
AND work_package_hierarchies.generations = 1
AND (work_package_hierarchies.ancestor_id = to_schedule.id
OR work_package_hierarchies.descendant_id = to_schedule.id)
AND ((work_package_hierarchies.ancestor_id = to_schedule.id AND NOT to_schedule.hierarchy_up AND work_package_hierarchies.generations = 1)
OR (work_package_hierarchies.descendant_id = to_schedule.id AND work_package_hierarchies.generations > 0))
) relations ON relations.to_id = to_schedule.id
LEFT JOIN work_packages related_work_packages
ON relations.from_id = related_work_packages.id

@ -59,24 +59,23 @@ class WorkPackages::ScheduleDependency
private
def build_dependencies
following = load_following(work_packages)
following = load_following
# Those variables are pure optimizations.
# We want to reuse the already loaded work packages as much as possible
# and we want to have them readily available as hashes.
self.known_work_packages += following
known_work_packages.uniq!
self.known_work_packages_by_id = known_work_packages.group_by(&:id)
self.known_work_packages_by_parent_id = known_work_packages.group_by(&:parent_id)
self.known_work_packages_by_id = known_work_packages.group_by(&:id).transform_values(&:first)
self.known_work_packages_by_parent_id = fetch_descendants.group_by(&:parent_id)
add_dependencies(following)
end
def load_following(work_packages)
def load_following
WorkPackage
.for_scheduling(work_packages)
.includes(:parent,
follows_relations: :to)
.includes(follows_relations: :to)
end
def add_dependencies(dependent_work_packages)
@ -127,6 +126,19 @@ class WorkPackages::ScheduleDependency
end
end
# Use a mixture of work packages that are already loaded to be scheduled themselves but also load
# all the rest of the descendants. There are two cases in which descendants are not loaded for scheduling:
# * manual scheduling: A descendant is either scheduled manually itself or all of its descendants are scheduled manually.
# * sibling: the descendant is not below a work package to be scheduled (e.g. one following another) but below an ancestor of
# a schedule work package.
def fetch_descendants
descendants = known_work_packages_by_id.values
descendants + WorkPackage
.with_ancestor(descendants)
.where.not(id: known_work_packages_by_id.keys)
end
class Dependency
def initialize(work_package, schedule_dependency)
self.schedule_dependency = schedule_dependency
@ -195,7 +207,7 @@ class WorkPackages::ScheduleDependency
parent = known_work_packages_by_id[work_package.parent_id]
if parent
parent + ancestors_from_preloaded(parent.first)
[parent] + ancestors_from_preloaded(parent)
end
end || []
end

@ -503,6 +503,22 @@ describe WorkPackages::Scopes::ForScheduling, 'allowed scope' do
end
end
end
context 'for a work package with a sibling and a successor that also has a sibling' do
let(:sibling) do
create(:work_package, project: project, parent: parent)
end
let(:successor_sibling) do
create(:work_package, project: project, parent: successor_parent)
end
let!(:existing_work_packages) { [parent, sibling, successor, successor_parent, successor_sibling] }
it 'contains the successor and the parents but not the siblings' do
expect(WorkPackage.for_scheduling([origin]))
.to match_array([successor, parent, successor_parent])
end
end
end
end
# rubocop:enable RSpec/MultipleMemoizedHelpers

Loading…
Cancel
Save