fix: set custom filter count in redis (#7164)

This commit is contained in:
Tejaswini Chile
2023-06-19 16:10:03 +05:30
committed by GitHub
parent 2c3337b117
commit 9d0de04f7c
9 changed files with 127 additions and 24 deletions

View File

@@ -1,4 +1,5 @@
class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseController class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_custom_filters, except: [:create] before_action :fetch_custom_filters, except: [:create]
before_action :fetch_custom_filter, only: [:show, :update, :destroy] before_action :fetch_custom_filter, only: [:show, :update, :destroy]
DEFAULT_FILTER_TYPE = 'conversation'.freeze DEFAULT_FILTER_TYPE = 'conversation'.freeze

View File

@@ -0,0 +1,9 @@
class CustomFiltersRecordsCountUpdateJob < ApplicationJob
queue_as :low
def perform
CustomFilter.find_each(batch_size: 25) do |filter|
SyncCustomFilterCountJob.perform_later(filter)
end
end
end

View File

@@ -0,0 +1,9 @@
class SyncCustomFilterCountJob < ApplicationJob
queue_as :low
def perform(filter)
Redis::Alfred.set(filter.filter_count_key, 0) if filter.filter_records.nil?
filter.set_record_count_in_redis
end
end

View File

@@ -24,6 +24,29 @@ class CustomFilter < ApplicationRecord
enum filter_type: { conversation: 0, contact: 1, report: 2 } enum filter_type: { conversation: 0, contact: 1, report: 2 }
validate :validate_number_of_filters validate :validate_number_of_filters
def records_count
fetch_record_count_from_redis
end
def filter_records
Conversations::FilterService.new(query.with_indifferent_access, user, account).perform
end
def set_record_count_in_redis
records = filter_records
Redis::Alfred.set(filter_count_key, records[:count][:all_count])
end
def fetch_record_count_from_redis
number_of_records = Redis::Alfred.get(filter_count_key)
SyncCustomFilterCountJob.perform_later(self) if number_of_records.nil?
number_of_records.to_i
end
def filter_count_key
format(::Redis::Alfred::CUSTOM_FILTER_RECORDS_COUNT_KEY, account_id: account_id, filter_id: id, user_id: user_id)
end
def validate_number_of_filters def validate_number_of_filters
return true if account.custom_filters.where(user_id: user_id).size < MAX_FILTER_PER_USER return true if account.custom_filters.where(user_id: user_id).size < MAX_FILTER_PER_USER

View File

@@ -0,0 +1,21 @@
class CustomFilterPolicy < ApplicationPolicy
def create?
@account_user.administrator? || @account_user.agent?
end
def show?
@account_user.administrator? || @account_user.agent?
end
def index?
@account_user.administrator? || @account_user.agent?
end
def update?
@account_user.administrator? || @account_user.agent?
end
def destroy?
@account_user.administrator? || @account_user.agent?
end
end

View File

@@ -1,6 +1,11 @@
class Conversations::FilterService < FilterService class Conversations::FilterService < FilterService
ATTRIBUTE_MODEL = 'conversation_attribute'.freeze ATTRIBUTE_MODEL = 'conversation_attribute'.freeze
def initialize(params, user, filter_account = nil)
@filter_account = filter_account
super(params, user)
end
def perform def perform
@conversations = conversation_query_builder @conversations = conversation_query_builder
mine_count, unassigned_count, all_count, = set_count_for_all_conversations mine_count, unassigned_count, all_count, = set_count_for_all_conversations
@@ -49,7 +54,8 @@ class Conversations::FilterService < FilterService
end end
def base_relation def base_relation
Current.account.conversations.left_outer_joins(:labels) account = @filter_account || Current.account
account.conversations.left_outer_joins(:labels)
end end
def current_page def current_page

View File

@@ -4,3 +4,4 @@ json.filter_type resource.filter_type
json.query resource.query json.query resource.query
json.created_at resource.created_at json.created_at resource.created_at
json.updated_at resource.updated_at json.updated_at resource.updated_at
json.count resource.records_count

View File

@@ -31,4 +31,5 @@ module Redis::RedisKeys
LATEST_CHATWOOT_VERSION = 'LATEST_CHATWOOT_VERSION'.freeze LATEST_CHATWOOT_VERSION = 'LATEST_CHATWOOT_VERSION'.freeze
# Check if a message create with same source-id is in progress? # Check if a message create with same source-id is in progress?
MESSAGE_SOURCE_KEY = 'MESSAGE_SOURCE_KEY::%<id>s'.freeze MESSAGE_SOURCE_KEY = 'MESSAGE_SOURCE_KEY::%<id>s'.freeze
CUSTOM_FILTER_RECORDS_COUNT_KEY = 'CUSTOM_FILTER::%<account_id>d::%<user_id>d::%<filter_id>d'.freeze
end end

View File

@@ -2,6 +2,24 @@ require 'rails_helper'
RSpec.describe 'Custom Filters API', type: :request do RSpec.describe 'Custom Filters API', type: :request do
let(:account) { create(:account) } let(:account) { create(:account) }
let(:user) { create(:user, account: account, role: :agent) }
let!(:custom_filter) { create(:custom_filter, user: user, account: account) }
before do
create(:conversation, account: account, assignee: user, status: 'open')
create(:conversation, account: account, assignee: user, status: 'resolved')
custom_filter.query = { payload: [
{
values: ['open'],
attribute_key: 'status',
query_operator: nil,
attribute_model: 'standard',
filter_operator: 'equal_to',
custom_attribute_type: ''
}
] }
custom_filter.save
end
describe 'GET /api/v1/accounts/{account.id}/custom_filters' do describe 'GET /api/v1/accounts/{account.id}/custom_filters' do
context 'when it is an unauthenticated user' do context 'when it is an unauthenticated user' do
@@ -13,9 +31,6 @@ RSpec.describe 'Custom Filters API', type: :request do
end end
context 'when it is an authenticated user' do context 'when it is an authenticated user' do
let(:user) { create(:user, account: account) }
let!(:custom_filter) { create(:custom_filter, user: user, account: account) }
it 'returns all custom_filter related to the user' do it 'returns all custom_filter related to the user' do
get "/api/v1/accounts/#{account.id}/custom_filters", get "/api/v1/accounts/#{account.id}/custom_filters",
headers: user.create_new_auth_token, headers: user.create_new_auth_token,
@@ -26,13 +41,21 @@ RSpec.describe 'Custom Filters API', type: :request do
expect(response_body.first['name']).to eq(custom_filter.name) expect(response_body.first['name']).to eq(custom_filter.name)
expect(response_body.first['query']).to eq(custom_filter.query) expect(response_body.first['query']).to eq(custom_filter.query)
end end
it 'returns custom_filter conversations count when set in redis' do
get "/api/v1/accounts/#{account.id}/custom_filters",
headers: user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
response_body = response.parsed_body
expect(response_body.first['name']).to eq(custom_filter.name)
expect(response_body.first['count']).to eq(custom_filter.fetch_record_count_from_redis)
end
end end
end end
describe 'GET /api/v1/accounts/{account.id}/custom_filters/:id' do describe 'GET /api/v1/accounts/{account.id}/custom_filters/:id' do
let(:user) { create(:user, account: account) }
let!(:custom_filter) { create(:custom_filter, user: user, account: account) }
context 'when it is an unauthenticated user' do context 'when it is an unauthenticated user' do
it 'returns unauthorized' do it 'returns unauthorized' do
get "/api/v1/accounts/#{account.id}/custom_filters/#{custom_filter.id}" get "/api/v1/accounts/#{account.id}/custom_filters/#{custom_filter.id}"
@@ -43,18 +66,29 @@ RSpec.describe 'Custom Filters API', type: :request do
context 'when it is an authenticated user' do context 'when it is an authenticated user' do
it 'shows the custom filter' do it 'shows the custom filter' do
custom_filter.set_record_count_in_redis
get "/api/v1/accounts/#{account.id}/custom_filters/#{custom_filter.id}", get "/api/v1/accounts/#{account.id}/custom_filters/#{custom_filter.id}",
headers: user.create_new_auth_token, headers: user.create_new_auth_token,
as: :json as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(response.body).to include(custom_filter.name) expect(response.body).to include(custom_filter.name)
json_response = response.parsed_body
expect(json_response['count']).to eq 1
end end
end end
end end
describe 'POST /api/v1/accounts/{account.id}/custom_filters' do describe 'POST /api/v1/accounts/{account.id}/custom_filters' do
let(:payload) { { custom_filter: { name: 'vip-customers', filter_type: 'conversation', query: { labels: ['vip-customers'] } } } } let(:payload) do
{ custom_filter: {
name: 'vip-customers', filter_type: 'conversation',
query: { payload: [{
values: ['open'], attribute_key: 'status', attribute_model: 'standard', filter_operator: 'equal_to'
}] }
} }
end
context 'when it is an unauthenticated user' do context 'when it is an unauthenticated user' do
it 'returns unauthorized' do it 'returns unauthorized' do
@@ -65,22 +99,18 @@ RSpec.describe 'Custom Filters API', type: :request do
end end
context 'when it is an authenticated user' do context 'when it is an authenticated user' do
let(:user) { create(:user, account: account) }
let(:new_account) { create(:account) }
let(:new_user) { create(:user, account: new_account) }
it 'creates the filter' do it 'creates the filter' do
expect do post "/api/v1/accounts/#{account.id}/custom_filters", headers: user.create_new_auth_token,
post "/api/v1/accounts/#{account.id}/custom_filters", headers: user.create_new_auth_token, params: payload
params: payload
end.to change(CustomFilter, :count).by(1)
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
json_response = response.parsed_body json_response = response.parsed_body
expect(json_response['name']).to eq 'vip-customers' expect(json_response['name']).to eq 'vip-customers'
expect(json_response['count']).to be_zero
end end
it 'gives the error for 51st record' do it 'gives the error for 51st record' do
CustomFilter.delete_all
CustomFilter::MAX_FILTER_PER_USER.times do CustomFilter::MAX_FILTER_PER_USER.times do
create(:custom_filter, user: user, account: account) create(:custom_filter, user: user, account: account)
end end
@@ -100,9 +130,14 @@ RSpec.describe 'Custom Filters API', type: :request do
end end
describe 'PATCH /api/v1/accounts/{account.id}/custom_filters/:id' do describe 'PATCH /api/v1/accounts/{account.id}/custom_filters/:id' do
let(:payload) { { custom_filter: { name: 'vip-customers', filter_type: 'contact', query: { labels: ['vip-customers'] } } } } let(:payload) do
let(:user) { create(:user, account: account) } { custom_filter: {
let!(:custom_filter) { create(:custom_filter, user: user, account: account) } name: 'vip-customers', filter_type: 'conversation',
query: { payload: [{
values: ['resolved'], attribute_key: 'status', attribute_model: 'standard', filter_operator: 'equal_to'
}] }
} }
end
context 'when it is an unauthenticated user' do context 'when it is an unauthenticated user' do
it 'returns unauthorized' do it 'returns unauthorized' do
@@ -122,8 +157,8 @@ RSpec.describe 'Custom Filters API', type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(custom_filter.reload.name).to eq('vip-customers') expect(custom_filter.reload.name).to eq('vip-customers')
expect(custom_filter.reload.filter_type).to eq('contact') expect(custom_filter.reload.filter_type).to eq('conversation')
expect(custom_filter.reload.query['labels']).to eq(['vip-customers']) expect(custom_filter.reload.query['payload'][0]['values']).to eq(['resolved'])
end end
it 'prevents the update of custom filter of another user/account' do it 'prevents the update of custom filter of another user/account' do
@@ -142,9 +177,6 @@ RSpec.describe 'Custom Filters API', type: :request do
end end
describe 'DELETE /api/v1/accounts/{account.id}/custom_filters/:id' do describe 'DELETE /api/v1/accounts/{account.id}/custom_filters/:id' do
let(:user) { create(:user, account: account) }
let!(:custom_filter) { create(:custom_filter, user: user, account: account) }
context 'when it is an unauthenticated user' do context 'when it is an unauthenticated user' do
it 'returns unauthorized' do it 'returns unauthorized' do
delete "/api/v1/accounts/#{account.id}/custom_filters/#{custom_filter.id}" delete "/api/v1/accounts/#{account.id}/custom_filters/#{custom_filter.id}"