fix: Improve performance of most hit APIs in widget (#11089)
- Cache campaigns for better performance - Fix N+1 queries in inbox members - Remove unused related articles
This commit is contained in:
@@ -2,6 +2,10 @@ class Api::V1::Widget::CampaignsController < Api::V1::Widget::BaseController
|
|||||||
skip_before_action :set_contact
|
skip_before_action :set_contact
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@campaigns = @web_widget.inbox.campaigns.where(enabled: true)
|
@campaigns = @web_widget
|
||||||
|
.inbox
|
||||||
|
.campaigns
|
||||||
|
.where(enabled: true, account_id: @web_widget.inbox.account_id)
|
||||||
|
.includes(:sender)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController
|
|||||||
skip_before_action :set_contact
|
skip_before_action :set_contact
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@inbox_members = @web_widget.inbox.inbox_members.includes(:user)
|
@inbox_members = @web_widget.inbox.inbox_members.includes(user: { avatar_attachment: :blob })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
const state = {
|
const state = {
|
||||||
records: [],
|
records: [],
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
|
hasFetched: false,
|
||||||
isError: false,
|
isError: false,
|
||||||
},
|
},
|
||||||
activeCampaign: {},
|
activeCampaign: {},
|
||||||
@@ -30,6 +31,7 @@ const resetCampaignTimers = (
|
|||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getCampaigns: $state => $state.records,
|
getCampaigns: $state => $state.records,
|
||||||
|
getUIFlags: $state => $state.uiFlags,
|
||||||
getActiveCampaign: $state => $state.activeCampaign,
|
getActiveCampaign: $state => $state.activeCampaign,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,15 +55,21 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
initCampaigns: async (
|
initCampaigns: async (
|
||||||
{ getters: { getCampaigns: campaigns }, dispatch },
|
{ getters: { getCampaigns: campaigns, getUIFlags: uiFlags }, dispatch },
|
||||||
{ currentURL, websiteToken, isInBusinessHours }
|
{ currentURL, websiteToken, isInBusinessHours }
|
||||||
) => {
|
) => {
|
||||||
if (!campaigns.length) {
|
if (!campaigns.length) {
|
||||||
dispatch('fetchCampaigns', {
|
// This check is added to ensure that the campaigns are fetched once
|
||||||
websiteToken,
|
// On high traffic sites, if the campaigns are empty, the API is called
|
||||||
currentURL,
|
// every time the user changes the URL (in case of the SPA)
|
||||||
isInBusinessHours,
|
// So, we need to ensure that the campaigns are fetched only once
|
||||||
});
|
if (!uiFlags.hasFetched) {
|
||||||
|
dispatch('fetchCampaigns', {
|
||||||
|
websiteToken,
|
||||||
|
currentURL,
|
||||||
|
isInBusinessHours,
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resetCampaignTimers(
|
resetCampaignTimers(
|
||||||
campaigns,
|
campaigns,
|
||||||
@@ -127,6 +135,7 @@ export const actions = {
|
|||||||
export const mutations = {
|
export const mutations = {
|
||||||
setCampaigns($state, data) {
|
setCampaigns($state, data) {
|
||||||
$state.records = data;
|
$state.records = data;
|
||||||
|
$state.uiFlags.hasFetched = true;
|
||||||
},
|
},
|
||||||
setActiveCampaign($state, data) {
|
setActiveCampaign($state, data) {
|
||||||
$state.activeCampaign = data;
|
$state.activeCampaign = data;
|
||||||
|
|||||||
@@ -63,15 +63,37 @@ describe('#actions', () => {
|
|||||||
};
|
};
|
||||||
it('sends correct actions if campaigns are empty', async () => {
|
it('sends correct actions if campaigns are empty', async () => {
|
||||||
await actions.initCampaigns(
|
await actions.initCampaigns(
|
||||||
{ dispatch, getters: { getCampaigns: [] } },
|
{
|
||||||
|
dispatch,
|
||||||
|
getters: { getCampaigns: [], getUIFlags: { hasFetched: false } },
|
||||||
|
},
|
||||||
actionParams
|
actionParams
|
||||||
);
|
);
|
||||||
expect(dispatch.mock.calls).toEqual([['fetchCampaigns', actionParams]]);
|
expect(dispatch.mock.calls).toEqual([['fetchCampaigns', actionParams]]);
|
||||||
expect(campaignTimer.initTimers).not.toHaveBeenCalled();
|
expect(campaignTimer.initTimers).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('do not refetch if the campaigns are fetched once', async () => {
|
||||||
|
await actions.initCampaigns(
|
||||||
|
{
|
||||||
|
dispatch,
|
||||||
|
getters: { getCampaigns: [], getUIFlags: { hasFetched: true } },
|
||||||
|
},
|
||||||
|
actionParams
|
||||||
|
);
|
||||||
|
expect(dispatch.mock.calls).toEqual([]);
|
||||||
|
expect(campaignTimer.initTimers).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('resets time if campaigns are available', async () => {
|
it('resets time if campaigns are available', async () => {
|
||||||
await actions.initCampaigns(
|
await actions.initCampaigns(
|
||||||
{ dispatch, getters: { getCampaigns: campaigns } },
|
{
|
||||||
|
dispatch,
|
||||||
|
getters: {
|
||||||
|
getCampaigns: campaigns,
|
||||||
|
getUIFlags: { hasFetched: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
actionParams
|
actionParams
|
||||||
);
|
);
|
||||||
expect(dispatch.mock.calls).toEqual([]);
|
expect(dispatch.mock.calls).toEqual([]);
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ vi.mock('widget/store/index.js', () => ({
|
|||||||
describe('#mutations', () => {
|
describe('#mutations', () => {
|
||||||
describe('#setCampaigns', () => {
|
describe('#setCampaigns', () => {
|
||||||
it('set campaign records', () => {
|
it('set campaign records', () => {
|
||||||
const state = { records: [] };
|
const state = { records: [], uiFlags: {} };
|
||||||
mutations.setCampaigns(state, campaigns);
|
mutations.setCampaigns(state, campaigns);
|
||||||
expect(state.records).toEqual(campaigns);
|
expect(state.records).toEqual(campaigns);
|
||||||
|
expect(state.uiFlags.hasFetched).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,6 @@ json.locale category.locale
|
|||||||
json.description category.description
|
json.description category.description
|
||||||
json.position category.position
|
json.position category.position
|
||||||
|
|
||||||
json.related_categories do
|
|
||||||
if category.related_categories.any?
|
|
||||||
json.array! category.related_categories.each do |related_category|
|
|
||||||
json.partial! partial: 'public/api/v1/models/associated_category', formats: [:json], category: related_category
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if category.parent_category.present?
|
|
||||||
json.parent_category do
|
|
||||||
json.partial! partial: 'public/api/v1/models/associated_category', formats: [:json], category: category.parent_category
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if category.root_category.present?
|
|
||||||
json.root_category do
|
|
||||||
json.partial! partial: 'public/api/v1/models/associated_category', formats: [:json], category: category.root_category
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
json.meta do
|
json.meta do
|
||||||
json.articles_count category.articles.published.size
|
json.articles_count category.articles.published.size
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user