Merge branch 'develop' into design/conv-card

This commit is contained in:
Sivin Varghese 2022-12-15 11:01:24 +05:30 committed by GitHub
commit 3d136a79fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 273 additions and 68 deletions

View file

@ -398,7 +398,7 @@ GEM
llhttp-ffi (0.4.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
loofah (2.18.0)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -488,8 +488,8 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
railties (6.1.6.1)
actionpack (= 6.1.6.1)
activesupport (= 6.1.6.1)

View file

@ -1,8 +1,7 @@
class Platform::Api::V1::AccountsController < PlatformController
def create
@resource = Account.new(account_params)
@resource = Account.create!(account_params)
update_resource_features
@resource.save!
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
end

View file

@ -29,6 +29,7 @@ const primaryMenuItems = accountId => [
icon: 'megaphone',
key: 'campaigns',
label: 'CAMPAIGNS',
featureFlag: 'campaigns',
toState: frontendURL(`accounts/${accountId}/campaigns`),
toStateName: 'settings_account_campaigns',
roles: ['administrator'],

View file

@ -67,6 +67,9 @@ export default {
if (Object.keys(this.enabledFeatures).length === 0) {
return false;
}
if (key === 'website') {
return this.enabledFeatures.channel_website;
}
if (key === 'facebook') {
return this.enabledFeatures.channel_facebook;
}

View file

@ -61,6 +61,7 @@ export default {
}
.colorpicker--selected {
border: 1px solid var(--color-border-light);
border-radius: $space-smaller;
cursor: pointer;
height: $space-large;

View file

@ -17,13 +17,22 @@ const formatArray = params => {
return params;
};
const generatePayloadForObject = item => {
if (item.action_params.id) {
item.action_params = [item.action_params.id];
} else {
item.action_params = [item.action_params];
}
return item.action_params;
};
const generatePayload = data => {
const actions = JSON.parse(JSON.stringify(data));
let payload = actions.map(item => {
if (Array.isArray(item.action_params)) {
item.action_params = formatArray(item.action_params);
} else if (typeof item.action_params === 'object') {
item.action_params = [item.action_params.id];
item.action_params = generatePayloadForObject(item);
} else if (!item.action_params) {
item.action_params = [];
} else {

View file

@ -134,7 +134,7 @@
"PHONE_NUMBER": {
"LABEL": "Phone number",
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
},
"API_CALLBACK": {
"TITLE": "Callback URL",
@ -185,7 +185,7 @@
"PHONE_NUMBER": {
"LABEL": "Phone number",
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
},
"SUBMIT_BUTTON": "Create Bandwidth Channel",
"API": {
@ -214,7 +214,7 @@
"PHONE_NUMBER": {
"LABEL": "Phone number",
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
},
"PHONE_NUMBER_ID": {
"LABEL": "Phone number ID",

View file

@ -69,11 +69,11 @@ export default {
}
.center--img {
left: 5%;
max-height: 86%;
max-width: 90%;
height: 96%;
left: 8%;
position: absolute;
top: 2%;
top: 8%;
width: 86%;
}
.center-container {
@ -101,7 +101,7 @@ export default {
align-items: flex-start;
display: flex;
justify-content: center;
padding: var(--space-larger);
padding: var(--space-large);
}
.testimonial-left--card {

View file

@ -171,11 +171,12 @@
<script>
import { mapGetters } from 'vuex';
import { required, url, minLength } from 'vuelidate/lib/validators';
import { required } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
import campaignMixin from 'shared/mixins/campaignMixin';
import WootDateTimePicker from 'dashboard/components/ui/DateTimePicker.vue';
import { URLPattern } from 'urlpattern-polyfill';
export default {
components: {
@ -221,8 +222,23 @@ export default {
},
endPoint: {
required,
minLength: minLength(7),
url,
shouldBeAValidURLPattern(value) {
try {
// eslint-disable-next-line
new URLPattern(value);
return true;
} catch (error) {
return false;
}
},
shouldStartWithHTTP(value) {
if (value) {
return (
value.startsWith('https://') || value.startsWith('http://')
);
}
return false;
},
},
timeOnPage: {
required,

View file

@ -30,7 +30,7 @@
<label :class="{ error: $v.selectedInbox.$error }">
{{ $t('CAMPAIGN.ADD.FORM.INBOX.LABEL') }}
<select v-model="selectedInbox" @change="onChangeInbox($event)">
<option v-for="item in inboxes" :key="item.name" :value="item.id">
<option v-for="item in inboxes" :key="item.id" :value="item.id">
{{ item.name }}
</option>
</select>
@ -111,10 +111,12 @@
<script>
import { mapGetters } from 'vuex';
import { required, url, minLength } from 'vuelidate/lib/validators';
import { required } from 'vuelidate/lib/validators';
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
import alertMixin from 'shared/mixins/alertMixin';
import campaignMixin from 'shared/mixins/campaignMixin';
import { URLPattern } from 'urlpattern-polyfill';
export default {
components: {
WootMessageEditor,
@ -152,8 +154,21 @@ export default {
},
endPoint: {
required,
minLength: minLength(7),
url,
shouldBeAValidURLPattern(value) {
try {
// eslint-disable-next-line
new URLPattern(value);
return true;
} catch (error) {
return false;
}
},
shouldStartWithHTTP(value) {
if (value) {
return value.startsWith('https://') || value.startsWith('http://');
}
return false;
},
},
timeOnPage: {
required,

View file

@ -62,7 +62,7 @@ import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import router from '../../../../index';
const shouldStartWithPlusSign = (value = '') => value.startsWith('+');
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
export default {
mixins: [alertMixin],
@ -78,7 +78,7 @@ export default {
},
validations: {
inboxName: { required },
phoneNumber: { required, shouldStartWithPlusSign },
phoneNumber: { required, isPhoneE164OrEmpty },
apiKey: { required },
},
methods: {

View file

@ -99,8 +99,7 @@ import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import router from '../../../../index';
const shouldStartWithPlusSign = (value = '') => value.startsWith('+');
import { isPhoneE164OrEmpty, isNumber } from 'shared/helpers/Validators';
export default {
mixins: [alertMixin],
@ -118,10 +117,10 @@ export default {
},
validations: {
inboxName: { required },
phoneNumber: { required, shouldStartWithPlusSign },
phoneNumber: { required, isPhoneE164OrEmpty },
apiKey: { required },
phoneNumberId: { required },
businessAccountId: { required },
phoneNumberId: { required, isNumber },
businessAccountId: { required, isNumber },
},
methods: {
async createChannel() {

View file

@ -110,8 +110,7 @@ import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { required } from 'vuelidate/lib/validators';
import router from '../../../../index';
const shouldStartWithPlusSign = (value = '') => value.startsWith('+');
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
export default {
mixins: [alertMixin],
@ -142,7 +141,7 @@ export default {
return {
channelName: { required },
messagingServiceSID: {},
phoneNumber: { shouldStartWithPlusSign },
phoneNumber: { required, isPhoneE164OrEmpty },
authToken: { required },
accountSID: { required },
medium: { required },

View file

@ -10,7 +10,7 @@ import {
widgetHolder,
createBubbleHolder,
createBubbleIcon,
bubbleImg,
bubbleSVG,
chatBubble,
closeBubble,
bubbleHolder,
@ -21,6 +21,7 @@ import {
addUnreadClass,
removeUnreadClass,
} from './bubbleHelpers';
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
import { dispatchWindowEvent } from 'shared/helpers/CustomEventHelper';
import { CHATWOOT_ERROR, CHATWOOT_READY } from '../widget/constants/sdkEvents';
import { SET_USER_ERROR } from '../widget/constants/errorTypes';
@ -277,9 +278,14 @@ export const IFrameHelper = {
closeBtnClassName += ' woot-widget-bubble--flat';
}
if (isWidgetColorLighter(widgetColor)) {
className += ' woot-widget-bubble-color--lighter';
closeBtnClassName += ' woot-widget-bubble-color--lighter';
}
const chatIcon = createBubbleIcon({
className,
src: bubbleImg,
path: bubbleSVG,
target: chatBubble,
});

View file

@ -2,8 +2,8 @@ import { addClasses, removeClasses, toggleClass } from './DOMHelpers';
import { IFrameHelper } from './IFrameHelper';
import { isExpandedView } from './settingsHelper';
export const bubbleImg =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAAwgJEBk0TVheY2R5eo+ut8jb5OXs8fX2+cjRDTIAAADsSURBVHgBldZbkoMgFIThRgQv8SKKgGf/C51UnJqaRI30/9zfe+NQUQ3TvG7bOk9DVeCmshmj/CuOTYnrdBfkUOg0zlOtl9OWVuEk4+QyZ3DIevmSt/ioTvK1VH/s5bY3YdM9SBZ/mUUyWgx+U06ycgp7D8msxSvtc4HXL9BLdj2elSEfhBJAI0QNgJEBI1BEBsQClVBVGDgwYOLAhJkDM1YOrNg4sLFAsLJgZsHEgoEFFQt0JAFGFjQsKAMJ0LFAexKgZYFyJIDxJIBNJEDNAtSJBLCeBDCOBFAPzwFA94ED+zmhwDO9358r8ANtIsMXi7qVAwAAAABJRU5ErkJggg==';
export const bubbleSVG =
'M240.808 240.808H122.123C56.6994 240.808 3.45695 187.562 3.45695 122.122C3.45695 56.7031 56.6994 3.45697 122.124 3.45697C187.566 3.45697 240.808 56.7031 240.808 122.122V240.808Z';
export const body = document.getElementsByTagName('body')[0];
export const widgetHolder = document.createElement('div');
@ -20,11 +20,27 @@ export const setBubbleText = bubbleText => {
}
};
export const createBubbleIcon = ({ className, src, target }) => {
export const createBubbleIcon = ({ className, path, target }) => {
let bubbleClassName = `${className} woot-elements--${window.$chatwoot.position}`;
const bubbleIcon = document.createElement('img');
bubbleIcon.src = src;
bubbleIcon.alt = 'bubble-icon';
const bubbleIcon = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
bubbleIcon.setAttributeNS(null, 'id', 'woot-widget-bubble-icon');
bubbleIcon.setAttributeNS(null, 'width', '24');
bubbleIcon.setAttributeNS(null, 'height', '24');
bubbleIcon.setAttributeNS(null, 'viewBox', '0 0 240 240');
bubbleIcon.setAttributeNS(null, 'fill', 'none');
bubbleIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
const bubblePath = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
bubblePath.setAttributeNS(null, 'd', path);
bubblePath.setAttributeNS(null, 'fill', '#FFFFFF');
bubbleIcon.appendChild(bubblePath);
target.appendChild(bubbleIcon);
if (isExpandedView(window.$chatwoot.type)) {

View file

@ -1,6 +1,7 @@
export const SDK_CSS = `
:root {
--b-100: #F2F3F7;
--s-700: #37546D;
}
.woot-widget-holder {
@ -64,7 +65,7 @@ export const SDK_CSS = `
width: 56px;
}
.woot-widget-bubble.woot-widget-bubble--flat img {
.woot-widget-bubble.woot-widget-bubble--flat svg {
margin: 16px;
}
@ -107,7 +108,11 @@ export const SDK_CSS = `
width: auto !important;
}
.woot-widget-bubble.woot-widget--expanded img {
.woot-widget-bubble.woot-widget--expanded.woot-widget-bubble-color--lighter div{
color: var(--s-700);
}
.woot-widget-bubble.woot-widget--expanded svg {
height: 20px;
margin: 14px 8px 14px 16px;
width: 20px;
@ -126,13 +131,17 @@ export const SDK_CSS = `
box-shadow: 0 8px 32px rgba(0, 0, 0, .4) !important;
}
.woot-widget-bubble img {
.woot-widget-bubble svg {
all: revert;
height: 24px;
margin: 20px;
width: 24px;
}
.woot-widget-bubble.woot-widget-bubble-color--lighter path{
fill: var(--s-700);
}
@media only screen and (min-width: 667px) {
.woot-widget-holder.woot-elements--left {
left: 20px;
@ -157,6 +166,10 @@ export const SDK_CSS = `
width: 2px;
}
.woot-widget-bubble-color--lighter.woot--close::before, .woot-widget-bubble-color--lighter.woot--close::after {
background-color: var(--s-700);
}
.woot--close::before {
transform: rotate(45deg);
}

View file

@ -33,7 +33,7 @@ export default {
margin-left: -$space-one;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.7);
border-top-color: lighten($color-woot, 10%);
border-top-color: rgba(255, 255, 255, 0.2);
animation: spinner 0.9s linear infinite;
}
}

View file

@ -16,3 +16,4 @@ export const isValidPassword = value => {
containsSpecialCharacter
);
};
export const isNumber = value => /^\d+$/.test(value);

View file

@ -0,0 +1,8 @@
export const isWidgetColorLighter = color => {
const colorToCheck = color.replace('#', '');
const c_r = parseInt(colorToCheck.substr(0, 2), 16);
const c_g = parseInt(colorToCheck.substr(2, 2), 16);
const c_b = parseInt(colorToCheck.substr(4, 2), 16);
const brightness = (c_r * 299 + c_g * 587 + c_b * 114) / 1000;
return brightness > 225;
};

View file

@ -1,5 +1,6 @@
import { shouldBeUrl } from '../Validators';
import { isValidPassword } from '../Validators';
import { isNumber } from '../Validators';
describe('#shouldBeUrl', () => {
it('should return correct url', () => {
@ -22,3 +23,15 @@ describe('#isValidPassword', () => {
expect(isValidPassword('testPass!')).toEqual(false);
});
});
describe('#isNumber', () => {
it('should return correct number', () => {
expect(isNumber('123')).toEqual(true);
});
it('should return wrong number', () => {
expect(isNumber('123-')).toEqual(false);
expect(isNumber('123./')).toEqual(false);
expect(isNumber('string')).toEqual(false);
});
});

View file

@ -0,0 +1,10 @@
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
describe('#isWidgetColorLighter', () => {
it('returns true if color is lighter', () => {
expect(isWidgetColorLighter('#ffffff')).toEqual(true);
});
it('returns false if color is darker', () => {
expect(isWidgetColorLighter('#000000')).toEqual(false);
});
});

View file

@ -1,10 +1,10 @@
<template>
<div class="file flex flex-row items-center p-3 cursor-pointer">
<div class="icon-wrap">
<div class="icon-wrap" :style="{ color: textColor }">
<fluent-icon icon="document" size="28" />
</div>
<div class="meta">
<div class="title">
<div class="title" :style="{ color: textColor }">
{{ title }}
</div>
<div class="link-wrap mb-1">
@ -12,6 +12,7 @@
class="download"
rel="noreferrer noopener nofollow"
target="_blank"
:style="{ color: textColor }"
:href="url"
>
{{ $t('COMPONENTS.FILE_BUBBLE.DOWNLOAD') }}
@ -23,6 +24,7 @@
<script>
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import { getContrastingTextColor } from '@chatwoot/utils';
export default {
components: {
@ -51,6 +53,9 @@ export default {
fileName() {
return this.url.substring(this.url.lastIndexOf('/') + 1);
},
textColor() {
return getContrastingTextColor(this.widgetColor);
},
},
methods: {
openLink() {

View file

@ -14,6 +14,12 @@
<div v-if="error" class="text-red-400 mt-2 text-xs font-medium">
{{ error }}
</div>
<div
v-if="!error && helpText"
class="text-red-400 mt-2 text-xs font-medium"
>
{{ helpText }}
</div>
</label>
</template>
<script>
@ -41,6 +47,10 @@ export default {
type: String,
default: '',
},
helpText: {
type: String,
default: '',
},
},
computed: {
labelClass() {

View file

@ -27,7 +27,15 @@
class="button clear-button"
@click="openConversationView"
>
<span class="flex items-center">
<span
class="flex items-center"
:class="{
'is-background-light': isBackgroundLighter,
}"
:style="{
color: widgetColor,
}"
>
<fluent-icon class="mr-2" size="16" icon="arrow-right" />
{{ $t('UNREAD_VIEW.VIEW_MESSAGES_BUTTON') }}
</span>
@ -43,6 +51,7 @@ import configMixin from '../mixins/configMixin';
import { ON_UNREAD_MESSAGE_CLICK } from '../constants/widgetBusEvents';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import UnreadMessage from 'widget/components/UnreadMessage.vue';
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
export default {
name: 'Unread',
@ -58,11 +67,17 @@ export default {
},
},
computed: {
...mapGetters({ unreadMessageCount: 'conversation/getUnreadMessageCount' }),
...mapGetters({
unreadMessageCount: 'conversation/getUnreadMessageCount',
widgetColor: 'appConfig/getWidgetColor',
}),
sender() {
const [firstMessage] = this.messages;
return firstMessage.sender || {};
},
isBackgroundLighter() {
return isWidgetColorLighter(this.widgetColor);
},
},
methods: {
openConversationView() {
@ -134,5 +149,8 @@ export default {
color: $color-body;
}
}
.is-background-light {
color: $color-body !important;
}
}
</style>

View file

@ -28,6 +28,7 @@
v-else
:url="attachment.data_url"
:is-in-progress="isInProgress"
:widget-color="widgetColor"
/>
</div>
</div>

View file

@ -1,5 +1,19 @@
export const stripTrailingSlash = ({ URL }) => {
return URL.replace(/\/$/, '');
import { URLPattern } from 'urlpattern-polyfill';
export const isPatternMatchingWithURL = (urlPattern, url) => {
let updatedUrlPattern = urlPattern;
const locationObj = new URL(url);
if (updatedUrlPattern.endsWith('/')) {
updatedUrlPattern = updatedUrlPattern.slice(0, -1) + '*\\?*\\#*';
}
if (locationObj.pathname.endsWith('/')) {
locationObj.pathname = locationObj.pathname.slice(0, -1);
}
const pattern = new URLPattern(updatedUrlPattern);
return pattern.test(locationObj.toString());
};
// Format all campaigns
@ -22,10 +36,7 @@ export const filterCampaigns = ({
isInBusinessHours,
}) => {
return campaigns.filter(campaign => {
const hasMatchingURL =
stripTrailingSlash({ URL: campaign.url }) ===
stripTrailingSlash({ URL: currentURL });
if (!hasMatchingURL) {
if (!isPatternMatchingWithURL(campaign.url, currentURL)) {
return false;
}
if (campaign.triggerOnlyDuringBusinessHours) {

View file

@ -1,7 +1,7 @@
import {
stripTrailingSlash,
formatCampaigns,
filterCampaigns,
isPatternMatchingWithURL,
} from '../campaignHelper';
import campaigns from './campaignFixtures';
@ -9,11 +9,35 @@ global.chatwootWebChannel = {
workingHoursEnabled: false,
};
describe('#Campaigns Helper', () => {
describe('stripTrailingSlash', () => {
it('should return striped trailing slash if url with trailing slash is passed', () => {
describe('#isPatternMatchingWithURL', () => {
it('returns correct value if a valid URL is passed', () => {
expect(
stripTrailingSlash({ URL: 'https://www.chatwoot.com/pricing/' })
).toBe('https://www.chatwoot.com/pricing');
isPatternMatchingWithURL(
'https://chatwoot.com/pricing*',
'https://chatwoot.com/pricing/'
)
).toBe(true);
expect(
isPatternMatchingWithURL(
'https://*.chatwoot.com/pricing/',
'https://app.chatwoot.com/pricing/'
)
).toBe(true);
expect(
isPatternMatchingWithURL(
'https://{*.}?chatwoot.com/pricing?test=true',
'https://app.chatwoot.com/pricing/?test=true'
)
).toBe(true);
expect(
isPatternMatchingWithURL(
'https://{*.}?chatwoot.com/pricing*\\?*',
'https://chatwoot.com/pricing/?test=true'
)
).toBe(true);
});
});

View file

@ -86,7 +86,8 @@ class Campaign < ApplicationRecord
def validate_url
return unless trigger_rules['url']
errors.add(:url, 'invalid') if inbox.inbox_type == 'Website' && !url_valid?(trigger_rules['url'])
use_http_protocol = trigger_rules['url'].starts_with?('http://') || trigger_rules['url'].starts_with?('https://')
errors.add(:url, 'invalid') if inbox.inbox_type == 'Website' && !use_http_protocol
end
def prevent_completed_campaign_from_update

View file

@ -16,7 +16,7 @@ module Featurable
include FlagShihTzu
has_flags FEATURES.merge(column: 'feature_flags').merge(QUERY_MODE)
after_initialize :enable_default_features
before_create :enable_default_features
end
def enable_features(*names)

View file

@ -3,7 +3,7 @@ json.name resource.name
json.locale resource.locale
json.domain resource.domain
json.support_email resource.support_email
json.enabled_features resource.enabled_features
json.features resource.enabled_features
json.custom_attributes resource.custom_attributes
json.limits resource.limits
json.status resource.status

View file

@ -1,6 +1,8 @@
# DO NOT change the order of features EVER
- name: inbound_emails
enabled: true
- name: channel_website
enabled: true
- name: channel_email
enabled: true
- name: channel_facebook
@ -13,6 +15,8 @@
enabled: false
- name: email_continuity_on_api_channel
enabled: false
- name: campaigns
enabled: true
- name: help_center
enabled: true
- name: agent_bots

View file

@ -0,0 +1,13 @@
class AddTwoFeaturesToAccounts < ActiveRecord::Migration[6.1]
def change
Account.find_in_batches do |account_batch|
account_batch.each do |account|
account.enable_features(
'campaigns',
'channel_website'
)
account.save!
end
end
end
end

View file

@ -55,6 +55,7 @@
"tailwindcss": "^1.9.6",
"turbolinks": "^5.2.0",
"url-loader": "^2.0.0",
"urlpattern-polyfill": "^6.0.2",
"v-tooltip": "~2.1.3",
"videojs-record": "^4.5.0",
"vue": "2.6.12",

View file

@ -42,28 +42,29 @@ RSpec.describe 'Platform Accounts API', type: :request do
json_response = JSON.parse(response.body)
expect(json_response['name']).to eq('Test Account')
expect(json_response['locale']).to eq('es')
expect(json_response['enabled_features']['agent_management']).to be(true)
expect(json_response['features']['agent_management']).to be(true)
end
it 'creates an account with feature flags' do
InstallationConfig.where(name: 'ACCOUNT_LEVEL_FEATURE_DEFAULTS').first_or_create!(value: [{ 'name' => 'inbox_management',
'enabled' => true },
{ 'name' => 'disable_branding',
'enabled' => true }])
'enabled' => true },
{ 'name' => 'help_center',
'enabled' => false }])
post '/platform/api/v1/accounts', params: { name: 'Test Account', features: {
ip_lookup: true,
help_center: true,
disable_branding: false
} }, headers: { api_access_token: platform_app.access_token.token }, as: :json
json_response = JSON.parse(response.body)
expect(json_response['name']).to include('Test Account')
expect(json_response['enabled_features']['inbox_management']).to be(true)
expect(json_response['enabled_features']['ip_lookup']).to be(true)
expect(json_response['enabled_features']['help_center']).to be(true)
expect(json_response['enabled_features']['disable_branding']).to be_nil
expect(json_response['features']['inbox_management']).to be(true)
expect(json_response['features']['ip_lookup']).to be(true)
expect(json_response['features']['help_center']).to be(true)
expect(json_response['features']['disable_branding']).to be_nil
end
it 'creates an account with limits settings' do

View file

@ -16381,6 +16381,13 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
urlpattern-polyfill@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-6.0.2.tgz#a193fe773459865a2a5c93b246bb794b13d07256"
integrity sha512-5vZjFlH9ofROmuWmXM9yj2wljYKgWstGwe8YTyiqM7hVum/g9LyCizPZtb3UqsuppVwety9QJmfc42VggLpTgg==
dependencies:
braces "^3.0.2"
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"