From 45d4d3660c646faf8b755e0e73c1022eae84fd69 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 15 Jul 2025 21:27:35 -0700 Subject: [PATCH 1/2] feat: Add private note action to automations (#11926) ## Summary - allow AutomationRule to accept `add_private_note` action - support `add_private_note` in automation action service - expose private note action in frontend constants and i18n - test new automation rule action ## Testing - `pnpm eslint app/javascript/dashboard/routes/dashboard/settings/automation/constants.js` - `bundle exec rubocop app/services/automation_rules/action_service.rb app/models/automation_rule.rb spec/services/automation_rules/action_service_spec.rb` - `bundle exec rspec spec/services/automation_rules/action_service_spec.rb` ------ https://chatgpt.com/codex/tasks/task_e_6870c5f7b8b88326a9bd60b2ba710ccd Co-authored-by: Muhsin Keloth --- .../dashboard/i18n/locale/en/automation.json | 1 + .../settings/automation/constants.js | 5 +++++ app/models/automation_rule.rb | 3 ++- .../automation_rules/action_service.rb | 7 ++++++ .../automation_rules/action_service_spec.rb | 22 +++++++++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/i18n/locale/en/automation.json b/app/javascript/dashboard/i18n/locale/en/automation.json index cb030332f..cf63de81c 100644 --- a/app/javascript/dashboard/i18n/locale/en/automation.json +++ b/app/javascript/dashboard/i18n/locale/en/automation.json @@ -146,6 +146,7 @@ "SEND_WEBHOOK_EVENT": "Send Webhook Event", "SEND_ATTACHMENT": "Send Attachment", "SEND_MESSAGE": "Send a Message", + "ADD_PRIVATE_NOTE": "Add a Private Note", "CHANGE_PRIORITY": "Change Priority", "ADD_SLA": "Add SLA", "OPEN_CONVERSATION": "Open conversation" diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js index 1b0f0599f..dfa6163d8 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js @@ -555,6 +555,11 @@ export const AUTOMATION_ACTION_TYPES = [ label: 'SEND_MESSAGE', inputType: 'textarea', }, + { + key: 'add_private_note', + label: 'ADD_PRIVATE_NOTE', + inputType: 'textarea', + }, { key: 'change_priority', label: 'CHANGE_PRIORITY', diff --git a/app/models/automation_rule.rb b/app/models/automation_rule.rb index c8f5a180d..6f3f47d9c 100644 --- a/app/models/automation_rule.rb +++ b/app/models/automation_rule.rb @@ -41,7 +41,8 @@ class AutomationRule < ApplicationRecord def actions_attributes %w[send_message add_label remove_label send_email_to_team assign_team assign_agent send_webhook_event mute_conversation - send_attachment change_status resolve_conversation open_conversation snooze_conversation change_priority send_email_transcript].freeze + send_attachment change_status resolve_conversation open_conversation snooze_conversation change_priority send_email_transcript + add_private_note].freeze end def file_base_data diff --git a/app/services/automation_rules/action_service.rb b/app/services/automation_rules/action_service.rb index 290e14a9e..e409faba2 100644 --- a/app/services/automation_rules/action_service.rb +++ b/app/services/automation_rules/action_service.rb @@ -47,6 +47,13 @@ class AutomationRules::ActionService < ActionService Messages::MessageBuilder.new(nil, @conversation, params).perform end + def add_private_note(message) + return if conversation_a_tweet? + + params = { content: message[0], private: true, content_attributes: { automation_rule_id: @rule.id } } + Messages::MessageBuilder.new(nil, @conversation.reload, params).perform + end + def send_email_to_team(params) teams = Team.where(id: params[0][:team_ids]) diff --git a/spec/services/automation_rules/action_service_spec.rb b/spec/services/automation_rules/action_service_spec.rb index bdfa7af0c..e63fd7545 100644 --- a/spec/services/automation_rules/action_service_spec.rb +++ b/spec/services/automation_rules/action_service_spec.rb @@ -117,5 +117,27 @@ RSpec.describe AutomationRules::ActionService do expect(mailer).to have_received(:conversation_transcript).exactly(1).times end end + + describe '#perform with add_private_note action' do + let(:message_builder) { double } + + before do + allow(Messages::MessageBuilder).to receive(:new).and_return(message_builder) + rule.actions.delete_if { |a| a['action_name'] == 'send_message' } + rule.actions << { action_name: 'add_private_note', action_params: ['Note'] } + end + + it 'will add private note' do + expect(message_builder).to receive(:perform) + described_class.new(rule, account, conversation).perform + end + + it 'will not add note if conversation is a tweet' do + twitter_inbox = create(:inbox, channel: create(:channel_twitter_profile, account: account)) + conversation = create(:conversation, inbox: twitter_inbox, additional_attributes: { type: 'tweet' }) + expect(message_builder).not_to receive(:perform) + described_class.new(rule, account, conversation).perform + end + end end end From 13b4fdb34ce13aba73483eb583e3849a546c81cb Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 15 Jul 2025 21:28:39 -0700 Subject: [PATCH 2/2] chore: Add submenu for super admin settings (#11860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Improve how settings are rendered in Chatwoot Super admin panel - Add google settings support - show setting for community edition ## Settings page - community edition Screenshot 2025-07-08 at 9 08 03 PM ## Expanded settings Screenshot 2025-07-03 at 2 17 16 AM --------- Co-authored-by: Sojan Jose Co-authored-by: Muhsin Keloth --- .../super_admin/app_configs_controller.rb | 3 +- .../super_admin/application_controller.rb | 3 +- .../helpers/super_admin/features.yml | 35 +++++++++++---- .../helpers/super_admin/features_helper.rb | 2 +- app/helpers/super_admin/navigation_helper.rb | 16 +++++++ .../super_admin/application/_icons.html.erb | 6 +++ .../application/_nav_item.html.erb | 4 +- .../application/_navigation.html.erb | 5 +-- .../application/_settings_menu.html.erb | 24 +++++++++++ .../_upgrade_button_community.html.erb | 4 ++ .../_upgrade_button_enterprise.html.erb | 4 ++ app/views/super_admin/settings/show.html.erb | 43 ++++++++++--------- config/installation_config.yml | 22 ++++++++++ lib/chatwoot_hub.rb | 4 ++ 14 files changed, 137 insertions(+), 38 deletions(-) rename {enterprise/app => app}/helpers/super_admin/features.yml (90%) rename {enterprise/app => app}/helpers/super_admin/features_helper.rb (78%) create mode 100644 app/helpers/super_admin/navigation_helper.rb create mode 100644 app/views/super_admin/application/_settings_menu.html.erb create mode 100644 app/views/super_admin/settings/_upgrade_button_community.html.erb create mode 100644 app/views/super_admin/settings/_upgrade_button_enterprise.html.erb diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index 3972d5a28..5cf158b98 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -41,7 +41,8 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController 'slack' => %w[SLACK_CLIENT_ID SLACK_CLIENT_SECRET], 'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT], 'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID WHATSAPP_API_VERSION], - 'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET] + 'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET], + 'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI] } @allowed_configs = mapping.fetch(@config, %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]) diff --git a/app/controllers/super_admin/application_controller.rb b/app/controllers/super_admin/application_controller.rb index 775fb34fc..5b04fbb44 100644 --- a/app/controllers/super_admin/application_controller.rb +++ b/app/controllers/super_admin/application_controller.rb @@ -7,8 +7,9 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController include ActionView::Helpers::TagHelper include ActionView::Context + include SuperAdmin::NavigationHelper - helper_method :render_vue_component + helper_method :render_vue_component, :settings_open?, :settings_pages # authenticiation done via devise : SuperAdmin Model before_action :authenticate_super_admin! diff --git a/enterprise/app/helpers/super_admin/features.yml b/app/helpers/super_admin/features.yml similarity index 90% rename from enterprise/app/helpers/super_admin/features.yml rename to app/helpers/super_admin/features.yml index f2b2b263c..f49004e79 100644 --- a/enterprise/app/helpers/super_admin/features.yml +++ b/app/helpers/super_admin/features.yml @@ -1,5 +1,7 @@ # TODO: Move this values to features.yml itself # No need to replicate the same values in two places + +# ------- Premium Features ------- # captain: name: 'Captain' description: 'Enable AI-powered conversations with your customers.' @@ -32,6 +34,15 @@ disable_branding: enabled: <%= (ChatwootHub.pricing_plan != 'community') %> icon: 'icon-sailbot-fill' enterprise: true + +# ------- Product Features ------- # +help_center: + name: 'Help Center' + description: 'Allow agents to create help center articles and publish them in a portal.' + enabled: true + icon: 'icon-book-2-line' + +# ------- Communication Channels ------- # live_chat: name: 'Live Chat' description: 'Improve your customer experience using a live chat on your website.' @@ -42,6 +53,12 @@ email: description: 'Manage your email customer interactions from Chatwoot.' enabled: true icon: 'icon-mail-send-fill' + config_key: 'email' +sms: + name: 'SMS' + description: 'Manage your SMS customer interactions from Chatwoot.' + enabled: true + icon: 'icon-message-line' messenger: name: 'Messenger' description: 'Stay connected with your customers on Facebook & Instagram.' @@ -69,22 +86,22 @@ line: description: 'Manage your Line customer interactions from Chatwoot.' enabled: true icon: 'icon-line-line' -sms: - name: 'SMS' - description: 'Manage your SMS customer interactions from Chatwoot.' + +# ------- OAuth & Authentication ------- # +google: + name: 'Google' + description: 'Configuration for setting up Google OAuth Integration' enabled: true - icon: 'icon-message-line' -help_center: - name: 'Help Center' - description: 'Allow agents to create help center articles and publish them in a portal.' - enabled: true - icon: 'icon-book-2-line' + icon: 'icon-google' + config_key: 'google' microsoft: name: 'Microsoft' description: 'Configuration for setting up Microsoft Email' enabled: true icon: 'icon-microsoft' config_key: 'microsoft' + +# ------- Third-party Integrations ------- # linear: name: 'Linear' description: 'Configuration for setting up Linear Integration' diff --git a/enterprise/app/helpers/super_admin/features_helper.rb b/app/helpers/super_admin/features_helper.rb similarity index 78% rename from enterprise/app/helpers/super_admin/features_helper.rb rename to app/helpers/super_admin/features_helper.rb index 2fbcd1715..475ad6d25 100644 --- a/enterprise/app/helpers/super_admin/features_helper.rb +++ b/app/helpers/super_admin/features_helper.rb @@ -1,6 +1,6 @@ module SuperAdmin::FeaturesHelper def self.available_features - YAML.load(ERB.new(Rails.root.join('enterprise/app/helpers/super_admin/features.yml').read).result).with_indifferent_access + YAML.load(ERB.new(Rails.root.join('app/helpers/super_admin/features.yml').read).result).with_indifferent_access end def self.plan_details diff --git a/app/helpers/super_admin/navigation_helper.rb b/app/helpers/super_admin/navigation_helper.rb new file mode 100644 index 000000000..5fca3fa76 --- /dev/null +++ b/app/helpers/super_admin/navigation_helper.rb @@ -0,0 +1,16 @@ +module SuperAdmin::NavigationHelper + def settings_open? + params[:controller].in? %w[super_admin/settings super_admin/app_configs] + end + + def settings_pages + features = SuperAdmin::FeaturesHelper.available_features.select do |_feature, attrs| + attrs['config_key'].present? && attrs['enabled'] + end + + # Add general at the beginning + general_feature = [['general', { 'config_key' => 'general', 'name' => 'General' }]] + + general_feature + features.to_a + end +end diff --git a/app/views/super_admin/application/_icons.html.erb b/app/views/super_admin/application/_icons.html.erb index 6669fe87d..a45c4c1b3 100644 --- a/app/views/super_admin/application/_icons.html.erb +++ b/app/views/super_admin/application/_icons.html.erb @@ -2,6 +2,12 @@ + + + + + + diff --git a/app/views/super_admin/application/_nav_item.html.erb b/app/views/super_admin/application/_nav_item.html.erb index a6366d8b5..a32fddd11 100644 --- a/app/views/super_admin/application/_nav_item.html.erb +++ b/app/views/super_admin/application/_nav_item.html.erb @@ -1,6 +1,4 @@ -
  • +
  • <% text_class_name = current_page?(url) ? 'text-woot-500 bg-slate-25' : 'text-slate-800' %> <%= link_to(url, class: text_class_name + " -ml-1 focus:outline-none cursor-pointer flex items-center px-2 py-1.5 text-slate-800 cursor-pointer hover:text-woot-500 hover:bg-slate-25 rounded-lg") do %> diff --git a/app/views/super_admin/application/_navigation.html.erb b/app/views/super_admin/application/_navigation.html.erb index f673c6bd1..787576d33 100644 --- a/app/views/super_admin/application/_navigation.html.erb +++ b/app/views/super_admin/application/_navigation.html.erb @@ -39,14 +39,13 @@ as defined by the routes in the `admin/` namespace label: display_resource_name(resource), } %> + <% end %> + <%= render 'settings_menu', open: settings_open? %>
      - <% if ChatwootApp.enterprise? %> - <%= render partial: "nav_item", locals: { icon: 'icon-settings-2-line', url: super_admin_settings_url, label: 'Settings' } %> - <% end %> <%= render partial: "nav_item", locals: { icon: 'icon-mist-fill', url: sidekiq_web_url, label: 'Sidekiq Dashboard' } %> <%= render partial: "nav_item", locals: { icon: 'icon-health-book-line', url: super_admin_instance_status_url, label: 'Instance Health' } %> <%= render partial: "nav_item", locals: { icon: 'icon-dashboard-line', url: '/', label: 'Agent Dashboard' } %> diff --git a/app/views/super_admin/application/_settings_menu.html.erb b/app/views/super_admin/application/_settings_menu.html.erb new file mode 100644 index 000000000..3d5173c12 --- /dev/null +++ b/app/views/super_admin/application/_settings_menu.html.erb @@ -0,0 +1,24 @@ +
    • +
      > + + <%= link_to super_admin_settings_url, class: 'flex items-center flex-1' do %> + + Settings + <% end %> + + + + +
        + <% settings_pages.each do |_feature_key, attrs| %> + <% url = super_admin_app_config_url(config: attrs['config_key']) %> +
      • + <% text_class = current_page?(url) ? 'text-woot-500 bg-slate-25' : 'text-slate-800' %> + <%= link_to url, class: text_class + ' -ml-1 flex items-center px-2 py-1.5 hover:text-woot-500 hover:bg-slate-25 rounded-lg' do %> + <%= attrs['name'] %> + <% end %> +
      • + <% end %> +
      +
      +
    • diff --git a/app/views/super_admin/settings/_upgrade_button_community.html.erb b/app/views/super_admin/settings/_upgrade_button_community.html.erb new file mode 100644 index 000000000..b80ee44c8 --- /dev/null +++ b/app/views/super_admin/settings/_upgrade_button_community.html.erb @@ -0,0 +1,4 @@ + + + Switch to Enterprise edition + diff --git a/app/views/super_admin/settings/_upgrade_button_enterprise.html.erb b/app/views/super_admin/settings/_upgrade_button_enterprise.html.erb new file mode 100644 index 000000000..4fbd96367 --- /dev/null +++ b/app/views/super_admin/settings/_upgrade_button_enterprise.html.erb @@ -0,0 +1,4 @@ + + + Upgrade now + diff --git a/app/views/super_admin/settings/show.html.erb b/app/views/super_admin/settings/show.html.erb index 4c15cbdab..fd424ed90 100644 --- a/app/views/super_admin/settings/show.html.erb +++ b/app/views/super_admin/settings/show.html.erb @@ -39,24 +39,26 @@
    -
    -
    -
    -

    Current plan

    - - - Refresh - + <% if ChatwootApp.enterprise? %> +
    +
    +
    +

    Current plan

    + + + Refresh + +
    +

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

    -

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

    + + +
    - - - -
    + <% end %> <% if ChatwootHub.pricing_plan != 'community' && User.count > ChatwootHub.pricing_plan_quantity %>
    @@ -99,10 +101,11 @@
    <% if !attrs[:enabled] %> <% end %>
    diff --git a/config/installation_config.yml b/config/installation_config.yml index b17d3cec0..2e8d94a96 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -87,11 +87,14 @@ # ------- Email Related Config ------- # - name: MAILER_INBOUND_EMAIL_DOMAIN + display_title: 'Inbound Email Domain' value: description: 'The domain name to be used for generating conversation continuity emails (reply+id@domain.com)' locked: false - name: MAILER_SUPPORT_EMAIL + display_title: 'Support Email' value: + description: 'The support email address for your installation' locked: false # ------- End of Email Related Config ------- # @@ -394,3 +397,22 @@ locked: false type: secret # ------- End of OG Image Related Config ------- # + +## ------ Configs added for Google OAuth ------ ## +- name: GOOGLE_OAUTH_CLIENT_ID + display_title: 'Google OAuth Client ID' + value: + locked: false + description: 'Google OAuth Client ID for email authentication' +- name: GOOGLE_OAUTH_CLIENT_SECRET + display_title: 'Google OAuth Client Secret' + value: + locked: false + description: 'Google OAuth Client Secret for email authentication' + type: secret +- name: GOOGLE_OAUTH_REDIRECT_URI + display_title: 'Google OAuth Redirect URI' + value: + locked: false + description: 'The redirect URI configured in your Google OAuth app' +## ------ End of Configs added for Google OAuth ------ ## diff --git a/lib/chatwoot_hub.rb b/lib/chatwoot_hub.rb index 0d99becd3..c18fb299b 100644 --- a/lib/chatwoot_hub.rb +++ b/lib/chatwoot_hub.rb @@ -19,10 +19,14 @@ class ChatwootHub end def self.pricing_plan + return 'community' unless ChatwootApp.enterprise? + InstallationConfig.find_by(name: 'INSTALLATION_PRICING_PLAN')&.value || 'community' end def self.pricing_plan_quantity + return 0 unless ChatwootApp.enterprise? + InstallationConfig.find_by(name: 'INSTALLATION_PRICING_PLAN_QUANTITY')&.value || 0 end