From 91d1d44aa933523f550ecd57738ce81b18965b4c Mon Sep 17 00:00:00 2001 From: amplitudes <62763456+amplitudesxd@users.noreply.github.com> Date: Mon, 29 May 2023 10:30:14 +0100 Subject: [PATCH 1/7] fix: profile picture distortion (#7203) By applying pr-1 on a container div instead of directly on the image (which distorts it), it creates the intended padding effect without any image distortion. --- app/views/public/api/v1/portals/articles/show.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/public/api/v1/portals/articles/show.html.erb b/app/views/public/api/v1/portals/articles/show.html.erb index 20f14e787..e3b560454 100644 --- a/app/views/public/api/v1/portals/articles/show.html.erb +++ b/app/views/public/api/v1/portals/articles/show.html.erb @@ -35,7 +35,9 @@
<% if @article.author&.avatar_url&.present? %> - <%= @article.author.display_name %> +
+ <%= @article.author.display_name %> +
<% end %>
<%= @article.author.available_name %>
From 3a7633b564b94be04c9359302f3aa8dd227feb8c Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 29 May 2023 21:57:24 +0530 Subject: [PATCH 2/7] feat: API to create HMAC verified conversations (#7209) Fixes: #6744 --- .../contacts/contact_inboxes_controller.rb | 4 +++- .../v1/accounts/conversations_controller.rb | 7 +++---- app/controllers/concerns/hmac_concern.rb | 5 +++++ .../contact_inboxes_controller_spec.rb | 19 ++++++++++++++++++- .../accounts/conversations_controller_spec.rb | 19 ++----------------- 5 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 app/controllers/concerns/hmac_concern.rb diff --git a/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb b/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb index b4287ae08..d985c8a73 100644 --- a/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb @@ -1,11 +1,13 @@ class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::Contacts::BaseController + include HmacConcern before_action :ensure_inbox, only: [:create] def create @contact_inbox = ContactInboxBuilder.new( contact: @contact, inbox: @inbox, - source_id: params[:source_id] + source_id: params[:source_id], + hmac_verified: hmac_verified? ).perform end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 1f3fbae0b..cd8547213 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -1,6 +1,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController include Events::Types include DateRangeHelper + include HmacConcern before_action :conversation, except: [:index, :meta, :search, :create, :filter] before_action :inbox, :contact, :contact_inbox, only: [:create] @@ -104,9 +105,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def set_conversation_status - # TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases - # commenting this out to see if there are any errors, if not we can remove this in subsequent releases - # status = params[:status] == 'bot' ? 'pending' : params[:status] @conversation.status = params[:status] @conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until] end @@ -152,7 +150,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro ContactInboxBuilder.new( contact: @contact, inbox: @inbox, - source_id: params[:source_id] + source_id: params[:source_id], + hmac_verified: hmac_verified? ).perform end diff --git a/app/controllers/concerns/hmac_concern.rb b/app/controllers/concerns/hmac_concern.rb new file mode 100644 index 000000000..abc55a394 --- /dev/null +++ b/app/controllers/concerns/hmac_concern.rb @@ -0,0 +1,5 @@ +module HmacConcern + def hmac_verified? + ActiveModel::Type::Boolean.new.cast(params[:hmac_verified]).present? + end +end diff --git a/spec/controllers/api/v1/accounts/contacts/contact_inboxes_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts/contact_inboxes_controller_spec.rb index 2bd393a5a..659d41f4a 100644 --- a/spec/controllers/api/v1/accounts/contacts/contact_inboxes_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/contacts/contact_inboxes_controller_spec.rb @@ -27,7 +27,9 @@ RSpec.describe '/api/v1/accounts/{account.id}/contacts/:id/contact_inboxes', typ end.to change(ContactInbox, :count).by(1) expect(response).to have_http_status(:success) - expect(contact.reload.contact_inboxes.map(&:inbox_id)).to include(channel_api.inbox.id) + contact_inbox = contact.reload.contact_inboxes.find_by(inbox_id: channel_api.inbox.id) + expect(contact_inbox).to be_present + expect(contact_inbox.hmac_verified).to be(false) end it 'creates a valid email contact inbox' do @@ -43,6 +45,21 @@ RSpec.describe '/api/v1/accounts/{account.id}/contacts/:id/contact_inboxes', typ expect(contact.reload.contact_inboxes.map(&:inbox_id)).to include(channel_email.inbox.id) end + it 'creates an hmac verified contact inbox' do + create(:inbox_member, inbox: channel_api.inbox, user: agent) + expect do + post "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/contact_inboxes", + params: { inbox_id: channel_api.inbox.id, hmac_verified: true }, + headers: agent.create_new_auth_token, + as: :json + end.to change(ContactInbox, :count).by(1) + + expect(response).to have_http_status(:success) + contact_inbox = contact.reload.contact_inboxes.find_by(inbox_id: channel_api.inbox.id) + expect(contact_inbox).to be_present + expect(contact_inbox.hmac_verified).to be(true) + end + it 'throws error for invalid source id' do create(:inbox_member, inbox: channel_twilio_sms.inbox, user: agent) expect do diff --git a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb index 496ce5cdb..f98dfba65 100644 --- a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb @@ -273,21 +273,6 @@ RSpec.describe 'Conversations API', type: :request do expect(response_data[:status]).to eq('pending') end - # TODO: remove this spec when we remove the condition check in controller - # Added for backwards compatibility for bot status - # remove this in subsequent release - # it 'creates a conversation as pending if status is specified as bot' do - # allow(Rails.configuration.dispatcher).to receive(:dispatch) - # post "/api/v1/accounts/#{account.id}/conversations", - # headers: agent.create_new_auth_token, - # params: { source_id: contact_inbox.source_id, status: 'bot' }, - # as: :json - - # expect(response).to have_http_status(:success) - # response_data = JSON.parse(response.body, symbolize_names: true) - # expect(response_data[:status]).to eq('pending') - # end - it 'creates a new conversation with message when message is passed' do allow(Rails.configuration.dispatcher).to receive(:dispatch) post "/api/v1/accounts/#{account.id}/conversations", @@ -304,13 +289,13 @@ RSpec.describe 'Conversations API', type: :request do it 'calls contact inbox builder if contact_id and inbox_id is present' do builder = double allow(Rails.configuration.dispatcher).to receive(:dispatch) - allow(ContactInboxBuilder).to receive(:new).and_return(builder) + allow(ContactInboxBuilder).to receive(:new).with(contact: contact, inbox: inbox, source_id: nil, hmac_verified: false).and_return(builder) allow(builder).to receive(:perform) expect(builder).to receive(:perform) post "/api/v1/accounts/#{account.id}/conversations", headers: agent.create_new_auth_token, - params: { contact_id: contact.id, inbox_id: inbox.id }, + params: { contact_id: contact.id, inbox_id: inbox.id, hmac_verified: 'false' }, as: :json end From ffc63646906b082f60d984697dbd9958c9f613da Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 29 May 2023 22:23:22 +0530 Subject: [PATCH 3/7] chore: Improve Whatsapp Templates Sync (#7210) - update the templates updated at, even if the API request fails ( to prevent jobs from stacking up in case of API failures upstream ) - sequence the job in batches of 25 requests per minutes schedule ( in case API response time is high, also not to send too many requests in a single batch ) - move the sync job re-rerun to 3 hours ( since we are updating the updated at even in case of failures )(prev 15 minutes ) Fixes: https://linear.app/chatwoot/issue/CW-1590 --- .../whatsapp/templates_sync_scheduler_job.rb | 6 ++---- .../providers/whatsapp_360_dialog_service.rb | 3 ++- .../providers/whatsapp_cloud_service.rb | 4 ++-- lib/limits.rb | 1 + .../templates_sync_scheduler_job_spec.rb | 2 +- .../whatsapp360_dialog_service_spec.rb | 21 +++++++++++++++++++ .../whatsapp_360_dialog_service_spec.rb | 1 - .../providers/whatsapp_cloud_service_spec.rb | 11 ++++++++++ 8 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb delete mode 100644 spec/services/whatsapp/providers/whatsapp_360_dialog_service_spec.rb diff --git a/app/jobs/channels/whatsapp/templates_sync_scheduler_job.rb b/app/jobs/channels/whatsapp/templates_sync_scheduler_job.rb index 1b11c32c4..ade3eb082 100644 --- a/app/jobs/channels/whatsapp/templates_sync_scheduler_job.rb +++ b/app/jobs/channels/whatsapp/templates_sync_scheduler_job.rb @@ -3,10 +3,8 @@ class Channels::Whatsapp::TemplatesSyncSchedulerJob < ApplicationJob def perform Channel::Whatsapp.where('message_templates_last_updated <= ? OR message_templates_last_updated IS NULL', - 15.minutes.ago).find_in_batches do |channels_batch| - channels_batch.each do |channel| - Channels::Whatsapp::TemplatesSyncJob.perform_later(channel) - end + 3.hours.ago).limit(Limits::BULK_EXTERNAL_HTTP_CALLS_LIMIT).all.each do |channel| + Channels::Whatsapp::TemplatesSyncJob.perform_later(channel) end end end diff --git a/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb b/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb index c67e448bb..94f4dc370 100644 --- a/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb +++ b/app/services/whatsapp/providers/whatsapp_360_dialog_service.rb @@ -23,7 +23,8 @@ class Whatsapp::Providers::Whatsapp360DialogService < Whatsapp::Providers::BaseS def sync_templates response = HTTParty.get("#{api_base_path}/configs/templates", headers: api_headers) - whatsapp_channel.update(message_templates: response['waba_templates'], message_templates_last_updated: Time.now.utc) if response.success? + whatsapp_channel[:message_templates] = response['waba_templates'] if response.success? + whatsapp_channel.update(message_templates_last_updated: Time.now.utc) end def validate_provider_config? diff --git a/app/services/whatsapp/providers/whatsapp_cloud_service.rb b/app/services/whatsapp/providers/whatsapp_cloud_service.rb index 9b6c793aa..fc8f729a5 100644 --- a/app/services/whatsapp/providers/whatsapp_cloud_service.rb +++ b/app/services/whatsapp/providers/whatsapp_cloud_service.rb @@ -24,8 +24,8 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi def sync_templates templates = fetch_whatsapp_templates("#{business_account_path}/message_templates?access_token=#{whatsapp_channel.provider_config['api_key']}") - - whatsapp_channel.update(message_templates: templates, message_templates_last_updated: Time.now.utc) if templates.present? + whatsapp_channel[:message_templates] = templates if templates.present? + whatsapp_channel.update(message_templates_last_updated: Time.now.utc) end def fetch_whatsapp_templates(url) diff --git a/lib/limits.rb b/lib/limits.rb index 1e2d36447..fe526748a 100644 --- a/lib/limits.rb +++ b/lib/limits.rb @@ -1,3 +1,4 @@ module Limits BULK_ACTIONS_LIMIT = 100 + BULK_EXTERNAL_HTTP_CALLS_LIMIT = 25 end diff --git a/spec/jobs/channels/whatsapp/templates_sync_scheduler_job_spec.rb b/spec/jobs/channels/whatsapp/templates_sync_scheduler_job_spec.rb index 1b1050b5d..e6545bfe3 100644 --- a/spec/jobs/channels/whatsapp/templates_sync_scheduler_job_spec.rb +++ b/spec/jobs/channels/whatsapp/templates_sync_scheduler_job_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Channels::Whatsapp::TemplatesSyncSchedulerJob do stub_request(:post, 'https://waba.360dialog.io/v1/configs/webhook') non_synced = create(:channel_whatsapp, sync_templates: false, message_templates_last_updated: nil) synced_recently = create(:channel_whatsapp, sync_templates: false, message_templates_last_updated: Time.zone.now) - synced_old = create(:channel_whatsapp, sync_templates: false, message_templates_last_updated: 16.minutes.ago) + synced_old = create(:channel_whatsapp, sync_templates: false, message_templates_last_updated: 4.hours.ago) described_class.perform_now expect(Channels::Whatsapp::TemplatesSyncJob).not_to( have_been_enqueued.with(synced_recently).on_queue('low') diff --git a/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb b/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb new file mode 100644 index 000000000..477674497 --- /dev/null +++ b/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb @@ -0,0 +1,21 @@ +## the specs are covered in send in spec/services/whatsapp/send_on_whatsapp_service_spec.rb +require 'rails_helper' + +describe Whatsapp::Providers::Whatsapp360DialogService do + subject(:service) { described_class.new(whatsapp_channel: whatsapp_channel) } + + let!(:whatsapp_channel) { create(:channel_whatsapp, sync_templates: false, validate_provider_config: false) } + + describe '#sync_templates' do + context 'when called' do + it 'updates message_templates_last_updated even when template request fails' do + stub_request(:get, 'https://waba.360dialog.io/v1/configs/templates') + .to_return(status: 401) + + timstamp = whatsapp_channel.reload.message_templates_last_updated + subject.sync_templates + expect(whatsapp_channel.reload.message_templates_last_updated).not_to eq(timstamp) + end + end + end +end diff --git a/spec/services/whatsapp/providers/whatsapp_360_dialog_service_spec.rb b/spec/services/whatsapp/providers/whatsapp_360_dialog_service_spec.rb deleted file mode 100644 index b36a9de56..000000000 --- a/spec/services/whatsapp/providers/whatsapp_360_dialog_service_spec.rb +++ /dev/null @@ -1 +0,0 @@ -## the specs are covered in send in spec/services/whatsapp/send_on_whatsapp_service_spec.rb diff --git a/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb b/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb index 3fbea97e9..32cd654ee 100644 --- a/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb +++ b/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb @@ -127,10 +127,21 @@ describe Whatsapp::Providers::WhatsappCloudService do ], paging: { prev: 'https://graph.facebook.com/v14.0/123456789/message_templates?access_token=test_key' } }.to_json } ) + timstamp = whatsapp_channel.reload.message_templates_last_updated expect(subject.sync_templates).to be(true) expect(whatsapp_channel.reload.message_templates.first).to eq({ id: '123456789', name: 'test_template' }.stringify_keys) expect(whatsapp_channel.reload.message_templates.second).to eq({ id: '123456789', name: 'next_template' }.stringify_keys) expect(whatsapp_channel.reload.message_templates.last).to eq({ id: '123456789', name: 'last_template' }.stringify_keys) + expect(whatsapp_channel.reload.message_templates_last_updated).not_to eq(timstamp) + end + + it 'updates message_templates_last_updated even when template request fails' do + stub_request(:get, 'https://graph.facebook.com/v14.0/123456789/message_templates?access_token=test_key') + .to_return(status: 401) + + timstamp = whatsapp_channel.reload.message_templates_last_updated + subject.sync_templates + expect(whatsapp_channel.reload.message_templates_last_updated).not_to eq(timstamp) end end end From f1a77ba934bb91f7012461ee1aa3b69a57e6b7e9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 29 May 2023 23:05:17 +0530 Subject: [PATCH 4/7] feat: allow superadmins to reset cache keys for IndexedDB (#7180) Allows super admins to reset the cache for an account. This will force the front end to fetch the data again on the next load. fixes: https://linear.app/chatwoot/issue/CW-1817 Co-authored-by: Sojan --- .../super_admin/accounts_controller.rb | 7 ++ app/models/concerns/cache_keys.rb | 30 +++++--- .../accounts/_reset_cache.html.erb | 9 +++ app/views/super_admin/accounts/show.html.erb | 2 + config/routes.rb | 1 + .../super_admin/accounts_controller_spec.rb | 28 ++++++- spec/models/concerns/cache_keys_spec.rb | 73 +++++++++++++++++++ 7 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 app/views/super_admin/accounts/_reset_cache.html.erb create mode 100644 spec/models/concerns/cache_keys_spec.rb diff --git a/app/controllers/super_admin/accounts_controller.rb b/app/controllers/super_admin/accounts_controller.rb index 6a5fb0fdc..5de25f677 100644 --- a/app/controllers/super_admin/accounts_controller.rb +++ b/app/controllers/super_admin/accounts_controller.rb @@ -50,6 +50,13 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController # rubocop:enable Rails/I18nLocaleTexts end + def reset_cache + requested_resource.reset_cache_keys + # rubocop:disable Rails/I18nLocaleTexts + redirect_back(fallback_location: [namespace, requested_resource], notice: 'Cache keys cleared') + # rubocop:enable Rails/I18nLocaleTexts + end + def destroy account = Account.find(params[:id]) diff --git a/app/models/concerns/cache_keys.rb b/app/models/concerns/cache_keys.rb index 4c27e6bbb..7a01f0925 100644 --- a/app/models/concerns/cache_keys.rb +++ b/app/models/concerns/cache_keys.rb @@ -4,29 +4,41 @@ module CacheKeys include CacheKeysHelper include Events::Types + included do + class_attribute :cacheable_models + self.cacheable_models = [Label, Inbox, Team] + end + def cache_keys - { - label: fetch_value_for_key(id, Label.name.underscore), - inbox: fetch_value_for_key(id, Inbox.name.underscore), - team: fetch_value_for_key(id, Team.name.underscore) - } + keys = {} + self.class.cacheable_models.each do |model| + keys[model.name.underscore.to_sym] = fetch_value_for_key(id, model.name.underscore) + end + + keys end def invalidate_cache_key_for(key) prefixed_cache_key = get_prefixed_cache_key(id, key) - Redis::Alfred.del(prefixed_cache_key) - dispatch_cache_udpate_event + Redis::Alfred.delete(prefixed_cache_key) + dispatch_cache_update_event end def update_cache_key(key) prefixed_cache_key = get_prefixed_cache_key(id, key) Redis::Alfred.set(prefixed_cache_key, Time.now.utc.to_i) - dispatch_cache_udpate_event + dispatch_cache_update_event + end + + def reset_cache_keys + self.class.cacheable_models.each do |model| + invalidate_cache_key_for(model.name.underscore) + end end private - def dispatch_cache_udpate_event + def dispatch_cache_update_event Rails.configuration.dispatcher.dispatch(ACCOUNT_CACHE_INVALIDATED, Time.zone.now, cache_keys: cache_keys, account: self) end end diff --git a/app/views/super_admin/accounts/_reset_cache.html.erb b/app/views/super_admin/accounts/_reset_cache.html.erb new file mode 100644 index 000000000..7710693b7 --- /dev/null +++ b/app/views/super_admin/accounts/_reset_cache.html.erb @@ -0,0 +1,9 @@ +
+
+ <%= form_for([:reset_cache, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %> +
+

This will clear the IndexedDB cache keys from redis.
The next load will fetch the data from backend.

+ <%= f.submit 'Reset Frontend Cache' %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/super_admin/accounts/show.html.erb b/app/views/super_admin/accounts/show.html.erb index 995af9ff0..65cb6c567 100644 --- a/app/views/super_admin/accounts/show.html.erb +++ b/app/views/super_admin/accounts/show.html.erb @@ -87,3 +87,5 @@ as well as a link to its edit page. <%= render partial: "seed_data", locals: {page: page} %> + +<%= render partial: "reset_cache", locals: {page: page} %> diff --git a/config/routes.rb b/config/routes.rb index 650f8a64c..fe5196fde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -397,6 +397,7 @@ Rails.application.routes.draw do # order of resources affect the order of sidebar navigation in super admin resources :accounts, only: [:index, :new, :create, :show, :edit, :update, :destroy] do post :seed, on: :member + post :reset_cache, on: :member end resources :users, only: [:index, :new, :create, :show, :edit, :update, :destroy] resources :access_tokens, only: [:index, :show] diff --git a/spec/controllers/super_admin/accounts_controller_spec.rb b/spec/controllers/super_admin/accounts_controller_spec.rb index 23235888a..ee295472d 100644 --- a/spec/controllers/super_admin/accounts_controller_spec.rb +++ b/spec/controllers/super_admin/accounts_controller_spec.rb @@ -15,8 +15,6 @@ RSpec.describe 'Super Admin accounts API', type: :request do end context 'when it is an authenticated user' do - let!(:account) { create(:account) } - it 'shows the list of accounts' do sign_in(super_admin, scope: :super_admin) get '/super_admin/accounts' @@ -27,6 +25,32 @@ RSpec.describe 'Super Admin accounts API', type: :request do end end + describe 'POST /super_admin/accounts/{account_id}/reset_cache' do + before do + create(:label, account: account) + create(:inbox, account: account) + create(:team, account: account) + end + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + post "/super_admin/accounts/#{account.id}/reset_cache" + expect(response).to have_http_status(:redirect) + end + end + + context 'when it is an authenticated user' do + it 'shows the list of accounts' do + expect(account.cache_keys.keys).to contain_exactly(:inbox, :label, :team) + sign_in(super_admin, scope: :super_admin) + post "/super_admin/accounts/#{account.id}/reset_cache" + expect(response).to have_http_status(:redirect) + expect(flash[:notice]).to eq('Cache keys cleared') + expect(account.reload.cache_keys.values.map(&:to_i)).to eq([0, 0, 0]) + end + end + end + describe 'DELETE /super_admin/accounts/{account_id}' do context 'when it is an unauthenticated user' do it 'returns unauthorized' do diff --git a/spec/models/concerns/cache_keys_spec.rb b/spec/models/concerns/cache_keys_spec.rb new file mode 100644 index 000000000..51d3c8b7a --- /dev/null +++ b/spec/models/concerns/cache_keys_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' + +RSpec.describe CacheKeys do + let(:test_model) do + Struct.new(:id) do + include CacheKeys + + def fetch_value_for_key(_id, _key) + 'value' + end + end.new(1) + end + + before do + allow(Redis::Alfred).to receive(:delete) + allow(Redis::Alfred).to receive(:set) + allow(Rails.configuration.dispatcher).to receive(:dispatch) + end + + describe '#cache_keys' do + it 'returns a hash of cache keys' do + expected_keys = test_model.class.cacheable_models.map do |model| + [model.name.underscore.to_sym, 'value'] + end.to_h + + expect(test_model.cache_keys).to eq(expected_keys) + end + end + + describe '#invalidate_cache_key_for' do + it 'deletes the cache key' do + test_model.invalidate_cache_key_for('label') + expect(Redis::Alfred).to have_received(:delete).with('idb-cache-key-account-1-label') + end + + it 'dispatches a cache update event' do + test_model.invalidate_cache_key_for('label') + expect(Rails.configuration.dispatcher).to have_received(:dispatch).with( + CacheKeys::ACCOUNT_CACHE_INVALIDATED, + kind_of(ActiveSupport::TimeWithZone), + cache_keys: test_model.cache_keys, + account: test_model + ) + end + end + + describe '#update_cache_key' do + it 'updates the cache key' do + allow(Time).to receive(:now).and_return(Time.parse('2023-05-29 00:00:00 UTC')) + test_model.update_cache_key('label') + expect(Redis::Alfred).to have_received(:set).with('idb-cache-key-account-1-label', Time.now.utc.to_i) + end + + it 'dispatches a cache update event' do + test_model.update_cache_key('label') + expect(Rails.configuration.dispatcher).to have_received(:dispatch).with( + CacheKeys::ACCOUNT_CACHE_INVALIDATED, + kind_of(ActiveSupport::TimeWithZone), + cache_keys: test_model.cache_keys, + account: test_model + ) + end + end + + describe '#reset_cache_keys' do + it 'invalidates all cache keys for cacheable models' do + test_model.reset_cache_keys + test_model.class.cacheable_models.each do |model| + expect(Redis::Alfred).to have_received(:delete).with("idb-cache-key-account-1-#{model.name.underscore}") + end + end + end +end From 412d750b6a2dfbccbfffe18e25434bfafeb3f362 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Tue, 30 May 2023 14:41:29 +0530 Subject: [PATCH 5/7] feat: auditlogs design refactor cw1764 (#7181) * chore: refactor auditlogs design * chore: refactor aduit log text * chore: fix 60% width for activity column * chore: improve log text formatting * Apply suggestions from code review Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> * feat: show agent names if available in auditlogs * chore: add sign_out * Apply suggestions from code review Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> * chore: handle custom user actions --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Muhsin Keloth Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> --- .../dashboard/i18n/locale/en/auditLogs.json | 15 +++- .../dashboard/settings/auditlogs/Index.vue | 83 +++++++++++++++---- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/app/javascript/dashboard/i18n/locale/en/auditLogs.json b/app/javascript/dashboard/i18n/locale/en/auditLogs.json index e288e2959..a2bb40135 100644 --- a/app/javascript/dashboard/i18n/locale/en/auditLogs.json +++ b/app/javascript/dashboard/i18n/locale/en/auditLogs.json @@ -10,15 +10,22 @@ "TITLE": "Manage Audit Logs", "DESC": "Audit Logs are trails for events and actions in a Chatwoot System.", "TABLE_HEADER": [ - "User", - "Action", - "IP Address", - "Time" + "Activity", + "Time", + "IP Address" ] }, "API": { "SUCCESS_MESSAGE": "AuditLogs retrieved successfully", "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + }, + "ACTION": { + "ADD": "created", + "EDIT": "updated", + "DELETE": "deleted", + "SIGN_IN": "signed in", + "SIGN_OUT": "signed out", + "SYSTEM": "System" } } } diff --git a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue index 63856b6d8..f11bbb6f4 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/Index.vue @@ -1,8 +1,8 @@