123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- class Selector::Sql < Selector::Base
- VALID_OPERATORS = [
- 'after (absolute)',
- 'after (relative)',
- 'before (absolute)',
- 'before (relative)',
- 'contains all not',
- 'contains all',
- 'contains not',
- 'contains one not',
- 'contains one',
- 'contains',
- 'does not match regex',
- 'ends with one of',
- 'ends with', # keep for compatibility with old conditions
- 'from (relative)',
- 'has changed',
- 'has reached warning',
- 'has reached',
- 'is any of',
- 'is in working time',
- 'is none of',
- 'is not in working time',
- 'is not',
- 'is set',
- 'is',
- 'matches regex',
- 'not set',
- 'starts with one of',
- 'starts with', # keep for compatibility with old conditions
- 'till (relative)',
- 'today',
- 'within last (relative)',
- 'within next (relative)',
- ].freeze
- attr_accessor :final_query, :final_bind_params, :final_tables, :changed_attributes
- def get
- @final_query = []
- @final_bind_params = []
- @final_tables = []
- @final_query = run(selector, 0)
- [query_sql, final_bind_params, tables_sql]
- rescue InvalidCondition => e
- Rails.logger.error "Selector::Sql.get->InvalidCondition: #{e}"
- nil
- rescue => e
- Rails.logger.error "Selector::Sql.get->default: #{e}"
- raise e
- end
- def query_sql
- Array(final_query).join(' AND ')
- end
- def tables_sql
- return '' if final_tables.blank?
- " #{final_tables.join(' ')}"
- end
- def run(block, level)
- if block.key?(:conditions)
- run_block(block, level)
- else
- query, bind_params, tables = condition_sql(block)
- @final_bind_params += bind_params
- @final_tables |= tables
- query
- end
- end
- def run_block(block, level)
- block_query = block[:conditions].map do |sub_block|
- run(sub_block, level + 1)
- end
- block_query = block_query.compact
- return if block_query.blank?
- return "NOT(#{block_query.join(' AND ')})" if block[:operator] == 'NOT'
- "(#{block_query.join(" #{block[:operator]} ")})"
- end
- def condition_sql(block_condition)
- current_user = options[:current_user]
- current_user_id = UserInfo.current_user_id
- if current_user
- current_user_id = current_user.id
- end
- raise InvalidCondition, "No block condition #{block_condition.inspect}" if block_condition.blank?
- raise InvalidCondition, "No block condition name #{block_condition.inspect}" if block_condition[:name].blank?
- # remember query and bind params
- query = []
- tables = []
- bind_params = []
- like = Rails.application.config.db_like
- attribute_table, attribute_name = block_condition[:name].split('.')
- # get tables to join
- return if !attribute_name
- return if !attribute_table
- sql_helper = SqlHelper.new(object: target_class)
- if attribute_table && %w[execution_time ticket_owner ticket_customer].exclude?(attribute_table) && attribute_table != target_name && tables.exclude?(attribute_table) && !(attribute_table == 'ticket' && attribute_name != 'mention_user_ids') && !(attribute_table == 'ticket' && attribute_name == 'mention_user_ids' && block_condition[:pre_condition] == 'not_set') && !(attribute_table == 'article' && attribute_name == 'action')
- case attribute_table
- when 'customer'
- tables |= ["INNER JOIN users customers ON #{target_table}.customer_id = customers.id"]
- sql_helper = SqlHelper.new(object: User, table_name: 'customers')
- when 'organization'
- tables |= ["LEFT JOIN organizations ON #{target_table}.organization_id = organizations.id"]
- sql_helper = SqlHelper.new(object: Organization)
- when 'owner'
- tables |= ['INNER JOIN users owners ON tickets.owner_id = owners.id']
- sql_helper = SqlHelper.new(object: User, table_name: 'owners')
- when 'article'
- tables |= ['INNER JOIN ticket_articles articles ON tickets.id = articles.ticket_id']
- sql_helper = SqlHelper.new(object: Ticket::Article, table_name: 'articles')
- when 'ticket_state'
- tables |= ['INNER JOIN ticket_states ON tickets.state_id = ticket_states.id']
- sql_helper = SqlHelper.new(object: Ticket::State)
- else
- raise "invalid selector #{attribute_table}, #{attribute_name}"
- end
- end
- validate_operator! block_condition
- validate_pre_condition_blank! block_condition
- validate_pre_condition_values! block_condition
- is_json_column = sql_helper.json_column?(attribute_name)
- # get attributes
- attribute = is_json_column ? sql_helper.json_db_column_with_key(attribute_name, 'value') : sql_helper.db_column(attribute_name)
- # magic block_condition
- if attribute_table == 'ticket' && attribute_name == 'out_of_office_replacement_id'
- attribute = "#{ActiveRecord::Base.connection.quote_table_name("#{attribute_table}s")}.#{ActiveRecord::Base.connection.quote_column_name('owner_id')}"
- end
- if attribute_table == 'ticket' && attribute_name == 'tags'
- block_condition[:value] = block_condition[:value].split(',').collect(&:strip)
- end
- # Performance: use left join instead of sub select if tags value is only one element and contains all is used
- if attribute_table == 'ticket' && attribute_name == 'tags' && block_condition[:operator] == 'contains all' && block_condition[:value].count == 1
- block_condition[:operator] = 'contains one'
- end
- # User customer tickets last_contact_at
- query_wrap = nil
- if attribute_table == 'ticket_customer' && attribute_name == 'last_contact_at'
- attribute = 'last_contact_at'
- query_wrap = 'users.id IN (SELECT DISTINCT tickets.customer_id FROM tickets WHERE ###QUERY###)'
- end
- # User customer tickets last_contact_agent_at
- if attribute_table == 'ticket_customer' && attribute_name == 'last_contact_agent_at'
- attribute = 'last_contact_agent_at'
- query_wrap = 'users.id IN (SELECT DISTINCT tickets.customer_id FROM tickets WHERE ###QUERY###)'
- end
- # User customer tickets last_contact_customer_at
- if attribute_table == 'ticket_customer' && attribute_name == 'last_contact_customer_at'
- attribute = 'last_contact_customer_at'
- query_wrap = 'users.id IN (SELECT DISTINCT tickets.customer_id FROM tickets WHERE ###QUERY###)'
- end
- # User customer tickets updated_at
- if attribute_table == 'ticket_customer' && attribute_name == 'updated_at'
- attribute = 'updated_at'
- query_wrap = 'users.id IN (SELECT DISTINCT tickets.customer_id FROM tickets WHERE ###QUERY###)'
- end
- #
- # checks
- #
- #
- if attribute_table == 'article' && options.key?(:article_id) && options[:article_id].blank? && attribute_name != 'action'
- query << '1 = 0'
- elsif block_condition[:operator].include?('in working time')
- raise __('Please enable execution_time feature to use it (currently only allowed for triggers and schedulers)') if !options[:execution_time]
- biz = Calendar.lookup(id: block_condition[:value])&.biz
- query << if biz.present? && attribute_name == 'calendar_id' && ((block_condition[:operator] == 'is in working time' && !biz.in_hours?(Time.zone.now)) || (block_condition[:operator] == 'is not in working time' && biz.in_hours?(Time.zone.now)))
- '1 = 0'
- else
- '1 = 1'
- end
- elsif block_condition[:operator] == 'has changed'
- query << if changed_attributes[ block_condition[:name] ]
- '1 = 1'
- else
- '1 = 0'
- end
- elsif block_condition[:operator] == 'has reached'
- query << if time_based_trigger?(block_condition, warning: false)
- '1 = 1'
- else
- '1 = 0'
- end
- elsif block_condition[:operator] == 'has reached warning'
- query << if time_based_trigger?(block_condition, warning: true)
- '1 = 1'
- else
- '1 = 0'
- end
- elsif attribute_table == 'ticket' && attribute_name == 'action'
- check = options[:ticket_action] == block_condition[:value] ? 1 : 0
- query << if update_action_requires_changed_attributes?(block_condition, check)
- '1 = 0'
- elsif block_condition[:operator] == 'is'
- "1 = #{check}"
- else
- "0 = #{check}" # is not
- end
- elsif attribute_table == 'article' && attribute_name == 'action'
- check = options[:article_id] ? 1 : 0
- query << if block_condition[:operator] == 'is'
- "1 = #{check}"
- else
- "0 = #{check}" # is not
- end
- elsif attribute_table == 'article' && attribute_name == 'time_accounting'
- tables |= ["LEFT JOIN ticket_time_accountings ON ticket_time_accountings.ticket_article_id = #{options[:article_id].to_i}"]
- query << if block_condition[:operator] == 'is set'
- 'ticket_time_accountings.id IS NOT NULL'
- else
- 'ticket_time_accountings.id IS NULL' # not set
- end
- # because of no grouping support we select not_set by sub select for mentions
- elsif attribute_table == 'ticket' && attribute_name == 'mention_user_ids'
- if block_condition[:pre_condition] == 'not_set'
- tables |= ["LEFT JOIN mentions ON tickets.id = mentions.mentionable_id AND mentions.mentionable_type = 'Ticket'"]
- query << if block_condition[:operator] == 'is'
- 'mentions.user_id IS NULL'
- else
- 'mentions.user_id IS NOT NULL'
- end
- else
- query << if block_condition[:operator] == 'is'
- tables |= ["LEFT JOIN mentions ON tickets.id = mentions.mentionable_id AND mentions.mentionable_type = 'Ticket'"]
- 'mentions.user_id IN (?)'
- else
- "tickets.id NOT IN (SELECT mentionable_id FROM mentions WHERE mentionable_type = 'Ticket' AND user_id IN (?))"
- end
- if block_condition[:pre_condition] == 'current_user.id'
- bind_params.push current_user_id
- else
- bind_params.push block_condition[:value]
- end
- end
- elsif attribute_table == 'user' && attribute_name == 'role_ids'
- query << if block_condition[:operator] == 'is'
- "users.id IN (
- SELECT
- roles_users.user_id
- FROM
- roles, roles_users
- WHERE
- roles.id = roles_users.role_id
- AND roles.id IN (?)
- GROUP BY
- roles_users.user_id
- )"
- else
- "users.id NOT IN (
- SELECT
- roles_users.user_id
- FROM
- roles, roles_users
- WHERE
- roles.id = roles_users.role_id
- AND roles.id IN (?)
- GROUP BY
- roles_users.user_id
- )"
- end
- bind_params.push block_condition[:value]
- elsif attribute_table == 'organization' && attribute_name == 'members_existing'
- query << if (block_condition[:operator] == 'is' && block_condition[:value].to_s == 'true') || (block_condition[:operator] == 'is not' && block_condition[:value].to_s != 'true')
- 'organizations.id IN (SELECT DISTINCT organizations.id FROM organizations, users WHERE organizations.id = users.organization_id)'
- else
- 'organizations.id NOT IN (SELECT DISTINCT organizations.id FROM organizations, users WHERE organizations.id = users.organization_id)'
- end
- elsif %w[ticket_customer ticket_owner].include?(attribute_table) && %w[existing open_existing].include?(attribute_name)
- distinct_column = if attribute_table == 'ticket_customer'
- 'customer_id'
- else
- 'owner_id'
- end
- query_where = ''
- if attribute_name == 'open_existing'
- query_where = ' WHERE state_id IN (?)'
- bind_params.push Ticket::State.by_category_ids(:open)
- end
- query << if (block_condition[:operator] == 'is' && block_condition[:value].to_s == 'true') || (block_condition[:operator] == 'is not' && block_condition[:value].to_s != 'true')
- "users.id IN (SELECT DISTINCT #{distinct_column} FROM tickets#{query_where})"
- else
- "users.id NOT IN (SELECT DISTINCT #{distinct_column} FROM tickets#{query_where})"
- end
- elsif block_condition[:operator] == 'starts with'
- query << "#{attribute} #{like} (?)"
- bind_params.push "#{SqlHelper.quote_like(block_condition[:value])}%"
- elsif block_condition[:operator] == 'starts with one of'
- block_condition[:value] = Array.wrap(block_condition[:value])
- sub_query = []
- block_condition[:value].each do |value|
- sub_query << "#{attribute} #{like} (?)"
- bind_params.push "#{SqlHelper.quote_like(value)}%"
- end
- query << "(#{sub_query.join(' OR ')})" if sub_query.present?
- elsif block_condition[:operator] == 'ends with'
- query << "#{attribute} #{like} (?)"
- bind_params.push "%#{SqlHelper.quote_like(block_condition[:value])}"
- elsif block_condition[:operator] == 'ends with one of'
- block_condition[:value] = Array.wrap(block_condition[:value])
- sub_query = []
- block_condition[:value].each do |value|
- sub_query << "#{attribute} #{like} (?)"
- bind_params.push "%#{SqlHelper.quote_like(value)}"
- end
- query << "(#{sub_query.join(' OR ')})" if sub_query.present?
- elsif block_condition[:operator] == 'is any of'
- block_condition[:value] = Array.wrap(block_condition[:value])
- block_condition[:value] = block_condition[:value].empty? ? [''] : block_condition[:value]
- sub_query = []
- block_condition[:value].each do |value|
- sub_query << "#{attribute} IN (?)"
- bind_params.push value
- end
- query << "(#{sub_query.join(' OR ')})" if sub_query.present?
- elsif block_condition[:operator] == 'is none of'
- block_condition[:value] = Array.wrap(block_condition[:value])
- block_condition[:value] = block_condition[:value].empty? ? [''] : block_condition[:value]
- sub_query = []
- block_condition[:value].each do |value|
- sub_query << "#{attribute} NOT IN (?)"
- bind_params.push value
- end
- query << "(#{sub_query.join(' AND ')})" if sub_query.present?
- elsif block_condition[:operator] == 'is'
- if block_condition[:pre_condition] == 'not_set'
- if attribute_name.match?(%r{^(created_by|updated_by|owner|customer|user)_id})
- query << "(#{attribute} IS NULL OR #{attribute} IN (?))"
- bind_params.push 1
- else
- query << "#{attribute} IS NULL"
- end
- elsif block_condition[:pre_condition] == 'current_user.id'
- raise "Use current_user.id in block_condition, but no current_user is set #{block_condition.inspect}" if !current_user_id
- query << "#{attribute} IN (?)"
- if attribute_name == 'out_of_office_replacement_id'
- bind_params.push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
- else
- bind_params.push current_user_id
- end
- elsif block_condition[:pre_condition] == 'current_user.organization_id'
- raise "Use current_user.id in block_condition, but no current_user is set #{block_condition.inspect}" if !current_user_id
- query << "#{attribute} IN (?)"
- user = User.find_by(id: current_user_id)
- bind_params.push user.all_organization_ids
- else
- # rubocop:disable Style/IfInsideElse, Metrics/BlockNesting
- if block_condition[:value].nil?
- query << "#{attribute} IS NULL"
- else
- if is_json_column
- query << "#{attribute} IN (?)"
- bind_params.push(Array.wrap(block_condition[:value]).map { |item| item[:value].to_s })
- elsif attribute_name == 'out_of_office_replacement_id'
- query << "#{attribute} IN (?)"
- bind_params.push User.where(id: Array.wrap(block_condition[:value])).map(&:out_of_office_agent_of).flatten.map(&:id)
- else
- block_condition[:value] = Array.wrap(block_condition[:value])
- query << if block_condition[:value].include?('')
- "(#{attribute} IN (?) OR #{attribute} IS NULL)"
- else
- "#{attribute} IN (?)"
- end
- bind_params.push block_condition[:value]
- end
- end
- # rubocop:enable Style/IfInsideElse, Metrics/BlockNesting
- end
- elsif block_condition[:operator] == 'is not'
- if block_condition[:pre_condition] == 'not_set'
- if attribute_name.match?(%r{^(created_by|updated_by|owner|customer|user)_id})
- query << "(#{attribute} IS NOT NULL AND #{attribute} NOT IN (?))"
- bind_params.push 1
- else
- query << "#{attribute} IS NOT NULL"
- end
- elsif block_condition[:pre_condition] == 'current_user.id'
- query << "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
- if attribute_name == 'out_of_office_replacement_id'
- bind_params.push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
- else
- bind_params.push current_user_id
- end
- elsif block_condition[:pre_condition] == 'current_user.organization_id'
- query << "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
- user = User.find_by(id: current_user_id)
- bind_params.push user.organization_id
- else
- # rubocop:disable Style/IfInsideElse, Metrics/BlockNesting
- if block_condition[:value].nil?
- query << "#{attribute} IS NOT NULL"
- else
- if is_json_column
- query << "#{attribute} NOT IN (?)"
- bind_params.push(Array.wrap(block_condition[:value]).map { |item| item[:value].to_s })
- elsif attribute_name == 'out_of_office_replacement_id'
- bind_params.push User.find(block_condition[:value]).out_of_office_agent_of.pluck(:id)
- query << "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
- else
- block_condition[:value] = Array.wrap(block_condition[:value])
- query << if block_condition[:value].include?('')
- "(#{attribute} IS NOT NULL AND #{attribute} NOT IN (?))"
- else
- "(#{attribute} IS NULL OR #{attribute} NOT IN (?))"
- end
- bind_params.push block_condition[:value]
- end
- end
- # rubocop:enable Style/IfInsideElse, Metrics/BlockNesting
- end
- elsif block_condition[:operator] == 'contains'
- query << "#{attribute} #{like} (?)"
- bind_params.push "%#{SqlHelper.quote_like(block_condition[:value])}%"
- elsif block_condition[:operator] == 'contains not'
- # NOT LIKE is always false on NULL values
- # https://github.com/zammad/zammad/issues/4948
- query << "#{attribute} NOT #{like} (?) OR #{attribute} IS NULL"
- bind_params.push "%#{SqlHelper.quote_like(block_condition[:value])}%"
- elsif block_condition[:operator] == 'matches regex'
- query << sql_helper.regex_match(attribute, negated: false)
- bind_params.push block_condition[:value]
- elsif block_condition[:operator] == 'does not match regex'
- query << sql_helper.regex_match(attribute, negated: true)
- bind_params.push block_condition[:value]
- elsif block_condition[:operator] == 'contains all'
- if attribute_table == 'ticket' && attribute_name == 'tags'
- query << "tickets.id IN (
- SELECT
- tags.o_id
- FROM
- tag_objects, tag_items, tags
- WHERE
- tag_objects.id = tags.tag_object_id
- AND tag_objects.name = 'Ticket'
- AND tag_items.id = tags.tag_item_id
- AND tag_items.name IN (?)
- GROUP BY
- tags.o_id
- HAVING
- COUNT(*) = ?
- )"
- bind_params.push block_condition[:value]
- bind_params.push block_condition[:value].count
- elsif sql_helper.containable?(attribute_name)
- query << sql_helper.array_contains_all(attribute_name, block_condition[:value])
- end
- elsif block_condition[:operator] == 'contains one'
- if attribute_name == 'tags' && attribute_table == 'ticket'
- tables |= ["LEFT JOIN tags ON tickets.id = tags.o_id LEFT JOIN tag_objects ON tag_objects.id = tags.tag_object_id AND tag_objects.name = 'Ticket' LEFT JOIN tag_items ON tag_items.id = tags.tag_item_id"]
- query << 'tag_items.name IN (?)'
- bind_params.push block_condition[:value]
- elsif sql_helper.containable?(attribute_name)
- query << sql_helper.array_contains_one(attribute_name, block_condition[:value])
- end
- elsif block_condition[:operator] == 'contains all not'
- if attribute_name == 'tags' && attribute_table == 'ticket'
- query << "tickets.id NOT IN (
- SELECT
- DISTINCT tags.o_id
- FROM
- tag_objects, tag_items, tags
- WHERE
- tag_objects.id = tags.tag_object_id
- AND tag_objects.name = 'Ticket'
- AND tag_items.id = tags.tag_item_id
- AND tag_items.name IN (?)
- GROUP BY
- tags.o_id
- HAVING
- COUNT(*) = ?
- )"
- bind_params.push block_condition[:value]
- bind_params.push block_condition[:value].count
- elsif sql_helper.containable?(attribute_name)
- query << sql_helper.array_contains_all(attribute_name, block_condition[:value], negated: true)
- end
- elsif block_condition[:operator] == 'contains one not'
- if attribute_name == 'tags' && attribute_table == 'ticket'
- query << "tickets.id NOT IN (
- SELECT
- DISTINCT tags.o_id
- FROM
- tag_objects, tag_items, tags
- WHERE
- tag_objects.id = tags.tag_object_id
- AND tag_objects.name = 'Ticket'
- AND tag_items.id = tags.tag_item_id
- AND tag_items.name IN (?)
- )"
- bind_params.push block_condition[:value]
- elsif sql_helper.containable?(attribute_name)
- query << sql_helper.array_contains_one(attribute_name, block_condition[:value], negated: true)
- end
- elsif block_condition[:operator] == 'today'
- Time.use_zone(Setting.get('timezone_default')) do
- day_start = Time.zone.now.beginning_of_day.utc
- day_end = Time.zone.now.end_of_day.utc
- query << "#{attribute} BETWEEN ? AND ?"
- bind_params.push day_start
- bind_params.push day_end
- end
- elsif block_condition[:operator] == 'before (absolute)'
- query << "#{attribute} <= ?"
- bind_params.push block_condition[:value]
- elsif block_condition[:operator] == 'after (absolute)'
- query << "#{attribute} >= ?"
- bind_params.push block_condition[:value]
- elsif block_condition[:operator] == 'within last (relative)'
- query << "#{attribute} BETWEEN ? AND ?"
- time = range(block_condition).ago
- bind_params.push time
- bind_params.push Time.zone.now
- elsif block_condition[:operator] == 'within next (relative)'
- query << "#{attribute} BETWEEN ? AND ?"
- time = range(block_condition).from_now
- bind_params.push Time.zone.now
- bind_params.push time
- elsif block_condition[:operator] == 'before (relative)'
- query << "#{attribute} <= ?"
- time = range(block_condition).ago
- bind_params.push time
- elsif block_condition[:operator] == 'after (relative)'
- query << "#{attribute} >= ?"
- time = range(block_condition).from_now
- bind_params.push time
- elsif block_condition[:operator] == 'till (relative)'
- query << "#{attribute} <= ?"
- time = range(block_condition).from_now
- bind_params.push time
- elsif block_condition[:operator] == 'from (relative)'
- query << "#{attribute} >= ?"
- time = range(block_condition).ago
- bind_params.push time
- else
- raise "Invalid operator '#{block_condition[:operator]}' for '#{block_condition[:value].inspect}'"
- end
- if query_wrap.present?
- query << query_wrap.gsub('###QUERY###', query.pop)
- end
- query.map! { "(#{_1})" }
- [query, bind_params, tables]
- end
- def range(selector)
- selector[:value].to_i.send(selector[:range].pluralize)
- rescue
- raise 'unknown selector'
- end
- def validate_operator!(condition)
- if condition[:operator].blank?
- raise "Invalid condition, operator missing #{condition.inspect}"
- end
- return true if self.class.valid_operator?(condition[:operator])
- raise "Invalid condition, operator '#{condition[:operator]}' is invalid #{condition.inspect}"
- end
- def time_based_trigger?(condition, warning:)
- case [condition[:name], options[:ticket_action]]
- in 'ticket.pending_time', 'reminder_reached'
- true
- in 'ticket.escalation_at', 'escalation'
- !warning
- in 'ticket.escalation_at', 'escalation_warning'
- warning
- else
- false
- end
- end
- # validate pre_condition values
- def validate_pre_condition_values!(condition)
- return if ['has changed', 'has reached', 'has reached warning'].include? condition[:operator]
- return if condition[:pre_condition].blank?
- return if %w[not_set current_user. specific].any? { |elem| condition[:pre_condition].start_with? elem }
- raise InvalidCondition, "Invalid condition pre_condition not set #{condition}!"
- end
- # validate value / allow blank but only if pre_condition exists and is not specific
- def validate_pre_condition_blank!(condition)
- return if ['has changed', 'has reached', 'has reached warning', 'is any of', 'is none of', 'is set', 'not set'].include? condition[:operator]
- if (condition[:operator] != 'today' && !condition.key?(:value)) ||
- (condition[:value].instance_of?(Array) && condition[:value].respond_to?(:blank?) && condition[:value].blank?) ||
- (condition[:operator].start_with?('contains') && condition[:value].respond_to?(:blank?) && condition[:value].blank?)
- raise InvalidCondition, "Invalid condition pre_condition nil #{condition}!" if condition[:pre_condition].nil?
- raise InvalidCondition, "Invalid condition pre_condition blank #{condition}!" if condition[:pre_condition].respond_to?(:blank?) && condition[:pre_condition].blank?
- raise InvalidCondition, "Invalid condition pre_condition specific #{condition}!" if condition[:pre_condition] == 'specific'
- end
- end
- def update_action_requires_changed_attributes?(condition, check)
- condition[:value] == 'update' && check && options[:changes_required] && changed_attributes.blank?
- end
- def self.valid_operator?(operator)
- VALID_OPERATORS.include?(operator)
- end
- def valid?
- object_count, _objects = target_class.selectors(selector, **options, limit: 1, execution_time: true, ticket_id: 1, access: 'ignore')
- !object_count.nil?
- rescue
- false
- end
- end
|