Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ad12742e | ||
|
|
d1ac5a4bd5 | ||
|
|
b96c3af414 |
@@ -94,10 +94,11 @@ class Messages::Instagram::BaseMessageBuilder < Messages::Messenger::MessageBuil
|
||||
def build_message
|
||||
# Duplicate webhook events may be sent for the same message
|
||||
# when a user is connected to the Instagram account through both Messenger and Instagram login.
|
||||
# There is chance for echo events to be sent for the same message.
|
||||
# Therefore, we need to check if the message already exists before creating it.
|
||||
return if message_already_exists?
|
||||
|
||||
return if @outgoing_echo
|
||||
|
||||
return if message_content.blank? && all_unsupported_files?
|
||||
|
||||
@message = conversation.messages.create!(message_params)
|
||||
|
||||
@@ -29,19 +29,6 @@
|
||||
--iris-11: 87 83 198;
|
||||
--iris-12: 39 41 98;
|
||||
|
||||
--blue-1: 251 253 255;
|
||||
--blue-2: 245 249 255;
|
||||
--blue-3: 233 243 255;
|
||||
--blue-4: 218 236 255;
|
||||
--blue-5: 201 226 255;
|
||||
--blue-6: 181 213 255;
|
||||
--blue-7: 155 195 252;
|
||||
--blue-8: 117 171 247;
|
||||
--blue-9: 39 129 246;
|
||||
--blue-10: 16 115 233;
|
||||
--blue-11: 8 109 224;
|
||||
--blue-12: 11 50 101;
|
||||
|
||||
--ruby-1: 255 252 253;
|
||||
--ruby-2: 255 247 248;
|
||||
--ruby-3: 254 234 237;
|
||||
@@ -144,19 +131,6 @@
|
||||
--iris-11: 158 177 255;
|
||||
--iris-12: 224 223 254;
|
||||
|
||||
--blue-1: 10 17 28;
|
||||
--blue-2: 15 24 38;
|
||||
--blue-3: 15 39 72;
|
||||
--blue-4: 10 49 99;
|
||||
--blue-5: 18 61 117;
|
||||
--blue-6: 29 84 134;
|
||||
--blue-7: 40 89 156;
|
||||
--blue-8: 48 106 186;
|
||||
--blue-9: 39 129 246;
|
||||
--blue-10: 21 116 231;
|
||||
--blue-11: 126 182 255;
|
||||
--blue-12: 205 227 255;
|
||||
|
||||
--ruby-1: 25 17 19;
|
||||
--ruby-2: 30 21 23;
|
||||
--ruby-3: 58 20 30;
|
||||
|
||||
-1
@@ -51,7 +51,6 @@ defineExpose({ dialogRef, contactsFormRef, onSuccess });
|
||||
<Button
|
||||
:label="t('DIALOG.BUTTONS.CANCEL')"
|
||||
variant="link"
|
||||
type="reset"
|
||||
class="h-10 hover:!no-underline hover:text-n-brand"
|
||||
@click="closeDialog"
|
||||
/>
|
||||
|
||||
+4
@@ -31,6 +31,10 @@ const sortMenus = [
|
||||
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.SORT_BY.OPTIONS.EMAIL'),
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.SORT_BY.OPTIONS.PHONE_NUMBER'),
|
||||
value: 'phone_number',
|
||||
},
|
||||
{
|
||||
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.SORT_BY.OPTIONS.COMPANY'),
|
||||
value: 'company_name',
|
||||
|
||||
@@ -218,14 +218,14 @@ const emitDateRange = () => {
|
||||
/>
|
||||
<div
|
||||
v-if="showDatePicker"
|
||||
class="flex absolute top-9 ltr:left-0 rtl:right-0 z-30 shadow-md select-none w-[880px] h-[490px] rounded-2xl bg-n-alpha-3 backdrop-blur-[100px] border-0 outline outline-1 outline-n-container"
|
||||
class="flex absolute top-9 ltr:left-0 rtl:right-0 z-30 shadow-md select-none w-[880px] h-[490px] rounded-2xl border border-slate-50 dark:border-slate-800 bg-white dark:bg-slate-800"
|
||||
>
|
||||
<CalendarDateRange
|
||||
:selected-range="selectedRange"
|
||||
@set-range="setDateRange"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col w-[680px] ltr:border-l rtl:border-r border-n-strong"
|
||||
class="flex flex-col w-[680px] ltr:border-l rtl:border-r border-slate-50 dark:border-slate-700/50"
|
||||
>
|
||||
<div class="flex justify-around h-fit">
|
||||
<!-- Calendars for Start and End Dates -->
|
||||
@@ -251,12 +251,12 @@ const emitDateRange = () => {
|
||||
@validate="updateManualInput($event, calendar)"
|
||||
@error="handleManualInputError($event)"
|
||||
/>
|
||||
<div class="py-5 border-b border-n-strong">
|
||||
<div class="py-5 border-b border-slate-50 dark:border-slate-700/50">
|
||||
<div
|
||||
class="flex flex-col items-center gap-2 px-5 min-w-[340px] max-h-[352px]"
|
||||
:class="
|
||||
calendar === START_CALENDAR &&
|
||||
'ltr:border-r rtl:border-l border-n-strong'
|
||||
'ltr:border-r rtl:border-l border-slate-50 dark:border-slate-700/50'
|
||||
"
|
||||
>
|
||||
<CalendarYear
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script setup>
|
||||
import { CALENDAR_PERIODS } from '../helpers/DatePickerHelper';
|
||||
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
defineProps({
|
||||
calendarType: {
|
||||
type: String,
|
||||
@@ -40,38 +38,42 @@ const onClickSetView = (type, mode) => {
|
||||
|
||||
<template>
|
||||
<div class="flex items-start justify-between w-full h-9">
|
||||
<NextButton
|
||||
slate
|
||||
ghost
|
||||
xs
|
||||
icon="i-lucide-chevron-left"
|
||||
class="rtl:rotate-180"
|
||||
<button
|
||||
class="p-1 rounded-lg hover:bg-slate-75 dark:hover:bg-slate-700/50 rtl:rotate-180"
|
||||
@click="onClickPrev(calendarType)"
|
||||
/>
|
||||
>
|
||||
<fluent-icon
|
||||
icon="chevron-left"
|
||||
size="14"
|
||||
class="text-slate-900 dark:text-slate-50"
|
||||
/>
|
||||
</button>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
v-if="firstButtonLabel"
|
||||
class="p-0 text-sm font-medium text-center text-n-slate-12 hover:text-n-brand"
|
||||
class="p-0 text-sm font-medium text-center text-slate-800 dark:text-slate-50 hover:text-woot-600 dark:hover:text-woot-600"
|
||||
@click="onClickSetView(calendarType, viewMode)"
|
||||
>
|
||||
{{ firstButtonLabel }}
|
||||
</button>
|
||||
<button
|
||||
v-if="buttonLabel"
|
||||
class="p-0 text-sm font-medium text-center text-n-slate-12"
|
||||
:class="{ 'hover:text-n-brand': viewMode }"
|
||||
class="p-0 text-sm font-medium text-center text-slate-800 dark:text-slate-50"
|
||||
:class="{ 'hover:text-woot-600 dark:hover:text-woot-600': viewMode }"
|
||||
@click="onClickSetView(calendarType, YEAR)"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
<NextButton
|
||||
slate
|
||||
ghost
|
||||
xs
|
||||
icon="i-lucide-chevron-right"
|
||||
class="rtl:rotate-180"
|
||||
<button
|
||||
class="p-1 rounded-lg hover:bg-slate-75 dark:hover:bg-slate-700/50 rtl:rotate-180"
|
||||
@click="onClickNext(calendarType)"
|
||||
/>
|
||||
>
|
||||
<fluent-icon
|
||||
icon="chevron-right"
|
||||
size="14"
|
||||
class="text-slate-900 dark:text-slate-50"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -65,7 +65,7 @@ const validateDate = () => {
|
||||
<input
|
||||
v-model="localDateValue"
|
||||
type="text"
|
||||
class="!text-sm !mb-0 disabled:!outline-n-strong"
|
||||
class="reset-base border bg-slate-25 dark:bg-slate-900 ring-offset-ash-900 border-slate-50 dark:border-slate-700/50 w-full disabled:text-slate-200 dark:disabled:text-slate-700 disabled:cursor-not-allowed text-slate-800 dark:text-slate-50 px-1.5 py-1 text-sm rounded-xl h-10"
|
||||
:placeholder="dateFormat"
|
||||
:disabled="isDisabled"
|
||||
@keypress.enter="validateDate"
|
||||
|
||||
@@ -18,7 +18,7 @@ const setDateRange = range => {
|
||||
<template>
|
||||
<div class="w-[200px] flex flex-col items-start">
|
||||
<h4
|
||||
class="w-full px-5 py-4 text-sm font-medium capitalize text-start text-n-slate-12"
|
||||
class="w-full px-5 py-4 text-sm font-medium capitalize text-start text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('DATE_PICKER.DATE_RANGE_OPTIONS.TITLE') }}
|
||||
</h4>
|
||||
@@ -26,11 +26,11 @@ const setDateRange = range => {
|
||||
<button
|
||||
v-for="range in dateRanges"
|
||||
:key="range.label"
|
||||
class="w-full px-5 py-3 text-sm font-medium truncate border-none rounded-none text-start hover:bg-n-alpha-2 dark:hover:bg-n-solid-3"
|
||||
class="w-full px-5 py-3 text-sm font-medium truncate border-none rounded-none text-start hover:bg-slate-50 dark:hover:bg-slate-700"
|
||||
:class="
|
||||
range.value === selectedRange
|
||||
? 'text-n-slate-12 bg-n-alpha-1 dark:bg-n-solid-active'
|
||||
: 'text-n-slate-12'
|
||||
? 'text-slate-800 dark:text-slate-50 bg-slate-50 dark:bg-slate-700'
|
||||
: 'text-slate-600 dark:text-slate-200'
|
||||
"
|
||||
@click="setDateRange(range)"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
const emit = defineEmits(['clear', 'change']);
|
||||
|
||||
const onClickClear = () => {
|
||||
@@ -13,19 +11,18 @@ const onClickApply = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[56px] flex justify-between gap-2 px-2 py-3 items-center">
|
||||
<NextButton
|
||||
slate
|
||||
ghost
|
||||
sm
|
||||
:label="$t('DATE_PICKER.CLEAR_BUTTON')"
|
||||
<div class="h-[56px] flex justify-between px-5 py-3 items-center">
|
||||
<button
|
||||
class="p-1.5 rounded-lg w-fit text-sm font-medium text-slate-600 dark:text-slate-200 hover:text-slate-800 dark:hover:text-slate-100"
|
||||
@click="onClickClear"
|
||||
/>
|
||||
<NextButton
|
||||
sm
|
||||
ghost
|
||||
:label="$t('DATE_PICKER.APPLY_BUTTON')"
|
||||
>
|
||||
{{ $t('DATE_PICKER.CLEAR_BUTTON') }}
|
||||
</button>
|
||||
<button
|
||||
class="p-1.5 rounded-lg w-fit text-sm font-medium text-woot-500 dark:text-woot-300 hover:text-woot-700 dark:hover:text-woot-500"
|
||||
@click="onClickApply"
|
||||
/>
|
||||
>
|
||||
{{ $t('DATE_PICKER.APPLY_BUTTON') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,12 +71,10 @@ const selectMonth = index => {
|
||||
<button
|
||||
v-for="(month, index) in months"
|
||||
:key="index"
|
||||
class="p-2 text-sm font-medium text-center text-n-slate-12 w-[92px] h-10 rounded-lg py-2.5 px-2"
|
||||
class="p-2 text-sm font-medium text-center text-slate-800 dark:text-slate-50 w-[92px] h-10 rounded-lg py-2.5 px-2 hover:bg-slate-75 dark:hover:bg-slate-700"
|
||||
:class="{
|
||||
'bg-n-brand text-white hover:bg-n-blue-10':
|
||||
'bg-woot-600 dark:bg-woot-600 text-white dark:text-white hover:bg-woot-500 dark:bg-woot-700':
|
||||
index === activeMonthIndex,
|
||||
'hover:bg-n-alpha-2 dark:hover:bg-n-solid-3':
|
||||
index !== activeMonthIndex,
|
||||
}"
|
||||
@click="selectMonth(index)"
|
||||
>
|
||||
|
||||
@@ -107,16 +107,17 @@ const isNextDayInRange = day => {
|
||||
};
|
||||
|
||||
const dayClasses = day => ({
|
||||
'text-n-slate-10 pointer-events-none': !isInCurrentMonth(day),
|
||||
'text-n-slate-12 hover:text-n-slate-12 hover:bg-n-blue-6 dark:hover:bg-n-blue-7':
|
||||
'text-slate-500 dark:text-slate-400 pointer-events-none':
|
||||
!isInCurrentMonth(day),
|
||||
'text-slate-800 dark:text-slate-50 hover:text-slate-800 dark:hover:text-white hover:bg-woot-100 dark:hover:bg-woot-700':
|
||||
isInCurrentMonth(day),
|
||||
'bg-n-brand text-white':
|
||||
'bg-woot-600 dark:bg-woot-600 text-white dark:text-white':
|
||||
isSelectedStartOrEndDate(day) && isInCurrentMonth(day),
|
||||
'bg-n-blue-4 dark:bg-n-blue-5':
|
||||
'bg-woot-50 dark:bg-woot-800':
|
||||
(isInRange(day) || isHoveringInRange(day)) &&
|
||||
!isSelectedStartOrEndDate(day) &&
|
||||
isInCurrentMonth(day),
|
||||
'outline outline-1 outline-n-blue-8 -outline-offset-1 !text-n-blue-text':
|
||||
'outline outline-1 outline-woot-200 -outline-offset-1 dark:outline-woot-700 text-woot-600 dark:text-woot-400':
|
||||
isToday(props.currentDate, day) && !isSelectedStartOrEndDate(day),
|
||||
});
|
||||
</script>
|
||||
@@ -163,7 +164,7 @@ const dayClasses = day => ({
|
||||
!isLastDayOfMonth(day) &&
|
||||
isInCurrentMonth(day)
|
||||
"
|
||||
class="absolute bottom-0 w-6 h-8 ltr:-right-4 rtl:-left-4 bg-n-blue-4 dark:bg-n-blue-5 -z-10"
|
||||
class="absolute bottom-0 w-6 h-8 ltr:-right-4 rtl:-left-4 bg-woot-50 dark:bg-woot-800 -z-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,10 +72,10 @@ const selectYear = year => {
|
||||
<button
|
||||
v-for="year in years"
|
||||
:key="year"
|
||||
class="p-2 text-sm font-medium text-center text-n-slate-12 w-[144px] h-10 rounded-lg py-2.5 px-2"
|
||||
class="p-2 text-sm font-medium text-center text-slate-800 dark:text-slate-50 w-[144px] h-10 rounded-lg py-2.5 px-2 hover:bg-slate-75 dark:hover:bg-slate-700"
|
||||
:class="{
|
||||
'bg-n-brand text-white hover:bg-n-blue-10': year === activeYear,
|
||||
'hover:bg-n-alpha-2 dark:hover:bg-n-solid-3': year !== activeYear,
|
||||
'bg-woot-600 dark:bg-woot-600 text-white dark:text-white hover:bg-woot-500 dark:hover:bg-woot-700':
|
||||
year === activeYear,
|
||||
}"
|
||||
@click="selectYear(year)"
|
||||
>
|
||||
|
||||
@@ -48,7 +48,7 @@ const openDatePicker = () => {
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="inline-flex relative items-center rounded-lg gap-2 py-1.5 px-3 h-8 bg-n-alpha-2 hover:bg-n-alpha-1 active:bg-n-alpha-1"
|
||||
class="inline-flex relative items-center rounded-lg gap-2 py-1.5 px-3 h-8 bg-slate-50 dark:bg-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800 active:bg-slate-75 dark:active:bg-slate-800"
|
||||
@click="openDatePicker"
|
||||
>
|
||||
<fluent-icon
|
||||
|
||||
@@ -220,7 +220,6 @@ const plugins = computed(() => {
|
||||
trigger: '@',
|
||||
showMenu: showUserMentions,
|
||||
searchTerm: mentionSearchKey,
|
||||
isAllowed: () => props.isPrivate,
|
||||
}),
|
||||
createSuggestionPlugin({
|
||||
trigger: '/',
|
||||
|
||||
@@ -69,6 +69,10 @@ const onAgentSelect = index => {
|
||||
v-if="items.length"
|
||||
ref="tagAgentsRef"
|
||||
class="vertical dropdown menu mention--box bg-n-solid-1 p-1 rounded-xl text-sm overflow-auto absolute w-full z-20 shadow-md left-0 leading-[1.2] bottom-full max-h-[12.5rem] border border-solid border-n-strong"
|
||||
:class="{
|
||||
'border-b-[0.5rem] border-solid border-white dark:!border-slate-700':
|
||||
items.length <= 4,
|
||||
}"
|
||||
>
|
||||
<li
|
||||
v-for="(agent, index) in items"
|
||||
|
||||
@@ -36,7 +36,7 @@ const useConversationSidebarItemsOrder = uiSettings => {
|
||||
const { conversation_sidebar_items_order: itemsOrder } = uiSettings.value;
|
||||
// If the sidebar order is not set, use the default order.
|
||||
if (!itemsOrder) {
|
||||
return [...DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER];
|
||||
return DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER;
|
||||
}
|
||||
// Create a copy of itemsOrder to avoid mutating the original store object.
|
||||
const itemsOrderCopy = [...itemsOrder];
|
||||
|
||||
@@ -16,15 +16,6 @@ export const getUserPermissions = (user, accountId) => {
|
||||
return currentAccount.permissions || [];
|
||||
};
|
||||
|
||||
export const getUserRole = (user, accountId) => {
|
||||
const currentAccount = getCurrentAccount(user, accountId) || {};
|
||||
if (currentAccount.custom_role_id) {
|
||||
return 'custom_role';
|
||||
}
|
||||
|
||||
return currentAccount.role || 'agent';
|
||||
};
|
||||
|
||||
const isPermissionsPresentInRoute = route =>
|
||||
route.meta && route.meta.permissions;
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import { applyPageFilters, applyRoleFilter, sortComparator } from './helpers';
|
||||
import { applyPageFilters, sortComparator } from './helpers';
|
||||
import filterQueryGenerator from 'dashboard/helper/filterQueryGenerator';
|
||||
import { matchesFilters } from './helpers/filterHelpers';
|
||||
import {
|
||||
getUserPermissions,
|
||||
getUserRole,
|
||||
} from '../../../helper/permissionsHelper';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
|
||||
export const getSelectedChatConversation = ({
|
||||
@@ -81,24 +77,10 @@ const getters = {
|
||||
return isUnAssigned && shouldFilter;
|
||||
});
|
||||
},
|
||||
getAllStatusChats: (_state, _, __, rootGetters) => activeFilters => {
|
||||
const currentUser = rootGetters.getCurrentUser;
|
||||
const currentUserId = rootGetters.getCurrentUser.id;
|
||||
const currentAccountId = rootGetters.getCurrentAccountId;
|
||||
|
||||
const permissions = getUserPermissions(currentUser, currentAccountId);
|
||||
const userRole = getUserRole(currentUser, currentAccountId);
|
||||
|
||||
getAllStatusChats: _state => activeFilters => {
|
||||
return _state.allConversations.filter(conversation => {
|
||||
const shouldFilter = applyPageFilters(conversation, activeFilters);
|
||||
const allowedForRole = applyRoleFilter(
|
||||
conversation,
|
||||
userRole,
|
||||
permissions,
|
||||
currentUserId
|
||||
);
|
||||
|
||||
return shouldFilter && allowedForRole;
|
||||
return shouldFilter;
|
||||
});
|
||||
},
|
||||
getChatListLoadingStatus: ({ listLoadingStatus }) => listLoadingStatus,
|
||||
|
||||
@@ -62,51 +62,6 @@ export const applyPageFilters = (conversation, filters) => {
|
||||
return shouldFilter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters conversations based on user role and permissions
|
||||
*
|
||||
* @param {Object} conversation - The conversation object to check permissions for
|
||||
* @param {string} role - The user's role (administrator, agent, etc.)
|
||||
* @param {Array<string>} permissions - List of permission strings the user has
|
||||
* @param {number|string} currentUserId - The ID of the current user
|
||||
* @returns {boolean} - Whether the user has permissions to access this conversation
|
||||
*/
|
||||
export const applyRoleFilter = (
|
||||
conversation,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
) => {
|
||||
// the role === "agent" check is typically not correct on it's own
|
||||
// the backend handles this by checking the custom_role_id at the user model
|
||||
// here however, the `getUserRole` returns "custom_role" if the id is present,
|
||||
// so we can check the role === "agent" directly
|
||||
if (['administrator', 'agent'].includes(role)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for full conversation management permission
|
||||
if (permissions.includes('conversation_manage')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const conversationAssignee = conversation.meta.assignee;
|
||||
const isUnassigned = !conversationAssignee;
|
||||
const isAssignedToUser = conversationAssignee?.id === currentUserId;
|
||||
|
||||
// Check unassigned management permission
|
||||
if (permissions.includes('conversation_unassigned_manage')) {
|
||||
return isUnassigned || isAssignedToUser;
|
||||
}
|
||||
|
||||
// Check participating conversation management permission
|
||||
if (permissions.includes('conversation_participating_manage')) {
|
||||
return isAssignedToUser;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const SORT_OPTIONS = {
|
||||
last_activity_at_asc: ['sortOnLastActivityAt', 'asc'],
|
||||
last_activity_at_desc: ['sortOnLastActivityAt', 'desc'],
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { applyRoleFilter } from '../helpers';
|
||||
|
||||
describe('Conversation Helpers', () => {
|
||||
describe('#applyRoleFilter', () => {
|
||||
// Test data for conversations
|
||||
const conversationWithAssignee = {
|
||||
meta: {
|
||||
assignee: {
|
||||
id: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const conversationWithDifferentAssignee = {
|
||||
meta: {
|
||||
assignee: {
|
||||
id: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const conversationWithoutAssignee = {
|
||||
meta: {
|
||||
assignee: null,
|
||||
},
|
||||
};
|
||||
|
||||
// Test for administrator role
|
||||
it('always returns true for administrator role regardless of permissions', () => {
|
||||
const role = 'administrator';
|
||||
const permissions = [];
|
||||
const currentUserId = 1;
|
||||
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithDifferentAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithoutAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// Test for agent role
|
||||
it('always returns true for agent role regardless of permissions', () => {
|
||||
const role = 'agent';
|
||||
const permissions = [];
|
||||
const currentUserId = 1;
|
||||
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithDifferentAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithoutAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// Test for custom role with 'conversation_manage' permission
|
||||
it('returns true for any user with conversation_manage permission', () => {
|
||||
const role = 'custom_role';
|
||||
const permissions = ['conversation_manage'];
|
||||
const currentUserId = 1;
|
||||
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithDifferentAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithoutAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// Test for custom role with 'conversation_unassigned_manage' permission
|
||||
describe('with conversation_unassigned_manage permission', () => {
|
||||
const role = 'custom_role';
|
||||
const permissions = ['conversation_unassigned_manage'];
|
||||
const currentUserId = 1;
|
||||
|
||||
it('returns true for conversations assigned to the user', () => {
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for unassigned conversations', () => {
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithoutAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for conversations assigned to other users', () => {
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithDifferentAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Test for custom role with 'conversation_participating_manage' permission
|
||||
describe('with conversation_participating_manage permission', () => {
|
||||
const role = 'custom_role';
|
||||
const permissions = ['conversation_participating_manage'];
|
||||
const currentUserId = 1;
|
||||
|
||||
it('returns true for conversations assigned to the user', () => {
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for unassigned conversations', () => {
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithoutAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for conversations assigned to other users', () => {
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithDifferentAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Test for user with no relevant permissions
|
||||
it('returns false for custom role without any relevant permissions', () => {
|
||||
const role = 'custom_role';
|
||||
const permissions = ['some_other_permission'];
|
||||
const currentUserId = 1;
|
||||
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithDifferentAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithoutAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
// Test edge cases for meta.assignee
|
||||
describe('handles edge cases with meta.assignee', () => {
|
||||
const role = 'custom_role';
|
||||
const permissions = ['conversation_unassigned_manage'];
|
||||
const currentUserId = 1;
|
||||
|
||||
it('treats undefined assignee as unassigned', () => {
|
||||
const conversationWithUndefinedAssignee = {
|
||||
meta: {
|
||||
assignee: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithUndefinedAssignee,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('handles empty meta object', () => {
|
||||
const conversationWithEmptyMeta = {
|
||||
meta: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
applyRoleFilter(
|
||||
conversationWithEmptyMeta,
|
||||
role,
|
||||
permissions,
|
||||
currentUserId
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,7 @@ export const SDK_CSS = `
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.woot-widget-bubble {
|
||||
.woot-widget-bubble[class] {
|
||||
background: #1f93ff;
|
||||
border-radius: 100px;
|
||||
border-width: 0px;
|
||||
|
||||
@@ -44,18 +44,15 @@ RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do
|
||||
|
||||
it 'blocks personal accounts signup' do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
# Test different case variations of Gmail
|
||||
['personal@gmail.com', 'personal@Gmail.com', 'personal@GMAIL.com', 'personal@Gmail.COM'].each do |email|
|
||||
set_omniauth_config(email)
|
||||
get '/omniauth/google_oauth2/callback'
|
||||
set_omniauth_config('personal@gmail.com')
|
||||
get '/omniauth/google_oauth2/callback'
|
||||
|
||||
# expect a 302 redirect to auth/google_oauth2/callback
|
||||
expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback')
|
||||
follow_redirect!
|
||||
# expect a 302 redirect to auth/google_oauth2/callback
|
||||
expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback')
|
||||
follow_redirect!
|
||||
|
||||
# expect a 302 redirect to app/login with error disallowing personal accounts
|
||||
expect(response).to redirect_to(%r{/app/login\?error=business-account-only$})
|
||||
end
|
||||
# expect a 302 redirect to app/login with error disallowing personal accounts
|
||||
expect(response).to redirect_to(%r{/app/login\?error=business-account-only$})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -315,21 +315,6 @@ export const colors = {
|
||||
12: 'rgb(var(--iris-12) / <alpha-value>)',
|
||||
},
|
||||
|
||||
blue: {
|
||||
1: 'rgb(var(--blue-1) / <alpha-value>)',
|
||||
2: 'rgb(var(--blue-2) / <alpha-value>)',
|
||||
3: 'rgb(var(--blue-3) / <alpha-value>)',
|
||||
4: 'rgb(var(--blue-4) / <alpha-value>)',
|
||||
5: 'rgb(var(--blue-5) / <alpha-value>)',
|
||||
6: 'rgb(var(--blue-6) / <alpha-value>)',
|
||||
7: 'rgb(var(--blue-7) / <alpha-value>)',
|
||||
8: 'rgb(var(--blue-8) / <alpha-value>)',
|
||||
9: 'rgb(var(--blue-9) / <alpha-value>)',
|
||||
10: 'rgb(var(--blue-10) / <alpha-value>)',
|
||||
11: 'rgb(var(--blue-11) / <alpha-value>)',
|
||||
12: 'rgb(var(--blue-12) / <alpha-value>)',
|
||||
},
|
||||
|
||||
ruby: {
|
||||
1: 'rgb(var(--ruby-1) / <alpha-value>)',
|
||||
2: 'rgb(var(--ruby-2) / <alpha-value>)',
|
||||
|
||||
Reference in New Issue
Block a user