Project

General

Profile

Patch #43990 » 0001-Add-detailed-issue-notifications.patch

Florian Walchshofer, 2026-04-24 23:25

View differences:

app/assets/stylesheets/application.css
1344 1344

  
1345 1345
fieldset.settings label { display: block; }
1346 1346
fieldset#notified_events .parent { padding-inline-start: 20px; }
1347
fieldset#notified_events .notification-details { padding-inline-start: 40px; border: 0px;}
1347 1348

  
1348 1349
span.required {color: var(--oc-red-9);}
1349 1350
.summary {font-style: italic;}
app/helpers/settings_helper.rb
122 122

  
123 123
  # Renders a notification field for a Redmine::Notifiable option
124 124
  def notification_field(notifiable)
125
    checked = setting_value('notified_events').include?(notifiable.name)
125 126
    tag_data =
126 127
      if notifiable.parent.present?
127
        {:parent_notifiable => notifiable.parent}
128
        {:parent_notifiable => notifiable.parent,
129
         :disables => "fieldset[data-parent-notifiable=#{notifiable.name}]"}
128 130
      else
129 131
        {:disables => "input[data-parent-notifiable=#{notifiable.name}]"}
130 132
      end
131 133
    tag = check_box_tag('settings[notified_events][]',
132 134
                        notifiable.name,
133
                        setting_value('notified_events').include?(notifiable.name),
135
                        checked,
134 136
                        :id => nil,
135 137
                        :data => tag_data)
136 138
    text = l_or_humanize(notifiable.name, :prefix => 'label_')
......
138 140
    if notifiable.parent.present?
139 141
      options[:class] = "parent"
140 142
    end
141
    content_tag(:label, tag + text, options)
143
    label = content_tag(:label, tag + text, options)
144

  
145
    if detailed_notifiable?(notifiable)
146
      label + notification_details_fieldset(notifiable, checked)
147
    else
148
      label + "<br />".html_safe
149
    end
142 150
  end
143 151

  
144 152
  def session_lifetime_options
......
248 256
     ['Retro', 'retro'],
249 257
     ['Robohash', 'robohash']]
250 258
  end
259

  
260
  def issue_attribute_options(except: [])
261
    attrs = Issue.new.safe_attribute_names
262
    options = attrs.filter_map do |attr|
263
      next if except.include?(attr)
264

  
265
      label = l("field_#{attr.gsub(/_id$/, '')}", default: nil)
266
      [label, attr] if label.present?
267
    end
268
    options << [l(:label_subtask_plural), 'child_id']
269
    options << [l(:field_parent_issue), 'parent_id']
270
    options.sort_by(&:first)
271
  end
272

  
273
  def issue_custom_field_options
274
    IssueCustomField
275
      .sorted
276
      .map { |cf| [cf.name, cf.id.to_s] }
277
  end
278

  
279
  def issue_relation_options
280
    IssueRelation::TYPES.map do |name, _|
281
      [l("label_relation_#{name}", default: nil) ||
282
       l("label_#{name}_to", default: nil) ||
283
       l("label_#{name}_by", default: nil) ||
284
       l("label_#{name}", default: nil) || name, name]
285
    end
286
  end
287

  
288
  def notification_details_fieldset(notifiable, checked)
289
    notifiable_setting_key = "notified_event_#{notifiable.name}_details"
290
    content_tag(
291
      :fieldset,
292
      class: 'notification-details',
293
      disabled: checked,
294
      data: { parent_notifiable: notifiable.name }
295
    ) do
296
      select_tag(
297
        "settings[#{notifiable_setting_key}][]",
298
        options_for_select(
299
          notifiable_field_options(notifiable),
300
          setting_value(notifiable_setting_key)
301
        ),
302
        multiple: true,
303
        size: 6,
304
        class: 'multiselect wide'
305
      ) +
306
      hidden_field_tag("settings[#{notifiable_setting_key}][]", "", id: nil)
307
    end
308
  end
309

  
310
  def notifiable_field_options(notifiable)
311
    case notifiable.name
312
    when 'issue_attr_updated'
313
      issue_attribute_options(except: %w[notes])
314
    when 'issue_relation_updated'
315
      issue_relation_options
316
    when 'issue_cf_updated'
317
      issue_custom_field_options
318
    else
319
      []
320
    end
321
  end
322

  
323
  def detailed_notifiable?(notifiable)
324
    notifiable_field_options(notifiable).present?
325
  end
251 326
end
app/models/journal.rb
378 378
        (
379 379
          Setting.notified_events.include?('issue_updated') ||
380 380
          (Setting.notified_events.include?('issue_note_added') && notes.present?) ||
381
          (Setting.notified_events.include?('issue_status_updated') && new_status.present?) ||
382
          (Setting.notified_events.include?('issue_assigned_to_updated') && detail_for_attribute('assigned_to_id').present?) ||
383
          (Setting.notified_events.include?('issue_priority_updated') && new_value_for('priority_id').present?) ||
384
          (Setting.notified_events.include?('issue_fixed_version_updated') && detail_for_attribute('fixed_version_id').present?) ||
385
          (Setting.notified_events.include?('issue_attachment_added') && details.any? {|d| d.property == 'attachment' && d.value })
381
          (Setting.notified_events.include?('issue_attachment_added') && details.any? {|d| d.property == 'attachment' && d.value }) ||
382
          notify_by_details?(Setting.notified_events.include?('issue_attr_updated'), 'attr', Setting.notified_event_issue_attr_updated_details) ||
383
          notify_by_details?(Setting.notified_events.include?('issue_relation_updated'), 'relation', Setting.notified_event_issue_relation_updated_details) ||
384
          notify_by_details?(Setting.notified_events.include?('issue_cf_updated'), 'cf', Setting.notified_event_issue_cf_updated_details)
386 385
        )
387 386
      Mailer.deliver_issue_edit(self)
388 387
    end
......
394 393
    end
395 394
    notified
396 395
  end
396

  
397
  def notify_by_details?(notify_all, property, prop_keys)
398
    return false unless notify_all || prop_keys.present?
399

  
400
    relevant_details = details.select { |d| d.property == property }
401
    return false if relevant_details.empty?
402
    return true if notify_all
403

  
404
    allowed = Array(prop_keys).map(&:to_s)
405
    relevant_details.any? do |detail|
406
      allowed.include?(detail.prop_key.to_s)
407
    end
408
  end
397 409
end
app/views/settings/_notifications.html.erb
13 13
<%= hidden_field_tag 'settings[notified_events][]', '' %>
14 14
<% @notifiables.each do |notifiable| %>
15 15
<%= notification_field notifiable %>
16
<br />
17 16
<% end %>
18 17
<p><%= check_all_links('notified_events') %></p>
19 18
</fieldset>
config/settings.yml
203 203
  default:
204 204
  - issue_added
205 205
  - issue_updated
206
notified_event_issue_attr_updated_details:
207
  serialized: true
208
  default: []
209
notified_event_issue_relation_updated_details:
210
  serialized: true
211
  default: []
212
notified_event_issue_cf_updated_details:
213
  serialized: true
214
  default: []
206 215
mail_handler_body_delimiters:
207 216
  default: ''
208 217
mail_handler_enable_regex_delimiters:
lib/redmine/notifiable.rb
29 29
      notifications << Notifiable.new('issue_added')
30 30
      notifications << Notifiable.new('issue_updated')
31 31
      notifications << Notifiable.new('issue_note_added', 'issue_updated')
32
      notifications << Notifiable.new('issue_status_updated', 'issue_updated')
33
      notifications << Notifiable.new('issue_assigned_to_updated', 'issue_updated')
34
      notifications << Notifiable.new('issue_priority_updated', 'issue_updated')
35
      notifications << Notifiable.new('issue_fixed_version_updated', 'issue_updated')
36 32
      notifications << Notifiable.new('issue_attachment_added', 'issue_updated')
33
      notifications << Notifiable.new('issue_attr_updated', 'issue_updated')
34
      notifications << Notifiable.new('issue_relation_updated', 'issue_updated')
35
      notifications << Notifiable.new('issue_cf_updated', 'issue_updated')
37 36
      notifications << Notifiable.new('news_added')
38 37
      notifications << Notifiable.new('news_comment_added')
39 38
      notifications << Notifiable.new('document_added')
(2-2/4)