parent
7c21cf2255
commit
6bfa551c85
16 changed files with 142 additions and 38 deletions
|
@ -28,7 +28,7 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def campaign_params
|
||||
params.require(:campaign).permit(:title, :description, :message, :enabled, :inbox_id, :sender_id,
|
||||
params.require(:campaign).permit(:title, :description, :message, :enabled, :trigger_only_during_business_hours, :inbox_id, :sender_id,
|
||||
:scheduled_at, audience: [:type, :id], trigger_rules: {})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"ERROR": "Time on page is required"
|
||||
},
|
||||
"ENABLED": "Enable campaign",
|
||||
"TRIGGER_ONLY_BUSINESS_HOURS": "Trigger only during business hours",
|
||||
"SUBMIT": "Add Campaign"
|
||||
},
|
||||
"API": {
|
||||
|
|
|
@ -146,6 +146,15 @@
|
|||
/>
|
||||
{{ $t('CAMPAIGN.ADD.FORM.ENABLED') }}
|
||||
</label>
|
||||
<label v-if="isOngoingType">
|
||||
<input
|
||||
v-model="triggerOnlyDuringBusinessHours"
|
||||
type="checkbox"
|
||||
value="triggerOnlyDuringBusinessHours"
|
||||
name="triggerOnlyDuringBusinessHours"
|
||||
/>
|
||||
{{ $t('CAMPAIGN.ADD.FORM.TRIGGER_ONLY_BUSINESS_HOURS') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
|
@ -185,6 +194,7 @@ export default {
|
|||
timeOnPage: 10,
|
||||
show: true,
|
||||
enabled: true,
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
scheduledAt: null,
|
||||
selectedAudience: [],
|
||||
senderList: [],
|
||||
|
@ -280,6 +290,9 @@ export default {
|
|||
inbox_id: this.selectedInbox,
|
||||
sender_id: this.selectedSender || null,
|
||||
enabled: this.enabled,
|
||||
trigger_only_during_business_hours:
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
this.triggerOnlyDuringBusinessHours,
|
||||
trigger_rules: {
|
||||
url: this.endPoint,
|
||||
time_on_page: this.timeOnPage,
|
||||
|
|
|
@ -87,6 +87,15 @@
|
|||
/>
|
||||
{{ $t('CAMPAIGN.ADD.FORM.ENABLED') }}
|
||||
</label>
|
||||
<label v-if="isOngoingType">
|
||||
<input
|
||||
v-model="triggerOnlyDuringBusinessHours"
|
||||
type="checkbox"
|
||||
value="triggerOnlyDuringBusinessHours"
|
||||
name="triggerOnlyDuringBusinessHours"
|
||||
/>
|
||||
{{ $t('CAMPAIGN.ADD.FORM.TRIGGER_ONLY_BUSINESS_HOURS') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<woot-button :is-loading="uiFlags.isCreating">
|
||||
|
@ -125,6 +134,7 @@ export default {
|
|||
selectedInbox: null,
|
||||
endPoint: '',
|
||||
timeOnPage: 10,
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
show: true,
|
||||
enabled: true,
|
||||
senderList: [],
|
||||
|
@ -209,6 +219,7 @@ export default {
|
|||
title,
|
||||
message,
|
||||
enabled,
|
||||
trigger_only_during_business_hours: triggerOnlyDuringBusinessHours,
|
||||
inbox: { id: inboxId },
|
||||
trigger_rules: { url: endPoint, time_on_page: timeOnPage },
|
||||
sender,
|
||||
|
@ -218,6 +229,7 @@ export default {
|
|||
this.endPoint = endPoint;
|
||||
this.timeOnPage = timeOnPage;
|
||||
this.selectedInbox = inboxId;
|
||||
this.triggerOnlyDuringBusinessHours = triggerOnlyDuringBusinessHours;
|
||||
this.selectedSender = (sender && sender.id) || 0;
|
||||
this.enabled = enabled;
|
||||
this.loadInboxMembers();
|
||||
|
@ -233,6 +245,9 @@ export default {
|
|||
title: this.title,
|
||||
message: this.message,
|
||||
inbox_id: this.$route.params.inboxId,
|
||||
trigger_only_during_business_hours:
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
this.triggerOnlyDuringBusinessHours,
|
||||
sender_id: this.selectedSender || null,
|
||||
enabled: this.enabled,
|
||||
trigger_rules: {
|
||||
|
|
|
@ -19,12 +19,14 @@ import Router from './views/Router';
|
|||
import { getLocale } from './helpers/urlParamsHelper';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Router,
|
||||
},
|
||||
mixins: [availabilityMixin],
|
||||
data() {
|
||||
return {
|
||||
showUnreadView: false,
|
||||
|
@ -219,7 +221,11 @@ export default {
|
|||
this.scrollConversationToBottom();
|
||||
} else if (message.event === 'change-url') {
|
||||
const { referrerURL, referrerHost } = message;
|
||||
this.initCampaigns({ currentURL: referrerURL, websiteToken });
|
||||
this.initCampaigns({
|
||||
currentURL: referrerURL,
|
||||
websiteToken,
|
||||
isInBusinessHours: this.isInBusinessHours,
|
||||
});
|
||||
window.referrerURL = referrerURL;
|
||||
bus.$emit(BUS_EVENTS.SET_REFERRER_HOST, referrerHost);
|
||||
} else if (message.event === 'toggle-close-button') {
|
||||
|
|
|
@ -7,17 +7,24 @@ export const formatCampaigns = ({ campaigns }) => {
|
|||
return campaigns.map(item => {
|
||||
return {
|
||||
id: item.id,
|
||||
triggerOnlyDuringBusinessHours:
|
||||
item.trigger_only_during_business_hours || false,
|
||||
timeOnPage: item?.trigger_rules?.time_on_page,
|
||||
url: item?.trigger_rules?.url,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Find all campaigns that matches the current URL
|
||||
export const filterCampaigns = ({ campaigns, currentURL }) => {
|
||||
return campaigns.filter(
|
||||
item =>
|
||||
stripTrailingSlash({ URL: item.url }) ===
|
||||
stripTrailingSlash({ URL: currentURL })
|
||||
// Filter all campaigns based on current URL and business availability time
|
||||
export const filterCampaigns = ({
|
||||
campaigns,
|
||||
currentURL,
|
||||
isInBusinessHours,
|
||||
}) => {
|
||||
return campaigns.filter(item =>
|
||||
item.triggerOnlyDuringBusinessHours
|
||||
? isInBusinessHours
|
||||
: stripTrailingSlash({ URL: item.url }) ===
|
||||
stripTrailingSlash({ URL: currentURL })
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export default [
|
||||
{
|
||||
id: 1,
|
||||
trigger_only_during_business_hours: false,
|
||||
trigger_rules: {
|
||||
time_on_page: 3,
|
||||
url: 'https://www.chatwoot.com/pricing',
|
||||
|
@ -8,6 +9,7 @@ export default [
|
|||
},
|
||||
{
|
||||
id: 2,
|
||||
trigger_only_during_business_hours: false,
|
||||
trigger_rules: {
|
||||
time_on_page: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
|
@ -3,8 +3,12 @@ import {
|
|||
formatCampaigns,
|
||||
filterCampaigns,
|
||||
} from '../campaignHelper';
|
||||
import campaigns from './camapginFixtures';
|
||||
describe('#Campagin Helper', () => {
|
||||
import campaigns from './campaignFixtures';
|
||||
|
||||
global.chatwootWebChannel = {
|
||||
workingHoursEnabled: false,
|
||||
};
|
||||
describe('#Campaigns Helper', () => {
|
||||
describe('stripTrailingSlash', () => {
|
||||
it('should return striped trailing slash if url with trailing slash is passed', () => {
|
||||
expect(
|
||||
|
@ -14,15 +18,17 @@ describe('#Campagin Helper', () => {
|
|||
});
|
||||
|
||||
describe('formatCampaigns', () => {
|
||||
it('should return formated campaigns if camapgins are passed', () => {
|
||||
it('should return formatted campaigns if campaigns are passed', () => {
|
||||
expect(formatCampaigns({ campaigns })).toStrictEqual([
|
||||
{
|
||||
id: 1,
|
||||
timeOnPage: 3,
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
url: 'https://www.chatwoot.com/pricing',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
timeOnPage: 6,
|
||||
url: 'https://www.chatwoot.com/about',
|
||||
},
|
||||
|
@ -30,7 +36,7 @@ describe('#Campagin Helper', () => {
|
|||
});
|
||||
});
|
||||
describe('filterCampaigns', () => {
|
||||
it('should return filtered campaigns if formatted camapgins are passed', () => {
|
||||
it('should return filtered campaigns if formatted campaigns are passed', () => {
|
||||
expect(
|
||||
filterCampaigns({
|
||||
campaigns: [
|
||||
|
|
|
@ -58,5 +58,9 @@ export default {
|
|||
closeMinute: workingHourConfig.close_minutes,
|
||||
};
|
||||
},
|
||||
isInBusinessHours() {
|
||||
const { workingHoursEnabled } = window.chatwootWebChannel;
|
||||
return workingHoursEnabled ? this.isInBetweenTheWorkingHours : true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,12 +14,18 @@ const state = {
|
|||
activeCampaign: {},
|
||||
};
|
||||
|
||||
const resetCampaignTimers = (campaigns, currentURL, websiteToken) => {
|
||||
const resetCampaignTimers = (
|
||||
campaigns,
|
||||
currentURL,
|
||||
websiteToken,
|
||||
isInBusinessHours
|
||||
) => {
|
||||
const formattedCampaigns = formatCampaigns({ campaigns });
|
||||
// Find all campaigns that matches the current URL
|
||||
const filteredCampaigns = filterCampaigns({
|
||||
campaigns: formattedCampaigns,
|
||||
currentURL,
|
||||
isInBusinessHours,
|
||||
});
|
||||
campaignTimer.initTimers({ campaigns: filteredCampaigns }, websiteToken);
|
||||
};
|
||||
|
@ -31,13 +37,21 @@ export const getters = {
|
|||
};
|
||||
|
||||
export const actions = {
|
||||
fetchCampaigns: async ({ commit }, { websiteToken, currentURL }) => {
|
||||
fetchCampaigns: async (
|
||||
{ commit },
|
||||
{ websiteToken, currentURL, isInBusinessHours }
|
||||
) => {
|
||||
try {
|
||||
const { data: campaigns } = await getCampaigns(websiteToken);
|
||||
commit('setCampaigns', campaigns);
|
||||
commit('setError', false);
|
||||
commit('setHasFetched', true);
|
||||
resetCampaignTimers(campaigns, currentURL, websiteToken);
|
||||
resetCampaignTimers(
|
||||
campaigns,
|
||||
currentURL,
|
||||
websiteToken,
|
||||
isInBusinessHours
|
||||
);
|
||||
} catch (error) {
|
||||
commit('setError', true);
|
||||
commit('setHasFetched', true);
|
||||
|
@ -45,12 +59,21 @@ export const actions = {
|
|||
},
|
||||
initCampaigns: async (
|
||||
{ getters: { getCampaigns: campaigns }, dispatch },
|
||||
{ currentURL, websiteToken }
|
||||
{ currentURL, websiteToken, isInBusinessHours }
|
||||
) => {
|
||||
if (!campaigns.length) {
|
||||
dispatch('fetchCampaigns', { websiteToken, currentURL });
|
||||
dispatch('fetchCampaigns', {
|
||||
websiteToken,
|
||||
currentURL,
|
||||
isInBusinessHours,
|
||||
});
|
||||
} else {
|
||||
resetCampaignTimers(campaigns, currentURL, websiteToken);
|
||||
resetCampaignTimers(
|
||||
campaigns,
|
||||
currentURL,
|
||||
websiteToken,
|
||||
isInBusinessHours
|
||||
);
|
||||
}
|
||||
},
|
||||
startCampaign: async ({ commit }, { websiteToken, campaignId }) => {
|
||||
|
|
|
@ -15,7 +15,11 @@ describe('#actions', () => {
|
|||
API.get.mockResolvedValue({ data: campaigns });
|
||||
await actions.fetchCampaigns(
|
||||
{ commit },
|
||||
{ websiteToken: 'XDsafmADasd', currentURL: 'https://chatwoot.com' }
|
||||
{
|
||||
websiteToken: 'XDsafmADasd',
|
||||
currentURL: 'https://chatwoot.com',
|
||||
isInBusinessHours: true,
|
||||
}
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['setCampaigns', campaigns],
|
||||
|
@ -25,7 +29,12 @@ describe('#actions', () => {
|
|||
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
|
||||
{
|
||||
campaigns: [
|
||||
{ id: 11, timeOnPage: '20', url: 'https://chatwoot.com' },
|
||||
{
|
||||
id: 11,
|
||||
timeOnPage: '20',
|
||||
url: 'https://chatwoot.com',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
'XDsafmADasd'
|
||||
|
@ -35,7 +44,11 @@ describe('#actions', () => {
|
|||
API.get.mockRejectedValue({ message: 'Authentication required' });
|
||||
await actions.fetchCampaigns(
|
||||
{ commit },
|
||||
{ websiteToken: 'XDsafmADasd', currentURL: 'https://www.chatwoot.com' }
|
||||
{
|
||||
websiteToken: 'XDsafmADasd',
|
||||
currentURL: 'https://www.chatwoot.com',
|
||||
isInBusinessHours: true,
|
||||
}
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['setError', true],
|
||||
|
@ -65,7 +78,12 @@ describe('#actions', () => {
|
|||
expect(campaignTimer.initTimers).toHaveBeenCalledWith(
|
||||
{
|
||||
campaigns: [
|
||||
{ id: 11, timeOnPage: '20', url: 'https://chatwoot.com' },
|
||||
{
|
||||
id: 11,
|
||||
timeOnPage: '20',
|
||||
url: 'https://chatwoot.com',
|
||||
triggerOnlyDuringBusinessHours: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
'XDsafmADasd'
|
||||
|
|
|
@ -2,22 +2,23 @@
|
|||
#
|
||||
# Table name: campaigns
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# audience :jsonb
|
||||
# campaign_status :integer default("active"), not null
|
||||
# campaign_type :integer default("ongoing"), not null
|
||||
# description :text
|
||||
# enabled :boolean default(TRUE)
|
||||
# message :text not null
|
||||
# scheduled_at :datetime
|
||||
# title :string not null
|
||||
# trigger_rules :jsonb
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# display_id :integer not null
|
||||
# inbox_id :bigint not null
|
||||
# sender_id :integer
|
||||
# id :bigint not null, primary key
|
||||
# audience :jsonb
|
||||
# campaign_status :integer default("active"), not null
|
||||
# campaign_type :integer default("ongoing"), not null
|
||||
# description :text
|
||||
# enabled :boolean default(TRUE)
|
||||
# message :text not null
|
||||
# scheduled_at :datetime
|
||||
# title :string not null
|
||||
# trigger_only_during_business_hours :boolean default(FALSE)
|
||||
# trigger_rules :jsonb
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint not null
|
||||
# display_id :integer not null
|
||||
# inbox_id :bigint not null
|
||||
# sender_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -17,5 +17,6 @@ if resource.campaign_type == 'one_off'
|
|||
json.audience resource.audience
|
||||
end
|
||||
json.trigger_rules resource.trigger_rules
|
||||
json.trigger_only_during_business_hours resource.trigger_only_during_business_hours
|
||||
json.created_at resource.created_at
|
||||
json.updated_at resource.updated_at
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
json.array! @campaigns do |campaign|
|
||||
json.id campaign.display_id
|
||||
json.trigger_rules campaign.trigger_rules
|
||||
json.trigger_only_during_business_hours campaign.trigger_only_during_business_hours
|
||||
json.message campaign.message
|
||||
json.sender campaign.sender&.slice(:name, :avatar_url)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddTriggerOnlyDuringBusinessHoursCollectToCampaigns < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :campaigns, :trigger_only_during_business_hours, :boolean, default: false
|
||||
end
|
||||
end
|
|
@ -147,6 +147,7 @@ ActiveRecord::Schema.define(version: 2021_09_29_150415) do
|
|||
t.integer "campaign_status", default: 0, null: false
|
||||
t.jsonb "audience", default: []
|
||||
t.datetime "scheduled_at"
|
||||
t.boolean "trigger_only_during_business_hours", default: false
|
||||
t.index ["account_id"], name: "index_campaigns_on_account_id"
|
||||
t.index ["campaign_status"], name: "index_campaigns_on_campaign_status"
|
||||
t.index ["campaign_type"], name: "index_campaigns_on_campaign_type"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue