Class: Todo

Inherits:
ApplicationRecord show all
Includes:
EachBatch, FromUnion, 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 =
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
ATTENTION_REQUESTED =
10
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,
  ATTENTION_REQUESTED => :attention_requested
}.freeze
ACTIONS_MULTIPLE_ALLOWED =
[Todo::MENTIONED, Todo::DIRECTLY_ADDRESSED].freeze

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

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)

110
111
112
# File 'app/models/todo.rb', line 110

def any_for_target?(target, state = nil)
  state.nil? ? exists?(target: target) : exists?(target: target, state: state)
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.


119
120
121
122
123
124
125
126
127
# File 'app/models/todo.rb', line 119

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.


164
165
166
167
168
169
170
171
172
173
174
# File 'app/models/todo.rb', line 164

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)

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

.distinct_user_idsObject


158
159
160
# File 'app/models/todo.rb', line 158

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`.


97
98
99
100
101
102
103
104
# File 'app/models/todo.rb', line 97

def for_group_ids_and_descendants(group_ids)
  groups = Group.groups_including_descendants_by(group_ids)

  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”


146
147
148
149
150
151
152
153
154
155
156
# File 'app/models/todo.rb', line 146

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.


132
133
134
135
136
137
138
139
140
141
# File 'app/models/todo.rb', line 132

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

#action_nameObject


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

def action_name
  ACTION_NAMES[action]
end

#assigned?Boolean

Returns:

  • (Boolean)

189
190
191
# File 'app/models/todo.rb', line 189

def assigned?
  action == ASSIGNED
end

#attention_requested?Boolean

Returns:

  • (Boolean)

197
198
199
# File 'app/models/todo.rb', line 197

def attention_requested?
  action == ATTENTION_REQUESTED
end

#bodyObject


213
214
215
216
217
218
219
# File 'app/models/todo.rb', line 213

def body
  if note.present?
    note.note
  else
    target.title
  end
end

#build_failed?Boolean

Returns:

  • (Boolean)

185
186
187
# File 'app/models/todo.rb', line 185

def build_failed?
  action == BUILD_FAILED
end

#done?Boolean

Returns:

  • (Boolean)

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

def done?
  state == 'done'
end

#for_alert?Boolean

Returns:

  • (Boolean)

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

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

#for_commit?Boolean

Returns:

  • (Boolean)

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

def for_commit?
  target_type == "Commit"
end

#for_design?Boolean

Returns:

  • (Boolean)

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

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

#merge_train_removed?Boolean

Returns:

  • (Boolean)

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

def merge_train_removed?
  action == MERGE_TRAIN_REMOVED
end

#resource_parentObject


177
178
179
# File 'app/models/todo.rb', line 177

def resource_parent
  project
end

#review_requested?Boolean

Returns:

  • (Boolean)

193
194
195
# File 'app/models/todo.rb', line 193

def review_requested?
  action == REVIEW_REQUESTED
end

#self_added?Boolean

Returns:

  • (Boolean)

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

def self_added?
  author == user
end

#self_assigned?Boolean

Returns:

  • (Boolean)

254
255
256
# File 'app/models/todo.rb', line 254

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

#targetObject

override to return commits, which are not active record


234
235
236
237
238
239
240
# File 'app/models/todo.rb', line 234

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

#target_referenceObject


242
243
244
245
246
247
248
# File 'app/models/todo.rb', line 242

def target_reference
  if for_commit?
    target.reference_link_text
  else
    target.to_reference
  end
end

#unmergeable?Boolean

Returns:

  • (Boolean)

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

def unmergeable?
  action == UNMERGEABLE
end