Commit 0cec0605 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'optimize-issue-board-lists' into 'master'

Remove priority sort from board list issues

See merge request gitlab-org/gitlab!70674
parents 2274b920 f2d3b94f
...@@ -107,8 +107,6 @@ class Issue < ApplicationRecord ...@@ -107,8 +107,6 @@ class Issue < ApplicationRecord
scope :order_due_date_asc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'ASC')) } scope :order_due_date_asc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'ASC')) }
scope :order_due_date_desc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'DESC')) } scope :order_due_date_desc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'DESC')) }
scope :order_closest_future_date, -> { reorder(Arel.sql('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC')) } scope :order_closest_future_date, -> { reorder(Arel.sql('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC')) }
scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) }
scope :order_relative_position_desc, -> { reorder(::Gitlab::Database.nulls_first_order('relative_position', 'DESC')) }
scope :order_closed_date_desc, -> { reorder(closed_at: :desc) } scope :order_closed_date_desc, -> { reorder(closed_at: :desc) }
scope :order_created_at_desc, -> { reorder(created_at: :desc) } scope :order_created_at_desc, -> { reorder(created_at: :desc) }
scope :order_severity_asc, -> { includes(:issuable_severity).order('issuable_severities.severity ASC NULLS FIRST') } scope :order_severity_asc, -> { includes(:issuable_severity).order('issuable_severities.severity ASC NULLS FIRST') }
...@@ -267,8 +265,8 @@ class Issue < ApplicationRecord ...@@ -267,8 +265,8 @@ class Issue < ApplicationRecord
'due_date' => -> { order_due_date_asc.with_order_id_desc }, 'due_date' => -> { order_due_date_asc.with_order_id_desc },
'due_date_asc' => -> { order_due_date_asc.with_order_id_desc }, 'due_date_asc' => -> { order_due_date_asc.with_order_id_desc },
'due_date_desc' => -> { order_due_date_desc.with_order_id_desc }, 'due_date_desc' => -> { order_due_date_desc.with_order_id_desc },
'relative_position' => -> { order_relative_position_asc.with_order_id_desc }, 'relative_position' => -> { order_by_relative_position },
'relative_position_asc' => -> { order_relative_position_asc.with_order_id_desc } 'relative_position_asc' => -> { order_by_relative_position }
} }
) )
end end
...@@ -278,7 +276,7 @@ class Issue < ApplicationRecord ...@@ -278,7 +276,7 @@ class Issue < ApplicationRecord
when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date
when 'due_date', 'due_date_asc' then order_due_date_asc.with_order_id_desc when 'due_date', 'due_date_asc' then order_due_date_asc.with_order_id_desc
when 'due_date_desc' then order_due_date_desc.with_order_id_desc when 'due_date_desc' then order_due_date_desc.with_order_id_desc
when 'relative_position', 'relative_position_asc' then order_relative_position_asc.with_order_id_desc when 'relative_position', 'relative_position_asc' then order_by_relative_position
when 'severity_asc' then order_severity_asc.with_order_id_desc when 'severity_asc' then order_severity_asc.with_order_id_desc
when 'severity_desc' then order_severity_desc.with_order_id_desc when 'severity_desc' then order_severity_desc.with_order_id_desc
else else
...@@ -286,13 +284,8 @@ class Issue < ApplicationRecord ...@@ -286,13 +284,8 @@ class Issue < ApplicationRecord
end end
end end
# `with_cte` argument allows sorting when using CTE queries and prevents def self.order_by_relative_position
# errors in postgres when using CTE search optimisation reorder(Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_id_asc]))
def self.order_by_position_and_priority(with_cte: false)
order = Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_highest_priority, column_order_id_desc])
order_labels_priority(with_cte: with_cte)
.reorder(order)
end end
def self.column_order_relative_position def self.column_order_relative_position
...@@ -307,25 +300,6 @@ class Issue < ApplicationRecord ...@@ -307,25 +300,6 @@ class Issue < ApplicationRecord
) )
end end
def self.column_order_highest_priority
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'highest_priority',
column_expression: Arel.sql('highest_priorities.label_priority'),
order_expression: Gitlab::Database.nulls_last_order('highest_priorities.label_priority', 'ASC'),
reversed_order_expression: Gitlab::Database.nulls_last_order('highest_priorities.label_priority', 'DESC'),
order_direction: :asc,
nullable: :nulls_last,
distinct: false
)
end
def self.column_order_id_desc
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id',
order_expression: arel_table[:id].desc
)
end
def self.column_order_id_asc def self.column_order_id_asc
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id', attribute_name: 'id',
......
...@@ -22,7 +22,7 @@ module Boards ...@@ -22,7 +22,7 @@ module Boards
def order(items) def order(items)
return items.order_closed_date_desc if list&.closed? return items.order_closed_date_desc if list&.closed?
items.order_by_position_and_priority(with_cte: params[:search].present?) items.order_by_relative_position
end end
def finder def finder
......
...@@ -82,7 +82,7 @@ module Issues ...@@ -82,7 +82,7 @@ module Issues
collection.each do |project| collection.each do |project|
caching.cache_current_project_id(project.id) caching.cache_current_project_id(project.id)
index += 1 index += 1
scope = Issue.in_projects(project).reorder(custom_reorder).select(:id, :relative_position) scope = Issue.in_projects(project).order_by_relative_position.select(:id, :relative_position)
with_retry(PREFETCH_ISSUES_BATCH_SIZE, 100) do |batch_size| with_retry(PREFETCH_ISSUES_BATCH_SIZE, 100) do |batch_size|
Gitlab::Pagination::Keyset::Iterator.new(scope: scope).each_batch(of: batch_size) do |batch| Gitlab::Pagination::Keyset::Iterator.new(scope: scope).each_batch(of: batch_size) do |batch|
...@@ -166,10 +166,6 @@ module Issues ...@@ -166,10 +166,6 @@ module Issues
@start_position ||= (RelativePositioning::START_POSITION - (gaps / 2) * gap_size).to_i @start_position ||= (RelativePositioning::START_POSITION - (gaps / 2) * gap_size).to_i
end end
def custom_reorder
::Gitlab::Pagination::Keyset::Order.build([Issue.column_order_relative_position, Issue.column_order_id_asc])
end
def with_retry(initial_batch_size, exit_batch_size) def with_retry(initial_batch_size, exit_batch_size)
retries = 0 retries = 0
batch_size = initial_batch_size batch_size = initial_batch_size
......
# frozen_string_literal: true
class UpdateIssuesRelativePositionIndexes < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
RELATIVE_POSITION_INDEX_NAME = 'idx_issues_on_project_id_and_rel_asc_and_id'
RELATIVE_POSITION_STATE_INDEX_NAME = 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id'
NEW_RELATIVE_POSITION_STATE_INDEX_NAME = 'idx_issues_on_project_id_and_rel_position_and_id_and_state_id'
def up
add_concurrent_index :issues, [:project_id, :relative_position, :id, :state_id], name: NEW_RELATIVE_POSITION_STATE_INDEX_NAME
remove_concurrent_index_by_name :issues, RELATIVE_POSITION_INDEX_NAME
remove_concurrent_index_by_name :issues, RELATIVE_POSITION_STATE_INDEX_NAME
end
def down
add_concurrent_index :issues, [:project_id, :relative_position, :state_id, :id], order: { id: :desc }, name: RELATIVE_POSITION_STATE_INDEX_NAME
add_concurrent_index :issues, [:project_id, :relative_position, :id], name: RELATIVE_POSITION_INDEX_NAME
remove_concurrent_index_by_name :issues, NEW_RELATIVE_POSITION_STATE_INDEX_NAME
end
end
8e54f43a955023e422bf40476f468fbdf04f06e806b08fddf35208c65607fec3
\ No newline at end of file
...@@ -24045,9 +24045,7 @@ CREATE INDEX idx_issues_on_project_id_and_created_at_and_id_and_state_id ON issu ...@@ -24045,9 +24045,7 @@ CREATE INDEX idx_issues_on_project_id_and_created_at_and_id_and_state_id ON issu
CREATE INDEX idx_issues_on_project_id_and_due_date_and_id_and_state_id ON issues USING btree (project_id, due_date, id, state_id) WHERE (due_date IS NOT NULL); CREATE INDEX idx_issues_on_project_id_and_due_date_and_id_and_state_id ON issues USING btree (project_id, due_date, id, state_id) WHERE (due_date IS NOT NULL);
CREATE INDEX idx_issues_on_project_id_and_rel_asc_and_id ON issues USING btree (project_id, relative_position, id); CREATE INDEX idx_issues_on_project_id_and_rel_position_and_id_and_state_id ON issues USING btree (project_id, relative_position, id, state_id);
CREATE INDEX idx_issues_on_project_id_and_rel_position_and_state_id_and_id ON issues USING btree (project_id, relative_position, state_id, id DESC);
CREATE INDEX idx_issues_on_project_id_and_updated_at_and_id_and_state_id ON issues USING btree (project_id, updated_at, id, state_id); CREATE INDEX idx_issues_on_project_id_and_updated_at_and_id_and_state_id ON issues USING btree (project_id, updated_at, id, state_id);
...@@ -203,17 +203,13 @@ When visiting a board, issues appear ordered in any list. You're able to change ...@@ -203,17 +203,13 @@ When visiting a board, issues appear ordered in any list. You're able to change
that order by dragging the issues. The changed order is saved, so that anybody who visits the same that order by dragging the issues. The changed order is saved, so that anybody who visits the same
board later sees the reordering, with some exceptions. board later sees the reordering, with some exceptions.
The first time an issue appears in any board (that is, the first time a user When an issue is created, the system assigns a relative order value that is greater than the maximum value
loads a board containing that issue), it is ordered in relation to other issues in that list. of that issue's project or root group. This means the issue will be at the bottom of any issue list that
The order is done according to [label priority](labels.md#label-priority). it appears in.
At this point, that issue is assigned a relative order value by the system, Any time you drag and reorder the issue, its relative order value changes accordingly.
with respect to the other issues in the list. Any time Then, any time that issue appears in any board, the ordering is done according to
you drag and reorder the issue, its relative order value changes accordingly. the updated relative order value. If a user in your GitLab instance
Also, any time that issue appears in any board, the ordering is done according to
the updated relative order value. It's only the first
time an issue appears that it takes from the priority order mentioned above. If a user in your GitLab instance
drags issue `A` above issue `B`, the ordering is maintained when these two issues are subsequently drags issue `A` above issue `B`, the ordering is maintained when these two issues are subsequently
loaded in any board in the same instance. This could be a different project board or a different group loaded in any board in the same instance. This could be a different project board or a different group
board, for example. board, for example.
......
...@@ -81,8 +81,8 @@ RSpec.describe 'Project issue boards', :js do ...@@ -81,8 +81,8 @@ RSpec.describe 'Project issue boards', :js do
context 'total weight' do context 'total weight' do
let!(:label) { create(:label, project: project, name: 'Label 1') } let!(:label) { create(:label, project: project, name: 'Label 1') }
let!(:list) { create(:list, board: board, label: label, position: 0) } let!(:list) { create(:list, board: board, label: label, position: 0) }
let!(:issue) { create(:issue, project: project, weight: 3) } let!(:issue) { create(:issue, project: project, weight: 3, relative_position: 2) }
let!(:issue_2) { create(:issue, project: project, weight: 2) } let!(:issue_2) { create(:issue, project: project, weight: 2, relative_position: 1) }
before do before do
project.add_developer(user) project.add_developer(user)
......
...@@ -22,7 +22,7 @@ RSpec.describe IssuablesAnalytics do ...@@ -22,7 +22,7 @@ RSpec.describe IssuablesAnalytics do
context 'when issuable relation is ordered by priority' do context 'when issuable relation is ordered by priority' do
it 'generates chart data correctly' do it 'generates chart data correctly' do
issues = project.issues.order_by_position_and_priority issues = project.issues.order_labels_priority
data = described_class.new(issuables: issues).data data = described_class.new(issuables: issues).data
seed.each_pair do |months_back, issues_count| seed.each_pair do |months_back, issues_count|
......
...@@ -116,7 +116,7 @@ RSpec.describe Boards::IssuesController do ...@@ -116,7 +116,7 @@ RSpec.describe Boards::IssuesController do
it 'does not query issues table more than once' do it 'does not query issues table more than once' do
recorder = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: board, list: list1) } recorder = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: board, list: list1) }
query_count = recorder.occurrences.select { |query,| query.start_with?('SELECT issues.*') }.each_value.first query_count = recorder.occurrences.select { |query,| query.match?(/FROM "?issues"?/) }.each_value.first
expect(query_count).to eq(1) expect(query_count).to eq(1)
end end
......
...@@ -35,7 +35,7 @@ RSpec.describe Resolvers::BoardListIssuesResolver do ...@@ -35,7 +35,7 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
# by relative_position and then ID # by relative_position and then ID
result = resolve_board_list_issues result = resolve_board_list_issues
expect(result.map(&:id)).to eq [issue3.id, issue1.id, issue2.id, issue4.id] expect(result.map(&:id)).to eq [issue1.id, issue3.id, issue2.id, issue4.id]
expect(result.map(&:relative_position)).not_to include(nil) expect(result.map(&:relative_position)).not_to include(nil)
end end
......
...@@ -338,7 +338,7 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -338,7 +338,7 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be(:relative_issue4) { create(:issue, project: project, relative_position: nil) } let_it_be(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
it 'sorts issues ascending' do it 'sorts issues ascending' do
expect(resolve_issues(sort: :relative_position_asc).to_a).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2] expect(resolve_issues(sort: :relative_position_asc).to_a).to eq [relative_issue3, relative_issue1, relative_issue2, relative_issue4]
end end
end end
......
...@@ -115,7 +115,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do ...@@ -115,7 +115,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
end end
it 'orders exported issues by custom column(relative_position)' do it 'orders exported issues by custom column(relative_position)' do
expected_issues = exportable.issues.order_relative_position_desc.order(id: :desc).map(&:to_json) expected_issues = exportable.issues.reorder(::Gitlab::Database.nulls_first_order('relative_position', 'DESC')).order(id: :desc).map(&:to_json)
expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues) expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues)
......
...@@ -73,7 +73,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -73,7 +73,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) }
expect(positions).to eq(project.issues.order_relative_position_asc.order(id: :asc).pluck(:relative_position, :id)) expect(positions).to eq(project.issues.reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')).order(id: :asc).pluck(:relative_position, :id))
end end
end end
...@@ -85,7 +85,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -85,7 +85,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) }
expect(positions).to eq(project.issues.order_relative_position_desc.order(id: :desc).pluck(:relative_position, :id)) expect(positions).to eq(project.issues.reorder(::Gitlab::Database.nulls_first_order('relative_position', 'DESC')).order(id: :desc).pluck(:relative_position, :id))
end end
end end
......
...@@ -222,17 +222,15 @@ RSpec.describe Issue do ...@@ -222,17 +222,15 @@ RSpec.describe Issue do
end end
end end
describe '#order_by_position_and_priority' do describe '#order_by_relative_position' do
let(:project) { reusable_project } let(:project) { reusable_project }
let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } let!(:issue1) { create(:issue, project: project) }
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let!(:issue2) { create(:issue, project: project) }
let!(:issue1) { create(:labeled_issue, project: project, labels: [p1]) }
let!(:issue2) { create(:labeled_issue, project: project, labels: [p2]) }
let!(:issue3) { create(:issue, project: project, relative_position: -200) } let!(:issue3) { create(:issue, project: project, relative_position: -200) }
let!(:issue4) { create(:issue, project: project, relative_position: -100) } let!(:issue4) { create(:issue, project: project, relative_position: -100) }
it 'returns ordered list' do it 'returns ordered list' do
expect(project.issues.order_by_position_and_priority) expect(project.issues.order_by_relative_position)
.to match [issue3, issue4, issue1, issue2] .to match [issue3, issue4, issue1, issue2]
end end
end end
......
...@@ -233,7 +233,7 @@ RSpec.describe 'getting an issue list for a project' do ...@@ -233,7 +233,7 @@ RSpec.describe 'getting an issue list for a project' do
let(:all_records) do let(:all_records) do
[ [
relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue5.iid, relative_issue3.iid, relative_issue1.iid,
relative_issue4.iid, relative_issue2.iid relative_issue2.iid, relative_issue4.iid
] ]
end end
end end
......
...@@ -27,7 +27,7 @@ RSpec.describe IssuePlacementWorker do ...@@ -27,7 +27,7 @@ RSpec.describe IssuePlacementWorker do
it 'places all issues created at most 5 minutes before this one at the end, most recent last' do it 'places all issues created at most 5 minutes before this one at the end, most recent last' do
expect { run_worker }.not_to change { irrelevant.reset.relative_position } expect { run_worker }.not_to change { irrelevant.reset.relative_position }
expect(project.issues.order_relative_position_asc) expect(project.issues.order_by_relative_position)
.to eq([issue_e, issue_b, issue_a, issue, issue_c, issue_f, issue_d]) .to eq([issue_e, issue_b, issue_a, issue, issue_c, issue_f, issue_d])
expect(project.issues.where(relative_position: nil)).not_to exist expect(project.issues.where(relative_position: nil)).not_to exist
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment