Commit dc197ed6 authored by Toon Claes's avatar Toon Claes

Merge branch '290116-notes-finder' into 'master'

Add sort param to NotesFinder

See merge request gitlab-org/gitlab!56174
parents 8306fb5f 928e328a
...@@ -17,6 +17,7 @@ class NotesFinder ...@@ -17,6 +17,7 @@ class NotesFinder
# target_id: integer # target_id: integer
# last_fetched_at: time # last_fetched_at: time
# search: string # search: string
# sort: string
# #
def initialize(current_user, params = {}) def initialize(current_user, params = {})
@project = params[:project] @project = params[:project]
...@@ -29,8 +30,7 @@ class NotesFinder ...@@ -29,8 +30,7 @@ class NotesFinder
notes = init_collection notes = init_collection
notes = since_fetch_at(notes) notes = since_fetch_at(notes)
notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter? notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
sort(notes)
notes.fresh
end end
def target def target
...@@ -173,6 +173,14 @@ class NotesFinder ...@@ -173,6 +173,14 @@ class NotesFinder
def notes_filter? def notes_filter?
@params[:notes_filter].present? @params[:notes_filter].present?
end end
def sort(notes)
sort = @params[:sort].presence
return notes.fresh unless sort
notes.order_by(sort)
end
end end
NotesFinder.prepend_if_ee('EE::NotesFinder') NotesFinder.prepend_if_ee('EE::NotesFinder')
...@@ -9,6 +9,7 @@ module Sortable ...@@ -9,6 +9,7 @@ module Sortable
included do included do
scope :with_order_id_desc, -> { order(self.arel_table['id'].desc) } scope :with_order_id_desc, -> { order(self.arel_table['id'].desc) }
scope :with_order_id_asc, -> { order(self.arel_table['id'].asc) }
scope :order_id_desc, -> { reorder(self.arel_table['id'].desc) } scope :order_id_desc, -> { reorder(self.arel_table['id'].desc) }
scope :order_id_asc, -> { reorder(self.arel_table['id'].asc) } scope :order_id_asc, -> { reorder(self.arel_table['id'].asc) }
scope :order_created_desc, -> { reorder(self.arel_table['created_at'].desc) } scope :order_created_desc, -> { reorder(self.arel_table['created_at'].desc) }
......
...@@ -19,6 +19,7 @@ class Note < ApplicationRecord ...@@ -19,6 +19,7 @@ class Note < ApplicationRecord
include Gitlab::SQL::Pattern include Gitlab::SQL::Pattern
include ThrottledTouch include ThrottledTouch
include FromUnion include FromUnion
include Sortable
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
...@@ -103,10 +104,9 @@ class Note < ApplicationRecord ...@@ -103,10 +104,9 @@ class Note < ApplicationRecord
scope :system, -> { where(system: true) } scope :system, -> { where(system: true) }
scope :user, -> { where(system: false) } scope :user, -> { where(system: false) }
scope :common, -> { where(noteable_type: ["", nil]) } scope :common, -> { where(noteable_type: ["", nil]) }
scope :fresh, -> { order(created_at: :asc, id: :asc) } scope :fresh, -> { order_created_asc.with_order_id_asc }
scope :updated_after, ->(time) { where('updated_at > ?', time) } scope :updated_after, ->(time) { where('updated_at > ?', time) }
scope :with_updated_at, ->(time) { where(updated_at: time) } scope :with_updated_at, ->(time) { where(updated_at: time) }
scope :by_updated_at, -> { reorder(:updated_at, :id) }
scope :inc_author_project, -> { includes(:project, :author) } scope :inc_author_project, -> { includes(:project, :author) }
scope :inc_author, -> { includes(:author) } scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do scope :inc_relations_for_view, -> do
...@@ -148,6 +148,8 @@ class Note < ApplicationRecord ...@@ -148,6 +148,8 @@ class Note < ApplicationRecord
after_commit :notify_after_destroy, on: :destroy after_commit :notify_after_destroy, on: :destroy
class << self class << self
extend Gitlab::Utils::Override
def model_name def model_name
ActiveModel::Name.new(self, nil, 'note') ActiveModel::Name.new(self, nil, 'note')
end end
...@@ -204,6 +206,13 @@ class Note < ApplicationRecord ...@@ -204,6 +206,13 @@ class Note < ApplicationRecord
def search(query) def search(query)
fuzzy_search(query, [:note]) fuzzy_search(query, [:note])
end end
# Override the `Sortable` module's `.simple_sorts` to remove name sorting,
# as a `Note` does not have any property that correlates to a "name".
override :simple_sorts
def simple_sorts
super.except('name_asc', 'name_desc')
end
end end
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
......
...@@ -9,7 +9,7 @@ module Integrations ...@@ -9,7 +9,7 @@ module Integrations
end end
def note_events_data def note_events_data
note = NotesFinder.new(current_user, project: project, target: project).execute.reorder(nil).last # rubocop: disable CodeReuse/ActiveRecord note = NotesFinder.new(current_user, project: project, target: project, sort: 'id_desc').execute.first
return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present? return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present?
......
...@@ -37,7 +37,7 @@ module Gitlab ...@@ -37,7 +37,7 @@ module Gitlab
end end
def fetch_page(relation) def fetch_page(relation)
relation = relation.by_updated_at relation = relation.order_updated_asc.with_order_id_asc
notes = relation.limit(LIMIT + 1).to_a notes = relation.limit(LIMIT + 1).to_a
return [notes, false] unless notes.size > LIMIT return [notes, false] unless notes.size > LIMIT
......
...@@ -213,6 +213,24 @@ RSpec.describe NotesFinder do ...@@ -213,6 +213,24 @@ RSpec.describe NotesFinder do
expect { described_class.new(user, params).execute }.to raise_error(RuntimeError) expect { described_class.new(user, params).execute }.to raise_error(RuntimeError)
end end
end end
describe 'sorting' do
it 'allows sorting' do
params = { project: project, sort: 'id_desc' }
expect(Note).to receive(:order_id_desc).once
described_class.new(user, params).execute
end
it 'defaults to sort by .fresh' do
params = { project: project }
expect(Note).to receive(:fresh).once
described_class.new(user, params).execute
end
end
end end
describe '.search' do describe '.search' do
......
...@@ -3,6 +3,31 @@ ...@@ -3,6 +3,31 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Sortable do RSpec.describe Sortable do
describe 'scopes' do
describe 'secondary ordering by id' do
let(:sorted_relation) { Group.all.order_created_asc }
def arel_orders(relation)
relation.arel.orders
end
it 'allows secondary ordering by id ascending' do
orders = arel_orders(sorted_relation.with_order_id_asc)
expect(orders.map { |arel| arel.expr.name }).to eq(%w(created_at id))
expect(orders).to all(be_kind_of(Arel::Nodes::Ascending))
end
it 'allows secondary ordering by id descending' do
orders = arel_orders(sorted_relation.with_order_id_desc)
expect(orders.map { |arel| arel.expr.name }).to eq(%w(created_at id))
expect(orders.first).to be_kind_of(Arel::Nodes::Ascending)
expect(orders.last).to be_kind_of(Arel::Nodes::Descending)
end
end
end
describe '.order_by' do describe '.order_by' do
let(:arel_table) { Group.arel_table } let(:arel_table) { Group.arel_table }
let(:relation) { Group.all } let(:relation) { Group.all }
......
...@@ -20,6 +20,7 @@ RSpec.describe Note do ...@@ -20,6 +20,7 @@ RSpec.describe Note do
it { is_expected.to include_module(Participable) } it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Mentionable) } it { is_expected.to include_module(Mentionable) }
it { is_expected.to include_module(Awardable) } it { is_expected.to include_module(Awardable) }
it { is_expected.to include_module(Sortable) }
end end
describe 'validation' do describe 'validation' do
...@@ -856,6 +857,12 @@ RSpec.describe Note do ...@@ -856,6 +857,12 @@ RSpec.describe Note do
end end
end end
describe '.simple_sorts' do
it 'does not contain name sorts' do
expect(described_class.simple_sorts.grep(/name/)).to be_empty
end
end
describe '#for_project_snippet?' do describe '#for_project_snippet?' do
it 'returns true for a project snippet note' do it 'returns true for a project snippet note' do
expect(build(:note_on_project_snippet).for_project_snippet?).to be true expect(build(:note_on_project_snippet).for_project_snippet?).to be true
...@@ -1322,7 +1329,7 @@ RSpec.describe Note do ...@@ -1322,7 +1329,7 @@ RSpec.describe Note do
let_it_be(:note1) { create(:note, note: 'Test 345') } let_it_be(:note1) { create(:note, note: 'Test 345') }
let_it_be(:note2) { create(:note, note: 'Test 789') } let_it_be(:note2) { create(:note, note: 'Test 789') }
describe '#for_note_or_capitalized_note' do describe '.for_note_or_capitalized_note' do
it 'returns the expected matching note' do it 'returns the expected matching note' do
notes = described_class.for_note_or_capitalized_note('Test 345') notes = described_class.for_note_or_capitalized_note('Test 345')
...@@ -1344,7 +1351,7 @@ RSpec.describe Note do ...@@ -1344,7 +1351,7 @@ RSpec.describe Note do
end end
end end
describe '#like_note_or_capitalized_note' do describe '.like_note_or_capitalized_note' do
it 'returns the expected matching note' do it 'returns the expected matching note' do
notes = described_class.like_note_or_capitalized_note('Test 345') notes = described_class.like_note_or_capitalized_note('Test 345')
...@@ -1367,8 +1374,9 @@ RSpec.describe Note do ...@@ -1367,8 +1374,9 @@ RSpec.describe Note do
expect(notes.second.id).to eq(note2.id) expect(notes.second.id).to eq(note2.id)
end end
end end
end
describe '#noteable_assignee_or_author' do describe '#noteable_assignee_or_author?' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:noteable) { create(:issue) } let(:noteable) { create(:issue) }
let(:note) { create(:note, project: noteable.project, noteable: noteable) } let(:note) { create(:note, project: noteable.project, noteable: noteable) }
...@@ -1431,7 +1439,6 @@ RSpec.describe Note do ...@@ -1431,7 +1439,6 @@ RSpec.describe Note do
it_behaves_like 'author check' it_behaves_like 'author check'
end end
end end
end
describe 'banzai_render_context' do describe 'banzai_render_context' do
let(:project) { build(:project_empty_repo) } let(:project) { build(:project_empty_repo) }
......
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