fix: Update article count in portal admin dashboard (#5647)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Tejaswini Chile 2022-10-22 02:13:15 +05:30 committed by GitHub
parent 95cc55d043
commit 782165478b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 157 additions and 94 deletions

View file

@ -5,9 +5,10 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
before_action :set_current_page, only: [:index] before_action :set_current_page, only: [:index]
def index def index
@articles_count = @portal.articles.count @portal_articles = @portal.articles
@articles = @portal.articles @all_articles = @portal_articles.search(list_params)
@articles = @articles.search(list_params) if list_params.present? @articles_count = @all_articles.count
@articles = @all_articles.page(@current_page)
end end
def create def create

View file

@ -5,6 +5,7 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
before_action :set_current_page, only: [:index] before_action :set_current_page, only: [:index]
def index def index
@current_locale = params[:locale]
@categories = @portal.categories.search(params) @categories = @portal.categories.search(params)
end end

View file

@ -14,7 +14,10 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
@portal.members << agents @portal.members << agents
end end
def show; end def show
@all_articles = @portal.articles
@articles = @all_articles.search(locale: params[:locale])
end
def create def create
@portal = Current.account.portals.build(portal_params) @portal = Current.account.portals.build(portal_params)

View file

@ -7,8 +7,8 @@ class CategoriesAPI extends PortalsAPI {
super('categories', { accountScoped: true }); super('categories', { accountScoped: true });
} }
get({ portalSlug }) { get({ portalSlug, locale }) {
return axios.get(`${this.url}/${portalSlug}/categories`); return axios.get(`${this.url}/${portalSlug}/categories?locale=${locale}`);
} }
create({ portalSlug, categoryObj }) { create({ portalSlug, categoryObj }) {

View file

@ -6,6 +6,10 @@ class PortalsAPI extends ApiClient {
super('portals', { accountScoped: true }); super('portals', { accountScoped: true });
} }
getPortal({ portalSlug, locale }) {
return axios.get(`${this.url}/${portalSlug}?locale=${locale}`);
}
updatePortal({ portalSlug, portalObj }) { updatePortal({ portalSlug, portalObj }) {
return axios.patch(`${this.url}/${portalSlug}`, portalObj); return axios.patch(`${this.url}/${portalSlug}`, portalObj);
} }

View file

@ -16,7 +16,7 @@
:accessible-menu-items="accessibleMenuItems" :accessible-menu-items="accessibleMenuItems"
:additional-secondary-menu-items="additionalSecondaryMenuItems" :additional-secondary-menu-items="additionalSecondaryMenuItems"
@open-popover="openPortalPopover" @open-popover="openPortalPopover"
@open-modal="onClickOpenAddCatogoryModal" @open-modal="onClickOpenAddCategoryModal"
/> />
<section class="app-content columns" :class="contentClassName"> <section class="app-content columns" :class="contentClassName">
<router-view /> <router-view />
@ -134,14 +134,14 @@ export default {
}, },
accessibleMenuItems() { accessibleMenuItems() {
if (!this.selectedPortal) return []; if (!this.selectedPortal) return [];
const { const {
meta: { allArticlesCount,
all_articles_count: allArticlesCount, mineArticlesCount,
mine_articles_count: mineArticlesCount, draftArticlesCount,
draft_articles_count: draftArticlesCount, archivedArticlesCount,
archived_articles_count: archivedArticlesCount, } = this.meta;
} = {},
} = this.selectedPortal;
return [ return [
{ {
icon: 'book', icon: 'book',
@ -216,6 +216,13 @@ export default {
return this.selectedPortal ? this.selectedPortal.name : ''; return this.selectedPortal ? this.selectedPortal.name : '';
}, },
}, },
watch: {
selectedPortal() {
this.fetchPortalsAndItsCategories();
},
},
mounted() { mounted() {
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
this.handleResize(); this.handleResize();
@ -251,12 +258,15 @@ export default {
toggleSidebar() { toggleSidebar() {
this.isSidebarOpen = !this.isSidebarOpen; this.isSidebarOpen = !this.isSidebarOpen;
}, },
fetchPortalsAndItsCategories() { async fetchPortalsAndItsCategories() {
this.$store.dispatch('portals/index').then(() => { await this.$store.dispatch('portals/index');
this.$store.dispatch('categories/index', {
portalSlug: this.selectedPortalSlug, const selectedPortalParam = {
}); portalSlug: this.selectedPortalSlug,
}); locale: this.selectedLocaleInPortal,
};
this.$store.dispatch('portals/show', selectedPortalParam);
this.$store.dispatch('categories/index', selectedPortalParam);
this.$store.dispatch('agents/get'); this.$store.dispatch('agents/get');
}, },
toggleKeyShortcutModal() { toggleKeyShortcutModal() {
@ -277,7 +287,7 @@ export default {
closePortalPopover() { closePortalPopover() {
this.showPortalPopover = false; this.showPortalPopover = false;
}, },
onClickOpenAddCatogoryModal() { onClickOpenAddCategoryModal() {
this.showAddCategoryModal = true; this.showAddCategoryModal = true;
}, },
onClickCloseAddCategoryModal() { onClickCloseAddCategoryModal() {

View file

@ -112,16 +112,8 @@ export default {
this.selectedLocale = this.locale || this.portal?.meta?.default_locale; this.selectedLocale = this.locale || this.portal?.meta?.default_locale;
}, },
methods: { methods: {
fetchPortalsAndItsCategories() {
this.$store.dispatch('portals/index').then(() => {
this.$store.dispatch('categories/index', {
portalSlug: this.portal.slug,
});
});
},
onClick(event, code, portal) { onClick(event, code, portal) {
event.preventDefault(); event.preventDefault();
this.fetchPortalsAndItsCategories();
this.$router.push({ this.$router.push({
name: 'list_all_locale_articles', name: 'list_all_locale_articles',
params: { params: {

View file

@ -104,9 +104,6 @@ export default {
} }
return null; return null;
}, },
articleCount() {
return this.articles ? this.articles.length : 0;
},
headerTitleInCategoryView() { headerTitleInCategoryView() {
return this.categories && this.categories.length return this.categories && this.categories.length
? this.selectedCategory.name ? this.selectedCategory.name

View file

@ -2,13 +2,13 @@ import categoriesAPI from 'dashboard/api/helpCenter/categories.js';
import { throwErrorMessage } from 'dashboard/store/utils/api'; import { throwErrorMessage } from 'dashboard/store/utils/api';
import types from '../../mutation-types'; import types from '../../mutation-types';
export const actions = { export const actions = {
index: async ({ commit }, { portalSlug }) => { index: async ({ commit }, { portalSlug, locale }) => {
try { try {
commit(types.SET_UI_FLAG, { isFetching: true }); commit(types.SET_UI_FLAG, { isFetching: true });
if (portalSlug) { if (portalSlug) {
const { const {
data: { payload }, data: { payload },
} = await categoriesAPI.get({ portalSlug }); } = await categoriesAPI.get({ portalSlug, locale });
commit(types.CLEAR_CATEGORIES); commit(types.CLEAR_CATEGORIES);
const categoryIds = payload.map(category => category.id); const categoryIds = payload.map(category => category.id);
commit(types.ADD_MANY_CATEGORIES, payload); commit(types.ADD_MANY_CATEGORIES, payload);

View file

@ -7,14 +7,12 @@ export const actions = {
try { try {
commit(types.SET_UI_FLAG, { isFetching: true }); commit(types.SET_UI_FLAG, { isFetching: true });
const { const {
data: { payload, meta }, data: { payload },
} = await portalAPIs.get(); } = await portalAPIs.get();
commit(types.CLEAR_PORTALS); commit(types.CLEAR_PORTALS);
const portalSlugs = payload.map(portal => portal.slug); const portalSlugs = payload.map(portal => portal.slug);
commit(types.ADD_MANY_PORTALS_ENTRY, payload); commit(types.ADD_MANY_PORTALS_ENTRY, payload);
commit(types.ADD_MANY_PORTALS_IDS, portalSlugs); commit(types.ADD_MANY_PORTALS_IDS, portalSlugs);
commit(types.SET_PORTALS_META, meta);
} catch (error) { } catch (error) {
throwErrorMessage(error); throwErrorMessage(error);
} finally { } finally {
@ -22,6 +20,21 @@ export const actions = {
} }
}, },
show: async ({ commit }, { portalSlug, locale }) => {
commit(types.SET_UI_FLAG, { isFetchingItem: true });
try {
const response = await portalAPIs.getPortal({ portalSlug, locale });
const {
data: { meta },
} = response;
commit(types.SET_PORTALS_META, meta);
} catch (error) {
// Ignore error
} finally {
commit(types.SET_UI_FLAG, { isFetchingItem: false });
}
},
create: async ({ commit }, params) => { create: async ({ commit }, params) => {
commit(types.SET_UI_FLAG, { isCreating: true }); commit(types.SET_UI_FLAG, { isCreating: true });
try { try {

View file

@ -20,7 +20,5 @@ export const getters = {
return portals; return portals;
}, },
count: state => state.portals.allIds.length || 0, count: state => state.portals.allIds.length || 0,
getMeta: state => { getMeta: state => state.meta,
return state.meta;
},
}; };

View file

@ -10,8 +10,10 @@ export const defaultPortalFlags = {
const state = { const state = {
meta: { meta: {
count: 0, allArticlesCount: 0,
currentPage: 1, mineArticlesCount: 0,
draftArticlesCount: 0,
archivedArticlesCount: 0,
}, },
portals: { portals: {

View file

@ -44,9 +44,16 @@ export const mutations = {
}, },
[types.SET_PORTALS_META]: ($state, data) => { [types.SET_PORTALS_META]: ($state, data) => {
const { portals_count: count, current_page: currentPage } = data; const {
Vue.set($state.meta, 'count', count); all_articles_count: allArticlesCount,
Vue.set($state.meta, 'currentPage', currentPage); mine_articles_count: mineArticlesCount,
draft_articles_count: draftArticlesCount,
archived_articles_count: archivedArticlesCount,
} = data;
Vue.set($state.meta, 'allArticlesCount', allArticlesCount);
Vue.set($state.meta, 'archivedArticlesCount', archivedArticlesCount);
Vue.set($state.meta, 'mineArticlesCount', mineArticlesCount);
Vue.set($state.meta, 'draftArticlesCount', draftArticlesCount);
}, },
[types.ADD_PORTAL_ID]($state, portalSlug) { [types.ADD_PORTAL_ID]($state, portalSlug) {

View file

@ -22,7 +22,6 @@ describe('#actions', () => {
[types.CLEAR_PORTALS], [types.CLEAR_PORTALS],
[types.ADD_MANY_PORTALS_ENTRY, apiResponse.payload], [types.ADD_MANY_PORTALS_ENTRY, apiResponse.payload],
[types.ADD_MANY_PORTALS_IDS, ['domain', 'campaign']], [types.ADD_MANY_PORTALS_IDS, ['domain', 'campaign']],
[types.SET_PORTALS_META, { current_page: 1, portals_count: 1 }],
[types.SET_UI_FLAG, { isFetching: false }], [types.SET_UI_FLAG, { isFetching: false }],
]); ]);
}); });
@ -66,6 +65,36 @@ describe('#actions', () => {
}); });
}); });
describe('#show', () => {
it('sends correct actions if API is success', async () => {
axios.get.mockResolvedValue({
data: { meta: { all_articles_count: 1 } },
});
await actions.show(
{ commit },
{
portalSlug: 'handbook',
locale: 'en',
}
);
expect(commit.mock.calls).toEqual([
[types.SET_UI_FLAG, { isFetchingItem: true }],
[types.SET_PORTALS_META, { all_articles_count: 1 }],
[types.SET_UI_FLAG, { isFetchingItem: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' });
await expect(
actions.create({ commit, dispatch, state: { portals: {} } }, {})
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[types.SET_UI_FLAG, { isCreating: true }],
[types.SET_UI_FLAG, { isCreating: false }],
]);
});
});
describe('#update', () => { describe('#update', () => {
it('sends correct actions if API is success', async () => { it('sends correct actions if API is success', async () => {
axios.patch.mockResolvedValue({ data: apiResponse.payload[1] }); axios.patch.mockResolvedValue({ data: apiResponse.payload[1] });

View file

@ -107,12 +107,20 @@ describe('#mutations', () => {
describe('#SET_PORTALS_META', () => { describe('#SET_PORTALS_META', () => {
it('add meta to state', () => { it('add meta to state', () => {
mutations[types.SET_PORTALS_META](state, { mutations[types.SET_PORTALS_META](state, {
portals_count: 10,
current_page: 1,
});
expect(state.meta).toEqual({
count: 10, count: 10,
currentPage: 1, currentPage: 1,
all_articles_count: 10,
archived_articles_count: 10,
draft_articles_count: 10,
mine_articles_count: 10,
});
expect(state.meta).toEqual({
count: 0,
currentPage: 1,
allArticlesCount: 10,
archivedArticlesCount: 10,
draftArticlesCount: 10,
mineArticlesCount: 10,
}); });
}); });
}); });

View file

@ -83,11 +83,7 @@ class Article < ApplicationRecord
).search_by_category_locale(params[:locale]).search_by_author(params[:author_id]).search_by_status(params[:status]) ).search_by_category_locale(params[:locale]).search_by_author(params[:author_id]).search_by_status(params[:status])
records = records.text_search(params[:query]) if params[:query].present? records = records.text_search(params[:query]) if params[:query].present?
records.page(current_page(params)) records
end
def self.current_page(params)
params[:page] || 1
end end
def associate_root_article(associated_article_id) def associate_root_article(associated_article_id)

View file

@ -13,12 +13,6 @@ json.category do
json.locale article.category.locale json.locale article.category.locale
end end
if article.portal.present?
json.portal do
json.partial! 'api/v1/accounts/portals/portal', formats: [:json], portal: article.portal
end
end
json.views article.views json.views article.views
if article.author.present? if article.author.present?

View file

@ -8,7 +8,7 @@ json.account_id article.account_id
if article.portal.present? if article.portal.present?
json.portal do json.portal do
json.partial! 'api/v1/accounts/portals/portal', formats: [:json], portal: article.portal json.partial! 'api/v1/accounts/portals/portal', formats: [:json], portal: article.portal, articles: []
end end
end end

View file

@ -3,10 +3,11 @@ json.payload do
end end
json.meta do json.meta do
json.current_page @current_page json.all_articles_count @portal_articles.size
json.articles_count @articles_count
json.archived_articles_count @articles.archived.size json.archived_articles_count @articles.archived.size
json.articles_count @articles_count
json.current_page @current_page
json.draft_articles_count @all_articles.draft.size
json.mine_articles_count @all_articles.search_by_author(current_user.id).size if current_user.present?
json.published_count @articles.published.size json.published_count @articles.published.size
json.draft_articles_count @articles.draft.size
json.mine_articles_count @articles.search_by_author(current_user.id).size if current_user.present?
end end

View file

@ -27,5 +27,5 @@ if category.root_category.present?
end end
json.meta do json.meta do
json.articles_count category.articles.size json.articles_count category.articles.search(locale: @current_locale).size
end end

View file

@ -28,11 +28,11 @@ json.portal_members do
end end
json.meta do json.meta do
json.all_articles_count portal.articles.size json.all_articles_count articles.try(:size)
json.archived_articles_count portal.articles.archived.size json.archived_articles_count articles.try(:archived).try(:size)
json.published_count portal.articles.published.size json.published_count articles.try(:published).try(:size)
json.draft_articles_count portal.articles.draft.size json.draft_articles_count articles.try(:draft).try(:size)
json.mine_articles_count portal.articles.search_by_author(current_user.id).size if current_user.present? json.mine_articles_count articles.search_by_author(current_user.id).try(:size) if current_user.present? && articles.any?
json.categories_count portal.categories.size json.categories_count portal.categories.try(:size)
json.default_locale portal.default_locale json.default_locale portal.default_locale
end end

View file

@ -1 +1 @@
json.partial! 'portal', portal: @portal json.partial! 'portal', portal: @portal, articles: []

View file

@ -1 +1 @@
json.partial! 'portal', portal: @portal json.partial! 'portal', portal: @portal, articles: []

View file

@ -1,5 +1,7 @@
json.payload do json.payload do
json.array! @portals, partial: 'portal', as: :portal json.array! @portals.each do |portal|
json.partial! 'portal', formats: [:json], portal: portal, articles: []
end
end end
json.meta do json.meta do

View file

@ -1 +1 @@
json.partial! 'portal', portal: @portal json.partial! 'portal', portal: @portal, articles: @articles

View file

@ -1 +1 @@
json.partial! 'portal', portal: @portal json.partial! 'portal', portal: @portal, articles: []

View file

@ -194,7 +194,8 @@ RSpec.describe 'Api::V1::Accounts::Articles', type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body) json_response = JSON.parse(response.body)
expect(json_response['payload'].count).to be 1 expect(json_response['payload'].count).to be 1
expect(json_response['meta']['articles_count']).to be 2 expect(json_response['meta']['all_articles_count']).to be 2
expect(json_response['meta']['articles_count']).to be 1
expect(json_response['meta']['mine_articles_count']).to be 1 expect(json_response['meta']['mine_articles_count']).to be 1
end end
end end

View file

@ -50,6 +50,25 @@ RSpec.describe 'Api::V1::Accounts::Portals', type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body) json_response = JSON.parse(response.body)
expect(json_response['name']).to eq portal.name expect(json_response['name']).to eq portal.name
expect(json_response['meta']['all_articles_count']).to eq 0
end
it 'returns portal articles metadata' do
portal.update(config: { allowed_locales: %w[en es], default_locale: 'en' })
en_cat = create(:category, locale: :en, portal_id: portal.id, slug: 'en-cat')
es_cat = create(:category, locale: :es, portal_id: portal.id, slug: 'es-cat')
create(:article, category_id: en_cat.id, portal_id: portal.id, author_id: agent.id)
create(:article, category_id: en_cat.id, portal_id: portal.id, author_id: admin.id)
create(:article, category_id: es_cat.id, portal_id: portal.id, author_id: agent.id)
get "/api/v1/accounts/#{account.id}/portals/#{portal.slug}?locale=en",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response['name']).to eq portal.name
expect(json_response['meta']['all_articles_count']).to eq 2
expect(json_response['meta']['mine_articles_count']).to eq 1
end end
end end
end end

View file

@ -126,20 +126,5 @@ RSpec.describe Article, type: :model do
expect(article.slug).to include('the-awesome-article-1') expect(article.slug).to include('the-awesome-article-1')
end end
end end
context 'with pagination' do
it 'returns paginated articles' do
build_list(:article, 30) do |record, i|
record.category_id = category_2.id
record.title = "title #{i}"
record.portal_id = portal_2.id
record.author_id = user.id
record.save!
end
params = { category_slug: 'category_2' }
records = portal_2.articles.search(params)
expect(records.count).to eq(25)
end
end
end end
end end