feat: Add support to uncategorized articles (#6912)

This commit is contained in:
Tejaswini Chile
2023-05-02 15:35:26 +05:30
committed by GitHub
parent c8041392dc
commit 847d7ea082
18 changed files with 103 additions and 46 deletions

View File

@@ -1,7 +1,7 @@
class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::BaseController
before_action :ensure_custom_domain_request, only: [:show, :index]
before_action :portal
before_action :set_category, except: [:index]
before_action :set_category, except: [:index, :show]
before_action :set_article, only: [:show]
layout 'portal'
@@ -16,7 +16,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
private
def set_article
@article = @category.articles.find(permitted_params[:id])
@article = @portal.articles.find_by(slug: permitted_params[:article_slug])
@article.increment_view_count
@parsed_content = render_article_content(@article.content)
end
@@ -39,7 +39,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
end
def permitted_params
params.permit(:slug, :category_slug, :locale, :id)
params.permit(:slug, :category_slug, :locale, :id, :article_slug)
end
def render_article_content(content)

View File

@@ -5,6 +5,7 @@ class Public::Api::V1::Portals::BaseController < PublicController
def set_locale(&)
switch_locale_with_portal(&) if params[:locale].present?
switch_locale_with_article(&) if params[:article_slug].present?
end
def switch_locale_with_portal(&)
@@ -19,4 +20,16 @@ class Public::Api::V1::Portals::BaseController < PublicController
I18n.with_locale(@locale, &)
end
def switch_locale_with_article(&)
article = Article.find_by(slug: params[:article_slug])
@locale = if article.category.present?
article.category.locale
else
'en'
end
I18n.with_locale(@locale, &)
end
end

View File

@@ -8,8 +8,8 @@ export const buildPortalArticleURL = (
portalSlug,
categorySlug,
locale,
articleId
articleSlug
) => {
const portalURL = buildPortalURL(portalSlug);
return `${portalURL}/${locale}/${categorySlug}/${articleId}`;
return `${portalURL}/articles/${articleSlug}`;
};

View File

@@ -20,9 +20,9 @@ describe('PortalHelper', () => {
hostURL: 'https://app.chatwoot.com',
helpCenterURL: 'https://help.chatwoot.com',
};
expect(buildPortalArticleURL('handbook', 'culture', 'fr', 1)).toEqual(
'https://help.chatwoot.com/hc/handbook/fr/culture/1'
);
expect(
buildPortalArticleURL('handbook', 'culture', 'fr', 'article-slug')
).toEqual('https://help.chatwoot.com/hc/handbook/articles/article-slug');
window.chatwootConfig = {};
});
});

View File

@@ -93,7 +93,7 @@ export default {
slug,
this.article.category.slug,
this.article.category.locale,
this.article.id
this.article.slug
);
},
},

View File

@@ -86,7 +86,7 @@ export default {
methods: {
generateArticleUrl(article) {
return `/hc/${article.portal.slug}/${article.category.locale}/${article.category.slug}/${article.id}`;
return `/hc/${article.portal.slug}/articles/${article.slug}`;
},
handleKeyboardEvent(e) {
this.processKeyDownEvent(e);

View File

@@ -41,7 +41,7 @@ class Article < ApplicationRecord
inverse_of: :associated_articles,
optional: true
belongs_to :account
belongs_to :category
belongs_to :category, optional: true
belongs_to :portal
belongs_to :author, class_name: 'User'
@@ -49,7 +49,6 @@ class Article < ApplicationRecord
before_validation :ensure_article_slug
validates :account_id, presence: true
validates :category_id, presence: true
validates :author_id, presence: true
validates :title, presence: true
validates :content, presence: true

View File

@@ -1,4 +1,5 @@
json.id article.id
json.slug article.slug
json.title article.title
json.content article.content
json.description article.description

View File

@@ -1,7 +1,7 @@
<section class="bg-white lg:container w-full py-6 px-4 flex flex-col h-full">
<div class="flex justify-between items-center w-full">
<h3 class="text-xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed hover:underline">
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>">
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/categories/<%= category.slug %>">
<%= category.name %>
</a>
</h3>
@@ -18,7 +18,7 @@
<% category.articles.published.order(position: :asc).take(5).each do |article| %>
<a
class="text-slate-800 hover:underline leading-8"
href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>/<%= article.id %>"
href="/hc/<%= portal.slug %>/articles/<%= article.slug %>"
>
<div class="flex justify-between content-center my-1 -mx-1 p-1 rounded-lg hover:bg-slate-25">
<%= article.title %>
@@ -42,7 +42,7 @@
</div>
<div>
<a
href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>"
href="/hc/<%= portal.slug %>/<%= category.locale %>/categories/<%= category.slug %>"
class="flex flex-row items-center text-base font-medium text-woot-500 hover:underline mt-4"
style="color: <%= portal.color %>"
>

View File

@@ -0,0 +1,35 @@
<section class="bg-white lg:container w-full py-6 px-4 flex flex-col h-full">
<div class="flex justify-between items-center w-full">
<h3 class="text-xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed hover:underline">
<%= category %>
</h3>
<span class="text-slate-500">
<%= render 'public/api/v1/portals/article_count', article_count: portal.articles.published.where(category_id: nil).size %>
</span>
</div>
<div class="py-4 w-full mt-2 flex-grow">
<% portal.articles.published.where(category_id: nil).order(position: :asc).take(5).each do |article| %>
<a
class="text-slate-800 hover:underline leading-8"
href="/hc/<%= portal.slug %>/articles/<%= article.slug %>"
>
<div class="flex justify-between content-center my-1 -mx-1 p-1 rounded-lg hover:bg-slate-25">
<%= article.title %>
<span class="flex items-center">
<svg
class="w-4 h-4 fill-current text-slate-700"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8.47 4.22a.75.75 0 0 0 0 1.06L15.19 12l-6.72 6.72a.75.75 0 1 0 1.06 1.06l7.25-7.25a.75.75 0 0 0 0-1.06L9.53 4.22a.75.75 0 0 0-1.06 0Z"
/>
</svg>
</span>
</div>
</a>
<% end %>
</div>
</section>

View File

@@ -13,6 +13,7 @@
<div class="bg-gradient-to-b from-white to-slate-50">
<div class="max-w-5xl px-8 lg:px-4 pt-8 pb-16 mx-auto space-y-4 w-full">
<% if @article.category.present? %>
<div>
<a
class="text-slate-700 hover:underline leading-8 text-sm font-medium"
@@ -23,11 +24,12 @@
<span class="text-xs text-slate-600 px-1">/</span>
<a
class="text-slate-700 hover:underline leading-8 text-sm font-medium"
href="/hc/<%= @portal.slug %>/<%= @article.category.locale %>/<%= @article.category.slug %>"
href="/hc/<%= @portal.slug %>/<%= @article.category.locale %>/categories/<%= @article.category.slug %>"
>
<%= @article.category.name %>
</a>
</div>
<% end %>
<h1 class="text-3xl font-bold md:tracking-normal leading-snug md:text-5xl text-slate-900">
<%= @article.title %>
</h1>

View File

@@ -2,7 +2,7 @@
<section class="bg-white lg:container w-full py-6 px-4 flex flex-col h-full">
<div class="flex justify-between items-center w-full">
<h3 class="text-xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed hover:underline"">
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>">
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/categories/<%= category.slug %>">
<%= category.name %>
</a>
</h3>
@@ -20,7 +20,7 @@
<div class="flex justify-between content-center h-8 my-1">
<a
class="text-slate-800 hover:underline leading-8"
href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>/<%= article.id %>"
href="/hc/<%= portal.slug %>/articles/<%= article.slug %>"
>
<%= article.title %>
</a>
@@ -45,7 +45,7 @@
</div>
<div>
<a
href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>"
href="/hc/<%= portal.slug %>/<%= category.locale %>/categories/<%= category.slug %>"
class="flex flex-row items-center text-base font-medium text-woot-600 hover:text-slate-900 hover:underline mt-4"
>
<%= I18n.t('public_portal.common.view_all_articles') %>

View File

@@ -35,7 +35,7 @@
<% @category.articles.published.order(:position).each do |article| %>
<a
class="text-slate-800 flex justify-between content-center mb-4 py-2"
href="/hc/<%= @portal.slug %>/<%= @category.locale %>/<%= @category.slug %>/<%= article.id %>"
href="/hc/<%= @portal.slug %>/articles/<%= article.slug %>"
>
<div>
<p class="font-medium mb-2 hover:underline"><%= article.title %></p>

View File

@@ -5,4 +5,10 @@
<%= render "public/api/v1/portals/category-block", category: category, portal: @portal %>
<% end %>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-x-32 gap-y-0 lg:gap-y-12">
<% if @portal.articles.where(status: :published, category_id: nil).order(position: :asc) %>
<%= render "public/api/v1/portals/uncategorized-block", category: "Uncategorized", portal: @portal %>
<% end %>
</div>
</div>

View File

@@ -338,9 +338,9 @@ Rails.application.routes.draw do
get 'hc/:slug/:locale', to: 'public/api/v1/portals#show'
get 'hc/:slug/:locale/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index'
get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show'
get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show'
get 'hc/:slug/:locale/categories/:category_slug', to: 'public/api/v1/portals/categories#show'
get 'hc/:slug/:locale/categories/:category_slug/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/articles/:article_slug', to: 'public/api/v1/portals/articles#show'
# ----------------------------------------------------------------------
# Used in mailer templates

View File

@@ -9,6 +9,7 @@ RSpec.describe 'Public Articles API', type: :request do
let!(:article) { create(:article, category: category, portal: portal, account_id: account.id, author_id: agent.id) }
before do
ENV['HELPCENTER_URL'] = ENV.fetch('FRONTEND_URL', nil)
create(:article, category: category, portal: portal, account_id: account.id, author_id: agent.id)
create(:article, category: category, portal: portal, account_id: account.id, author_id: agent.id, associated_article_id: article.id)
create(:article, category: category_2, portal: portal, account_id: account.id, author_id: agent.id, associated_article_id: article.id)
@@ -17,7 +18,7 @@ RSpec.describe 'Public Articles API', type: :request do
describe 'GET /public/api/v1/portals/:slug/articles' do
it 'Fetch all articles in the portal' do
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/articles"
get "/hc/#{portal.slug}/#{category.locale}/categories/#{category.slug}/articles"
expect(response).to have_http_status(:success)
end
@@ -31,7 +32,7 @@ RSpec.describe 'Public Articles API', type: :request do
content: 'this is some test and funny content')
expect(article2.id).not_to be_nil
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/articles",
get "/hc/#{portal.slug}/#{category.locale}/categories/#{category.slug}/articles",
headers: agent.create_new_auth_token,
params: { query: 'funny' }
expect(response).to have_http_status(:success)
@@ -40,14 +41,14 @@ RSpec.describe 'Public Articles API', type: :request do
describe 'GET /public/api/v1/portals/:slug/articles/:id' do
it 'Fetch article with the id' do
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/#{article.id}"
get "/hc/#{portal.slug}/articles/#{article.slug}"
expect(response).to have_http_status(:success)
expect(article.reload.views).to eq 1
end
it 'returns the article with the id with a different locale' do
article_in_locale = create(:article, category: category_2, portal: portal, account_id: account.id, author_id: agent.id)
get "/hc/#{portal.slug}/#{category_2.locale}/#{category_2.slug}/#{article_in_locale.id}"
get "/hc/#{portal.slug}/articles/#{article_in_locale.slug}"
expect(response).to have_http_status(:success)
end
end

View File

@@ -12,7 +12,9 @@ RSpec.describe 'Public Categories API', type: :request do
describe 'GET /public/api/v1/portals/:portal_slug/categories' do
it 'Fetch all categories in the portal' do
get "/hc/#{portal.slug}/categories"
category = portal.categories.first
get "/hc/#{portal.slug}/#{category.locale}/categories"
expect(response).to have_http_status(:success)
end
@@ -20,9 +22,9 @@ RSpec.describe 'Public Categories API', type: :request do
describe 'GET /public/api/v1/portals/:portal_slug/categories/:slug' do
it 'Fetch category with the slug' do
category_locale = 'en'
category = portal.categories.first
get "/hc/#{portal.slug}/#{category_locale}/categories"
get "/hc/#{portal.slug}/#{category.locale}/categories/#{category.slug}"
expect(response).to have_http_status(:success)
end

View File

@@ -3,7 +3,6 @@ require 'rails_helper'
RSpec.describe Article, type: :model do
context 'with validations' do
it { is_expected.to validate_presence_of(:account_id) }
it { is_expected.to validate_presence_of(:category_id) }
it { is_expected.to validate_presence_of(:author_id) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:content) }
@@ -11,7 +10,6 @@ RSpec.describe Article, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:account) }
it { is_expected.to belong_to(:category) }
it { is_expected.to belong_to(:author) }
end