From 2c94c890772de324248ee1306c80ff6f47c4598c Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Sat, 29 Jun 2024 01:21:27 +0530 Subject: [PATCH 01/22] feat: Add video message viewer in agent widget bubble (#9691) Fixes https://linear.app/chatwoot/issue/CW-3384/video-message-display-issue --- .../widget/components/AgentMessage.vue | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/javascript/widget/components/AgentMessage.vue b/app/javascript/widget/components/AgentMessage.vue index eb64e0c36..c38726031 100755 --- a/app/javascript/widget/components/AgentMessage.vue +++ b/app/javascript/widget/components/AgentMessage.vue @@ -30,7 +30,7 @@ />
+ + + @@ -84,6 +92,7 @@ import AgentMessageBubble from 'widget/components/AgentMessageBubble.vue'; import MessageReplyButton from 'widget/components/MessageReplyButton.vue'; import timeMixin from 'dashboard/mixins/time'; import ImageBubble from 'widget/components/ImageBubble.vue'; +import VideoBubble from 'widget/components/VideoBubble.vue'; import FileBubble from 'widget/components/FileBubble.vue'; import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue'; import { MESSAGE_TYPE } from 'widget/helpers/constants'; @@ -100,6 +109,7 @@ export default { components: { AgentMessageBubble, ImageBubble, + VideoBubble, Thumbnail, UserMessage, FileBubble, @@ -120,6 +130,7 @@ export default { data() { return { hasImageError: false, + hasVideoError: false, }; }, computed: { @@ -215,15 +226,20 @@ export default { watch: { message() { this.hasImageError = false; + this.hasVideoError = false; }, }, mounted() { this.hasImageError = false; + this.hasVideoError = false; }, methods: { onImageLoadError() { this.hasImageError = true; }, + onVideoLoadError() { + this.hasVideoError = true; + }, toggleReply() { emitter.emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.message); }, From 46621b098316260d6db97c87c126dd17bd118ad9 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Fri, 28 Jun 2024 12:52:48 -0700 Subject: [PATCH 02/22] chore: Add permissions to auth data (#9695) This API change sets the foundation for an upcoming frontend update, transitioning from a role-based model to a permission-based model. This new approach will determine eligibility for various actions and UI elements based on specific permissions rather than roles, enhancing flexibility and security in user access management. --- app/models/account_user.rb | 4 ++++ app/views/api/v1/models/_user.json.jbuilder | 2 ++ spec/controllers/devise/session_controller_spec.rb | 11 +++++++++++ spec/models/account_user_spec.rb | 11 +++++++++++ 4 files changed, 28 insertions(+) diff --git a/app/models/account_user.rb b/app/models/account_user.rb index 78b874334..176d52102 100644 --- a/app/models/account_user.rb +++ b/app/models/account_user.rb @@ -49,6 +49,10 @@ class AccountUser < ApplicationRecord ::Agents::DestroyJob.perform_later(account, user) end + def permissions + administrator? ? ['administrator'] : ['agent'] + end + def push_event_data { id: id, diff --git a/app/views/api/v1/models/_user.json.jbuilder b/app/views/api/v1/models/_user.json.jbuilder index 74df836c0..0e8c95adb 100644 --- a/app/views/api/v1/models/_user.json.jbuilder +++ b/app/views/api/v1/models/_user.json.jbuilder @@ -14,6 +14,7 @@ json.provider resource.provider json.pubsub_token resource.pubsub_token json.custom_attributes resource.custom_attributes if resource.custom_attributes.present? json.role resource.active_account_user&.role +json.permissions resource.active_account_user&.permissions json.ui_settings resource.ui_settings json.uid resource.uid json.type resource.type @@ -24,6 +25,7 @@ json.accounts do json.status account_user.account.status json.active_at account_user.active_at json.role account_user.role + json.permissions account_user.permissions # the actual availability user has configured json.availability account_user.availability # availability derived from presence diff --git a/spec/controllers/devise/session_controller_spec.rb b/spec/controllers/devise/session_controller_spec.rb index 42f8b8bd8..ad6d76bf9 100644 --- a/spec/controllers/devise/session_controller_spec.rb +++ b/spec/controllers/devise/session_controller_spec.rb @@ -41,6 +41,17 @@ RSpec.describe 'Session', type: :request do expect(response).to have_http_status(:success) expect(response.body).to include(user_with_new_pwd.email) end + + it 'returns the permission of the user' do + params = { email: user.email, password: 'Password1!' } + + post new_user_session_url, + params: params, + as: :json + + expect(response).to have_http_status(:success) + expect(response.parsed_body['data']['permissions']).to eq(['agent']) + end end context 'when it is invalid sso auth token' do diff --git a/spec/models/account_user_spec.rb b/spec/models/account_user_spec.rb index 2b8ef790a..e5a560fe9 100644 --- a/spec/models/account_user_spec.rb +++ b/spec/models/account_user_spec.rb @@ -17,6 +17,17 @@ RSpec.describe AccountUser do end end + describe 'permissions' do + it 'returns the right permissions' do + expect(account_user.permissions).to eq(['agent']) + end + + it 'returns the right permissions for administrator' do + account_user.administrator! + expect(account_user.permissions).to eq(['administrator']) + end + end + describe 'destroy call agent::destroy service' do it 'gets created with the right default settings' do create(:conversation, account: account_user.account, assignee: account_user.user, inbox: inbox) From 5520bf68f37c5bbc189f5c4bbbdc325e1a4fad22 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 1 Jul 2024 11:11:57 +0530 Subject: [PATCH 03/22] feat: disable scripts on password reset page (#9693) --- app/controllers/dashboard_controller.rb | 12 +++++++++++- app/javascript/v3/views/index.js | 12 ++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index e656c2550..332f1528f 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -37,7 +37,7 @@ class DashboardController < ActionController::Base end def set_dashboard_scripts - @dashboard_scripts = GlobalConfig.get_value('DASHBOARD_SCRIPTS') + @dashboard_scripts = sensitive_path? ? nil : GlobalConfig.get_value('DASHBOARD_SCRIPTS') end def ensure_installation_onboarding @@ -75,4 +75,14 @@ class DashboardController < ActionController::Base 'application' end end + + def sensitive_path? + # dont load dashboard scripts on sensitive paths like password reset + sensitive_paths = [edit_user_password_path].freeze + + # remove app prefix + current_path = request.path.gsub(%r{^/app}, '') + + sensitive_paths.include?(current_path) + end end diff --git a/app/javascript/v3/views/index.js b/app/javascript/v3/views/index.js index 57da18116..dd30f0372 100644 --- a/app/javascript/v3/views/index.js +++ b/app/javascript/v3/views/index.js @@ -6,12 +6,16 @@ import { validateRouteAccess } from '../helpers/RouteHelper'; export const router = new VueRouter({ mode: 'history', routes }); +const sensitiveRouteNames = ['auth_password_edit']; + export const initalizeRouter = () => { router.beforeEach((to, _, next) => { - AnalyticsHelper.page(to.name || '', { - path: to.path, - name: to.name, - }); + if (!sensitiveRouteNames.includes(to.name)) { + AnalyticsHelper.page(to.name || '', { + path: to.path, + name: to.name, + }); + } return validateRouteAccess(to, next, window.chatwootConfig); }); From cc4851b19d1e3aedced341b01ff3bee5e43e0983 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 3 Jul 2024 15:13:16 -0700 Subject: [PATCH 04/22] chore: Move frontend authorization to permission based system (#9709) We previously relied on user roles to determine whether to render specific routes in our frontend components. A permissions-based model is replacing this approach. Follow up: #9695 Co-authored-by: Pranav --- .../dashboard/components/layout/Sidebar.vue | 11 ++- .../layout/config/sidebarItems/primaryMenu.js | 9 +- .../layout/config/sidebarItems/settings.js | 46 +++++++++- .../layout/sidebarComponents/Secondary.vue | 26 +++--- .../sidebarComponents/SecondaryNavItem.vue | 47 +++++----- .../dashboard/components/policy.vue | 23 +++++ .../dashboard/helper/permissionsHelper.js | 34 +++++++ .../dashboard/helper/routeHelpers.js | 23 ++--- .../helper/specs/permissionsHelper.spec.js | 84 ++++++++++++++++++ .../helper/specs/routeHelpers.spec.js | 66 +++++++------- .../dashboard/modules/search/search.routes.js | 4 +- .../routes/dashboard/contacts/routes.js | 16 +++- .../conversation/conversation.routes.js | 64 ++++++++++---- .../routes/dashboard/dashboard.routes.js | 4 +- .../dashboard/helpcenter/helpcenter.routes.js | 88 ++++++++++++++----- .../routes/dashboard/inbox/routes.js | 8 +- .../routes/dashboard/notifications/routes.js | 4 +- .../settings/account/account.routes.js | 8 +- .../settings/agentBots/agentBot.routes.js | 16 +++- .../dashboard/settings/agents/agent.routes.js | 5 +- .../settings/attributes/attributes.routes.js | 5 +- .../settings/auditlogs/audit.routes.js | 5 +- .../settings/automation/automation.routes.js | 5 +- .../settings/billing/billing.routes.js | 8 +- .../settings/campaigns/campaigns.routes.js | 8 +- .../settings/canned/canned.routes.js | 5 +- .../dashboard/settings/inbox/inbox.routes.js | 25 ++++-- .../integrationapps/integrations.routes.js | 8 +- .../integrations/integrations.routes.js | 20 +++-- .../settings/labels/labels.routes.js | 8 +- .../settings/macros/macros.routes.js | 12 ++- .../settings/profile/profile.routes.js | 8 +- .../settings/reports/reports.routes.js | 36 ++++++-- .../dashboard/settings/settings.routes.js | 4 +- .../dashboard/settings/sla/sla.routes.js | 8 +- .../dashboard/settings/teams/teams.routes.js | 29 ++++-- app/javascript/dashboard/routes/index.js | 31 +------ 37 files changed, 582 insertions(+), 229 deletions(-) create mode 100644 app/javascript/dashboard/components/policy.vue create mode 100644 app/javascript/dashboard/helper/permissionsHelper.js create mode 100644 app/javascript/dashboard/helper/specs/permissionsHelper.spec.js diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index 0f3a1a857..4ccaa97df 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -20,7 +20,7 @@ :teams="teams" :custom-views="customViews" :menu-config="activeSecondaryMenu" - :current-role="currentRole" + :current-user="currentUser" :is-on-chatwoot-cloud="isOnChatwootCloud" @add-label="showAddLabelPopup" @toggle-accounts="toggleAccountModal" @@ -37,7 +37,8 @@ import alertMixin from 'shared/mixins/alertMixin'; import PrimarySidebar from './sidebarComponents/Primary.vue'; import SecondarySidebar from './sidebarComponents/Secondary.vue'; import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins'; -import router from '../../routes'; +import router, { routesWithPermissions } from '../../routes'; +import { hasPermissions } from '../../helper/permissionsHelper'; export default { components: { @@ -98,9 +99,13 @@ export default { return getSidebarItems(this.accountId); }, primaryMenuItems() { + const userPermissions = this.currentUser.permissions; const menuItems = this.sideMenuConfig.primaryMenu; return menuItems.filter(menuItem => { - const isAvailableForTheUser = menuItem.roles.includes(this.currentRole); + const isAvailableForTheUser = hasPermissions( + routesWithPermissions[menuItem.toStateName], + userPermissions + ); if (!isAvailableForTheUser) { return false; diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js b/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js index 7513e3d1c..92b8765c6 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/primaryMenu.js @@ -9,7 +9,6 @@ const primaryMenuItems = accountId => [ featureFlag: FEATURE_FLAGS.INBOX_VIEW, toState: frontendURL(`accounts/${accountId}/inbox-view`), toStateName: 'inbox_view', - roles: ['administrator', 'agent'], }, { icon: 'chat', @@ -17,7 +16,6 @@ const primaryMenuItems = accountId => [ label: 'CONVERSATIONS', toState: frontendURL(`accounts/${accountId}/dashboard`), toStateName: 'home', - roles: ['administrator', 'agent'], }, { icon: 'book-contacts', @@ -26,7 +24,6 @@ const primaryMenuItems = accountId => [ featureFlag: FEATURE_FLAGS.CRM, toState: frontendURL(`accounts/${accountId}/contacts`), toStateName: 'contacts_dashboard', - roles: ['administrator', 'agent'], }, { icon: 'arrow-trending-lines', @@ -34,8 +31,7 @@ const primaryMenuItems = accountId => [ label: 'REPORTS', featureFlag: FEATURE_FLAGS.REPORTS, toState: frontendURL(`accounts/${accountId}/reports`), - toStateName: 'settings_account_reports', - roles: ['administrator'], + toStateName: 'account_overview_reports', }, { icon: 'megaphone', @@ -44,7 +40,6 @@ const primaryMenuItems = accountId => [ featureFlag: FEATURE_FLAGS.CAMPAIGNS, toState: frontendURL(`accounts/${accountId}/campaigns`), toStateName: 'ongoing_campaigns', - roles: ['administrator'], }, { icon: 'library', @@ -54,7 +49,6 @@ const primaryMenuItems = accountId => [ alwaysVisibleOnChatwootInstances: true, toState: frontendURL(`accounts/${accountId}/portals`), toStateName: 'default_portal_articles', - roles: ['administrator'], }, { icon: 'settings', @@ -62,7 +56,6 @@ const primaryMenuItems = accountId => [ label: 'SETTINGS', toState: frontendURL(`accounts/${accountId}/settings`), toStateName: 'settings_home', - roles: ['administrator', 'agent'], }, ]; diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js index f450bd7a7..f7d63f6a1 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js @@ -24,7 +24,6 @@ const settings = accountId => ({ 'settings_inbox_list', 'settings_inbox_new', 'settings_inbox_show', - 'settings_inbox', 'settings_inboxes_add_agents', 'settings_inboxes_page_channel', 'settings_integrations_dashboard_apps', @@ -46,6 +45,9 @@ const settings = accountId => ({ icon: 'briefcase', label: 'ACCOUNT_SETTINGS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/general`), toStateName: 'general_settings_index', }, @@ -53,6 +55,9 @@ const settings = accountId => ({ icon: 'people', label: 'AGENTS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/agents/list`), toStateName: 'agent_list', featureFlag: FEATURE_FLAGS.AGENT_MANAGEMENT, @@ -61,6 +66,9 @@ const settings = accountId => ({ icon: 'people-team', label: 'TEAMS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/teams/list`), toStateName: 'settings_teams_list', featureFlag: FEATURE_FLAGS.TEAM_MANAGEMENT, @@ -69,6 +77,9 @@ const settings = accountId => ({ icon: 'mail-inbox-all', label: 'INBOXES', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`), toStateName: 'settings_inbox_list', featureFlag: FEATURE_FLAGS.INBOX_MANAGEMENT, @@ -77,6 +88,9 @@ const settings = accountId => ({ icon: 'tag', label: 'LABELS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/labels/list`), toStateName: 'labels_list', featureFlag: FEATURE_FLAGS.LABELS, @@ -85,6 +99,9 @@ const settings = accountId => ({ icon: 'code', label: 'CUSTOM_ATTRIBUTES', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL( `accounts/${accountId}/settings/custom-attributes/list` ), @@ -95,6 +112,9 @@ const settings = accountId => ({ icon: 'automation', label: 'AUTOMATION', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/automation/list`), toStateName: 'automation_list', featureFlag: FEATURE_FLAGS.AUTOMATIONS, @@ -103,6 +123,9 @@ const settings = accountId => ({ icon: 'bot', label: 'AGENT_BOTS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, globalConfigFlag: 'csmlEditorHost', toState: frontendURL(`accounts/${accountId}/settings/agent-bots`), toStateName: 'agent_bots', @@ -112,6 +135,9 @@ const settings = accountId => ({ icon: 'flash-settings', label: 'MACROS', hasSubMenu: false, + meta: { + permissions: ['administrator', 'agent'], + }, toState: frontendURL(`accounts/${accountId}/settings/macros`), toStateName: 'macros_wrapper', featureFlag: FEATURE_FLAGS.MACROS, @@ -120,6 +146,9 @@ const settings = accountId => ({ icon: 'chat-multiple', label: 'CANNED_RESPONSES', hasSubMenu: false, + meta: { + permissions: ['administrator', 'agent'], + }, toState: frontendURL( `accounts/${accountId}/settings/canned-response/list` ), @@ -130,6 +159,9 @@ const settings = accountId => ({ icon: 'flash-on', label: 'INTEGRATIONS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/integrations`), toStateName: 'settings_integrations', featureFlag: FEATURE_FLAGS.INTEGRATIONS, @@ -138,6 +170,9 @@ const settings = accountId => ({ icon: 'star-emphasis', label: 'APPLICATIONS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/applications`), toStateName: 'settings_applications', featureFlag: FEATURE_FLAGS.INTEGRATIONS, @@ -146,6 +181,9 @@ const settings = accountId => ({ icon: 'key', label: 'AUDIT_LOGS', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/audit-log/list`), toStateName: 'auditlogs_list', isEnterpriseOnly: true, @@ -156,6 +194,9 @@ const settings = accountId => ({ icon: 'document-list-clock', label: 'SLA', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/sla/list`), toStateName: 'sla_list', isEnterpriseOnly: true, @@ -166,6 +207,9 @@ const settings = accountId => ({ icon: 'credit-card-person', label: 'BILLING', hasSubMenu: false, + meta: { + permissions: ['administrator'], + }, toState: frontendURL(`accounts/${accountId}/settings/billing`), toStateName: 'billing_settings_index', showOnlyOnCloud: true, diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue index f291d221e..b4cd3ca90 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue @@ -29,6 +29,8 @@ import SecondaryNavItem from './SecondaryNavItem.vue'; import AccountContext from './AccountContext.vue'; import { mapGetters } from 'vuex'; import { FEATURE_FLAGS } from '../../../featureFlags'; +import { hasPermissions } from '../../../helper/permissionsHelper'; +import { routesWithPermissions } from '../../../routes'; export default { components: { @@ -60,9 +62,9 @@ export default { type: Object, default: () => {}, }, - currentRole: { - type: String, - default: '', + currentUser: { + type: Object, + default: () => {}, }, isOnChatwootCloud: { type: Boolean, @@ -80,16 +82,16 @@ export default { return this.customViews.filter(view => view.filter_type === 'contact'); }, accessibleMenuItems() { - if (!this.currentRole) { - return []; - } - const menuItemsFilteredByRole = this.menuConfig.menuItems.filter( - menuItem => - window.roleWiseRoutes[this.currentRole].indexOf( - menuItem.toStateName - ) > -1 + const menuItemsFilteredByPermissions = this.menuConfig.menuItems.filter( + menuItem => { + const { permissions: userPermissions = [] } = this.currentUser; + return hasPermissions( + routesWithPermissions[menuItem.toStateName], + userPermissions + ); + } ); - return menuItemsFilteredByRole.filter(item => { + return menuItemsFilteredByPermissions.filter(item => { if (item.showOnlyOnCloud) { return this.isOnChatwootCloud; } diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue index a7009523a..bc3b03646 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/SecondaryNavItem.vue @@ -65,27 +65,29 @@ :show-child-count="showChildCount(child.count)" :child-item-count="child.count" /> - -
  • - - - {{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }} - - -
  • -
    + + +
  • + + + {{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }} + + +
  • +
    +
    @@ -105,9 +107,10 @@ import { isOnMentionsView, isOnUnattendedView, } from '../../../store/modules/conversations/helpers/actionHelpers'; +import Policy from '../../policy.vue'; export default { - components: { SecondaryChildNavItem }, + components: { SecondaryChildNavItem, Policy }, mixins: [adminMixin, configMixin], props: { menuItem: { diff --git a/app/javascript/dashboard/components/policy.vue b/app/javascript/dashboard/components/policy.vue new file mode 100644 index 000000000..f888de5a2 --- /dev/null +++ b/app/javascript/dashboard/components/policy.vue @@ -0,0 +1,23 @@ + + + diff --git a/app/javascript/dashboard/helper/permissionsHelper.js b/app/javascript/dashboard/helper/permissionsHelper.js new file mode 100644 index 000000000..135238371 --- /dev/null +++ b/app/javascript/dashboard/helper/permissionsHelper.js @@ -0,0 +1,34 @@ +export const hasPermissions = ( + requiredPermissions = [], + availablePermissions = [] +) => { + return requiredPermissions.some(permission => + availablePermissions.includes(permission) + ); +}; + +const isPermissionsPresentInRoute = route => + route.meta && route.meta.permissions; + +export const buildPermissionsFromRouter = (routes = []) => + routes.reduce((acc, route) => { + if (route.name) { + if (!isPermissionsPresentInRoute(route)) { + // eslint-disable-next-line + console.error(route); + throw new Error( + "The route doesn't have the required permissions defined" + ); + } + acc[route.name] = route.meta.permissions; + } + + if (route.children) { + acc = { + ...acc, + ...buildPermissionsFromRouter(route.children), + }; + } + + return acc; + }, {}); diff --git a/app/javascript/dashboard/helper/routeHelpers.js b/app/javascript/dashboard/helper/routeHelpers.js index 68cf0a627..6c4036dd1 100644 --- a/app/javascript/dashboard/helper/routeHelpers.js +++ b/app/javascript/dashboard/helper/routeHelpers.js @@ -1,19 +1,16 @@ +import { hasPermissions } from './permissionsHelper'; + // eslint-disable-next-line default-param-last export const getCurrentAccount = ({ accounts } = {}, accountId) => { return accounts.find(account => account.id === accountId); }; -// eslint-disable-next-line default-param-last -export const getUserRole = ({ accounts } = {}, accountId) => { - const currentAccount = getCurrentAccount({ accounts }, accountId) || {}; - return currentAccount.role || null; +export const routeIsAccessibleFor = (route, userPermissions = []) => { + const { meta: { permissions: routePermissions = [] } = {} } = route; + return hasPermissions(routePermissions, userPermissions); }; -export const routeIsAccessibleFor = (route, role, roleWiseRoutes) => { - return roleWiseRoutes[role].includes(route); -}; - -const validateActiveAccountRoutes = (to, user, roleWiseRoutes) => { +const validateActiveAccountRoutes = (to, user) => { // If the current account is active, then check for the route permissions const accountDashboardURL = `accounts/${to.params.accountId}/dashboard`; @@ -22,15 +19,13 @@ const validateActiveAccountRoutes = (to, user, roleWiseRoutes) => { return accountDashboardURL; } - const userRole = getUserRole(user, Number(to.params.accountId)); - const isAccessible = routeIsAccessibleFor(to.name, userRole, roleWiseRoutes); + const isAccessible = routeIsAccessibleFor(to, user.permissions); // If the route is not accessible for the user, return to dashboard screen return isAccessible ? null : accountDashboardURL; }; -export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => { +export const validateLoggedInRoutes = (to, user) => { const currentAccount = getCurrentAccount(user, Number(to.params.accountId)); - // If current account is missing, either user does not have // access to the account or the account is deleted, return to login screen if (!currentAccount) { @@ -40,7 +35,7 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => { const isCurrentAccountActive = currentAccount.status === 'active'; if (isCurrentAccountActive) { - return validateActiveAccountRoutes(to, user, roleWiseRoutes); + return validateActiveAccountRoutes(to, user); } // If the current account is not active, then redirect the user to the suspended screen diff --git a/app/javascript/dashboard/helper/specs/permissionsHelper.spec.js b/app/javascript/dashboard/helper/specs/permissionsHelper.spec.js new file mode 100644 index 000000000..34f434f7d --- /dev/null +++ b/app/javascript/dashboard/helper/specs/permissionsHelper.spec.js @@ -0,0 +1,84 @@ +import { + buildPermissionsFromRouter, + hasPermissions, +} from '../permissionsHelper'; + +describe('hasPermissions', () => { + it('returns true if permission is present', () => { + expect( + hasPermissions(['contact_manage'], ['team_manage', 'contact_manage']) + ).toBe(true); + }); + + it('returns true if permission is not present', () => { + expect( + hasPermissions(['contact_manage'], ['team_manage', 'user_manage']) + ).toBe(false); + expect(hasPermissions()).toBe(false); + expect(hasPermissions([])).toBe(false); + }); +}); + +describe('buildPermissionsFromRouter', () => { + it('returns a valid object when routes have permissions defined', () => { + expect( + buildPermissionsFromRouter([ + { + path: 'agent', + name: 'agent_list', + meta: { permissions: ['agent_admin'] }, + }, + { + path: 'inbox', + children: [ + { + path: '', + name: 'inbox_list', + meta: { permissions: ['inbox_admin'] }, + }, + ], + }, + { + path: 'conversations', + children: [ + { + path: '', + children: [ + { + path: 'attachments', + name: 'attachments_list', + meta: { permissions: ['conversation_admin'] }, + }, + ], + }, + ], + }, + ]) + ).toEqual({ + agent_list: ['agent_admin'], + inbox_list: ['inbox_admin'], + attachments_list: ['conversation_admin'], + }); + }); + + it('throws an error if a named routed does not have permissions defined', () => { + expect(() => { + buildPermissionsFromRouter([ + { + path: 'agent', + name: 'agent_list', + }, + ]); + }).toThrow("The route doesn't have the required permissions defined"); + + expect(() => { + buildPermissionsFromRouter([ + { + path: 'agent', + name: 'agent_list', + meta: {}, + }, + ]); + }).toThrow("The route doesn't have the required permissions defined"); + }); +}); diff --git a/app/javascript/dashboard/helper/specs/routeHelpers.spec.js b/app/javascript/dashboard/helper/specs/routeHelpers.spec.js index 1e100a678..5aa9c4ee2 100644 --- a/app/javascript/dashboard/helper/specs/routeHelpers.spec.js +++ b/app/javascript/dashboard/helper/specs/routeHelpers.spec.js @@ -1,7 +1,6 @@ import { getConversationDashboardRoute, getCurrentAccount, - getUserRole, isAConversationRoute, routeIsAccessibleFor, validateLoggedInRoutes, @@ -15,24 +14,11 @@ describe('#getCurrentAccount', () => { }); }); -describe('#getUserRole', () => { - it('should return the current role', () => { - expect( - getUserRole({ accounts: [{ id: 1, role: 'administrator' }] }, 1) - ).toEqual('administrator'); - expect(getUserRole({ accounts: [] }, 1)).toEqual(null); - }); -}); - describe('#routeIsAccessibleFor', () => { it('should return the correct access', () => { - const roleWiseRoutes = { agent: ['conversations'], admin: ['billing'] }; - expect(routeIsAccessibleFor('billing', 'agent', roleWiseRoutes)).toEqual( - false - ); - expect(routeIsAccessibleFor('billing', 'admin', roleWiseRoutes)).toEqual( - true - ); + let route = { meta: { permissions: ['administrator'] } }; + expect(routeIsAccessibleFor(route, ['agent'])).toEqual(false); + expect(routeIsAccessibleFor(route, ['administrator'])).toEqual(true); }); }); @@ -40,11 +26,7 @@ describe('#validateLoggedInRoutes', () => { describe('when account access is missing', () => { it('should return the login route', () => { expect( - validateLoggedInRoutes( - { params: { accountId: 1 } }, - { accounts: [] }, - {} - ) + validateLoggedInRoutes({ params: { accountId: 1 } }, { accounts: [] }) ).toEqual(`app/login`); }); }); @@ -53,9 +35,12 @@ describe('#validateLoggedInRoutes', () => { it('return suspended route', () => { expect( validateLoggedInRoutes( - { name: 'conversations', params: { accountId: 1 } }, - { accounts: [{ id: 1, role: 'agent', status: 'suspended' }] }, - { agent: ['conversations'] } + { + name: 'conversations', + params: { accountId: 1 }, + meta: { permissions: ['agent'] }, + }, + { accounts: [{ id: 1, role: 'agent', status: 'suspended' }] } ) ).toEqual(`accounts/1/suspended`); }); @@ -65,9 +50,22 @@ describe('#validateLoggedInRoutes', () => { it('returns null (no action required)', () => { expect( validateLoggedInRoutes( - { name: 'conversations', params: { accountId: 1 } }, - { accounts: [{ id: 1, role: 'agent', status: 'active' }] }, - { agent: ['conversations'] } + { + name: 'conversations', + params: { accountId: 1 }, + meta: { permissions: ['agent'] }, + }, + { + permissions: ['agent'], + accounts: [ + { + id: 1, + role: 'agent', + permissions: ['agent'], + status: 'active', + }, + ], + } ) ).toEqual(null); }); @@ -76,9 +74,12 @@ describe('#validateLoggedInRoutes', () => { it('returns dashboard url', () => { expect( validateLoggedInRoutes( - { name: 'conversations', params: { accountId: 1 } }, - { accounts: [{ id: 1, role: 'agent', status: 'active' }] }, - { admin: ['conversations'], agent: [] } + { + name: 'billing', + params: { accountId: 1 }, + meta: { permissions: ['administrator'] }, + }, + { accounts: [{ id: 1, role: 'agent', status: 'active' }] } ) ).toEqual(`accounts/1/dashboard`); }); @@ -88,8 +89,7 @@ describe('#validateLoggedInRoutes', () => { expect( validateLoggedInRoutes( { name: 'account_suspended', params: { accountId: 1 } }, - { accounts: [{ id: 1, role: 'agent', status: 'active' }] }, - { agent: ['account_suspended'] } + { accounts: [{ id: 1, role: 'agent', status: 'active' }] } ) ).toEqual(`accounts/1/dashboard`); }); diff --git a/app/javascript/dashboard/modules/search/search.routes.js b/app/javascript/dashboard/modules/search/search.routes.js index 320f64a44..d2d7b19c4 100644 --- a/app/javascript/dashboard/modules/search/search.routes.js +++ b/app/javascript/dashboard/modules/search/search.routes.js @@ -7,7 +7,9 @@ export const routes = [ { path: frontendURL('accounts/:accountId/search'), name: 'search', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: SearchView, }, ]; diff --git a/app/javascript/dashboard/routes/dashboard/contacts/routes.js b/app/javascript/dashboard/routes/dashboard/contacts/routes.js index 10a560740..a07ca6bf3 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/routes.js +++ b/app/javascript/dashboard/routes/dashboard/contacts/routes.js @@ -7,13 +7,17 @@ export const routes = [ { path: frontendURL('accounts/:accountId/contacts'), name: 'contacts_dashboard', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ContactsView, }, { path: frontendURL('accounts/:accountId/contacts/custom_view/:id'), name: 'contacts_segments_dashboard', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ContactsView, props: route => { return { segmentsId: route.params.id }; @@ -22,7 +26,9 @@ export const routes = [ { path: frontendURL('accounts/:accountId/labels/:label/contacts'), name: 'contacts_labels_dashboard', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ContactsView, props: route => { return { label: route.params.label }; @@ -31,7 +37,9 @@ export const routes = [ { path: frontendURL('accounts/:accountId/contacts/:contactId'), name: 'contact_profile_dashboard', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ContactManageView, props: route => { return { contactId: route.params.contactId }; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js index 14487682e..7a81e2f4e 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js +++ b/app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js @@ -7,7 +7,9 @@ export default { { path: frontendURL('accounts/:accountId/dashboard'), name: 'home', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: () => { return { inboxId: 0 }; @@ -16,7 +18,9 @@ export default { { path: frontendURL('accounts/:accountId/conversations/:conversation_id'), name: 'inbox_conversation', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => { return { inboxId: 0, conversationId: route.params.conversation_id }; @@ -25,7 +29,9 @@ export default { { path: frontendURL('accounts/:accountId/inbox/:inbox_id'), name: 'inbox_dashboard', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => { return { inboxId: route.params.inbox_id }; @@ -36,7 +42,9 @@ export default { 'accounts/:accountId/inbox/:inbox_id/conversations/:conversation_id' ), name: 'conversation_through_inbox', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => { return { @@ -48,7 +56,9 @@ export default { { path: frontendURL('accounts/:accountId/label/:label'), name: 'label_conversations', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ label: route.params.label }), }, @@ -57,7 +67,9 @@ export default { 'accounts/:accountId/label/:label/conversations/:conversation_id' ), name: 'conversations_through_label', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ conversationId: route.params.conversation_id, @@ -67,7 +79,9 @@ export default { { path: frontendURL('accounts/:accountId/team/:teamId'), name: 'team_conversations', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ teamId: route.params.teamId }), }, @@ -76,7 +90,9 @@ export default { 'accounts/:accountId/team/:teamId/conversations/:conversationId' ), name: 'conversations_through_team', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ conversationId: route.params.conversationId, @@ -86,7 +102,9 @@ export default { { path: frontendURL('accounts/:accountId/custom_view/:id'), name: 'folder_conversations', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ foldersId: route.params.id }), }, @@ -95,7 +113,9 @@ export default { 'accounts/:accountId/custom_view/:id/conversations/:conversation_id' ), name: 'conversations_through_folders', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ conversationId: route.params.conversation_id, @@ -105,7 +125,9 @@ export default { { path: frontendURL('accounts/:accountId/mentions/conversations'), name: 'conversation_mentions', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: () => ({ conversationType: 'mention' }), }, @@ -114,7 +136,9 @@ export default { 'accounts/:accountId/mentions/conversations/:conversationId' ), name: 'conversation_through_mentions', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ conversationId: route.params.conversationId, @@ -124,7 +148,9 @@ export default { { path: frontendURL('accounts/:accountId/unattended/conversations'), name: 'conversation_unattended', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: () => ({ conversationType: 'unattended' }), }, @@ -133,7 +159,9 @@ export default { 'accounts/:accountId/unattended/conversations/:conversationId' ), name: 'conversation_through_unattended', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ conversationId: route.params.conversationId, @@ -143,7 +171,9 @@ export default { { path: frontendURL('accounts/:accountId/participating/conversations'), name: 'conversation_participating', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: () => ({ conversationType: 'participating' }), }, @@ -152,7 +182,9 @@ export default { 'accounts/:accountId/participating/conversations/:conversationId' ), name: 'conversation_through_participating', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ConversationView, props: route => ({ conversationId: route.params.conversationId, diff --git a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js index 9e18ca42c..b02f29342 100644 --- a/app/javascript/dashboard/routes/dashboard/dashboard.routes.js +++ b/app/javascript/dashboard/routes/dashboard/dashboard.routes.js @@ -28,7 +28,9 @@ export default { { path: frontendURL('accounts/:accountId/suspended'), name: 'account_suspended', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: Suspended, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js b/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js index a165b0277..0326534f3 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js @@ -30,13 +30,17 @@ const portalRoutes = [ { path: getPortalRoute(''), name: 'default_portal_articles', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator'], + }, component: DefaultPortalArticles, }, { path: getPortalRoute('all'), name: 'list_all_portals', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllPortals, }, { @@ -47,55 +51,73 @@ const portalRoutes = [ path: '', name: 'new_portal_information', component: PortalDetails, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':portalSlug/customization', name: 'portal_customization', component: PortalCustomization, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':portalSlug/finish', name: 'portal_finish', component: PortalSettingsFinish, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, { path: getPortalRoute(':portalSlug'), name: 'portalSlug', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ShowPortal, }, { path: getPortalRoute(':portalSlug/edit'), - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: EditPortal, children: [ { path: '', name: 'edit_portal_information', component: EditPortalBasic, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'customizations', name: 'edit_portal_customization', component: EditPortalCustomization, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'locales', name: 'edit_portal_locales', component: EditPortalLocales, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'categories', name: 'list_all_locale_categories', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllCategories, }, ], @@ -106,39 +128,51 @@ const articleRoutes = [ { path: getPortalRoute(':portalSlug/:locale/articles'), name: 'list_all_locale_articles', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllArticles, }, { path: getPortalRoute(':portalSlug/:locale/articles/new'), name: 'new_article', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: NewArticle, }, { path: getPortalRoute(':portalSlug/:locale/articles/mine'), name: 'list_mine_articles', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllArticles, }, { path: getPortalRoute(':portalSlug/:locale/articles/archived'), name: 'list_archived_articles', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllArticles, }, { path: getPortalRoute(':portalSlug/:locale/articles/draft'), name: 'list_draft_articles', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllArticles, }, { path: getPortalRoute(':portalSlug/:locale/articles/:articleSlug'), name: 'edit_article', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: EditArticle, }, ]; @@ -147,19 +181,25 @@ const categoryRoutes = [ { path: getPortalRoute(':portalSlug/:locale/categories'), name: 'all_locale_categories', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllCategories, }, { path: getPortalRoute(':portalSlug/:locale/categories/new'), name: 'new_category_in_locale', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: NewCategory, }, { path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'), name: 'show_category', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListAllArticles, }, { @@ -167,13 +207,17 @@ const categoryRoutes = [ ':portalSlug/:locale/categories/:categorySlug/articles' ), name: 'show_category_articles', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: ListCategoryArticles, }, { path: getPortalRoute(':portalSlug/:locale/categories/:categorySlug'), name: 'edit_category', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: EditCategory, }, ]; diff --git a/app/javascript/dashboard/routes/dashboard/inbox/routes.js b/app/javascript/dashboard/routes/dashboard/inbox/routes.js index 9c26fa64e..729fee76d 100644 --- a/app/javascript/dashboard/routes/dashboard/inbox/routes.js +++ b/app/javascript/dashboard/routes/dashboard/inbox/routes.js @@ -12,13 +12,17 @@ export const routes = [ path: '', name: 'inbox_view', component: InboxEmptyStateView, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, { path: ':notification_id', name: 'inbox_view_conversation', component: InboxDetailView, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/notifications/routes.js b/app/javascript/dashboard/routes/dashboard/notifications/routes.js index 38812fc92..e3d125333 100644 --- a/app/javascript/dashboard/routes/dashboard/notifications/routes.js +++ b/app/javascript/dashboard/routes/dashboard/notifications/routes.js @@ -18,7 +18,9 @@ export const routes = [ path: '', name: 'notifications_index', component: NotificationsView, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js index 0acfd5bd5..2742621e9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/account/account.routes.js @@ -6,7 +6,9 @@ export default { routes: [ { path: frontendURL('accounts/:accountId/settings/general'), - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: SettingsContent, props: { headerTitle: 'GENERAL_SETTINGS.TITLE', @@ -18,7 +20,9 @@ export default { path: '', name: 'general_settings_index', component: Index, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js b/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js index 382594c73..55210397f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/agentBots/agentBot.routes.js @@ -8,7 +8,9 @@ export default { routes: [ { path: frontendURL('accounts/:accountId/settings/agent-bots'), - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: SettingsContent, props: { headerTitle: 'AGENT_BOTS.HEADER', @@ -20,19 +22,25 @@ export default { path: '', name: 'agent_bots', component: Bot, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'csml/new', name: 'agent_bots_csml_new', component: CsmlNewBot, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'csml/:botId', name: 'agent_bots_csml_edit', component: CsmlEditBot, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js b/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js index cd23432ff..854450c5d 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/agents/agent.routes.js @@ -15,14 +15,15 @@ export default { children: [ { path: '', - name: 'agents_wrapper', redirect: 'list', }, { path: 'list', name: 'agent_list', component: AgentHome, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js b/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js index 72d79a9de..93c4f8eda 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/attributes/attributes.routes.js @@ -15,14 +15,15 @@ export default { children: [ { path: '', - name: 'attributes_wrapper', redirect: 'list', }, { path: 'list', name: 'attributes_list', component: AttributesHome, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js index 00c1fda74..acf061288 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/auditlogs/audit.routes.js @@ -16,13 +16,14 @@ export default { children: [ { path: '', - name: 'auditlogs_wrapper', redirect: 'list', }, { path: 'list', name: 'auditlogs_list', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: AuditLogsHome, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js b/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js index 3a5d6c887..14b03a45a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/automation.routes.js @@ -15,14 +15,15 @@ export default { children: [ { path: '', - name: 'automation_wrapper', redirect: 'list', }, { path: 'list', name: 'automation_list', component: Automation, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js b/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js index 7a37f320a..f1042e81a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/billing/billing.routes.js @@ -6,7 +6,9 @@ export default { routes: [ { path: frontendURL('accounts/:accountId/settings/billing'), - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: SettingsContent, props: { headerTitle: 'BILLING_SETTINGS.TITLE', @@ -18,7 +20,9 @@ export default { path: '', name: 'billing_settings_index', component: Index, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js index 1ac007ed6..5c8581981 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/campaigns/campaigns.routes.js @@ -19,7 +19,9 @@ export default { { path: 'ongoing', name: 'ongoing_campaigns', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: Index, }, ], @@ -35,7 +37,9 @@ export default { { path: 'one_off', name: 'one_off', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: Index, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js index 73b370e2a..236801c8c 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/canned/canned.routes.js @@ -16,13 +16,14 @@ export default { children: [ { path: '', - name: 'canned_wrapper', redirect: 'list', }, { path: 'list', name: 'canned_list', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: CannedHome, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js b/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js index 4e4c14c00..3866b96bd 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/inbox.routes.js @@ -28,14 +28,15 @@ export default { children: [ { path: '', - name: 'settings_inbox', redirect: 'list', }, { path: 'list', name: 'settings_inbox_list', component: InboxHome, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'new', @@ -45,19 +46,25 @@ export default { path: '', name: 'settings_inbox_new', component: ChannelList, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':inbox_id/finish', name: 'settings_inbox_finish', component: FinishSetup, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':sub_page', name: 'settings_inboxes_page_channel', component: channelFactory.create(), - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, props: route => { return { channel_name: route.params.sub_page }; }, @@ -65,7 +72,9 @@ export default { { path: ':inbox_id/agents', name: 'settings_inboxes_add_agents', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: AddAgents, }, ], @@ -74,7 +83,9 @@ export default { path: ':inboxId', name: 'settings_inbox_show', component: Settings, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js index a0f8477b2..d3074f62d 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrationapps/integrations.routes.js @@ -26,13 +26,17 @@ export default { path: '', name: 'settings_applications', component: Index, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':integration_id', name: 'settings_applications_integration', component: IntegrationHooks, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, props: route => ({ integrationId: route.params.integration_id, }), diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js index 542cf591e..bdebc0329 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js @@ -30,32 +30,42 @@ export default { path: '', name: 'settings_integrations', component: Index, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'webhook', component: Webhook, name: 'settings_integrations_webhook', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'dashboard-apps', component: DashboardApps, name: 'settings_integrations_dashboard_apps', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'slack', name: 'settings_integrations_slack', component: Slack, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, props: route => ({ code: route.query.code }), }, { path: ':integration_id', name: 'settings_integrations_integration', component: ShowIntegration, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, props: route => { return { integrationId: route.params.integration_id, diff --git a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js index 4fc514da6..088c565d0 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/labels/labels.routes.js @@ -17,13 +17,17 @@ export default { { path: '', name: 'labels_wrapper', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, redirect: 'list', }, { path: 'list', name: 'labels_list', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: Index, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js index 58a26819f..06d0014b9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/macros.routes.js @@ -23,19 +23,25 @@ export default { path: '', name: 'macros_wrapper', component: Macros, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, { path: 'new', name: 'macros_new', component: MacroEditor, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, { path: ':macroId/edit', name: 'macros_edit', component: MacroEditor, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js b/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js index 357c20e85..31f386e37 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/profile.routes.js @@ -8,14 +8,18 @@ export default { { path: frontendURL('accounts/:accountId/profile'), name: 'profile_settings', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, component: SettingsContent, children: [ { path: 'settings', name: 'profile_settings_index', component: Index, - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, }, ], }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js index 3792567da..eaca0e76d 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js @@ -29,7 +29,9 @@ export default { { path: 'overview', name: 'account_overview_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: LiveReports, }, ], @@ -46,7 +48,9 @@ export default { { path: 'conversation', name: 'conversation_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: Index, }, ], @@ -63,7 +67,9 @@ export default { { path: 'csat', name: 'csat_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: CsatResponses, }, ], @@ -80,7 +86,9 @@ export default { { path: 'bot', name: 'bot_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: BotReports, }, ], @@ -97,7 +105,9 @@ export default { { path: 'agent', name: 'agent_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: AgentReports, }, ], @@ -114,7 +124,9 @@ export default { { path: 'label', name: 'label_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: LabelReports, }, ], @@ -131,7 +143,9 @@ export default { { path: 'inboxes', name: 'inbox_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: InboxReports, }, ], @@ -147,7 +161,9 @@ export default { { path: 'teams', name: 'team_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: TeamReports, }, ], @@ -164,7 +180,9 @@ export default { { path: 'sla', name: 'sla_reports', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: SLAReports, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js index dd5291fe8..2829ed1d9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/settings.routes.js @@ -24,7 +24,9 @@ export default { { path: frontendURL('accounts/:accountId/settings'), name: 'settings_home', - roles: ['administrator', 'agent'], + meta: { + permissions: ['administrator', 'agent'], + }, redirect: () => { if (store.getters.getCurrentRole === 'administrator') { return frontendURL('accounts/:accountId/settings/general'); diff --git a/app/javascript/dashboard/routes/dashboard/settings/sla/sla.routes.js b/app/javascript/dashboard/routes/dashboard/settings/sla/sla.routes.js index a9c68470a..762d85d58 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/sla/sla.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/sla/sla.routes.js @@ -13,13 +13,17 @@ export default { { path: '', name: 'sla_wrapper', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, redirect: 'list', }, { path: 'list', name: 'sla_list', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: Index, }, ], diff --git a/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js b/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js index 4228e72e7..c968351f4 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/teams/teams.routes.js @@ -29,14 +29,15 @@ export default { children: [ { path: '', - name: 'settings_teams', redirect: 'list', }, { path: 'list', name: 'settings_teams_list', component: TeamsHome, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'new', @@ -46,18 +47,24 @@ export default { path: '', name: 'settings_teams_new', component: CreateTeam, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':teamId/finish', name: 'settings_teams_finish', component: FinishSetup, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: ':teamId/agents', name: 'settings_teams_add_agents', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: AddAgents, }, ], @@ -70,18 +77,24 @@ export default { path: '', name: 'settings_teams_edit', component: EditTeam, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'agents', name: 'settings_teams_edit_members', component: EditAgents, - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, }, { path: 'finish', name: 'settings_teams_edit_finish', - roles: ['administrator'], + meta: { + permissions: ['administrator'], + }, component: FinishSetup, }, ], diff --git a/app/javascript/dashboard/routes/index.js b/app/javascript/dashboard/routes/index.js index e2245e20f..70d870700 100644 --- a/app/javascript/dashboard/routes/index.js +++ b/app/javascript/dashboard/routes/index.js @@ -5,33 +5,12 @@ import dashboard from './dashboard/dashboard.routes'; import store from '../store'; import { validateLoggedInRoutes } from '../helper/routeHelpers'; import AnalyticsHelper from '../helper/AnalyticsHelper'; +import { buildPermissionsFromRouter } from '../helper/permissionsHelper'; const routes = [...dashboard.routes]; -window.roleWiseRoutes = { - agent: [], - administrator: [], -}; - -// generateRoleWiseRoute - updates window object with agent/admin route -const generateRoleWiseRoute = route => { - route.forEach(element => { - if (element.children) { - generateRoleWiseRoute(element.children); - } - if (element.roles) { - element.roles.forEach(roleEl => { - window.roleWiseRoutes[roleEl].push(element.name); - }); - } - }); -}; -// Create a object of routes -// accessible by each role. -// returns an object with roles as keys and routeArr as values -generateRoleWiseRoute(routes); - export const router = new VueRouter({ mode: 'history', routes }); +export const routesWithPermissions = buildPermissionsFromRouter(routes); export const validateAuthenticateRoutePermission = (to, next, { getters }) => { const { isLoggedIn, getCurrentUser: user } = getters; @@ -45,11 +24,7 @@ export const validateAuthenticateRoutePermission = (to, next, { getters }) => { return next(frontendURL(`accounts/${user.account_id}/dashboard`)); } - const nextRoute = validateLoggedInRoutes( - to, - getters.getCurrentUser, - window.roleWiseRoutes - ); + const nextRoute = validateLoggedInRoutes(to, getters.getCurrentUser); return nextRoute ? next(frontendURL(nextRoute)) : next(); }; From aaf47b4c1fc20e787cd088b4bd2efc7fb092efcb Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 3 Jul 2024 15:48:57 -0700 Subject: [PATCH 05/22] chore: [Snyk] Security upgrade sidekiq from 7.2.4 to 7.3.0 (#9710) Upgrade gems to mitigate vulnerabilities. Co-authored-by: snyk-bot --- Gemfile | 2 +- Gemfile.lock | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 5e1e5917f..ad66c46c6 100644 --- a/Gemfile +++ b/Gemfile @@ -116,7 +116,7 @@ gem 'sentry-ruby', require: false gem 'sentry-sidekiq', '>= 5.18.0', require: false ##-- background job processing --## -gem 'sidekiq', '>= 7.2.4' +gem 'sidekiq', '>= 7.3.0' # We want cron jobs gem 'sidekiq-cron', '>= 1.12.0' diff --git a/Gemfile.lock b/Gemfile.lock index 04a8fb534..54226c13a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -434,6 +434,7 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) + logger (1.6.0) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -715,11 +716,12 @@ GEM sexp_processor (4.17.0) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) - sidekiq (7.2.4) + sidekiq (7.3.0) concurrent-ruby (< 2) connection_pool (>= 2.3.0) + logger rack (>= 2.2.4) - redis-client (>= 0.19.0) + redis-client (>= 0.22.2) sidekiq-cron (1.12.0) fugit (~> 1.8) globalid (>= 1.0.1) @@ -935,7 +937,7 @@ DEPENDENCIES sentry-ruby sentry-sidekiq (>= 5.18.0) shoulda-matchers - sidekiq (>= 7.2.4) + sidekiq (>= 7.3.0) sidekiq-cron (>= 1.12.0) simplecov (= 0.17.1) slack-ruby-client (~> 2.2.0) From 6ae606c981ffdbf5f711de28fb7e587fe5d0da20 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 4 Jul 2024 13:13:03 +0530 Subject: [PATCH 06/22] fix: Custom snooze is not working in mobile view (#9717) # Pull Request Template ## Description Currently, when a user navigates to a chat and attempts to access the custom snooze modal, it is not visible, making it unable to set custom snooze options. With this fix, the custom snooze modal will correctly display even when a chat is open in mobile view. **Cause of this issue** The `` component is added to the `` component. To accommodate small screen views, we are using the expanded view. However, if we open a chat and select the custom snooze option from the chat header in the message view, the `` component is hidden in the `` component. **Solution** So, I moved the `` to the wrapper component `` so we can use in all cases like, 1. Right-click to custom snooze 2. CMD bar custom snooze 3. Small screen custom snooze --- .../dashboard/components/ChatList.vue | 58 ----------------- .../conversation/ConversationView.vue | 62 ++++++++++++++++++- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index eb410f7d5..ee5354027 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -111,15 +111,6 @@ @updateFolder="onUpdateSavedFilter" /> - - -
    @@ -152,10 +143,6 @@ import { isOnUnattendedView, } from '../store/modules/conversations/helpers/actionHelpers'; import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events'; -import { CMD_SNOOZE_CONVERSATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents'; -import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers'; -import { getUnixTime } from 'date-fns'; -import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue'; import IntersectionObserver from './IntersectionObserver.vue'; export default { @@ -170,7 +157,6 @@ export default { ConversationBulkActions, IntersectionObserver, VirtualList, - CustomSnoozeModal, }, mixins: [ timeMixin, @@ -247,7 +233,6 @@ export default { root: this.$refs.conversationList, rootMargin: '100px 0px 100px 0px', }, - showCustomSnoozeModal: false, itemComponent: ConversationItem, // virtualListExtraProps is to pass the props to the conversationItem component. @@ -283,7 +268,6 @@ export default { campaigns: 'campaigns/getAllCampaigns', labels: 'labels/getLabels', selectedConversations: 'bulkActions/getSelectedConversationIds', - contextMenuChatId: 'getContextMenuChatId', }), hasAppliedFilters() { return this.appliedFilters.length !== 0; @@ -517,11 +501,6 @@ export default { this.$emitter.on('fetch_conversation_stats', () => { this.$store.dispatch('conversationStats/get', this.conversationFilters); }); - - this.$emitter.on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation); - }, - beforeDestroy() { - this.$emitter.off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation); }, methods: { updateVirtualListProps(key, value) { @@ -999,43 +978,6 @@ export default { onContextMenuToggle(state) { this.isContextMenuOpen = state; }, - onCmdSnoozeConversation(snoozeType) { - if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) { - this.showCustomSnoozeModal = true; - } else { - this.toggleStatus( - wootConstants.STATUS_TYPE.SNOOZED, - findSnoozeTime(snoozeType) || null - ); - } - }, - chooseSnoozeTime(customSnoozeTime) { - this.showCustomSnoozeModal = false; - if (customSnoozeTime) { - this.toggleStatus( - wootConstants.STATUS_TYPE.SNOOZED, - getUnixTime(customSnoozeTime) - ); - } - }, - toggleStatus(status, snoozedUntil) { - this.$store - .dispatch('toggleStatus', { - conversationId: this.currentChat?.id || this.contextMenuChatId, - status, - snoozedUntil, - }) - .then(() => { - this.$store.dispatch('setContextMenuChatId', null); - this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS')); - }); - }, - hideCustomSnoozeModal() { - // if we select custom snooze and then the custom snooze modal is open - // Then if the custom snooze modal is closed and set the context menu chat id to null - this.$store.dispatch('setContextMenuChatId', null); - this.showCustomSnoozeModal = false; - }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue b/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue index 2235b38f0..cd3cdfb80 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue @@ -22,25 +22,40 @@ :is-on-expanded-layout="isOnExpandedLayout" @contact-panel-toggle="onToggleContactPanel" /> + + + From 31bcdaa3ddfdb43fae1118f4ef5d6123af42a73c Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 4 Jul 2024 23:50:07 -0700 Subject: [PATCH 07/22] chore: Upgrade ruby to 3.3.3 (#9664) - Upgrade the ruby version to 3.3.3 --------- Co-authored-by: Vishnu Narayanan --- .circleci/config.yml | 2 +- .devcontainer/docker-compose.yml | 4 ++-- .ruby-version | 2 +- Gemfile | 2 +- Gemfile.lock | 31 +++++++++++++++------------- deployment/chatwoot-web.1.service | 6 +++--- deployment/chatwoot-worker.1.service | 6 +++--- deployment/setup_20.04.sh | 4 ++-- docker/Dockerfile | 10 ++++----- 9 files changed, 35 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c67063ae6..cdddc4a06 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ defaults: &defaults working_directory: ~/build docker: # specify the version you desire here - - image: cimg/ruby:3.2.2-browsers + - image: cimg/ruby:3.3.3-browsers # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 17021d1e7..a804bb15c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -12,7 +12,7 @@ services: args: VARIANT: "ubuntu-22.04" NODE_VERSION: "20.9.0" - RUBY_VERSION: "3.2.2" + RUBY_VERSION: "3.3.3" # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. USER_UID: "1000" USER_GID: "1000" @@ -25,7 +25,7 @@ services: args: VARIANT: "ubuntu-22.04" NODE_VERSION: "20.9.0" - RUBY_VERSION: "3.2.2" + RUBY_VERSION: "3.3.3" # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. USER_UID: "1000" USER_GID: "1000" diff --git a/.ruby-version b/.ruby-version index be94e6f53..619b53766 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.2 +3.3.3 diff --git a/Gemfile b/Gemfile index ad66c46c6..d7bfb6258 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby '3.2.2' +ruby '3.3.3' ##-- base gems for rails --## gem 'rack-cors', '2.0.0', require: 'rack/cors' diff --git a/Gemfile.lock b/Gemfile.lock index 54226c13a..23da215bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -183,13 +183,16 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.4) - ddtrace (1.11.1) - debase-ruby_core_source (>= 0.10.16, <= 3.2.0) - libdatadog (~> 2.0.0.1.0) - libddwaf (~> 1.8.2.0.0) + datadog-ci (0.8.3) msgpack - debase-ruby_core_source (3.2.0) + date (3.3.4) + ddtrace (1.23.2) + datadog-ci (~> 0.8.1) + debase-ruby_core_source (= 3.3.1) + libdatadog (~> 7.0.0.1.0) + libddwaf (~> 1.14.0.0.0) + msgpack + debase-ruby_core_source (3.3.1) debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) @@ -416,15 +419,15 @@ GEM addressable (~> 2.8) letter_opener (1.8.1) launchy (>= 2.2, < 3) - libdatadog (2.0.0.1.0) - libdatadog (2.0.0.1.0-x86_64-linux) - libddwaf (1.8.2.0.0) + libdatadog (7.0.0.1.0) + libdatadog (7.0.0.1.0-x86_64-linux) + libddwaf (1.14.0.0.0) ffi (~> 1.0) - libddwaf (1.8.2.0.0-arm64-darwin) + libddwaf (1.14.0.0.0-arm64-darwin) ffi (~> 1.0) - libddwaf (1.8.2.0.0-x86_64-darwin) + libddwaf (1.14.0.0.0-x86_64-darwin) ffi (~> 1.0) - libddwaf (1.8.2.0.0-x86_64-linux) + libddwaf (1.14.0.0.0-x86_64-linux) ffi (~> 1.0) line-bot-api (1.28.0) liquid (5.4.0) @@ -962,7 +965,7 @@ DEPENDENCIES working_hours RUBY VERSION - ruby 3.2.2p185 + ruby 3.3.3p89 BUNDLED WITH - 2.4.6 + 2.5.14 diff --git a/deployment/chatwoot-web.1.service b/deployment/chatwoot-web.1.service index 37889b1aa..049ec85db 100644 --- a/deployment/chatwoot-web.1.service +++ b/deployment/chatwoot-web.1.service @@ -16,10 +16,10 @@ KillMode=mixed StandardInput=null SyslogIdentifier=%p -Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.2.2/bin:/home/chatwoot/.rvm/gems/ruby-3.2.2@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.2.2/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" +Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3/bin:/home/chatwoot/.rvm/gems/ruby-3.3.3@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.3.3/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" Environment="PORT=3000" Environment="RAILS_ENV=production" Environment="NODE_ENV=production" Environment="RAILS_LOG_TO_STDOUT=true" -Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.2.2" -Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.2.2:/home/chatwoot/.rvm/gems/ruby-3.2.2@global" +Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.3.3" +Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3:/home/chatwoot/.rvm/gems/ruby-3.3.3@global" diff --git a/deployment/chatwoot-worker.1.service b/deployment/chatwoot-worker.1.service index 8000743c4..cbb97e88c 100644 --- a/deployment/chatwoot-worker.1.service +++ b/deployment/chatwoot-worker.1.service @@ -16,10 +16,10 @@ KillMode=mixed StandardInput=null SyslogIdentifier=%p -Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.2.2/bin:/home/chatwoot/.rvm/gems/ruby-3.2.2@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.2.2/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" +Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3/bin:/home/chatwoot/.rvm/gems/ruby-3.3.3@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.3.3/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" Environment="PORT=3000" Environment="RAILS_ENV=production" Environment="NODE_ENV=production" Environment="RAILS_LOG_TO_STDOUT=true" -Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.2.2" -Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.2.2:/home/chatwoot/.rvm/gems/ruby-3.2.2@global" +Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.3.3" +Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3:/home/chatwoot/.rvm/gems/ruby-3.3.3@global" diff --git a/deployment/setup_20.04.sh b/deployment/setup_20.04.sh index 8918e9d54..2f8d83f8b 100644 --- a/deployment/setup_20.04.sh +++ b/deployment/setup_20.04.sh @@ -336,8 +336,8 @@ function setup_chatwoot() { sudo -i -u chatwoot << EOF rvm --version rvm autolibs disable - rvm install "ruby-3.2.2" - rvm use 3.2.2 --default + rvm install "ruby-3.3.3" + rvm use 3.3.3 --default git clone https://github.com/chatwoot/chatwoot.git cd chatwoot diff --git a/docker/Dockerfile b/docker/Dockerfile index 6503530b7..9dcc73ad9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # pre-build stage -FROM ruby:3.2.2-alpine3.18 AS pre-builder +FROM ruby:3.3.3-alpine3.19 AS pre-builder # ARG default to production settings # For development docker-compose file overrides ARGS @@ -25,7 +25,7 @@ RUN apk update && apk add --no-cache \ tzdata \ postgresql-dev \ postgresql-client \ - nodejs-current \ + nodejs=20.12.1-r0 \ yarn \ git \ && mkdir -p /var/app \ @@ -67,13 +67,13 @@ RUN if [ "$RAILS_ENV" = "production" ]; then \ RUN git rev-parse HEAD > /app/.git_sha # Remove unnecessary files -RUN rm -rf /gems/ruby/3.2.0/cache/*.gem \ - && find /gems/ruby/3.2.0/gems/ \( -name "*.c" -o -name "*.o" \) -delete \ +RUN rm -rf /gems/ruby/3.3.0/cache/*.gem \ + && find /gems/ruby/3.3.0/gems/ \( -name "*.c" -o -name "*.o" \) -delete \ && rm -rf .git \ && rm .gitignore # final build stage -FROM ruby:3.2.2-alpine3.18 +FROM ruby:3.3.3-alpine3.19 ARG BUNDLE_WITHOUT="development:test" From fe246698b6dcd0d0164accb9f5f57760b381591a Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Mon, 8 Jul 2024 13:00:54 -0500 Subject: [PATCH 08/22] chore: [Snyk] Fix for 1 vulnerabilities (#9720) - updates for security vulnerabilities Co-authored-by: snyk-bot --- Gemfile | 4 ++-- Gemfile.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index d7bfb6258..f690e6876 100644 --- a/Gemfile +++ b/Gemfile @@ -111,9 +111,9 @@ gem 'elastic-apm', require: false gem 'newrelic_rpm', require: false gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false gem 'scout_apm', require: false -gem 'sentry-rails', '>= 5.18.0', require: false +gem 'sentry-rails', '>= 5.18.1', require: false gem 'sentry-ruby', require: false -gem 'sentry-sidekiq', '>= 5.18.0', require: false +gem 'sentry-sidekiq', '>= 5.18.1', require: false ##-- background job processing --## gem 'sidekiq', '>= 7.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 23da215bc..98a1e9345 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -464,7 +464,7 @@ GEM mini_magick (4.12.0) mini_mime (1.1.5) mini_portile2 (2.8.7) - minitest (5.24.0) + minitest (5.24.1) mock_redis (0.36.0) ruby2_keywords msgpack (1.7.0) @@ -707,14 +707,14 @@ GEM activesupport (>= 4) selectize-rails (0.12.6) semantic_range (3.0.0) - sentry-rails (5.18.0) + sentry-rails (5.18.1) railties (>= 5.0) - sentry-ruby (~> 5.18.0) - sentry-ruby (5.18.0) + sentry-ruby (~> 5.18.1) + sentry-ruby (5.18.1) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-sidekiq (5.18.0) - sentry-ruby (~> 5.18.0) + sentry-sidekiq (5.18.1) + sentry-ruby (~> 5.18.1) sidekiq (>= 3.0) sexp_processor (4.17.0) shoulda-matchers (5.3.0) @@ -936,9 +936,9 @@ DEPENDENCIES scout_apm scss_lint seed_dump - sentry-rails (>= 5.18.0) + sentry-rails (>= 5.18.1) sentry-ruby - sentry-sidekiq (>= 5.18.0) + sentry-sidekiq (>= 5.18.1) shoulda-matchers sidekiq (>= 7.3.0) sidekiq-cron (>= 1.12.0) From a2cb932d541a71b0384fb9c6df95c5ce5143929a Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 9 Jul 2024 09:03:05 -0700 Subject: [PATCH 09/22] chore: Upgrade csv-safe to the latest version (#9739) The following error starting is shown on the console after the ruby upgrade. csv.rb was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.4.0. Add csv to your Gemfile or gemspec. Also contact author of csv-safe-3.2.1 to add csv into its gemspec. Csv-safe has already added a patch via https://github.com/zvory/csv-safe/pull/20. This PR updates the version to the latest version of csv-safe (3.3.1) --- Gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 98a1e9345..91d3b3d8b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -174,7 +174,9 @@ GEM crack (0.4.5) rexml crass (1.0.6) - csv-safe (3.2.1) + csv (3.3.0) + csv-safe (3.3.1) + csv (~> 3.0) cypress-on-rails (1.16.0) rack database_cleaner (2.0.2) From 9498d1f0036f6e08fe47eb7448a65e8a0a307719 Mon Sep 17 00:00:00 2001 From: wudi Date: Wed, 10 Jul 2024 13:16:45 +0800 Subject: [PATCH 10/22] fix: Localize 'Social Profiles' text in ContactForm (#9745) --- .../routes/dashboard/conversation/contact/ContactForm.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue index b958d4211..cf4028976 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue @@ -117,7 +117,7 @@ />
    - +
    From 9de8c27368ba4da91f4416403108934f257cea9c Mon Sep 17 00:00:00 2001 From: Pranav Date: Wed, 10 Jul 2024 08:32:16 -0700 Subject: [PATCH 11/22] feat: Use vitest instead of jest, run all the specs anywhere in app/ folder in the CI (#9722) Due to the pattern `**/specs/*.spec.js` defined in CircleCI, none of the frontend spec in the folders such as `specs//getters.spec.js` were not executed in Circle CI. This PR fixes the issue, along with the following changes: - Use vitest instead of jest - Remove jest dependancies - Update tests to work with vitest --------- Co-authored-by: Muhsin Keloth --- .circleci/config.yml | 5 +- .eslintrc.js | 2 +- app/javascript/dashboard/App.Vue.spec.js | 11 - .../api/enterprise/specs/account.spec.js | 8 +- .../dashboard/api/specs/account.spec.js | 8 +- .../api/specs/accountActions.spec.js | 8 +- .../dashboard/api/specs/agents.spec.js | 2 +- .../dashboard/api/specs/article.spec.js | 40 +- .../api/specs/assignableAgents.spec.js | 8 +- .../api/specs/channel/fbChannel.spec.js | 8 +- .../dashboard/api/specs/contacts.spec.js | 8 +- .../dashboard/api/specs/conversations.spec.js | 8 +- .../dashboard/api/specs/csatReports.spec.js | 8 +- .../api/specs/inbox/conversation.spec.js | 8 +- .../dashboard/api/specs/inbox/message.spec.js | 8 +- .../dashboard/api/specs/inboxes.spec.js | 8 +- .../dashboard/api/specs/integrations.spec.js | 8 +- .../api/specs/integrations/dyte.spec.js | 16 +- .../api/specs/integrations/linear.spec.js | 56 +- .../dashboard/api/specs/notifications.spec.js | 8 +- .../dashboard/api/specs/reports.spec.js | 8 +- .../dashboard/api/specs/slaReports.spec.js | 8 +- .../dashboard/api/specs/teams.spec.js | 8 +- .../specs/AccountSelector.spec.js | 8 +- .../specs/AgentDetails.spec.js | 6 +- .../specs/NotificationBell.spec.js | 6 +- .../layout/specs/AvailabilityStatus.spec.js | 21 +- .../components/specs/SidemenuIcon.spec.js | 2 +- .../__snapshots__/SidemenuIcon.spec.js.snap | 4 +- .../helpers/specs/DatePickerHelper.spec.js | 14 +- .../widgets/conversation/ReplyBox.vue | 2 +- .../conversation/specs/MoreActions.spec.js | 24 +- .../mentionSelectionKeyboardMixin.spec.js | 8 +- .../composables/spec/emitter.spec.js | 12 +- .../AnalyticsHelper/specs/helper.spec.js | 16 +- .../AnalyticsHelper/specs/plugin.spec.js | 31 +- .../specs/CacheHelper/DataManger.spec.js | 2 +- .../helper/specs/ReconnectService.spec.js | 62 +- .../dashboard/helper/specs/commons.spec.js | 41 +- .../helper/specs/directives/resize.spec.js | 14 +- .../helper/specs/editorHelper.spec.js | 14 +- .../helper/specs/themeHelper.spec.js | 2 +- .../helper/specs/uploadHelper.spec.js | 3 +- .../dashboard/mixins/specs/aiMixin.spec.js | 18 +- .../mixins/specs/attributeMixin.spec.js | 2 +- .../mixins/specs/fileUploadMixin.spec.js | 24 +- .../dashboard/mixins/specs/time.spec.js | 8 +- .../dashboard/mixins/specs/uiSettings.spec.js | 2 +- .../dashboard/routes/dashboard/Dashboard.vue | 2 +- .../components/specs/AddReminder.spec.js | 6 +- .../conversation/conversation.routes.js | 2 +- .../components/HelpCenterLayout.vue | 5 +- .../dashboard/helpcenter/helpcenter.routes.js | 38 +- .../pages/categories/NameEmojiInput.vue | 2 +- .../settings/inbox/channel-factory.js | 18 +- .../specs/Filters/FiltersAgents.spec.js | 6 +- .../specs/Filters/FiltersDateGroupBy.spec.js | 2 +- .../specs/Filters/FiltersDateRange.spec.js | 2 +- .../specs/Filters/FiltersInboxes.spec.js | 4 +- .../specs/Filters/FiltersLabels.spec.js | 4 +- .../specs/Filters/FiltersRatings.spec.js | 2 +- .../specs/Filters/FiltersTeams.spec.js | 2 +- .../__snapshots__/CSATMetrics.spec.js.snap | 8 +- app/javascript/dashboard/routes/index.spec.js | 31 +- .../helpCenterArticles/specs/action.spec.js | 8 +- .../specs/actions.spec.js | 4 +- .../helpCenterPortals/specs/actions.spec.js | 6 +- .../modules/specs/account/actions.spec.js | 4 +- .../modules/specs/account/getters.spec.js | 10 +- .../modules/specs/agentBots/agentBots.spec.js | 4 +- .../modules/specs/agents/actions.spec.js | 6 +- .../modules/specs/attributes/actions.spec.js | 4 +- .../store/modules/specs/auth/actions.spec.js | 25 +- .../store/modules/specs/auth/getters.spec.js | 3 - .../modules/specs/automations/actions.spec.js | 4 +- .../modules/specs/bulkActions/actions.spec.js | 4 +- .../modules/specs/campaigns/actions.spec.js | 4 +- .../contactConversations/actions.spec.js | 4 +- .../specs/contactLabels/actions.spec.js | 4 +- .../specs/contactNotes/actions.spec.js | 4 +- .../modules/specs/contacts/actions.spec.js | 4 +- .../specs/conversationLabels/actions.spec.js | 4 +- .../specs/conversationPage/actions.spec.js | 2 +- .../specs/conversationSearch/actions.spec.js | 4 +- .../specs/conversationStats/actions.spec.js | 4 +- .../conversationTypingStatus/actions.spec.js | 2 +- .../conversationWatchers/actions.spec.js | 4 +- .../specs/conversations/actions.spec.js | 12 +- .../specs/conversations/mutations.spec.js | 14 +- .../store/modules/specs/csat/actions.spec.js | 4 +- .../modules/specs/customViews/actions.spec.js | 4 +- .../specs/dashboardApps/actions.spec.js | 4 +- .../specs/draftMessages/actions.spec.js | 4 +- .../inboxAssignableMembers/actions.spec.js | 4 +- .../modules/specs/inboxes/actions.spec.js | 6 +- .../specs/integrations/actions.spec.js | 4 +- .../modules/specs/labels/actions.spec.js | 6 +- .../modules/specs/macros/actions.spec.js | 4 +- .../specs/notifications/actions.spec.js | 4 +- .../modules/specs/reports/actions.spec.js | 13 +- .../store/modules/specs/sla/actions.spec.js | 6 +- .../modules/specs/slaReports/actions.spec.js | 4 +- .../modules/specs/teamMembers/actions.spec.js | 4 +- .../store/modules/specs/teams/actions.spec.js | 6 +- .../userNotificationSettings/actions.spec.js | 4 +- .../modules/specs/webhooks/actions.spec.js | 4 +- app/javascript/portal/specs/portal.spec.js | 47 +- .../portal/specs/portalTheme.spec.js | 18 +- .../sdk/specs/cookieHelpers.spec.js | 72 +- .../components/specs/DateSeparator.spec.js | 2 +- .../shared/components/specs/Spinner.spec.js | 2 +- .../__snapshots__/DateSeparator.spec.js.snap | 9 +- .../specs/__snapshots__/Spinner.spec.js.snap | 5 +- .../shared/helpers/specs/CustomErrors.spec.js | 2 +- .../helpers/specs/CustomEventHelper.spec.js | 2 +- .../shared/helpers/specs/mitt.spec.js | 2 +- .../shared/mixins/specs/rtlMixin.spec.js | 29 +- .../v3/helpers/specs/RouteHelper.spec.js | 24 +- .../widget/api/specs/endPoints.spec.js | 44 +- .../widget/components/ChatInputWrap.vue | 2 +- .../helpers/specs/urlParamsHelper.spec.js | 2 +- .../widget/helpers/specs/utils.spec.js | 2 +- .../mixins/specs/availabilityMixin.spec.js | 26 +- .../mixins/specs/nextAvailabilityTime.spec.js | 73 +- .../store/modules/specs/agent/actions.spec.js | 10 +- .../modules/specs/appConfig/actions.spec.js | 2 +- .../modules/specs/article/actions.spec.js | 10 +- .../modules/specs/campaign/actions.spec.js | 12 +- .../modules/specs/campaign/getters.spec.js | 5 +- .../modules/specs/campaign/mutations.spec.js | 5 +- .../modules/specs/contact/actions.spec.js | 18 +- .../specs/conversation/actions.spec.js | 16 +- .../conversationAttributes/actions.spec.js | 4 +- .../modules/specs/message/actions.spec.js | 4 +- app/test-matchers.js | 423 --- jest.config.js | 49 - jest.setup.js | 3 - package.json | 21 +- vite.config.ts | 42 + yarn.lock | 2528 ++++++----------- 140 files changed, 1678 insertions(+), 2810 deletions(-) delete mode 100644 app/javascript/dashboard/App.Vue.spec.js delete mode 100644 app/test-matchers.js delete mode 100644 jest.config.js delete mode 100644 jest.setup.js create mode 100644 vite.config.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index cdddc4a06..15c40dc77 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -130,10 +130,7 @@ jobs: command: | mkdir -p ~/tmp/test-results/frontend_specs ~/tmp/cc-test-reporter before-build - TESTFILES=$(circleci tests glob **/specs/*.spec.js | circleci tests split --split-by=timings) - yarn test:coverage --profile 10 \ - --out ~/tmp/test-results/yarn.xml \ - -- ${TESTFILES} + yarn test:coverage - run: name: Code Climate Test Coverage command: | diff --git a/.eslintrc.js b/.eslintrc.js index 03e7e995b..18162ee76 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -65,10 +65,10 @@ module.exports = { }, env: { browser: true, - jest: true, node: true, }, globals: { bus: true, + vi: true, }, }; diff --git a/app/javascript/dashboard/App.Vue.spec.js b/app/javascript/dashboard/App.Vue.spec.js deleted file mode 100644 index 43c6a74ed..000000000 --- a/app/javascript/dashboard/App.Vue.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import App from './App'; -import '../../test-matchers'; - -describe(`App component`, () => { - it(`should be a component`, () => { - // Arrange - // Act - expect(App).toBeVueComponent('App'); - // Assert - }); -}); diff --git a/app/javascript/dashboard/api/enterprise/specs/account.spec.js b/app/javascript/dashboard/api/enterprise/specs/account.spec.js index 6c9dca986..4fb1bd0ee 100644 --- a/app/javascript/dashboard/api/enterprise/specs/account.spec.js +++ b/app/javascript/dashboard/api/enterprise/specs/account.spec.js @@ -15,10 +15,10 @@ describe('#enterpriseAccountAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/account.spec.js b/app/javascript/dashboard/api/specs/account.spec.js index 7e213b2a8..4da8b3a46 100644 --- a/app/javascript/dashboard/api/specs/account.spec.js +++ b/app/javascript/dashboard/api/specs/account.spec.js @@ -15,10 +15,10 @@ describe('#accountAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/accountActions.spec.js b/app/javascript/dashboard/api/specs/accountActions.spec.js index 330c117ff..dc73e4948 100644 --- a/app/javascript/dashboard/api/specs/accountActions.spec.js +++ b/app/javascript/dashboard/api/specs/accountActions.spec.js @@ -10,10 +10,10 @@ describe('#ContactsAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/agents.spec.js b/app/javascript/dashboard/api/specs/agents.spec.js index 20dd36688..0df0fd8d9 100644 --- a/app/javascript/dashboard/api/specs/agents.spec.js +++ b/app/javascript/dashboard/api/specs/agents.spec.js @@ -14,7 +14,7 @@ describe('#AgentAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/article.spec.js b/app/javascript/dashboard/api/specs/article.spec.js index 02c0f82d8..71128682c 100644 --- a/app/javascript/dashboard/api/specs/article.spec.js +++ b/app/javascript/dashboard/api/specs/article.spec.js @@ -14,10 +14,10 @@ describe('#PortalAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -44,10 +44,10 @@ describe('#PortalAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -71,10 +71,10 @@ describe('#PortalAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -98,10 +98,10 @@ describe('#PortalAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -129,10 +129,10 @@ describe('#PortalAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/assignableAgents.spec.js b/app/javascript/dashboard/api/specs/assignableAgents.spec.js index 5280162b3..d553d55cb 100644 --- a/app/javascript/dashboard/api/specs/assignableAgents.spec.js +++ b/app/javascript/dashboard/api/specs/assignableAgents.spec.js @@ -4,10 +4,10 @@ describe('#AssignableAgentsAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js b/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js index 2cdcec56e..c79051977 100644 --- a/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js +++ b/app/javascript/dashboard/api/specs/channel/fbChannel.spec.js @@ -13,10 +13,10 @@ describe('#FBChannel', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/contacts.spec.js b/app/javascript/dashboard/api/specs/contacts.spec.js index b4eaf7333..0059518b0 100644 --- a/app/javascript/dashboard/api/specs/contacts.spec.js +++ b/app/javascript/dashboard/api/specs/contacts.spec.js @@ -17,10 +17,10 @@ describe('#ContactsAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/conversations.spec.js b/app/javascript/dashboard/api/specs/conversations.spec.js index 6b3db7404..7ae4eb774 100644 --- a/app/javascript/dashboard/api/specs/conversations.spec.js +++ b/app/javascript/dashboard/api/specs/conversations.spec.js @@ -16,10 +16,10 @@ describe('#ConversationApi', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/csatReports.spec.js b/app/javascript/dashboard/api/specs/csatReports.spec.js index 7c1707e1e..788f0eba2 100644 --- a/app/javascript/dashboard/api/specs/csatReports.spec.js +++ b/app/javascript/dashboard/api/specs/csatReports.spec.js @@ -11,10 +11,10 @@ describe('#Reports API', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js index 3287d7477..dd1615802 100644 --- a/app/javascript/dashboard/api/specs/inbox/conversation.spec.js +++ b/app/javascript/dashboard/api/specs/inbox/conversation.spec.js @@ -24,10 +24,10 @@ describe('#ConversationAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/inbox/message.spec.js b/app/javascript/dashboard/api/specs/inbox/message.spec.js index 0d45b2157..941f5c99c 100644 --- a/app/javascript/dashboard/api/specs/inbox/message.spec.js +++ b/app/javascript/dashboard/api/specs/inbox/message.spec.js @@ -15,10 +15,10 @@ describe('#ConversationAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/inboxes.spec.js b/app/javascript/dashboard/api/specs/inboxes.spec.js index 8834ceb07..628ce0f34 100644 --- a/app/javascript/dashboard/api/specs/inboxes.spec.js +++ b/app/javascript/dashboard/api/specs/inboxes.spec.js @@ -17,10 +17,10 @@ describe('#InboxesAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/integrations.spec.js b/app/javascript/dashboard/api/specs/integrations.spec.js index 5ccbda436..cc20fd8f6 100644 --- a/app/javascript/dashboard/api/specs/integrations.spec.js +++ b/app/javascript/dashboard/api/specs/integrations.spec.js @@ -18,10 +18,10 @@ describe('#integrationAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/integrations/dyte.spec.js b/app/javascript/dashboard/api/specs/integrations/dyte.spec.js index 4bbe0484a..1c544f976 100644 --- a/app/javascript/dashboard/api/specs/integrations/dyte.spec.js +++ b/app/javascript/dashboard/api/specs/integrations/dyte.spec.js @@ -11,10 +11,10 @@ describe('#accountAPI', () => { describe('createAMeeting', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -39,10 +39,10 @@ describe('#accountAPI', () => { describe('addParticipantToMeeting', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/integrations/linear.spec.js b/app/javascript/dashboard/api/specs/integrations/linear.spec.js index cc16feb16..e4bf679a6 100644 --- a/app/javascript/dashboard/api/specs/integrations/linear.spec.js +++ b/app/javascript/dashboard/api/specs/integrations/linear.spec.js @@ -16,10 +16,10 @@ describe('#linearAPI', () => { describe('getTeams', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -41,10 +41,10 @@ describe('#linearAPI', () => { describe('getTeamEntities', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -66,10 +66,10 @@ describe('#linearAPI', () => { describe('createIssue', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -96,10 +96,10 @@ describe('#linearAPI', () => { describe('link_issue', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -125,10 +125,10 @@ describe('#linearAPI', () => { describe('getLinkedIssue', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -150,10 +150,10 @@ describe('#linearAPI', () => { describe('unlinkIssue', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { @@ -178,10 +178,10 @@ describe('#linearAPI', () => { describe('searchIssues', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/notifications.spec.js b/app/javascript/dashboard/api/specs/notifications.spec.js index fe748fe19..770a6840d 100644 --- a/app/javascript/dashboard/api/specs/notifications.spec.js +++ b/app/javascript/dashboard/api/specs/notifications.spec.js @@ -13,10 +13,10 @@ describe('#NotificationAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/reports.spec.js b/app/javascript/dashboard/api/specs/reports.spec.js index 05d4a152c..e458633d0 100644 --- a/app/javascript/dashboard/api/specs/reports.spec.js +++ b/app/javascript/dashboard/api/specs/reports.spec.js @@ -20,10 +20,10 @@ describe('#Reports API', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/slaReports.spec.js b/app/javascript/dashboard/api/specs/slaReports.spec.js index f540b6acc..827b44cad 100644 --- a/app/javascript/dashboard/api/specs/slaReports.spec.js +++ b/app/javascript/dashboard/api/specs/slaReports.spec.js @@ -12,10 +12,10 @@ describe('#SLAReports API', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/api/specs/teams.spec.js b/app/javascript/dashboard/api/specs/teams.spec.js index 3a59f2c51..c7bfc4d1c 100644 --- a/app/javascript/dashboard/api/specs/teams.spec.js +++ b/app/javascript/dashboard/api/specs/teams.spec.js @@ -16,10 +16,10 @@ describe('#TeamsAPI', () => { describe('API calls', () => { const originalAxios = window.axios; const axiosMock = { - post: jest.fn(() => Promise.resolve()), - get: jest.fn(() => Promise.resolve()), - patch: jest.fn(() => Promise.resolve()), - delete: jest.fn(() => Promise.resolve()), + post: vi.fn(() => Promise.resolve()), + get: vi.fn(() => Promise.resolve()), + patch: vi.fn(() => Promise.resolve()), + delete: vi.fn(() => Promise.resolve()), }; beforeEach(() => { diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js index b10ba9a57..fb76e0ff4 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js +++ b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js @@ -1,12 +1,12 @@ -import AccountSelector from '../AccountSelector'; +import AccountSelector from '../AccountSelector.vue'; import { createLocalVue, mount } from '@vue/test-utils'; import Vuex from 'vuex'; import VueI18n from 'vue-i18n'; import i18n from 'dashboard/i18n'; -import WootModal from 'dashboard/components/Modal'; -import WootModalHeader from 'dashboard/components/ModalHeader'; -import FluentIcon from 'shared/components/FluentIcon/DashboardIcon'; +import WootModal from 'dashboard/components/Modal.vue'; +import WootModalHeader from 'dashboard/components/ModalHeader.vue'; +import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue'; const localVue = createLocalVue(); localVue.component('woot-modal', WootModal); diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js index 073881a58..ca6c00a43 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js +++ b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AgentDetails.spec.js @@ -1,12 +1,12 @@ -import AgentDetails from '../AgentDetails'; +import AgentDetails from '../AgentDetails.vue'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import VueI18n from 'vue-i18n'; import VTooltip from 'v-tooltip'; import i18n from 'dashboard/i18n'; -import Thumbnail from 'dashboard/components/widgets/Thumbnail'; -import WootButton from 'dashboard/components/ui/WootButton'; +import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue'; +import WootButton from 'dashboard/components/ui/WootButton.vue'; const localVue = createLocalVue(); localVue.use(Vuex); localVue.use(VueI18n); diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js index 64d1fe791..85596bb3e 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js +++ b/app/javascript/dashboard/components/layout/sidebarComponents/specs/NotificationBell.spec.js @@ -1,13 +1,15 @@ -import NotificationBell from '../NotificationBell'; +import NotificationBell from '../NotificationBell.vue'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import VueI18n from 'vue-i18n'; +import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue'; import i18n from 'dashboard/i18n'; const localVue = createLocalVue(); localVue.use(Vuex); localVue.use(VueI18n); +localVue.component('fluent-icon', FluentIcon); const i18nConfig = new VueI18n({ locale: 'en', @@ -27,7 +29,7 @@ describe('notificationBell', () => { beforeEach(() => { actions = { - showNotification: jest.fn(), + showNotification: vi.fn(), }; modules = { auth: { diff --git a/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js b/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js index 50d959b33..e6864d052 100644 --- a/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js +++ b/app/javascript/dashboard/components/layout/specs/AvailabilityStatus.spec.js @@ -2,15 +2,21 @@ import AvailabilityStatus from '../AvailabilityStatus.vue'; import { createLocalVue, mount } from '@vue/test-utils'; import Vuex from 'vuex'; import VueI18n from 'vue-i18n'; +import VTooltip from 'v-tooltip'; + +import WootButton from 'dashboard/components/ui/WootButton.vue'; +import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue'; +import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue'; +import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue'; +import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider.vue'; +import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue'; -import WootButton from 'dashboard/components/ui/WootButton'; -import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem'; -import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu'; -import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader'; -import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider'; import i18n from 'dashboard/i18n'; const localVue = createLocalVue(); +localVue.use(VTooltip, { + defaultHtml: false, +}); localVue.use(Vuex); localVue.use(VueI18n); localVue.component('woot-button', WootButton); @@ -18,12 +24,14 @@ localVue.component('woot-dropdown-header', WootDropdownHeader); localVue.component('woot-dropdown-menu', WootDropdownMenu); localVue.component('woot-dropdown-divider', WootDropdownDivider); localVue.component('woot-dropdown-item', WootDropdownItem); +localVue.component('fluent-icon', FluentIcon); const i18nConfig = new VueI18n({ locale: 'en', messages: i18n }); describe('AvailabilityStatus', () => { const currentAvailability = 'online'; const currentAccountId = '1'; + const currentUserAutoOffline = false; let store = null; let actions = null; let modules = null; @@ -31,7 +39,7 @@ describe('AvailabilityStatus', () => { beforeEach(() => { actions = { - updateAvailability: jest.fn(() => { + updateAvailability: vi.fn(() => { return Promise.resolve(); }), }; @@ -41,6 +49,7 @@ describe('AvailabilityStatus', () => { getters: { getCurrentUserAvailability: () => currentAvailability, getCurrentAccountId: () => currentAccountId, + getCurrentUserAutoOffline: () => currentUserAutoOffline, }, }, }; diff --git a/app/javascript/dashboard/components/specs/SidemenuIcon.spec.js b/app/javascript/dashboard/components/specs/SidemenuIcon.spec.js index fea394572..5029eed48 100644 --- a/app/javascript/dashboard/components/specs/SidemenuIcon.spec.js +++ b/app/javascript/dashboard/components/specs/SidemenuIcon.spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import SidemenuIcon from '../SidemenuIcon'; +import SidemenuIcon from '../SidemenuIcon.vue'; describe('SidemenuIcon', () => { test('matches snapshot', () => { diff --git a/app/javascript/dashboard/components/specs/__snapshots__/SidemenuIcon.spec.js.snap b/app/javascript/dashboard/components/specs/__snapshots__/SidemenuIcon.spec.js.snap index 577dbb3aa..0229af06e 100644 --- a/app/javascript/dashboard/components/specs/__snapshots__/SidemenuIcon.spec.js.snap +++ b/app/javascript/dashboard/components/specs/__snapshots__/SidemenuIcon.spec.js.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`SidemenuIcon matches snapshot 1`] = ` +exports[`SidemenuIcon > matches snapshot 1`] = `