Refactoring: Use ticket scope policies more consistently, various small optimizations.
This commit is contained in:
parent
602308801e
commit
d2efdb547d
15 changed files with 81 additions and 154 deletions
|
@ -27,13 +27,13 @@ module TicketStats
|
||||||
|
|
||||||
# created
|
# created
|
||||||
created = TicketPolicy::ReadScope.new(current_user).resolve
|
created = TicketPolicy::ReadScope.new(current_user).resolve
|
||||||
.where('created_at > ? AND created_at < ?', date_start, date_end)
|
.where(created_at: (date_start..date_end))
|
||||||
.where(condition)
|
.where(condition)
|
||||||
.count
|
.count
|
||||||
|
|
||||||
# closed
|
# closed
|
||||||
closed = TicketPolicy::ReadScope.new(current_user).resolve
|
closed = TicketPolicy::ReadScope.new(current_user).resolve
|
||||||
.where('close_at > ? AND close_at < ?', date_start, date_end)
|
.where(close_at: (date_start..date_end))
|
||||||
.where(condition)
|
.where(condition)
|
||||||
.count
|
.count
|
||||||
|
|
||||||
|
|
|
@ -324,18 +324,15 @@ class TicketsController < ApplicationController
|
||||||
customer_id: ticket.customer_id,
|
customer_id: ticket.customer_id,
|
||||||
state_id: Ticket::State.by_category(:open).select(:id),
|
state_id: Ticket::State.by_category(:open).select(:id),
|
||||||
)
|
)
|
||||||
.where.not(id: [ ticket.id ])
|
.where.not(id: ticket.id)
|
||||||
.order(created_at: :desc)
|
.order(created_at: :desc)
|
||||||
.limit(6)
|
.limit(6)
|
||||||
|
|
||||||
# if we do not have open related tickets, search for any tickets
|
# if we do not have open related tickets, search for any tickets
|
||||||
tickets ||= TicketPolicy::ReadScope.new(current_user).resolve
|
tickets ||= TicketPolicy::ReadScope.new(current_user).resolve
|
||||||
.where(
|
.where(customer_id: ticket.customer_id)
|
||||||
customer_id: ticket.customer_id,
|
.where.not(state_id: Ticket::State.by_category(:merged).pluck(:id))
|
||||||
).where.not(
|
.where.not(id: ticket.id)
|
||||||
state_id: Ticket::State.by_category(:merged).pluck(:id),
|
|
||||||
)
|
|
||||||
.where.not(id: [ ticket.id ])
|
|
||||||
.order(created_at: :desc)
|
.order(created_at: :desc)
|
||||||
.limit(6)
|
.limit(6)
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ module HasGroups
|
||||||
return false if !groups_access_permission?
|
return false if !groups_access_permission?
|
||||||
|
|
||||||
group_id = self.class.ensure_group_id_parameter(group_id)
|
group_id = self.class.ensure_group_id_parameter(group_id)
|
||||||
access = self.class.ensure_group_access_list_parameter(access)
|
access = Array(access).map(&:to_sym) | [:full]
|
||||||
|
|
||||||
# check direct access
|
# check direct access
|
||||||
return true if group_through.klass.eager_load(:group).exists?(
|
return true if group_through.klass.eager_load(:group).exists?(
|
||||||
|
@ -104,7 +104,7 @@ module HasGroups
|
||||||
return [] if !active?
|
return [] if !active?
|
||||||
return [] if !groups_access_permission?
|
return [] if !groups_access_permission?
|
||||||
|
|
||||||
access = self.class.ensure_group_access_list_parameter(access)
|
access = Array(access).map(&:to_sym) | [:full]
|
||||||
foreign_key = group_through.foreign_key
|
foreign_key = group_through.foreign_key
|
||||||
klass = group_through.klass
|
klass = group_through.klass
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ module HasGroups
|
||||||
# @return [Array<Class>]
|
# @return [Array<Class>]
|
||||||
def group_access(group_id, access)
|
def group_access(group_id, access)
|
||||||
group_id = ensure_group_id_parameter(group_id)
|
group_id = ensure_group_id_parameter(group_id)
|
||||||
access = ensure_group_access_list_parameter(access)
|
access = Array(access).map(&:to_sym) | [:full]
|
||||||
|
|
||||||
# check direct access
|
# check direct access
|
||||||
instances = joins(group_through.name)
|
instances = joins(group_through.name)
|
||||||
|
@ -364,11 +364,5 @@ module HasGroups
|
||||||
|
|
||||||
group_or_id.id
|
group_or_id.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_group_access_list_parameter(access)
|
|
||||||
access = [access] if access.is_a?(String)
|
|
||||||
access.push('full') if access.exclude?('full')
|
|
||||||
access
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ module HasRoles
|
||||||
return false if !groups_access_permission?
|
return false if !groups_access_permission?
|
||||||
|
|
||||||
group_id = self.class.ensure_group_id_parameter(group_id)
|
group_id = self.class.ensure_group_id_parameter(group_id)
|
||||||
access = self.class.ensure_group_access_list_parameter(access)
|
access = Array(access).map(&:to_sym) | [:full]
|
||||||
|
|
||||||
RoleGroup.eager_load(:group, :role).exists?(
|
RoleGroup.eager_load(:group, :role).exists?(
|
||||||
role_id: roles.pluck(:id),
|
role_id: roles.pluck(:id),
|
||||||
|
@ -74,7 +74,7 @@ module HasRoles
|
||||||
# @return [Array<Integer>]
|
# @return [Array<Integer>]
|
||||||
def role_access(group_id, access)
|
def role_access(group_id, access)
|
||||||
group_id = ensure_group_id_parameter(group_id)
|
group_id = ensure_group_id_parameter(group_id)
|
||||||
access = ensure_group_access_list_parameter(access)
|
access = Array(access).map(&:to_sym) | [:full]
|
||||||
|
|
||||||
role_ids = RoleGroup.eager_load(:role).where(group_id: group_id, access: access, roles: { active: true }).pluck(:role_id)
|
role_ids = RoleGroup.eager_load(:role).where(group_id: group_id, access: access, roles: { active: true }).pluck(:role_id)
|
||||||
join_table = reflect_on_association(:roles).join_table
|
join_table = reflect_on_association(:roles).join_table
|
||||||
|
@ -105,11 +105,5 @@ module HasRoles
|
||||||
|
|
||||||
group_or_id.id
|
group_or_id.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_group_access_list_parameter(access)
|
|
||||||
access = [access] if access.is_a?(String)
|
|
||||||
access.push('full') if access.exclude?('full')
|
|
||||||
access
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -463,20 +463,13 @@ get count of tickets and tickets which match on selector
|
||||||
return [ticket_count, tickets]
|
return [ticket_count, tickets]
|
||||||
end
|
end
|
||||||
|
|
||||||
ticket_count = "TicketPolicy::#{access.camelize}Scope".constantize
|
|
||||||
.new(current_user).resolve
|
|
||||||
.distinct
|
|
||||||
.where(query, *bind_params)
|
|
||||||
.joins(tables)
|
|
||||||
.count
|
|
||||||
tickets = "TicketPolicy::#{access.camelize}Scope".constantize
|
tickets = "TicketPolicy::#{access.camelize}Scope".constantize
|
||||||
.new(current_user).resolve
|
.new(current_user).resolve
|
||||||
.distinct
|
.distinct
|
||||||
.where(query, *bind_params)
|
.where(query, *bind_params)
|
||||||
.joins(tables)
|
.joins(tables)
|
||||||
.limit(limit)
|
|
||||||
|
|
||||||
return [ticket_count, tickets]
|
return [tickets.count, tickets.limit(limit)]
|
||||||
rescue ActiveRecord::StatementInvalid => e
|
rescue ActiveRecord::StatementInvalid => e
|
||||||
Rails.logger.error e
|
Rails.logger.error e
|
||||||
raise ActiveRecord::Rollback
|
raise ActiveRecord::Rollback
|
||||||
|
|
|
@ -93,8 +93,7 @@ returns
|
||||||
return [] if overviews.blank?
|
return [] if overviews.blank?
|
||||||
|
|
||||||
ticket_attributes = Ticket.new.attributes
|
ticket_attributes = Ticket.new.attributes
|
||||||
list = []
|
overviews.map do |overview|
|
||||||
overviews.each do |overview|
|
|
||||||
query_condition, bind_condition, tables = Ticket.selector2sql(overview.condition, current_user: user)
|
query_condition, bind_condition, tables = Ticket.selector2sql(overview.condition, current_user: user)
|
||||||
direction = overview.order[:direction]
|
direction = overview.order[:direction]
|
||||||
order_by = overview.order[:by]
|
order_by = overview.order[:by]
|
||||||
|
@ -150,7 +149,7 @@ returns
|
||||||
.joins(tables)
|
.joins(tables)
|
||||||
.count()
|
.count()
|
||||||
|
|
||||||
item = {
|
{
|
||||||
overview: {
|
overview: {
|
||||||
name: overview.name,
|
name: overview.name,
|
||||||
id: overview.id,
|
id: overview.id,
|
||||||
|
@ -160,10 +159,7 @@ returns
|
||||||
tickets: tickets,
|
tickets: tickets,
|
||||||
count: count,
|
count: count,
|
||||||
}
|
}
|
||||||
|
|
||||||
list.push item
|
|
||||||
end
|
end
|
||||||
list
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -173,42 +173,19 @@ returns
|
||||||
|
|
||||||
def self.list_by_customer(data)
|
def self.list_by_customer(data)
|
||||||
|
|
||||||
# get closed/open states
|
base_query = TicketPolicy::ReadScope.new(data[:current_user]).resolve
|
||||||
state_id_list_open = Ticket::State.by_category(:open).pluck(:id)
|
.joins(state: :state_type)
|
||||||
state_id_list_closed = Ticket::State.by_category(:closed).pluck(:id)
|
.where(customer_id: data[:customer_id])
|
||||||
|
|
||||||
# get tickets
|
|
||||||
tickets_open = TicketPolicy::ReadScope.new(data[:current_user]).resolve
|
|
||||||
.where(
|
|
||||||
customer_id: data[:customer_id],
|
|
||||||
state_id: state_id_list_open
|
|
||||||
)
|
|
||||||
.limit(data[:limit] || 15)
|
.limit(data[:limit] || 15)
|
||||||
.order(created_at: :desc)
|
.order(created_at: :desc)
|
||||||
assets = {}
|
|
||||||
ticket_ids_open = []
|
|
||||||
tickets_open.each do |ticket|
|
|
||||||
ticket_ids_open.push ticket.id
|
|
||||||
assets = ticket.assets(assets)
|
|
||||||
end
|
|
||||||
|
|
||||||
tickets_closed = TicketPolicy::ReadScope.new(data[:current_user]).resolve
|
open_tickets = base_query.where(ticket_state_types: { name: Ticket::State::TYPES[:open] })
|
||||||
.where(
|
closed_tickets = base_query.where(ticket_state_types: { name: Ticket::State::TYPES[:closed] })
|
||||||
customer_id: data[:customer_id],
|
|
||||||
state_id: state_id_list_closed
|
|
||||||
)
|
|
||||||
.limit(data[:limit] || 15)
|
|
||||||
.order(created_at: :desc)
|
|
||||||
ticket_ids_closed = []
|
|
||||||
tickets_closed.each do |ticket|
|
|
||||||
ticket_ids_closed.push ticket.id
|
|
||||||
assets = ticket.assets(assets)
|
|
||||||
end
|
|
||||||
|
|
||||||
{
|
{
|
||||||
ticket_ids_open: ticket_ids_open,
|
ticket_ids_open: open_tickets.map(&:id),
|
||||||
ticket_ids_closed: ticket_ids_closed,
|
ticket_ids_closed: closed_tickets.map(&:id),
|
||||||
assets: assets,
|
assets: (open_tickets | closed_tickets).reduce({}) { |hash, ticket| ticket.assets(hash) },
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -190,45 +190,34 @@ returns
|
||||||
return tickets
|
return tickets
|
||||||
end
|
end
|
||||||
|
|
||||||
# do query
|
|
||||||
# - stip out * we already search for *query* -
|
|
||||||
|
|
||||||
order_select_sql = sql_helper.get_order_select(sort_by, order_by, 'tickets.updated_at')
|
|
||||||
order_sql = sql_helper.get_order(sort_by, order_by, 'tickets.updated_at DESC')
|
order_sql = sql_helper.get_order(sort_by, order_by, 'tickets.updated_at DESC')
|
||||||
if query
|
|
||||||
query.delete! '*'
|
|
||||||
tickets_all = TicketPolicy::ReadScope.new(current_user).resolve
|
tickets_all = TicketPolicy::ReadScope.new(current_user).resolve
|
||||||
.select("DISTINCT(tickets.id), #{order_select_sql}")
|
|
||||||
.where('(tickets.title LIKE ? OR tickets.number LIKE ? OR ticket_articles.body LIKE ? OR ticket_articles.from LIKE ? OR ticket_articles.to LIKE ? OR ticket_articles.subject LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%")
|
|
||||||
.joins(:articles)
|
|
||||||
.order(Arel.sql(order_sql))
|
.order(Arel.sql(order_sql))
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
|
||||||
|
ticket_ids = if query
|
||||||
|
tickets_all.joins(:articles)
|
||||||
|
.where(<<~SQL.squish, query: "%#{query.delete('*')}%")
|
||||||
|
tickets.title LIKE :query
|
||||||
|
OR tickets.number LIKE :query
|
||||||
|
OR ticket_articles.body LIKE :query
|
||||||
|
OR ticket_articles.from LIKE :query
|
||||||
|
OR ticket_articles.to LIKE :query
|
||||||
|
OR ticket_articles.subject LIKE :query
|
||||||
|
SQL
|
||||||
else
|
else
|
||||||
query_condition, bind_condition, tables = selector2sql(condition)
|
query_condition, bind_condition, tables = selector2sql(condition)
|
||||||
tickets_all = TicketPolicy::ReadScope.new(current_user).resolve
|
|
||||||
.select("DISTINCT(tickets.id), #{order_select_sql}")
|
tickets_all.joins(tables)
|
||||||
.joins(tables)
|
|
||||||
.where(query_condition, *bind_condition)
|
.where(query_condition, *bind_condition)
|
||||||
.order(Arel.sql(order_sql))
|
end.pluck(:id)
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
# build result list
|
if full
|
||||||
if !full
|
ticket_ids.map { |id| Ticket.lookup(id: id) }
|
||||||
ids = []
|
else
|
||||||
tickets_all.each do |ticket|
|
ticket_ids
|
||||||
ids.push ticket.id
|
|
||||||
end
|
end
|
||||||
return ids
|
|
||||||
end
|
|
||||||
|
|
||||||
tickets = []
|
|
||||||
tickets_all.each do |ticket|
|
|
||||||
tickets.push Ticket.lookup(id: ticket.id)
|
|
||||||
end
|
|
||||||
tickets
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,22 @@ class Ticket::State < ApplicationModel
|
||||||
|
|
||||||
attr_accessor :callback_loop
|
attr_accessor :callback_loop
|
||||||
|
|
||||||
|
TYPES = {
|
||||||
|
open: ['new', 'open', 'pending reminder', 'pending action'],
|
||||||
|
pending_reminder: ['pending reminder'],
|
||||||
|
pending_action: ['pending action'],
|
||||||
|
pending: ['pending reminder', 'pending action'],
|
||||||
|
work_on: %w[new open],
|
||||||
|
work_on_all: ['new', 'open', 'pending reminder'],
|
||||||
|
viewable: ['new', 'open', 'pending reminder', 'pending action', 'closed', 'removed'],
|
||||||
|
viewable_agent_new: ['new', 'open', 'pending reminder', 'pending action', 'closed'],
|
||||||
|
viewable_agent_edit: ['open', 'pending reminder', 'pending action', 'closed'],
|
||||||
|
viewable_customer_new: %w[new closed],
|
||||||
|
viewable_customer_edit: %w[open closed],
|
||||||
|
closed: %w[closed],
|
||||||
|
merged: %w[merged],
|
||||||
|
}.freeze
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
looks up states for a given category
|
looks up states for a given category
|
||||||
|
@ -32,42 +48,11 @@ returns:
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def self.by_category(category)
|
def self.by_category(*categories)
|
||||||
|
state_types = TYPES.slice(*categories.map(&:to_sym)).values.uniq
|
||||||
|
raise ArgumentError, "No such categories (#{categories.join(', ')})" if state_types.empty?
|
||||||
|
|
||||||
case category.to_sym
|
Ticket::State.joins(:state_type).where(ticket_state_types: { name: state_types })
|
||||||
when :open
|
|
||||||
state_types = ['new', 'open', 'pending reminder', 'pending action']
|
|
||||||
when :pending_reminder
|
|
||||||
state_types = ['pending reminder']
|
|
||||||
when :pending_action
|
|
||||||
state_types = ['pending action']
|
|
||||||
when :pending
|
|
||||||
state_types = ['pending reminder', 'pending action']
|
|
||||||
when :work_on
|
|
||||||
state_types = %w[new open]
|
|
||||||
when :work_on_all
|
|
||||||
state_types = ['new', 'open', 'pending reminder']
|
|
||||||
when :viewable
|
|
||||||
state_types = ['new', 'open', 'pending reminder', 'pending action', 'closed', 'removed']
|
|
||||||
when :viewable_agent_new
|
|
||||||
state_types = ['new', 'open', 'pending reminder', 'pending action', 'closed']
|
|
||||||
when :viewable_agent_edit
|
|
||||||
state_types = ['open', 'pending reminder', 'pending action', 'closed']
|
|
||||||
when :viewable_customer_new
|
|
||||||
state_types = %w[new closed]
|
|
||||||
when :viewable_customer_edit
|
|
||||||
state_types = %w[open closed]
|
|
||||||
when :closed
|
|
||||||
state_types = %w[closed]
|
|
||||||
when :merged
|
|
||||||
state_types = %w[merged]
|
|
||||||
end
|
|
||||||
|
|
||||||
raise "Unknown category '#{category}'" if state_types.blank?
|
|
||||||
|
|
||||||
Ticket::State.where(
|
|
||||||
state_type_id: Ticket::StateType.where(name: state_types)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
|
@ -22,9 +22,8 @@ class TicketPolicy < ApplicationPolicy
|
||||||
bind = []
|
bind = []
|
||||||
|
|
||||||
if user.permissions?('ticket.agent')
|
if user.permissions?('ticket.agent')
|
||||||
access_type = self.class.name.demodulize.slice(%r{.*(?=Scope)}).underscore
|
|
||||||
sql.push('group_id IN (?)')
|
sql.push('group_id IN (?)')
|
||||||
bind.push(user.group_ids_access(access_type))
|
bind.push(user.group_ids_access(self.class::ACCESS_TYPE))
|
||||||
end
|
end
|
||||||
|
|
||||||
if user.organization&.shared
|
if user.organization&.shared
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
class TicketPolicy < ApplicationPolicy
|
class TicketPolicy < ApplicationPolicy
|
||||||
class FullScope < BaseScope
|
class FullScope < BaseScope
|
||||||
|
ACCESS_TYPE = :full
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
class TicketPolicy < ApplicationPolicy
|
class TicketPolicy < ApplicationPolicy
|
||||||
class OverviewScope < BaseScope
|
class OverviewScope < BaseScope
|
||||||
|
ACCESS_TYPE = :overview
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
|
|
||||||
class TicketPolicy < ApplicationPolicy
|
class TicketPolicy < ApplicationPolicy
|
||||||
class ReadScope < BaseScope
|
class ReadScope < BaseScope
|
||||||
|
ACCESS_TYPE = :read
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,9 +44,9 @@ RSpec.describe Ticket::State, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with invalid category name' do
|
context 'with invalid category name' do
|
||||||
it 'raises RuntimeError' do
|
it 'raises ArgumentError' do
|
||||||
expect { described_class.by_category(:invalidcategoryname) }
|
expect { described_class.by_category(:invalidcategoryname) }
|
||||||
.to raise_error(RuntimeError)
|
.to raise_error(ArgumentError)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ RSpec.shared_examples 'for agent user' do |access_type|
|
||||||
context 'with direct access via User#groups' do
|
context 'with direct access via User#groups' do
|
||||||
let(:user) { create(:agent, groups: member_groups) }
|
let(:user) { create(:agent, groups: member_groups) }
|
||||||
|
|
||||||
context 'when checkin for "full" access' do
|
context 'when checking for "full" access' do
|
||||||
# this is already true by default, but it doesn't hurt to be explicit
|
# this is already true by default, but it doesn't hurt to be explicit
|
||||||
before { user.user_groups.each { |ug| ug.update_columns(access: 'full') } }
|
before { user.user_groups.each { |ug| ug.update_columns(access: 'full') } }
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ RSpec.shared_examples 'for agent user' do |access_type|
|
||||||
let(:user) { create(:agent).tap { |u| u.roles << role } }
|
let(:user) { create(:agent).tap { |u| u.roles << role } }
|
||||||
let(:role) { create(:role, groups: member_groups) }
|
let(:role) { create(:role, groups: member_groups) }
|
||||||
|
|
||||||
context 'when checkin for "full" access' do
|
context 'when checking for "full" access' do
|
||||||
# this is already true by default, but it doesn't hurt to be explicit
|
# this is already true by default, but it doesn't hurt to be explicit
|
||||||
before { role.role_groups.each { |rg| rg.update_columns(access: 'full') } }
|
before { role.role_groups.each { |rg| rg.update_columns(access: 'full') } }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue