diff --git a/.circleci/config.yml b/.circleci/config.yml
index c67063ae6b31d..15c40dc77f81c 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
@@ -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/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index 17021d1e7538b..a804bb15caf4d 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/.eslintrc.js b/.eslintrc.js
index 03e7e995b8f26..18162ee762a06 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/.ruby-version b/.ruby-version
index be94e6f53db6b..619b537668489 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-3.2.2
+3.3.3
diff --git a/Gemfile b/Gemfile
index 5e1e5917fa223..f690e6876d61f 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'
@@ -111,12 +111,12 @@ 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.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 04a8fb534aaf4..91d3b3d8b6ad3 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)
@@ -183,13 +185,16 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
+ datadog-ci (0.8.3)
+ msgpack
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)
+ 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.2.0)
+ debase-ruby_core_source (3.3.1)
debug (1.8.0)
irb (>= 1.5.0)
reline (>= 0.3.1)
@@ -416,15 +421,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)
@@ -434,6 +439,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)
@@ -460,7 +466,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)
@@ -703,23 +709,24 @@ 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)
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)
@@ -931,11 +938,11 @@ 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.2.4)
+ sidekiq (>= 7.3.0)
sidekiq-cron (>= 1.12.0)
simplecov (= 0.17.1)
slack-ruby-client (~> 2.2.0)
@@ -960,7 +967,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/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index e656c25501e94..332f1528fc1e5 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -37,7 +37,7 @@ def set_global_config
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 @@ def set_application_pack
'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/dashboard/App.Vue.spec.js b/app/javascript/dashboard/App.Vue.spec.js
deleted file mode 100644
index 43c6a74edc43f..0000000000000
--- 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 6c9dca986dea3..4fb1bd0ee26bd 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 7e213b2a81184..4da8b3a465e12 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 330c117ff7f36..dc73e49489642 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 20dd36688ad1a..0df0fd8d98b9e 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 02c0f82d80519..71128682c9913 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 5280162b35809..d553d55cb5385 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 2cdcec56e3c0e..c79051977f5d1 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 b4eaf73332b06..0059518b0a66f 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 6b3db7404e7c1..7ae4eb774773f 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 7c1707e1ee3ec..788f0eba2e726 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 3287d7477997d..dd1615802daa4 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 0d45b21575985..941f5c99c1192 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 8834ceb077705..628ce0f34b757 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 5ccbda436b9a9..cc20fd8f68bf1 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 4bbe0484a658a..1c544f9765444 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 cc16feb16a5e0..e4bf679a66b9e 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 fe748fe199b99..770a6840da9f8 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 05d4a152c1767..e458633d09603 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 f540b6acc75ab..827b44cad29e8 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 3a59f2c510452..c7bfc4d1ce088 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/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue
index eb410f7d55ca3..ee53540274d3b 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/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue
index 0f3a1a85758dc..4ccaa97dfab89 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 7513e3d1c5642..92b8765c6ed53 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 f450bd7a78474..f7d63f6a1aa41 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 f291d221e9d54..b4cd3ca90c652 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 a7009523ab2f1..bc3b036463cf8 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"
/>
-
-
-
- newLinkClick(e, navigate)"
- >
- {{ $t(`SIDEBAR.${menuItem.newLinkTag}`) }}
-
-
-
-
+
+
+
+
+ newLinkClick(e, navigate)"
+ >
+ {{ $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/layout/sidebarComponents/specs/AccountSelector.spec.js b/app/javascript/dashboard/components/layout/sidebarComponents/specs/AccountSelector.spec.js
index b10ba9a579b4c..fb76e0ff4a9dd 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 073881a58c0f8..ca6c00a43ada5 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 64d1fe7918eef..85596bb3e9d5c 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 50d959b33b7ff..e6864d0520fb2 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/policy.vue b/app/javascript/dashboard/components/policy.vue
new file mode 100644
index 0000000000000..f888de5a29425
--- /dev/null
+++ b/app/javascript/dashboard/components/policy.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components/specs/SidemenuIcon.spec.js b/app/javascript/dashboard/components/specs/SidemenuIcon.spec.js
index fea394572deac..5029eed48e5b7 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 577dbb3aae32b..0229af06e2944 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`] = `