Skip to content

Commit

Permalink
chore: Execute campaigns based on matching URL (chatwoot#2254)
Browse files Browse the repository at this point in the history
  • Loading branch information
muhsin-k authored May 17, 2021
1 parent 18cea3b commit 610a7c6
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 45 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "es5"
"trailingComma": "es5",
"arrowParens": "avoid"
}
36 changes: 36 additions & 0 deletions app/javascript/sdk/DOMHelpers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SDK_CSS } from './sdk.js';
import { IFrameHelper } from './IFrameHelper';

export const loadCSS = () => {
const css = document.createElement('style');
Expand Down Expand Up @@ -65,3 +66,38 @@ export const toggleClass = (elm, classes) => {
export const removeClass = (elm, classes) => {
classHelper(classes, 'remove', elm);
};

export const onLocationChange = ({ referrerURL, referrerHost }) => {
IFrameHelper.events.onLocationChange({
referrerURL,
referrerHost,
});
};

export const onLocationChangeListener = () => {
let oldHref = document.location.href;
const referrerHost = document.location.host;
const config = {
childList: true,
subtree: true,
};
onLocationChange({
referrerURL: oldHref,
referrerHost,
});

const bodyList = document.querySelector('body');
const observer = new MutationObserver(mutations => {
mutations.forEach(() => {
if (oldHref !== document.location.href) {
oldHref = document.location.href;
onLocationChange({
referrerURL: oldHref,
referrerHost,
});
}
});
});

observer.observe(bodyList, config);
};
28 changes: 12 additions & 16 deletions app/javascript/sdk/IFrameHelper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import Cookies from 'js-cookie';
import { wootOn, addClass, loadCSS, removeClass } from './DOMHelpers';
import {
wootOn,
addClass,
loadCSS,
removeClass,
onLocationChangeListener,
} from './DOMHelpers';
import {
body,
widgetHolder,
Expand Down Expand Up @@ -47,7 +53,6 @@ export const IFrameHelper = {
widgetHolder.appendChild(iframe);
body.appendChild(widgetHolder);
IFrameHelper.initPostMessageCommunication();
IFrameHelper.initLocationListener();
IFrameHelper.initWindowSizeListener();
IFrameHelper.preventDefaultScroll();
},
Expand All @@ -60,11 +65,6 @@ export const IFrameHelper = {
'*'
);
},
initLocationListener: () => {
window.onhashchange = () => {
IFrameHelper.setCurrentUrl();
};
},
initPostMessageCommunication: () => {
window.onmessage = e => {
if (
Expand Down Expand Up @@ -113,7 +113,6 @@ export const IFrameHelper = {
IFrameHelper.onLoad({
widgetColor: message.config.channelConfig.widgetColor,
});
IFrameHelper.setCurrentUrl();
IFrameHelper.toggleCloseButton();

if (window.$chatwoot.user) {
Expand All @@ -140,6 +139,9 @@ export const IFrameHelper = {
IFrameHelper.pushEvent('webwidget.triggered');
}
},
onLocationChange: ({ referrerURL, referrerHost }) => {
IFrameHelper.sendMessage('change-url', { referrerURL, referrerHost });
},

setUnreadMode: message => {
const { unreadMessageCount } = message;
Expand Down Expand Up @@ -167,6 +169,7 @@ export const IFrameHelper = {
pushEvent: eventName => {
IFrameHelper.sendMessage('push-event', { eventName });
},

onLoad: ({ widgetColor }) => {
const iframe = IFrameHelper.getAppFrame();
iframe.style.visibility = '';
Expand All @@ -175,9 +178,8 @@ export const IFrameHelper = {
if (IFrameHelper.getBubbleHolder().length) {
return;
}

createBubbleHolder();

onLocationChangeListener();
if (!window.$chatwoot.hideMessageBubble) {
const chatIcon = createBubbleIcon({
className: 'woot-widget-bubble',
Expand All @@ -198,12 +200,6 @@ export const IFrameHelper = {
onClickChatBubble();
}
},
setCurrentUrl: () => {
IFrameHelper.sendMessage('set-current-url', {
referrerURL: window.location.href,
referrerHost: window.location.host,
});
},
toggleCloseButton: () => {
if (window.matchMedia('(max-width: 668px)').matches) {
IFrameHelper.sendMessage('toggle-close-button', {
Expand Down
13 changes: 7 additions & 6 deletions app/javascript/widget/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -37,6 +36,7 @@ export default {
...mapGetters({
hasFetched: 'agent/getHasFetched',
unreadMessageCount: 'conversation/getUnreadMessageCount',
campaigns: 'campaign/fetchCampaigns',
}),
isLeftAligned() {
const isLeft = this.widgetPosition === 'left';
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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') {
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/widget/api/campaign.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/widget/api/endPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -79,7 +79,7 @@ const triggerCampaign = (token, campaignId) => ({
},
},
params: {
website_token: token,
website_token: websiteToken,
},
});

Expand Down
24 changes: 24 additions & 0 deletions app/javascript/widget/api/specs/endPoints.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});
});
});
23 changes: 23 additions & 0 deletions app/javascript/widget/helpers/campaignHelper.js
Original file line number Diff line number Diff line change
@@ -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 })
);
};
35 changes: 23 additions & 12 deletions app/javascript/widget/helpers/campaignTimer.js
Original file line number Diff line number Diff line change
@@ -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();
16 changes: 16 additions & 0 deletions app/javascript/widget/helpers/specs/camapginFixtures.js
Original file line number Diff line number Diff line change
@@ -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',
},
},
];
59 changes: 59 additions & 0 deletions app/javascript/widget/helpers/specs/campaignHelper.spec.js
Original file line number Diff line number Diff line change
@@ -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',
},
]);
});
});
});
Loading

0 comments on commit 610a7c6

Please sign in to comment.