taskbar.rb 4.8 KB

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