From 832a7fdae0790b4f5f9eafaa0d44142055853ad7 Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Sat, 15 Aug 2026 13:23:03 +0700 Subject: [PATCH] [verified] Remove Chatwoot Hub sync paths --- .../installation/onboarding_controller.rb | 9 +- .../super_admin/settings_controller.rb | 7 -- app/jobs/internal/check_new_versions_job.rb | 13 +-- .../trigger_daily_scheduled_items_job.rb | 20 +---- .../installation/onboarding/index.html.erb | 7 -- app/views/super_admin/settings/show.html.erb | 4 - config/routes.rb | 4 +- .../internal/check_new_versions_job.rb | 28 ------ lib/chatwoot_hub.rb | 90 ------------------- .../onboarding_controller_spec.rb | 13 +-- .../super_admin/settings_controller_spec.rb | 13 +++ .../internal/check_new_versions_job_spec.rb | 24 ++--- .../internal/check_new_versions_job_spec.rb | 11 ++- .../trigger_daily_scheduled_items_job_spec.rb | 21 +---- spec/lib/chatwoot_hub_spec.rb | 69 ++------------ 15 files changed, 43 insertions(+), 290 deletions(-) create mode 100644 spec/controllers/super_admin/settings_controller_spec.rb diff --git a/app/controllers/installation/onboarding_controller.rb b/app/controllers/installation/onboarding_controller.rb index 272b4e5eb..e9cad7581 100644 --- a/app/controllers/installation/onboarding_controller.rb +++ b/app/controllers/installation/onboarding_controller.rb @@ -23,18 +23,11 @@ class Installation::OnboardingController < ApplicationController private def onboarding_params - params.permit(:subscribe_to_updates, user: [:name, :company, :email]) + params.permit(user: [:name, :company, :email]) end def finish_onboarding ::Redis::Alfred.delete(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) - return if onboarding_params[:subscribe_to_updates].blank? - - ChatwootHub.register_instance( - onboarding_params.dig(:user, :company), - onboarding_params.dig(:user, :name), - onboarding_params.dig(:user, :email) - ) end def ensure_installation_onboarding diff --git a/app/controllers/super_admin/settings_controller.rb b/app/controllers/super_admin/settings_controller.rb index 685e6a8bd..18f560886 100644 --- a/app/controllers/super_admin/settings_controller.rb +++ b/app/controllers/super_admin/settings_controller.rb @@ -1,10 +1,3 @@ class SuperAdmin::SettingsController < SuperAdmin::ApplicationController def show; end - - def refresh - Internal::CheckNewVersionsJob.perform_now - # rubocop:disable Rails/I18nLocaleTexts - redirect_to super_admin_settings_path, notice: 'Instance status refreshed' - # rubocop:enable Rails/I18nLocaleTexts - end end diff --git a/app/jobs/internal/check_new_versions_job.rb b/app/jobs/internal/check_new_versions_job.rb index a141385d0..b0d190838 100644 --- a/app/jobs/internal/check_new_versions_job.rb +++ b/app/jobs/internal/check_new_versions_job.rb @@ -2,18 +2,7 @@ class Internal::CheckNewVersionsJob < ApplicationJob queue_as :scheduled_jobs def perform - return unless Rails.env.production? - - @instance_info = ChatwootHub.sync_with_hub - update_version_info - end - - private - - def update_version_info - return if @instance_info['version'].blank? - - ::Redis::Alfred.set(::Redis::Alfred::LATEST_CHATWOOT_VERSION, @instance_info['version']) + # Version checks are intentionally local-only in the private fork. end end diff --git a/app/jobs/internal/trigger_daily_scheduled_items_job.rb b/app/jobs/internal/trigger_daily_scheduled_items_job.rb index b09193e92..61816559b 100644 --- a/app/jobs/internal/trigger_daily_scheduled_items_job.rb +++ b/app/jobs/internal/trigger_daily_scheduled_items_job.rb @@ -2,25 +2,7 @@ class Internal::TriggerDailyScheduledItemsJob < ApplicationJob queue_as :scheduled_jobs def perform - # Schedule daily deferred jobs here so each installation can spread load - # across the day without changing its slot on deploys or restarts. - schedule_version_check - end - - private - - def schedule_version_check - return unless Rails.env.production? - - Internal::CheckNewVersionsJob.set(wait_until: version_check_run_at).perform_later - end - - def version_check_run_at - Time.current.utc.beginning_of_day + designated_minute.minutes - end - - def designated_minute - @designated_minute ||= Digest::MD5.hexdigest(ChatwootHub.installation_identifier).hex % 1440 + # Daily local scheduled work is extended by feature modules when present. end end diff --git a/app/views/installation/onboarding/index.html.erb b/app/views/installation/onboarding/index.html.erb index 8b337d16c..d9cfb3b01 100644 --- a/app/views/installation/onboarding/index.html.erb +++ b/app/views/installation/onboarding/index.html.erb @@ -59,13 +59,6 @@ -
- <%= check_box_tag "subscribe_to_updates", 'true', true %> - -
- diff --git a/app/views/super_admin/settings/show.html.erb b/app/views/super_admin/settings/show.html.erb index fd424ed90..809fb0ff0 100644 --- a/app/views/super_admin/settings/show.html.erb +++ b/app/views/super_admin/settings/show.html.erb @@ -44,10 +44,6 @@

Current plan

- - - Refresh -

<%= SuperAdmin::FeaturesHelper.plan_details.html_safe %>

diff --git a/config/routes.rb b/config/routes.rb index d0fc7da06..93986a984 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -730,9 +730,7 @@ Rails.application.routes.draw do resources :platform_banners resource :instance_status, only: [:show] - resource :settings, only: [:show] do - get :refresh, on: :collection - end + resource :settings, only: [:show] # resources that doesn't appear in primary navigation in super admin resources :account_users, only: [:new, :create, :show, :destroy] diff --git a/enterprise/app/jobs/enterprise/internal/check_new_versions_job.rb b/enterprise/app/jobs/enterprise/internal/check_new_versions_job.rb index b1f5d62ec..77e9ae5a9 100644 --- a/enterprise/app/jobs/enterprise/internal/check_new_versions_job.rb +++ b/enterprise/app/jobs/enterprise/internal/check_new_versions_job.rb @@ -1,30 +1,2 @@ module Enterprise::Internal::CheckNewVersionsJob - def perform - super - update_plan_info - reconcile_premium_config_and_features - end - - private - - def update_plan_info - return if @instance_info.blank? - - update_installation_config(key: 'INSTALLATION_PRICING_PLAN', value: @instance_info['plan']) - update_installation_config(key: 'INSTALLATION_PRICING_PLAN_QUANTITY', value: @instance_info['plan_quantity']) - update_installation_config(key: 'CHATWOOT_SUPPORT_WEBSITE_TOKEN', value: @instance_info['chatwoot_support_website_token']) - update_installation_config(key: 'CHATWOOT_SUPPORT_IDENTIFIER_HASH', value: @instance_info['chatwoot_support_identifier_hash']) - update_installation_config(key: 'CHATWOOT_SUPPORT_SCRIPT_URL', value: @instance_info['chatwoot_support_script_url']) - end - - def update_installation_config(key:, value:) - config = InstallationConfig.find_or_initialize_by(name: key) - config.value = value - config.locked = true - config.save! - end - - def reconcile_premium_config_and_features - Internal::ReconcilePlanConfigService.new.perform - end end diff --git a/lib/chatwoot_hub.rb b/lib/chatwoot_hub.rb index 95679ed73..0d94090fa 100644 --- a/lib/chatwoot_hub.rb +++ b/lib/chatwoot_hub.rb @@ -1,4 +1,3 @@ -# TODO: lets use HTTParty instead of RestClient class ChatwootHub DEFAULT_BASE_URL = 'https://hub.2.chatwoot.com'.freeze @@ -6,22 +5,6 @@ class ChatwootHub DEFAULT_BASE_URL end - def self.ping_url - "#{base_url}/ping" - end - - def self.registration_url - "#{base_url}/instances" - end - - def self.push_notification_url - "#{base_url}/send_push" - end - - def self.events_url - "#{base_url}/events" - end - def self.billing_base_url "#{base_url}/billing" end @@ -55,79 +38,6 @@ class ChatwootHub support_identifier_hash: InstallationConfig.find_by(name: 'CHATWOOT_SUPPORT_IDENTIFIER_HASH')&.value } end - - def self.instance_config - { - installation_identifier: installation_identifier, - installation_version: Chatwoot.config[:version], - installation_host: URI.parse(ENV.fetch('FRONTEND_URL', '')).host, - installation_env: ENV.fetch('INSTALLATION_ENV', ''), - edition: ENV.fetch('CW_EDITION', '') - } - end - - def self.instance_metrics - { - accounts_count: fetch_count(Account), - users_count: fetch_count(User), - inboxes_count: fetch_count(Inbox), - conversations_count: fetch_count(Conversation), - incoming_messages_count: fetch_count(Message.incoming), - outgoing_messages_count: fetch_count(Message.outgoing), - additional_information: {} - } - end - - def self.fetch_count(model) - model.last&.id || 0 - end - - def self.sync_with_hub - begin - info = instance_config - info = info.merge(instance_metrics) unless ENV['DISABLE_TELEMETRY'] - response = RestClient.post(ping_url, info.to_json, { content_type: :json, accept: :json }) - parsed_response = JSON.parse(response) - rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e - Rails.logger.error "Exception: #{e.message}" - rescue StandardError => e - ChatwootExceptionTracker.new(e).capture_exception - end - parsed_response - end - - def self.register_instance(company_name, owner_name, owner_email) - info = { company_name: company_name, owner_name: owner_name, owner_email: owner_email, subscribed_to_mailers: true } - RestClient.post(registration_url, info.merge(instance_config).to_json, { content_type: :json, accept: :json }) - rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e - Rails.logger.error "Exception: #{e.message}" - rescue StandardError => e - ChatwootExceptionTracker.new(e).capture_exception - end - - def self.send_push(fcm_options) - send_push_with_response(fcm_options) - rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e - Rails.logger.error "Exception: #{e.message}" - rescue StandardError => e - ChatwootExceptionTracker.new(e).capture_exception - end - - def self.send_push_with_response(fcm_options) - info = { fcm_options: fcm_options } - RestClient.post(push_notification_url, info.merge(instance_config).to_json, { content_type: :json, accept: :json }) - end - - def self.emit_event(event_name, event_data) - return if ENV['DISABLE_TELEMETRY'] - - info = { event_name: event_name, event_data: event_data } - RestClient.post(events_url, info.merge(instance_config).to_json, { content_type: :json, accept: :json }) - rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e - Rails.logger.error "Exception: #{e.message}" - rescue StandardError => e - ChatwootExceptionTracker.new(e).capture_exception - end end ChatwootHub.singleton_class.prepend_mod_with('ChatwootHub') diff --git a/spec/controllers/installation/onboarding_controller_spec.rb b/spec/controllers/installation/onboarding_controller_spec.rb index 37a2d8822..e1082d1cc 100644 --- a/spec/controllers/installation/onboarding_controller_spec.rb +++ b/spec/controllers/installation/onboarding_controller_spec.rb @@ -13,10 +13,11 @@ RSpec.describe 'Installation::Onboarding API', type: :request do end context 'when CHATWOOT_INSTALLATION_ONBOARDING redis key is set' do - it 'returns onboarding page' do + it 'returns onboarding page without a subscription checkbox' do Redis::Alfred.set(Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING, true) get '/installation/onboarding' expect(response).to have_http_status(:success) + expect(response.body).not_to include('subscribe_to_updates') Redis::Alfred.delete(Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) end end @@ -28,7 +29,6 @@ RSpec.describe 'Installation::Onboarding API', type: :request do before do allow(AccountBuilder).to receive(:new).and_return(account_builder) allow(account_builder).to receive(:perform).and_return(true) - allow(ChatwootHub).to receive(:register_instance).and_return(true) Redis::Alfred.set(Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING, true) end @@ -42,14 +42,9 @@ RSpec.describe 'Installation::Onboarding API', type: :request do expect(Redis::Alfred.get(Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)).to be_nil end - it 'will not call register instance when checkboxes are unchecked' do - post '/installation/onboarding', params: { user: {} } - expect(ChatwootHub).not_to have_received(:register_instance) - end - - it 'will call register instance when checkboxes are checked' do + it 'accepts the subscription parameter without remote registration' do post '/installation/onboarding', params: { user: {}, subscribe_to_updates: 1 } - expect(ChatwootHub).to have_received(:register_instance) + expect(response).to have_http_status(:redirect) end end diff --git a/spec/controllers/super_admin/settings_controller_spec.rb b/spec/controllers/super_admin/settings_controller_spec.rb new file mode 100644 index 000000000..178f16b93 --- /dev/null +++ b/spec/controllers/super_admin/settings_controller_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe SuperAdmin::SettingsController do + it 'does not expose a manual refresh action' do + expect(described_class.action_methods).not_to include('refresh') + end + + it 'does not leave a manual refresh link in the settings view' do + template = Rails.root.join('app/views/super_admin/settings/show.html.erb').read + + expect(template).not_to include('refresh_super_admin_settings_url') + end +end diff --git a/spec/enterprise/jobs/enterprise/internal/check_new_versions_job_spec.rb b/spec/enterprise/jobs/enterprise/internal/check_new_versions_job_spec.rb index 48664c904..66b8598a2 100644 --- a/spec/enterprise/jobs/enterprise/internal/check_new_versions_job_spec.rb +++ b/spec/enterprise/jobs/enterprise/internal/check_new_versions_job_spec.rb @@ -8,25 +8,19 @@ RSpec.describe Internal::CheckNewVersionsJob do before do allow(Internal::ReconcilePlanConfigService).to receive(:new).and_return(reconsile_premium_config_service) allow(reconsile_premium_config_service).to receive(:perform) - allow(Rails.env).to receive(:production?).and_return(true) end - it 'updates the plan info' do - data = { 'version' => '1.2.3', 'plan' => 'enterprise', 'plan_quantity' => 1, 'chatwoot_support_website_token' => '123', - 'chatwoot_support_identifier_hash' => '123', 'chatwoot_support_script_url' => '123' } - allow(ChatwootHub).to receive(:sync_with_hub).and_return(data) + it 'does not reconcile enterprise configuration from a remote response' do job - expect(InstallationConfig.find_by(name: 'INSTALLATION_PRICING_PLAN').value).to eq 'enterprise' - expect(InstallationConfig.find_by(name: 'INSTALLATION_PRICING_PLAN_QUANTITY').value).to eq 1 - expect(InstallationConfig.find_by(name: 'CHATWOOT_SUPPORT_WEBSITE_TOKEN').value).to eq '123' - expect(InstallationConfig.find_by(name: 'CHATWOOT_SUPPORT_IDENTIFIER_HASH').value).to eq '123' - expect(InstallationConfig.find_by(name: 'CHATWOOT_SUPPORT_SCRIPT_URL').value).to eq '123' + + expect(reconsile_premium_config_service).not_to have_received(:perform) end - it 'calls Internal::ReconcilePlanConfigService' do - data = { 'version' => '1.2.3' } - allow(ChatwootHub).to receive(:sync_with_hub).and_return(data) - job - expect(reconsile_premium_config_service).to have_received(:perform) + it 'does not update installation configuration' do + expect { job }.not_to( + change do + InstallationConfig.where(name: %w[INSTALLATION_PRICING_PLAN INSTALLATION_PRICING_PLAN_QUANTITY]).pluck(:name, :serialized_value) + end + ) end end diff --git a/spec/jobs/internal/check_new_versions_job_spec.rb b/spec/jobs/internal/check_new_versions_job_spec.rb index 82424e202..57418ccf1 100644 --- a/spec/jobs/internal/check_new_versions_job_spec.rb +++ b/spec/jobs/internal/check_new_versions_job_spec.rb @@ -3,12 +3,11 @@ require 'rails_helper' RSpec.describe Internal::CheckNewVersionsJob do subject(:job) { described_class.perform_now } - it 'updates the latest chatwoot version in redis' do - data = { 'version' => '1.2.3' } - allow(Rails.env).to receive(:production?).and_return(true) - allow(ChatwootHub).to receive(:sync_with_hub).and_return(data) + it 'does not write a remote version result' do + allow(Redis::Alfred).to receive(:set) + job - expect(ChatwootHub).to have_received(:sync_with_hub) - expect(Redis::Alfred.get(Redis::Alfred::LATEST_CHATWOOT_VERSION)).to eq data['version'] + + expect(Redis::Alfred).not_to have_received(:set) end end diff --git a/spec/jobs/internal/trigger_daily_scheduled_items_job_spec.rb b/spec/jobs/internal/trigger_daily_scheduled_items_job_spec.rb index 8b7a6da1c..0f21b4436 100644 --- a/spec/jobs/internal/trigger_daily_scheduled_items_job_spec.rb +++ b/spec/jobs/internal/trigger_daily_scheduled_items_job_spec.rb @@ -3,14 +3,8 @@ require 'rails_helper' RSpec.describe Internal::TriggerDailyScheduledItemsJob do subject(:perform_job) { described_class.perform_now } - let(:installation_id) { 'test-installation-id' } - let(:designated_minute) { Digest::MD5.hexdigest(installation_id).hex % 1440 } - let(:scheduled_time) { Time.current.utc.beginning_of_day + designated_minute.minutes } - let(:configured_job) { instance_double(ActiveJob::ConfiguredJob, perform_later: true) } - before do - allow(ChatwootHub).to receive(:installation_identifier).and_return(installation_id) - allow(Internal::CheckNewVersionsJob).to receive(:set).and_return(configured_job) + allow(Internal::CheckNewVersionsJob).to receive(:set) end it 'enqueues the job' do @@ -18,20 +12,9 @@ RSpec.describe Internal::TriggerDailyScheduledItemsJob do .on_queue('scheduled_jobs') end - it 'schedules the version check at a stable minute in production' do + it 'does not enqueue the remote version check in production' do allow(Rails.env).to receive(:production?).and_return(true) - travel_to Time.zone.parse('2026-03-17 08:00:00 UTC') do - perform_job - - expect(Internal::CheckNewVersionsJob).to have_received(:set).with(wait_until: scheduled_time) - expect(configured_job).to have_received(:perform_later) - end - end - - it 'does not schedule the version check outside production' do - allow(Rails.env).to receive(:production?).and_return(false) - perform_job expect(Internal::CheckNewVersionsJob).not_to have_received(:set) diff --git a/spec/lib/chatwoot_hub_spec.rb b/spec/lib/chatwoot_hub_spec.rb index a1051e619..237de5bba 100644 --- a/spec/lib/chatwoot_hub_spec.rb +++ b/spec/lib/chatwoot_hub_spec.rb @@ -1,6 +1,12 @@ require 'rails_helper' describe ChatwootHub do + it 'does not expose remote sync, registration, or event APIs' do + expect(described_class).not_to respond_to(:sync_with_hub) + expect(described_class).not_to respond_to(:register_instance) + expect(described_class).not_to respond_to(:emit_event) + end + describe '.base_url' do it 'uses the static hub url' do expect(described_class::DEFAULT_BASE_URL).to eq('https://hub.2.chatwoot.com') @@ -13,67 +19,4 @@ describe ChatwootHub do expect(installation_identifier).not_to be_nil expect(described_class.installation_identifier).to eq installation_identifier end - - context 'when fetching sync_with_hub' do - it 'get latest version from chatwoot hub' do - version = '1.1.1' - allow(RestClient).to receive(:post).and_return({ version: version }.to_json) - expect(described_class.sync_with_hub['version']).to eq version - expect(RestClient).to have_received(:post).with(described_class.ping_url, described_class.instance_config - .merge(described_class.instance_metrics).to_json, { content_type: :json, accept: :json }) - end - - it 'will not send instance metrics when telemetry is disabled' do - version = '1.1.1' - with_modified_env DISABLE_TELEMETRY: 'true' do - allow(RestClient).to receive(:post).and_return({ version: version }.to_json) - expect(described_class.sync_with_hub['version']).to eq version - expect(RestClient).to have_received(:post).with(described_class.ping_url, - described_class.instance_config.to_json, { content_type: :json, accept: :json }) - end - end - - it 'returns nil when chatwoot hub is down' do - allow(RestClient).to receive(:post).and_raise(ExceptionList::REST_CLIENT_EXCEPTIONS.sample) - expect(described_class.sync_with_hub).to be_nil - end - end - - context 'when register instance' do - let(:company_name) { 'test' } - let(:owner_name) { 'test' } - let(:owner_email) { 'test@test.com' } - - it 'sends info of registration' do - info = { company_name: company_name, owner_name: owner_name, owner_email: owner_email, subscribed_to_mailers: true } - allow(RestClient).to receive(:post) - described_class.register_instance(company_name, owner_name, owner_email) - expect(RestClient).to have_received(:post).with(described_class.registration_url, - info.merge(described_class.instance_config).to_json, { content_type: :json, accept: :json }) - end - end - - context 'when sending events' do - let(:event_name) { 'sample_event' } - let(:event_data) { { 'sample_data' => 'sample_data' } } - - it 'will send instance events' do - info = { event_name: event_name, event_data: event_data } - allow(RestClient).to receive(:post) - described_class.emit_event(event_name, event_data) - expect(RestClient).to have_received(:post).with(described_class.events_url, - info.merge(described_class.instance_config).to_json, { content_type: :json, accept: :json }) - end - - it 'will not send instance events when telemetry is disabled' do - with_modified_env DISABLE_TELEMETRY: 'true' do - info = { event_name: event_name, event_data: event_data } - allow(RestClient).to receive(:post) - described_class.emit_event(event_name, event_data) - expect(RestClient).not_to have_received(:post) - .with(described_class.events_url, - info.merge(described_class.instance_config).to_json, { content_type: :json, accept: :json }) - end - end - end end