notes_finder.rb 2.88 KB
Newer Older
1
class NotesFinder
2
3
  FETCH_OVERLAP = 5.seconds

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  # Used to filter Notes
  # When used with target_type and target_id this returns notes specifically for the controller
  #
  # Arguments:
  #   current_user - which user check authorizations with
  #   project - which project to look for notes on
  #   params:
  #     target_type: string
  #     target_id: integer
  #     last_fetched_at: time
  #     search: string
  #
  def initialize(project, current_user, params = {})
    @project = project
    @current_user = current_user
    @params = params
    init_collection
  end

  def execute
    @notes = since_fetch_at(@params[:last_fetched_at]) if @params[:last_fetched_at]
    @notes
  end

  private

  def init_collection
Douwe Maan's avatar
Douwe Maan committed
31
32
33
34
35
36
    @notes =
      if @params[:target_id]
        on_target(@params[:target_type], @params[:target_id])
      else
        notes_of_any_type
      end
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
  end

  def notes_of_any_type
    types = %w(commit issue merge_request snippet)
    note_relations = types.map { |t| notes_for_type(t) }
    note_relations.map!{ |notes| search(@params[:search], notes) } if @params[:search]
    UnionFinder.new.find_union(note_relations, Note)
  end

  def noteables_for_type(noteable_type)
    case noteable_type
    when "issue"
      IssuesFinder.new(@current_user, project_id: @project.id).execute
    when "merge_request"
      MergeRequestsFinder.new(@current_user, project_id: @project.id).execute
    when "snippet", "project_snippet"
      SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project)
    else
      raise 'invalid target_type'
    end
  end

  def notes_for_type(noteable_type)
    if noteable_type == "commit"
      if Ability.allowed?(@current_user, :download_code, @project)
        @project.notes.where(noteable_type: 'Commit')
      else
        Note.none
      end
    else
      finder = noteables_for_type(noteable_type)
      @project.notes.where(noteable_type: finder.base_class.name, noteable_id: finder.reorder(nil))
    end
  end

  def on_target(target_type, target_id)
    if target_type == "commit"
      notes_for_type('commit').for_commit_id(target_id)
    else
      target = noteables_for_type(target_type).find(target_id)

      if target.respond_to?(:related_notes)
        target.related_notes
80
      else
81
        target.notes
82
      end
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
    end
  end

  # Searches for notes matching the given query.
  #
  # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
  #
  def search(query, notes_relation = @notes)
    pattern = "%#{query}%"
    notes_relation.where(Note.arel_table[:note].matches(pattern))
  end

  # Notes changed since last fetch
  # Uses overlapping intervals to avoid worrying about race conditions
  def since_fetch_at(fetch_time)
    # Default to 0 to remain compatible with old clients
    last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i)
100

101
    @notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP).fresh
102
103
  end
end