zammad/zammad

View on GitHub
lib/transaction_dispatcher.rb

Summary

Maintainability
C
1 day
Test Coverage
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class TransactionDispatcher

  def self.reset
    EventBuffer.reset('transaction')
  end

  def self.commit(params = {})

    # add attribute of interface handle (e. g. to send (no) notifications if a agent
    # is creating a ticket via application_server, but send it if it's created via
    # postmaster)
    params[:interface_handle] = ApplicationHandleInfo.current

    # execute object transactions
    TransactionDispatcher.perform(params)
  end

  def self.perform(params)

    # return if we run import mode
    return if Setting.get('import_mode')

    # get buffer
    list = EventBuffer.list('transaction')

    # reset buffer
    EventBuffer.reset('transaction')

    # get async backends
    sync_backends = []
    Setting.where(area: 'Transaction::Backend::Sync').reorder(:name).each do |setting|
      backend = Setting.get(setting.name)
      next if params[:disable]&.include?(backend)

      sync_backends.push backend.constantize
    end

    # get uniq objects
    list_objects = get_uniq_changes(list)
    list_objects.each_value do |objects|
      objects.each_value do |item|

        # execute sync backends
        sync_backends.each do |backend|
          execute_single_backend(backend, item, params)
        end

        # execute async backends
        TransactionJob.perform_later(item, params)
      end
    end
  end

  def self.execute_single_backend(backend, item, params)
    Rails.logger.debug { "Execute single backend #{backend}" }
    begin
      UserInfo.current_user_id = nil
      integration = backend.new(item, params)
      integration.perform
    rescue => e
      Rails.logger.error e
    end
  end

=begin

  result = get_uniq_changes(events)

  result = {
    'Ticket' =>
      1 => {
        object: 'Ticket',
        type: 'create',
        object_id: 123,
        article_id: 123,
        user_id: 123,
        created_at: Time.zone.now,
      },
      9 => {
        object: 'Ticket',
        type: 'update',
        object_id: 123,
        changes: {
          attribute1: [before, now],
          attribute2: [before, now],
        },
        user_id: 123,
        created_at: Time.zone.now,
      },
    },
  }

  result = {
    'Ticket' =>
      9 => {
        object: 'Ticket',
        type: 'update',
        object_id: 123,
        article_id: 123,
        changes: {
          attribute1: [before, now],
          attribute2: [before, now],
        },
        user_id: 123,
        created_at: Time.zone.now,
      },
    },
  }

=end

  def self.get_uniq_changes(events)
    list_objects = {}
    events.each do |event|

      # simulate article create as ticket update
      article = nil
      if event[:object] == 'Ticket::Article'
        article = Ticket::Article.find_by(id: event[:id])
        next if !article
        next if event[:type] == 'update'

        # set new event infos
        ticket = Ticket.find_by(id: article.ticket_id)
        event[:object] = 'Ticket'
        event[:id] = ticket.id
        event[:type] = 'update'
        event[:changes] = nil
      end

      # get current state of objects
      object = event[:object].constantize.find_by(id: event[:id])

      # next if object is already deleted
      next if !object

      if !list_objects[event[:object]]
        list_objects[event[:object]] = {}
      end
      if !list_objects[event[:object]][object.id]
        list_objects[event[:object]][object.id] = {}
      end
      store = list_objects[event[:object]][object.id]
      store[:object] = event[:object]
      store[:object_id] = object.id
      store[:user_id] = event[:user_id]
      store[:created_at] = event[:created_at]

      if !store[:type] || store[:type] == 'update'
        store[:type] = event[:type]
      end

      # merge changes
      if event[:changes]
        if store[:changes]
          event[:changes].each do |key, value|
            if store[:changes][key]
              store[:changes][key][1] = value[1]
            else
              store[:changes][key] = value
            end
          end
        else
          store[:changes] = event[:changes]
        end
      end

      # remember article id if exists
      if article
        store[:article_id] = article.id
      end
    end
    list_objects
  end

  # Used as ActiveRecord lifecycle callback on the class.
  def self.after_create(record)

    # return if we run import mode
    return true if Setting.get('import_mode')

    e = {
      object:     record.class.name,
      type:       'create',
      data:       record,
      id:         record.id,
      user_id:    record.created_by_id,
      created_at: Time.zone.now,
    }
    EventBuffer.add('transaction', e)
    true
  end

  # Used as ActiveRecord lifecycle callback on the class.
  def self.after_update(record)

    # return if we run import mode
    return true if Setting.get('import_mode')

    # ignore certain attributes
    real_changes = {}
    record.saved_changes.each do |key, value|
      next if key == 'updated_at'
      next if key == 'article_count'
      next if key == 'create_article_type_id'
      next if key == 'create_article_sender_id'

      real_changes[key] = value
    end

    # do not send anything if nothing has changed
    return true if real_changes.blank?

    changed_by_id = if record.respond_to?(:updated_by_id)
                      record.updated_by_id
                    else
                      record.created_by_id
                    end

    e = {
      object:     record.class.name,
      type:       'update',
      data:       record,
      changes:    real_changes,
      id:         record.id,
      user_id:    changed_by_id,
      created_at: Time.zone.now,
    }
    EventBuffer.add('transaction', e)
    true
  end

end