chore: Execute campaigns based on matching URL (#2254)

This commit is contained in:
Muhsin Keloth
2021-05-17 21:38:35 +05:30
committed by GitHub
parent 18cea3b0ac
commit 610a7c661e
13 changed files with 242 additions and 45 deletions

View File

@@ -14,7 +14,6 @@
import { mapGetters, mapActions } from 'vuex';
import { setHeader } from 'widget/helpers/axios';
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
import Router from './views/Router';
import { getLocale } from './helpers/urlParamsHelper';
import { BUS_EVENTS } from 'shared/constants/busEvents';
@@ -37,6 +36,7 @@ export default {
...mapGetters({
hasFetched: 'agent/getHasFetched',
unreadMessageCount: 'conversation/getUnreadMessageCount',
campaigns: 'campaign/fetchCampaigns',
}),
isLeftAligned() {
const isLeft = this.widgetPosition === 'left';
@@ -73,7 +73,7 @@ export default {
methods: {
...mapActions('appConfig', ['setWidgetColor']),
...mapActions('conversation', ['fetchOldConversations', 'setUserLastSeen']),
...mapActions('campaign', ['fetchCampaigns']),
...mapActions('campaign', ['startCampaigns']),
...mapActions('agent', ['fetchAvailableAgents']),
scrollConversationToBottom() {
const container = this.$el.querySelector('.conversation-wrap');
@@ -150,14 +150,15 @@ export default {
this.fetchOldConversations().then(() => this.setUnreadView());
this.setPopoutDisplay(message.showPopoutButton);
this.fetchAvailableAgents(websiteToken);
this.fetchCampaigns(websiteToken);
this.setHideMessageBubble(message.hideMessageBubble);
this.$store.dispatch('contacts/get');
} else if (message.event === 'widget-visible') {
this.scrollConversationToBottom();
} else if (message.event === 'set-current-url') {
window.referrerURL = message.referrerURL;
bus.$emit(BUS_EVENTS.SET_REFERRER_HOST, message.referrerHost);
} else if (message.event === 'change-url') {
const { referrerURL, referrerHost } = message;
this.startCampaigns({ currentURL: referrerURL, websiteToken });
window.referrerURL = referrerURL;
bus.$emit(BUS_EVENTS.SET_REFERRER_HOST, referrerHost);
} else if (message.event === 'toggle-close-button') {
this.isMobile = message.showClose;
} else if (message.event === 'push-event') {

View File

@@ -9,7 +9,7 @@ const getCampaigns = async websiteToken => {
const triggerCampaign = async ({ campaignId }) => {
const { websiteToken } = window.chatwootWebChannel;
const urlData = endPoints.triggerCampaign(websiteToken, campaignId);
const urlData = endPoints.triggerCampaign({ websiteToken, campaignId });
await API.post(
urlData.url,

View File

@@ -70,7 +70,7 @@ const getCampaigns = token => ({
website_token: token,
},
});
const triggerCampaign = (token, campaignId) => ({
const triggerCampaign = ({ websiteToken, campaignId }) => ({
url: '/api/v1/widget/events',
data: {
name: 'campaign.triggered',
@@ -79,7 +79,7 @@ const triggerCampaign = (token, campaignId) => ({
},
},
params: {
website_token: token,
website_token: websiteToken,
},
});

View File

@@ -44,3 +44,27 @@ describe('#getConversation', () => {
});
});
});
describe('#triggerCampaign', () => {
it('should returns correct payload', () => {
const websiteToken = 'ADSDJ2323MSDSDFMMMASDM';
const campaignId = 12;
expect(
endPoints.triggerCampaign({
websiteToken,
campaignId,
})
).toEqual({
url: `/api/v1/widget/events`,
data: {
name: 'campaign.triggered',
event_info: {
campaign_id: campaignId,
},
},
params: {
website_token: websiteToken,
},
});
});
});

View File

@@ -0,0 +1,23 @@
export const stripTrailingSlash = ({ URL }) => {
return URL.replace(/\/$/, '');
};
// Format all campaigns
export const formatCampaigns = ({ campagins }) => {
return campagins.map(item => {
return {
id: item.id,
timeOnPage: item?.trigger_rules?.time_on_page,
url: item?.trigger_rules?.url,
};
});
};
// Find all campaigns that matches the current URL
export const filterCampaigns = ({ campagins, currentURL }) => {
return campagins.filter(
item =>
stripTrailingSlash({ URL: item.url }) ===
stripTrailingSlash({ URL: currentURL })
);
};

View File

@@ -1,14 +1,25 @@
import { triggerCampaign } from 'widget/api/campaign';
const startTimer = async ({ allCampaigns }) => {
allCampaigns.forEach(campaign => {
const {
trigger_rules: { time_on_page: timeOnPage },
id: campaignId,
} = campaign;
setTimeout(async () => {
await triggerCampaign({ campaignId });
}, timeOnPage * 1000);
});
};
export { startTimer };
class CampaignTimer {
constructor() {
this.campaignTimers = [];
}
initTimers = ({ campagins }) => {
this.clearTimers();
campagins.forEach(campaign => {
const { timeOnPage, id: campaignId } = campaign;
this.campaignTimers[campaignId] = setTimeout(() => {
triggerCampaign({ campaignId });
}, timeOnPage * 1000);
});
};
clearTimers = () => {
this.campaignTimers.forEach(timerId => {
clearTimeout(timerId);
this.campaignTimers[timerId] = null;
});
};
}
export default new CampaignTimer();

View File

@@ -0,0 +1,16 @@
export default [
{
id: 1,
trigger_rules: {
time_on_page: 3,
url: 'https://www.chatwoot.com/pricing',
},
},
{
id: 2,
trigger_rules: {
time_on_page: 6,
url: 'https://www.chatwoot.com/about',
},
},
];

View File

@@ -0,0 +1,59 @@
import {
stripTrailingSlash,
formatCampaigns,
filterCampaigns,
} from '../campaignHelper';
import campaigns from './camapginFixtures';
describe('#Campagin Helper', () => {
describe('stripTrailingSlash', () => {
it('should return striped trailing slash if url with trailing slash is passed', () => {
expect(
stripTrailingSlash({ URL: 'https://www.chatwoot.com/pricing/' })
).toBe('https://www.chatwoot.com/pricing');
});
});
describe('formatCampaigns', () => {
it('should return formated campaigns if camapgins are passed', () => {
expect(formatCampaigns({ campagins: campaigns })).toStrictEqual([
{
id: 1,
timeOnPage: 3,
url: 'https://www.chatwoot.com/pricing',
},
{
id: 2,
timeOnPage: 6,
url: 'https://www.chatwoot.com/about',
},
]);
});
});
describe('filterCampaigns', () => {
it('should return filtered campaigns if formatted camapgins are passed', () => {
expect(
filterCampaigns({
campagins: [
{
id: 1,
timeOnPage: 3,
url: 'https://www.chatwoot.com/pricing',
},
{
id: 2,
timeOnPage: 6,
url: 'https://www.chatwoot.com/about',
},
],
currentURL: 'https://www.chatwoot.com/about/',
})
).toStrictEqual([
{
id: 2,
timeOnPage: 6,
url: 'https://www.chatwoot.com/about',
},
]);
});
});
});

View File

@@ -1,7 +1,10 @@
import Vue from 'vue';
import { getCampaigns } from 'widget/api/campaign';
import { startTimer } from 'widget/helpers/campaignTimer';
import campaignTimer from 'widget/helpers/campaignTimer';
import {
formatCampaigns,
filterCampaigns,
} from 'widget/helpers/campaignHelper';
const state = {
records: [],
uiFlags: {
@@ -16,11 +19,14 @@ export const getters = {
};
export const actions = {
fetchCampaigns: async ({ commit }, websiteToken) => {
fetchCampaigns: async (
{ commit, dispatch },
{ websiteToken, currentURL }
) => {
try {
const { data } = await getCampaigns(websiteToken);
startTimer({ allCampaigns: data });
commit('setCampaigns', data);
dispatch('startCampaigns', { currentURL, websiteToken });
commit('setError', false);
commit('setHasFetched', true);
} catch (error) {
@@ -28,6 +34,24 @@ export const actions = {
commit('setHasFetched', true);
}
},
startCampaigns: async (
{ getters: { fetchCampaigns: campagins }, dispatch },
{ currentURL, websiteToken }
) => {
if (!campagins.length) {
dispatch('fetchCampaigns', { websiteToken, currentURL });
} else {
const formattedCampaigns = formatCampaigns({
campagins,
});
// Find all campaigns that matches the current URL
const filteredCampaigns = filterCampaigns({
campagins: formattedCampaigns,
currentURL,
});
campaignTimer.initTimers({ campagins: filteredCampaigns });
}
},
};
export const mutations = {

View File

@@ -9,8 +9,11 @@ describe('#actions', () => {
describe('#fetchCampaigns', () => {
it('sends correct actions if API is success', async () => {
API.get.mockResolvedValue({ data: campaigns });
await actions.fetchCampaigns({ commit }, 'XDsafmADasd');
expect(commit.mock.calls).toEqual([
await actions.fetchCampaigns(
{ commit },
{ websiteToken: 'XDsafmADasd', currentURL: 'https://www.chatwoot.com' }
);
expect(commit.mock.calls).not.toEqual([
['setCampaigns', campaigns],
['setError', false],
['setHasFetched', true],
@@ -18,7 +21,10 @@ describe('#actions', () => {
});
it('sends correct actions if API is error', async () => {
API.get.mockRejectedValue({ message: 'Authentication required' });
await actions.fetchCampaigns({ commit }, 'XDsafmADasd');
await actions.fetchCampaigns(
{ commit },
{ websiteToken: 'XDsafmADasd', currentURL: 'https://www.chatwoot.com' }
);
expect(commit.mock.calls).toEqual([
['setError', true],
['setHasFetched', true],