Class: Todo

Inherits:
ApplicationRecord show all
Includes:
EachBatch, FromUnion, IgnorableColumns, Sortable
Defined in:
app/models/todo.rb

Constant Summary collapse

WAIT_FOR_DELETE =

Time to wait for todos being removed when not visible for user anymore. Prevents TODOs being removed by mistake, for example, removing access from a user and giving it back again.

1.hour
ASSIGNED =

Actions

1
MENTIONED =
2
BUILD_FAILED =
3
MARKED =
4
APPROVAL_REQUIRED =

This is an EE-only feature

5
UNMERGEABLE =
6
DIRECTLY_ADDRESSED =
7
MERGE_TRAIN_REMOVED =

This is an EE-only feature

8
REVIEW_REQUESTED =
9
MEMBER_ACCESS_REQUESTED =
10
REVIEW_SUBMITTED =

This is an EE-only feature

11
ACTION_NAMES =
{
  ASSIGNED => :assigned,
  REVIEW_REQUESTED => :review_requested,
  MENTIONED => :mentioned,
  BUILD_FAILED => :build_failed,
  MARKED => :marked,
  APPROVAL_REQUIRED => :approval_required,
  UNMERGEABLE => :unmergeable,
  DIRECTLY_ADDRESSED => :directly_addressed,
  MERGE_TRAIN_REMOVED => :merge_train_removed,
  MEMBER_ACCESS_REQUESTED => :member_access_requested,
  REVIEW_SUBMITTED => :review_submitted
}.freeze
ACTIONS_MULTIPLE_ALLOWED =
[Todo::MENTIONED, Todo::DIRECTLY_ADDRESSED, Todo::MEMBER_ACCESS_REQUESTED].freeze

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from ResetOnUnionError

ResetOnUnionError::MAX_RESET_PERIOD

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from SensitiveSerializableHash

#serializable_hash

Class Method Details

.any_for_target?(target, state = nil) ⇒ Boolean

Returns ‘true` if the current user has any todos for the given target with the optional given state.

target - The value of the ‘target_type` column, such as `Issue`. state - The value of the `state` column, such as `pending` or `done`.

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/models/todo.rb', line 121

def any_for_target?(target, state = nil)
  conditions = {}

  if target.respond_to?(:todoable_target_type_name)
    conditions[:target_type] = target.todoable_target_type_name
    conditions[:target_id] = target.id
  else
    conditions[:target] = target
  end

  conditions[:state] = state unless state.nil?

  exists?(conditions)
end

.batch_update(**new_attributes) ⇒ Object

Updates attributes of a relation of todos to the new state.

new_attributes - The new attributes of the todos.

Returns an ‘Array` containing the IDs of the updated todos.



141
142
143
144
145
146
147
148
149
# File 'app/models/todo.rb', line 141

def batch_update(**new_attributes)
  # Only update those that have different state
  base = where.not(state: new_attributes[:state]).except(:order)
  ids = base.pluck(:id)

  base.update_all(new_attributes.merge(updated_at: Time.current))

  ids
end

.count_grouped_by_user_id_and_stateObject

Count todos grouped by user_id and state, using an UNION query so we can utilize the partial indexes for each state.



186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/models/todo.rb', line 186

def count_grouped_by_user_id_and_state
  grouped_count = select(:user_id, 'count(id) AS count').group(:user_id)

  done = grouped_count.where(state: :done).select("'done' AS state")
  pending = grouped_count.where(state: :pending).select("'pending' AS state")
  union = unscoped.from_union([done, pending], remove_duplicates: false)
    .select(:user_id, :count, :state)

  connection.select_all(union).each_with_object({}) do |row, counts|
    counts[[row['user_id'], row['state']]] = row['count']
  end
end

.distinct_user_idsObject



180
181
182
# File 'app/models/todo.rb', line 180

def distinct_user_ids
  distinct.pluck(:user_id)
end

.for_group_ids_and_descendants(group_ids) ⇒ Object

Returns all todos for the given group ids and their descendants.

group_ids - Group Ids to retrieve todos for.

Returns an ‘ActiveRecord::Relation`.



107
108
109
110
111
112
113
114
115
# File 'app/models/todo.rb', line 107

def for_group_ids_and_descendants(group_ids)
  groups = Group.where(id: group_ids).self_and_descendants

  from_union(
    [
      for_project(Project.for_group(groups)),
      for_group(groups)
    ])
end

.order_by_labels_priorityObject

Order by priority depending on which issue/merge request the Todo belongs to Todos with highest priority first then oldest todos Need to order by created_at last because of differences on Mysql and Postgres when joining by type “Merge_request/Issue”



168
169
170
171
172
173
174
175
176
177
178
# File 'app/models/todo.rb', line 168

def order_by_labels_priority
  highest_priority = highest_label_priority(
    target_type_column: "todos.target_type",
    target_column: "todos.target_id",
    project_column: "todos.project_id"
  ).arel.as('highest_priority')

  select(arel_table[Arel.star], highest_priority)
    .order(Arel.sql('highest_priority').asc.nulls_last)
    .order('todos.created_at')
end

.sort_by_attribute(method) ⇒ Object

Priority sorting isn’t displayed in the dropdown, because we don’t show milestones, but still show something if the user has a URL with that selected.



154
155
156
157
158
159
160
161
162
163
# File 'app/models/todo.rb', line 154

def sort_by_attribute(method)
  sorted =
    case method.to_s
    when 'priority', 'label_priority' then order_by_labels_priority
    else order_by(method)
    end

  # Break ties with the ID column for pagination
  sorted.order(id: :desc)
end

Instance Method Details

#access_request_url(only_path: false) ⇒ Object



236
237
238
239
240
241
242
243
244
# File 'app/models/todo.rb', line 236

def access_request_url(only_path: false)
  if target.instance_of? Group
    Gitlab::Routing.url_helpers.group_group_members_url(self.target, tab: 'access_requests', only_path: only_path)
  elsif target.instance_of? Project
    Gitlab::Routing.url_helpers.project_project_members_url(self.target, tab: 'access_requests', only_path: only_path)
  else
    ""
  end
end

#action_nameObject



250
251
252
# File 'app/models/todo.rb', line 250

def action_name
  ACTION_NAMES[action]
end

#assigned?Boolean

Returns:

  • (Boolean)


212
213
214
# File 'app/models/todo.rb', line 212

def assigned?
  action == ASSIGNED
end

#bodyObject



254
255
256
257
258
259
260
261
262
# File 'app/models/todo.rb', line 254

def body
  if note.present?
    note.note
  elsif member_access_requested?
    target.full_path
  else
    target.title
  end
end

#build_failed?Boolean

Returns:

  • (Boolean)


208
209
210
# File 'app/models/todo.rb', line 208

def build_failed?
  action == BUILD_FAILED
end

#done?Boolean

Returns:

  • (Boolean)


246
247
248
# File 'app/models/todo.rb', line 246

def done?
  state == 'done'
end

#for_alert?Boolean

Returns:

  • (Boolean)


272
273
274
# File 'app/models/todo.rb', line 272

def for_alert?
  target_type == AlertManagement::Alert.name
end

#for_commit?Boolean

Returns:

  • (Boolean)


264
265
266
# File 'app/models/todo.rb', line 264

def for_commit?
  target_type == "Commit"
end

#for_design?Boolean

Returns:

  • (Boolean)


268
269
270
# File 'app/models/todo.rb', line 268

def for_design?
  target_type == DesignManagement::Design.name
end

#for_issue_or_work_item?Boolean

Returns:

  • (Boolean)


276
277
278
# File 'app/models/todo.rb', line 276

def for_issue_or_work_item?
  [Issue.name, WorkItem.name].any?(target_type)
end

#member_access_requested?Boolean

Returns:

  • (Boolean)


224
225
226
# File 'app/models/todo.rb', line 224

def member_access_requested?
  action == MEMBER_ACCESS_REQUESTED
end

#member_access_typeObject



232
233
234
# File 'app/models/todo.rb', line 232

def member_access_type
  target.class.name.downcase
end

#merge_train_removed?Boolean

Returns:

  • (Boolean)


220
221
222
# File 'app/models/todo.rb', line 220

def merge_train_removed?
  action == MERGE_TRAIN_REMOVED
end

#resource_parentObject



200
201
202
# File 'app/models/todo.rb', line 200

def resource_parent
  project || group
end

#review_requested?Boolean

Returns:

  • (Boolean)


216
217
218
# File 'app/models/todo.rb', line 216

def review_requested?
  action == REVIEW_REQUESTED
end

#review_submitted?Boolean

Returns:

  • (Boolean)


228
229
230
# File 'app/models/todo.rb', line 228

def 
  action == REVIEW_SUBMITTED
end

#self_added?Boolean

Returns:

  • (Boolean)


303
304
305
# File 'app/models/todo.rb', line 303

def self_added?
  author == user
end

#self_assigned?Boolean

Returns:

  • (Boolean)


307
308
309
# File 'app/models/todo.rb', line 307

def self_assigned?
  self_added? && (assigned? || review_requested?)
end

#targetObject

override to return commits, which are not active record



281
282
283
284
285
286
287
288
289
290
291
# File 'app/models/todo.rb', line 281

def target
  if for_commit?
    begin
      project.commit(commit_id)
    rescue StandardError
      nil
    end
  else
    super
  end
end

#target_referenceObject



293
294
295
296
297
298
299
300
301
# File 'app/models/todo.rb', line 293

def target_reference
  if for_commit?
    target.reference_link_text
  elsif member_access_requested?
    target.full_path
  else
    target.to_reference
  end
end

#unmergeable?Boolean

Returns:

  • (Boolean)


204
205
206
# File 'app/models/todo.rb', line 204

def unmergeable?
  action == UNMERGEABLE
end