taskbar.rb 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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. # Returns IDs of objects referenced by the taskbars.
  35. # Works on scopes, relations etc.
  36. #
  37. # @return [Hash{Symbol=>Array<Integer>}] of arrays of object IDs
  38. #
  39. # @example
  40. #
  41. # user.taskbars.to_object_ids # => { user_ids: [1, 2, 3], organization_ids: [1, 2, 3], ticket_ids: [1, 2, 3] }
  42. #
  43. def self.to_object_ids
  44. all.each_with_object({ user_ids: [], organization_ids: [], ticket_ids: [] }) do |elem, memo|
  45. case elem.params
  46. in { user_id: }
  47. memo[:user_ids] << user_id.to_i
  48. in { organization_id: }
  49. memo[:organization_ids] << organization_id.to_i
  50. in { ticket_id: }
  51. memo[:ticket_ids] << ticket_id.to_i
  52. else
  53. end
  54. end
  55. end
  56. def self.taskbar_entities
  57. @taskbar_entities ||= begin
  58. ApplicationModel.descendants.select { |model| model.included_modules.include?(HasTaskbars) }.each_with_object([]) do |model, result|
  59. model.taskbar_entities&.each do |entity|
  60. result << entity
  61. end
  62. end | TASKBAR_STATIC_ENTITIES
  63. end
  64. end
  65. def self.taskbar_ignore_state_updates_entities
  66. @taskbar_ignore_state_updates_entities ||= begin
  67. ApplicationModel.descendants.select { |model| model.included_modules.include?(HasTaskbars) }.each_with_object([]) do |model, result|
  68. model.taskbar_ignore_state_updates_entities&.each do |entity|
  69. result << entity
  70. end
  71. end
  72. end
  73. end
  74. def state_changed?
  75. return false if state.blank?
  76. state.each do |key, value|
  77. if value.is_a? Hash
  78. value.each do |key1, value1|
  79. next if value1.blank?
  80. next if key1 == 'form_id'
  81. return true
  82. end
  83. else
  84. next if value.blank?
  85. next if key == 'form_id'
  86. return true
  87. end
  88. end
  89. false
  90. end
  91. def attributes_with_association_names(empty_keys: false)
  92. add_attachments_to_attributes(super)
  93. end
  94. def attributes_with_association_ids
  95. add_attachments_to_attributes(super)
  96. end
  97. def as_json(options = {})
  98. add_attachments_to_attributes(super)
  99. end
  100. def preferences_task_info
  101. output = { user_id:, apps: { app.to_sym => { last_contact: last_contact, changed: state_changed? } } }
  102. output[:id] = id if persisted?
  103. output
  104. end
  105. def related_taskbars
  106. self.class.related_taskbars(self)
  107. end
  108. def touch_last_contact!
  109. # Don't inform the current user (only!) about live user and item updates.
  110. self.skip_live_user_trigger = true
  111. self.skip_item_trigger = true
  112. self.last_contact = Time.zone.now
  113. save!
  114. end
  115. def saved_change_to_dirty?
  116. return false if !saved_change_to_preferences?
  117. !!preferences[:dirty] != !!preferences_previously_was[:dirty]
  118. end
  119. def collect_related_tasks
  120. related_taskbars.map(&:preferences_task_info)
  121. .tap { |arr| arr.push(preferences_task_info) if !destroyed? }
  122. .each_with_object({}) { |elem, memo| reduce_related_tasks(elem, memo) }
  123. .values
  124. .sort_by { |elem| elem[:id] || Float::MAX } # sort by IDs to pass old tests
  125. end
  126. private
  127. def update_last_contact
  128. return if local_update
  129. return if changes.blank?
  130. return if changed_only_prio?
  131. if changes['notify']
  132. count = 0
  133. changes.each_key do |attribute|
  134. next if attribute == 'updated_at'
  135. next if attribute == 'created_at'
  136. count += 1
  137. end
  138. return true if count <= 1
  139. end
  140. self.last_contact = Time.zone.now
  141. end
  142. def set_user
  143. return if local_update
  144. return if !UserInfo.current_user_id
  145. self.user_id = UserInfo.current_user_id
  146. end
  147. def update_preferences_infos
  148. return if key == 'Search'
  149. return if local_update
  150. return if changed_only_prio?
  151. preferences = self.preferences || {}
  152. preferences[:tasks] = collect_related_tasks
  153. # remember preferences for current taskbar
  154. self.preferences = preferences if !destroyed?
  155. end
  156. def changed_only_prio?
  157. changed_attribute_names_to_save.to_set == Set.new(%w[updated_at prio])
  158. end
  159. def reduce_related_tasks(elem, memo)
  160. key = elem[:user_id]
  161. if memo[key]
  162. memo[key].deep_merge! elem
  163. return
  164. end
  165. memo[key] = elem
  166. end
  167. def update_related_taskbars
  168. return if key == 'Search'
  169. return if local_update
  170. return if changed_only_prio?
  171. TaskbarUpdateRelatedTasksJob.perform_later(related_taskbars.map(&:id))
  172. end
  173. def notify_clients
  174. return if !saved_change_to_attribute?('preferences')
  175. data = {
  176. event: 'taskbar:preferences',
  177. data: {
  178. id: id,
  179. key: key,
  180. preferences: preferences,
  181. },
  182. }
  183. PushMessages.send_to(
  184. user_id,
  185. data,
  186. )
  187. end
  188. end