diff --git a/.circleci/config.yml b/.circleci/config.yml index f764cb611..59702c139 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -144,7 +144,7 @@ jobs: # Backend tests with parallelization backend-tests: <<: *defaults - parallelism: 20 + parallelism: 18 steps: - checkout - node/install: diff --git a/app/controllers/concerns/portal_home_data.rb b/app/controllers/concerns/portal_home_data.rb new file mode 100644 index 000000000..633071301 --- /dev/null +++ b/app/controllers/concerns/portal_home_data.rb @@ -0,0 +1,29 @@ +module PortalHomeData + extend ActiveSupport::Concern + + private + + def load_home_data + base_articles = @portal.articles.published.where(locale: @locale).includes(:author, :category) + @visible_categories = @portal.categories + .where(locale: @locale) + .joins(:articles).where(articles: { status: :published }) + .order(position: :asc) + .group('categories.id') + @popular_topics = @visible_categories.first(3) + @featured = base_articles.order_by_views.limit(6) + @category_contributors = build_category_contributors(@visible_categories) + end + + def build_category_contributors(categories) + category_ids = categories.map(&:id) + return {} if category_ids.empty? + + @portal.articles + .published + .where(locale: @locale, category_id: category_ids) + .includes(:author) + .group_by(&:category_id) + .transform_values { |articles| articles.filter_map(&:author).uniq.first(3) } + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index b6df015f7..a369830b6 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,5 +1,6 @@ class DashboardController < ActionController::Base include SwitchLocale + include PortalHomeData GLOBAL_CONFIG_KEYS = %w[ LOGO @@ -63,6 +64,10 @@ class DashboardController < ActionController::Base return unless @portal @locale = @portal.default_locale + if @portal.layout == 'documentation' + request.variant = :documentation + load_home_data + end render 'public/api/v1/portals/show', layout: 'portal', portal: @portal and return end diff --git a/app/controllers/public/api/v1/portals/base_controller.rb b/app/controllers/public/api/v1/portals/base_controller.rb index 2991b84d2..323440304 100644 --- a/app/controllers/public/api/v1/portals/base_controller.rb +++ b/app/controllers/public/api/v1/portals/base_controller.rb @@ -39,9 +39,11 @@ class Public::Api::V1::Portals::BaseController < PublicController end def switch_locale_with_portal(&) - @locale = validate_and_get_locale(params[:locale]) + # Keep @locale as the portal's own locale code (e.g. th_TH) for content queries, + # while UI translations fall back to an available I18n locale (e.g. th). + @locale = params[:locale] - I18n.with_locale(@locale, &) + I18n.with_locale(validate_and_get_locale(@locale), &) end def switch_locale_with_article(&) @@ -49,13 +51,12 @@ class Public::Api::V1::Portals::BaseController < PublicController Rails.logger.info "Article: not found for slug: #{params[:article_slug]}" render_404 && return if article.blank? - article_locale = if article.category.present? - article.category.locale - else - article.locale - end - @locale = validate_and_get_locale(article_locale) - I18n.with_locale(@locale, &) + @locale = if article.category.present? + article.category.locale + else + article.locale + end + I18n.with_locale(validate_and_get_locale(@locale), &) end def allow_iframe_requests diff --git a/app/controllers/public/api/v1/portals_controller.rb b/app/controllers/public/api/v1/portals_controller.rb index 57db11aec..4982278d7 100644 --- a/app/controllers/public/api/v1/portals_controller.rb +++ b/app/controllers/public/api/v1/portals_controller.rb @@ -1,4 +1,6 @@ class Public::Api::V1::PortalsController < Public::Api::V1::Portals::BaseController + include PortalHomeData + before_action :ensure_custom_domain_request, only: [:show] before_action :redirect_to_portal_with_locale, only: [:show] before_action :portal @@ -31,28 +33,4 @@ class Public::Api::V1::PortalsController < Public::Api::V1::Portals::BaseControl portal redirect_to "/hc/#{@portal.slug}/#{@portal.default_locale}" end - - def load_home_data - base_articles = @portal.articles.published.where(locale: @locale).includes(:author, :category) - @visible_categories = @portal.categories - .where(locale: @locale) - .joins(:articles).where(articles: { status: :published }) - .order(position: :asc) - .group('categories.id') - @popular_topics = @visible_categories.first(3) - @featured = base_articles.order_by_views.limit(6) - @category_contributors = build_category_contributors(@visible_categories) - end - - def build_category_contributors(categories) - category_ids = categories.map(&:id) - return {} if category_ids.empty? - - @portal.articles - .published - .where(locale: @locale, category_id: category_ids) - .includes(:author) - .group_by(&:category_id) - .transform_values { |articles| articles.filter_map(&:author).uniq.first(3) } - end end diff --git a/spec/controllers/dashboard_custom_domain_spec.rb b/spec/controllers/dashboard_custom_domain_spec.rb new file mode 100644 index 000000000..4d791defb --- /dev/null +++ b/spec/controllers/dashboard_custom_domain_spec.rb @@ -0,0 +1,50 @@ +require 'rails_helper' + +describe 'GET / on a help center custom domain', type: :request do + let(:account) { create(:account) } + let(:agent) { create(:user, account: account, role: :agent) } + + around do |example| + with_modified_env FRONTEND_URL: 'http://www.chatwoot.test' do + example.run + end + end + + context 'when the portal uses the documentation layout' do + let!(:portal) do + create(:portal, account: account, slug: 'doc-portal', custom_domain: 'docs.example.com', + config: { allowed_locales: ['en'], default_locale: 'en', layout: 'documentation' }) + end + let!(:category) do + create(:category, name: 'Getting Started', portal: portal, account_id: account.id, locale: 'en', slug: 'getting-started') + end + + before do + create(:article, category: category, portal: portal, account: account, author: agent, locale: 'en', status: :published) + end + + it 'renders the documentation home in place without redirecting' do + host! portal.custom_domain + get '/' + + expect(response).to have_http_status(:success) + expect(response.body).to include('sidebar-drawer-checkbox') + expect(response.body).to include('Getting Started') + end + end + + context 'when the portal uses the classic layout' do + let!(:portal) do + create(:portal, account: account, slug: 'classic-portal', custom_domain: 'classic.example.com', + config: { allowed_locales: ['en'], default_locale: 'en', layout: 'classic' }) + end + + it 'renders the classic home without the documentation layout' do + host! portal.custom_domain + get '/' + + expect(response).to have_http_status(:success) + expect(response.body).not_to include('sidebar-drawer-checkbox') + end + end +end diff --git a/spec/controllers/public/api/v1/portals/articles_controller_spec.rb b/spec/controllers/public/api/v1/portals/articles_controller_spec.rb index 8bebd3b9d..89d9de6b4 100644 --- a/spec/controllers/public/api/v1/portals/articles_controller_spec.rb +++ b/spec/controllers/public/api/v1/portals/articles_controller_spec.rb @@ -211,4 +211,30 @@ RSpec.describe 'Public Articles API', type: :request do expect(response.headers['Content-Type']).to eq('image/png') end end + + describe 'documentation layout sidebar for a region-variant locale' do + let!(:th_portal) do + create(:portal, slug: 'th-portal', custom_domain: 'th.example.com', + config: { allowed_locales: ['th_TH'], default_locale: 'th_TH', layout: 'documentation' }) + end + let!(:th_category) do + create(:category, name: 'TH Category', portal: th_portal, account_id: account.id, locale: 'th_TH', slug: 'th-cat') + end + let!(:th_article) do + create(:article, category: th_category, portal: th_portal, account_id: account.id, author_id: agent.id, locale: 'th_TH') + end + + before do + create(:article, category: th_category, portal: th_portal, account_id: account.id, author_id: agent.id, + locale: 'th_TH', title: 'Sibling In Sidebar', status: :published) + end + + it 'lists the category and sibling articles using the full portal locale' do + host! 'th.example.com' + get "/hc/#{th_portal.slug}/articles/#{th_article.slug}" + + expect(response).to have_http_status(:success) + expect(response.body).to include('Sibling In Sidebar') + end + end end