taskbar.rb 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. class Taskbar < ApplicationModel
  3. include ChecksClientNotification
  4. include ::Taskbar::HasAttachments
  5. include Taskbar::Assets
  6. include Taskbar::TriggersSubscriptions
  7. include Taskbar::List
  8. TASKBAR_APPS = %w[desktop mobile].freeze
  9. TASKBAR_STATIC_ENTITIES = %w[
  10. Search
  11. ].freeze
  12. store :state
  13. store :params
  14. store :preferences
  15. belongs_to :user
  16. validates :app, inclusion: { in: TASKBAR_APPS }
  17. validates :key, uniqueness: { scope: %i[user_id app] }
  18. before_validation :set_user
  19. before_create :update_last_contact, :update_preferences_infos
  20. before_update :update_last_contact, :update_preferences_infos
  21. after_update :notify_clients
  22. after_destroy :update_preferences_infos, :notify_clients
  23. after_commit :update_related_taskbars
  24. association_attributes_ignored :user
  25. client_notification_events_ignored :create, :update, :touch
  26. client_notification_send_to :user_id
  27. attr_accessor :local_update
  28. default_scope { order(:id) }
  29. scope :related_taskbars, lambda { |taskbar|
  30. where(key: taskbar.key)
  31. .where.not(id: taskbar.id)
  32. }
  33. scope :app, ->(app) { where(app:) }
  34. def self.taskbar_entities
  35. @taskbar_entities ||= begin
  36. ApplicationModel.descendants.select { |model| model.included_modules.include?(HasTaskbars) }.each_with_object([]) do |model, result|
  37. model.taskbar_entities&.each do |entity|
  38. result << entity
  39. end
  40. end | TASKBAR_STATIC_ENTITIES
  41. end
  42. end
  43. def self.taskbar_ignore_state_updates_entities
  44. @taskbar_ignore_state_updates_entities ||= begin
  45. ApplicationModel.descendants.select { |model| model.included_modules.include?(HasTaskbars) }.each_with_object([]) do |model, result|
  46. model.taskbar_ignore_state_updates_entities&.each do |entity|
  47. result << entity
  48. end
  49. end
  50. end
  51. end
  52. def state_changed?
  53. return false if state.blank?
  54. state.each do |key, value|
  55. if value.is_a? Hash
  56. value.each do |key1, value1|
  57. next if value1.blank?
  58. next if key1 == 'form_id'
  59. return true
  60. end
  61. else
  62. next if value.blank?
  63. next if key == 'form_id'
  64. return true
  65. end
  66. end
  67. false
  68. end
  69. def attributes_with_association_names(empty_keys: false)
  70. add_attachments_to_attributes(super)
  71. end
  72. def attributes_with_association_ids
  73. add_attachments_to_attributes(super)
  74. end
  75. def as_json(options = {})
  76. add_attachments_to_attributes(super)
  77. end
  78. def preferences_task_info
  79. output = { user_id:, apps: { app.to_sym => { last_contact: last_contact, changed: state_changed? } } }
  80. output[:id] = id if persisted?
  81. output
  82. end
  83. def related_taskbars
  84. self.class.related_taskbars(self)
  85. end
  86. def touch_last_contact!
  87. # Don't inform the current user (only!) about live user and item updates.
  88. self.skip_live_user_trigger = true
  89. self.skip_item_trigger = true
  90. self.last_contact = Time.zone.now
  91. save!
  92. end
  93. def saved_change_to_dirty?
  94. return false if !saved_change_to_preferences?
  95. !!preferences[:dirty] != !!preferences_previously_was[:dirty]
  96. end
  97. def collect_related_tasks
  98. related_taskbars.map(&:preferences_task_info)
  99. .tap { |arr| arr.push(preferences_task_info) if !destroyed? }
  100. .each_with_object({}) { |elem, memo| reduce_related_tasks(elem, memo) }
  101. .values
  102. .sort_by { |elem| elem[:id] || Float::MAX } # sort by IDs to pass old tests
  103. end
  104. private
  105. def update_last_contact
  106. return if local_update
  107. return if changes.blank?
  108. return if changed_only_prio?
  109. if changes['notify']
  110. count = 0
  111. changes.each_key do |attribute|
  112. next if attribute == 'updated_at'
  113. next if attribute == 'created_at'
  114. count += 1
  115. end
  116. return true if count <= 1
  117. end
  118. self.last_contact = Time.zone.now
  119. end
  120. def set_user
  121. return if local_update
  122. return if !UserInfo.current_user_id
  123. self.user_id = UserInfo.current_user_id
  124. end
  125. def update_preferences_infos
  126. return if key == 'Search'
  127. return if local_update
  128. return if changed_only_prio?
  129. preferences = self.preferences || {}
  130. preferences[:tasks] = collect_related_tasks
  131. # remember preferences for current taskbar
  132. self.preferences = preferences if !destroyed?
  133. end
  134. def changed_only_prio?
  135. changed_attribute_names_to_save.to_set == Set.new(%w[updated_at prio])
  136. end
  137. def reduce_related_tasks(elem, memo)
  138. key = elem[:user_id]
  139. if memo[key]
  140. memo[key].deep_merge! elem
  141. return
  142. end
  143. memo[key] = elem
  144. end
  145. def update_related_taskbars
  146. return if key == 'Search'
  147. return if local_update
  148. return if changed_only_prio?
  149. TaskbarUpdateRelatedTasksJob.perform_later(related_taskbars.map(&:id))
  150. end
  151. def notify_clients
  152. return if !saved_change_to_attribute?('preferences')
  153. data = {
  154. event: 'taskbar:preferences',
  155. data: {
  156. id: id,
  157. key: key,
  158. preferences: preferences,
  159. },
  160. }
  161. PushMessages.send_to(
  162. user_id,
  163. data,
  164. )
  165. end
  166. end