chore: Use per-account feature flag for App Store channel

This commit is contained in:
Muhsin
2026-05-22 11:56:21 +04:00
parent 15227a424a
commit 361b28cb03
15 changed files with 47 additions and 51 deletions
@@ -31,7 +31,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end
def create
return render_app_store_feature_disabled if app_store_channel_requested? && !app_store_reviews_enabled?
return render_app_store_feature_disabled if app_store_channel_requested? && !Current.account.feature_enabled?(:channel_app_store)
ActiveRecord::Base.transaction do
channel = create_channel
@@ -110,10 +110,6 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
render json: { message: 'App Store Reviews channel is not enabled for this account' }, status: :forbidden
end
def app_store_reviews_enabled?
GlobalConfigService.load('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').to_s == 'true'
end
def update_inbox_working_hours
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
end
-1
View File
@@ -74,7 +74,6 @@ class DashboardController < ActionController::Base
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
INSTAGRAM_APP_ID: GlobalConfigService.load('INSTAGRAM_APP_ID', ''),
TIKTOK_APP_ID: GlobalConfigService.load('TIKTOK_APP_ID', ''),
ENABLE_APP_STORE_REVIEWS_CHANNEL: GlobalConfigService.load('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false'),
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v18.0'),
WHATSAPP_APP_ID: GlobalConfigService.load('WHATSAPP_APP_ID', ''),
WHATSAPP_CONFIGURATION_ID: GlobalConfigService.load('WHATSAPP_CONFIGURATION_ID', ''),
@@ -27,10 +27,6 @@ const hasTiktokConfigured = computed(() => {
return window.chatwootConfig?.tiktokAppId;
});
const isAppStoreReviewsEnabled = computed(() => {
return window.chatwootConfig?.enableAppStoreReviewsChannel === 'true';
});
const isActive = computed(() => {
const { key } = props.channel;
if (Object.keys(props.enabledFeatures).length === 0) {
@@ -57,7 +53,7 @@ const isActive = computed(() => {
}
if (key === 'app_store') {
return isAppStoreReviewsEnabled.value;
return props.enabledFeatures.channel_app_store;
}
if (key === 'voice') {
@@ -2,19 +2,12 @@ class Inboxes::FetchAppStoreReviewInboxesJob < ApplicationJob
queue_as :scheduled_jobs
def perform
return unless app_store_reviews_enabled?
Inbox.where(channel_type: 'Channel::AppStore').find_each(batch_size: 100) do |inbox|
next if inbox.account.suspended?
next unless inbox.account.feature_enabled?(:channel_app_store)
next unless inbox.channel.sync_due?
::Inboxes::FetchAppStoreReviewsJob.perform_later(inbox.channel)
end
end
private
def app_store_reviews_enabled?
GlobalConfigService.load('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').to_s == 'true'
end
end
@@ -2,7 +2,7 @@ class Inboxes::FetchAppStoreReviewsJob < ApplicationJob
queue_as :scheduled_jobs
def perform(channel)
return unless app_store_reviews_enabled?
return unless channel.account.feature_enabled?(:channel_app_store)
channel.fetch_reviews.each do |review_payload|
::AppStore::ReviewBuilder.new(review_payload: review_payload, channel: channel).perform
@@ -14,10 +14,4 @@ class Inboxes::FetchAppStoreReviewsJob < ApplicationJob
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: channel.account).capture_exception
end
private
def app_store_reviews_enabled?
GlobalConfigService.load('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').to_s == 'true'
end
end
@@ -21,7 +21,7 @@ class AppStore::SendOnAppStoreService < Base::SendOnChannelService
end
def validate_feature_enabled!
return if GlobalConfigService.load('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').to_s == 'true'
return if message.account.feature_enabled?(:channel_app_store)
raise 'App Store Reviews channel is not enabled for this account.'
end
+3 -3
View File
@@ -108,10 +108,10 @@
display_name: Custom Tools
enabled: false
premium: true
- name: message_reply_to
display_name: Message Reply To
- name: channel_app_store
display_name: App Store Reviews Channel
enabled: false
deprecated: true
chatwoot_internal: true
- name: insert_article_in_reply
display_name: Insert Article in Reply
enabled: false
-5
View File
@@ -277,11 +277,6 @@
display_title: 'Inactive WhatsApp Numbers'
description: 'Comma-separated list of WhatsApp numbers that should be rejected with a 422 error'
type: code
- name: ENABLE_APP_STORE_REVIEWS_CHANNEL
value: false
display_title: 'Enable App Store Reviews Channel'
description: 'Enable the App Store Reviews inbox channel'
type: boolean
# ------- End of Chatwoot Internal Config for Cloud ----#
# ------- Chatwoot Internal Config for Self Hosted ----#
@@ -0,0 +1,11 @@
class RepurposeMessageReplyToFlagForChannelAppStore < ActiveRecord::Migration[7.1]
def up
# The message_reply_to flag (deprecated) has been renamed to channel_app_store.
# Disable it on any accounts that had message_reply_to enabled so the repurposed
# flag starts in its intended default-off state.
Account.feature_channel_app_store.find_each(batch_size: 100) do |account|
account.disable_features(:channel_app_store)
account.save!(validate: false)
end
end
end
+1 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2026_05_20_090000) do
ActiveRecord::Schema[7.1].define(version: 2026_05_22_080000) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -435,9 +435,6 @@ RSpec.describe 'Inboxes API', type: :request do
end
it 'does not create an app store inbox when the feature is disabled' do
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('false')
post "/api/v1/accounts/#{account.id}/inboxes",
headers: admin.create_new_auth_token,
params: { name: 'App Store Reviews',
@@ -452,8 +449,7 @@ RSpec.describe 'Inboxes API', type: :request do
it 'creates an app store inbox when the feature is enabled' do
app_store_client = instance_double(AppStoreConnect::Client)
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('true')
account.enable_features!(:channel_app_store)
allow(AppStoreConnect::Client).to receive(:new).and_return(app_store_client)
allow(app_store_client).to receive(:fetch_app).and_return(
{
@@ -10,8 +10,8 @@ RSpec.describe Inboxes::FetchAppStoreReviewInboxesJob do
let(:suspended_channel) { create(:channel_app_store, account: suspended_account, last_synced_at: 2.hours.ago) }
before do
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('true')
account.enable_features!(:channel_app_store)
suspended_account.enable_features!(:channel_app_store)
end
it 'enqueues the job' do
@@ -32,7 +32,7 @@ RSpec.describe Inboxes::FetchAppStoreReviewInboxesJob do
end
it 'skips channels when the feature is disabled for the account' do
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('false')
account.disable_features!(:channel_app_store)
due_channel
expect(Inboxes::FetchAppStoreReviewsJob).not_to receive(:perform_later)
@@ -8,8 +8,7 @@ RSpec.describe Inboxes::FetchAppStoreReviewsJob do
let(:review_builder) { instance_double(AppStore::ReviewBuilder, perform: true) }
before do
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('true')
channel.account.enable_features!(:channel_app_store)
end
it 'enqueues the job' do
@@ -43,7 +42,7 @@ RSpec.describe Inboxes::FetchAppStoreReviewsJob do
end
it 'does not fetch reviews when the feature is disabled for the account' do
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('false')
channel.account.disable_features!(:channel_app_store)
expect(channel).not_to receive(:fetch_reviews)
+17
View File
@@ -50,6 +50,23 @@ RSpec.describe Account do
end
end
describe 'app store channel feature flag' do
let(:account) { create(:account) }
it 'stores app store channel feature state in feature flags' do
expect(account.feature_enabled?(:channel_app_store)).to be false
account.enable_features!(:channel_app_store)
expect(account.reload.feature_enabled?(:channel_app_store)).to be true
expect(account.enabled_features).to include('channel_app_store' => true)
account.disable_features!(:channel_app_store)
expect(account.reload.feature_enabled?(:channel_app_store)).to be false
end
end
describe 'conversation unread counts feature flag' do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
@@ -3,7 +3,8 @@
require 'rails_helper'
RSpec.describe AppStore::SendOnAppStoreService do
let(:channel) { create(:channel_app_store) }
let(:account) { create(:account) }
let(:channel) { create(:channel_app_store, account: account) }
let(:inbox) { channel.inbox }
let(:contact) { create(:contact, account: inbox.account) }
let(:contact_inbox) { create(:contact_inbox, inbox: inbox, contact: contact, source_id: 'review-1') }
@@ -12,8 +13,7 @@ RSpec.describe AppStore::SendOnAppStoreService do
let(:exception_tracker) { instance_double(ChatwootExceptionTracker, capture_exception: true) }
before do
allow(GlobalConfigService).to receive(:load).and_call_original
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('true')
account.enable_features!(:channel_app_store)
allow(Messages::StatusUpdateService).to receive(:new).and_return(status_update_service)
allow(ChatwootExceptionTracker).to receive(:new).and_return(exception_tracker)
end
@@ -58,7 +58,7 @@ RSpec.describe AppStore::SendOnAppStoreService do
end
it 'marks the message as failed when the feature is disabled' do
allow(GlobalConfigService).to receive(:load).with('ENABLE_APP_STORE_REVIEWS_CHANNEL', 'false').and_return('false')
account.disable_features!(:channel_app_store)
message = create(:message, message_type: :outgoing, inbox: inbox, conversation: conversation, account: inbox.account, content: 'Thanks')
described_class.new(message: message).perform