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