From 41c5e7d3f124b78b870997ff3f2632421fec77a7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 23 Aug 2024 09:27:17 +0530
Subject: [PATCH 1/3] chore(deps): bump rexml from 3.3.4 to 3.3.6 (#10013)
Bumps [rexml](https://github.com/ruby/rexml) from 3.3.4 to 3.3.6.
Release notes
Sourced from rexml's
releases.
REXML 3.3.6 - 2024-08-22
Improvements
Fixes
-
Fixed a bug that default entity expansions are counted for
security check. Default entity expansions should not be counted
because they don't have a security risk.
-
Fixed a parser bug that parameter entity references in internal
subsets are expanded. It's not allowed in the XML specification.
-
Fixed a stream parser bug that user-defined entity references in
text aren't expanded.
Thanks
-
Viktor Ivarsson
-
NAITOH Jun
-
l33thaxor
REXML 3.3.5 - 2024-08-12
Fixes
- Fixed a bug that
REXML::Security.entity_expansion_text_limit
check has wrong text size calculation in SAX and pull parsers.
- GH-193
- GH-195
- Reported by Viktor Ivarsson.
- Patch by NAITOH Jun.
... (truncated)
Changelog
Sourced from rexml's
changelog.
3.3.6 - 2024-08-22 {#version-3-3-6}
Improvements
Fixes
-
Fixed a bug that default entity expansions are counted for
security check. Default entity expansions should not be counted
because they don't have a security risk.
-
Fixed a parser bug that parameter entity references in internal
subsets are expanded. It's not allowed in the XML specification.
-
Fixed a stream parser bug that user-defined entity references in
text aren't expanded.
Thanks
-
Viktor Ivarsson
-
NAITOH Jun
-
l33thaxor
3.3.5 - 2024-08-12 {#version-3-3-5}
Fixes
- Fixed a bug that
REXML::Security.entity_expansion_text_limit
check has wrong text size calculation in SAX and pull parsers.
- GH-193
- GH-195
- Reported by Viktor Ivarsson.
- Patch by NAITOH Jun.
... (truncated)
Commits
95871f3
Add 3.3.6 entry
7cb5eae
parser tree: improve namespace conflicted attribute check
performance
6109e01
Fix a bug that Stream parser doesn't expand the user-defined entity
reference...
cb15858
parser: keep the current namespaces instead of stack of Set
2b47b16
parser: move duplicated end tag check to BaseParser
35e1681
test tree-parser: move common method to base class
6e00a14
test: fix indent
df3a0cc
test: fix indent
fdbffe7
Use loop instead of recursive call for Element#namespace
6422fa3
Use loop instead of recursive call for Element#root
- Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Gemfile.lock | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 1bd5f4f05..06f479659 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -634,7 +634,7 @@ GEM
retriable (3.1.2)
reverse_markdown (2.1.1)
nokogiri
- rexml (3.3.4)
+ rexml (3.3.6)
strscan
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
From b61ad6e41aca1394547e463a3cc9e16f8ce0762b Mon Sep 17 00:00:00 2001
From: Sojan Jose
Date: Fri, 23 Aug 2024 04:48:28 -0700
Subject: [PATCH 2/3] feat: Add APIs to manage custom roles in Chatwoot
(#9995)
Co-authored-by: Pranav
---
.../api/v1/accounts/agents_controller.rb | 14 +-
app/models/account_user.rb | 30 +--
app/views/api/v1/models/_agent.json.jbuilder | 1 +
config/routes.rb | 1 +
db/migrate/20240726220747_add_custom_roles.rb | 16 ++
db/schema.rb | 14 +-
.../v1/accounts/custom_roles_controller.rb | 31 ++++
.../api/v1/accounts/agents_controller.rb | 9 +
enterprise/app/models/custom_role.rb | 42 +++++
.../app/models/enterprise/account_user.rb | 5 +
.../app/models/enterprise/concerns/account.rb | 1 +
.../enterprise/concerns/account_user.rb | 7 +
enterprise/app/policies/custom_role_policy.rb | 21 +++
.../custom_roles/create.json.jbuilder | 1 +
.../accounts/custom_roles/index.json.jbuilder | 3 +
.../accounts/custom_roles/show.json.jbuilder | 1 +
.../custom_roles/update.json.jbuilder | 1 +
.../api/v1/models/_custom_role.json.jbuilder | 6 +
.../accounts/custom_roles_controller_spec.rb | 174 ++++++++++++++++++
.../api/v1/accounts/agents_controller_spec.rb | 24 +++
spec/enterprise/models/account_spec.rb | 8 +-
spec/enterprise/models/account_user_spec.rb | 28 ++-
spec/enterprise/models/custom_role_spec.rb | 12 ++
spec/factories/custom_roles.rb | 8 +
24 files changed, 440 insertions(+), 18 deletions(-)
create mode 100644 db/migrate/20240726220747_add_custom_roles.rb
create mode 100644 enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb
create mode 100644 enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
create mode 100644 enterprise/app/models/custom_role.rb
create mode 100644 enterprise/app/models/enterprise/account_user.rb
create mode 100644 enterprise/app/models/enterprise/concerns/account_user.rb
create mode 100644 enterprise/app/policies/custom_role_policy.rb
create mode 100644 enterprise/app/views/api/v1/accounts/custom_roles/create.json.jbuilder
create mode 100644 enterprise/app/views/api/v1/accounts/custom_roles/index.json.jbuilder
create mode 100644 enterprise/app/views/api/v1/accounts/custom_roles/show.json.jbuilder
create mode 100644 enterprise/app/views/api/v1/accounts/custom_roles/update.json.jbuilder
create mode 100644 enterprise/app/views/api/v1/models/_custom_role.json.jbuilder
create mode 100644 spec/enterprise/controllers/api/v1/accounts/custom_roles_controller_spec.rb
create mode 100644 spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb
create mode 100644 spec/enterprise/models/custom_role_spec.rb
create mode 100644 spec/factories/custom_roles.rb
diff --git a/app/controllers/api/v1/accounts/agents_controller.rb b/app/controllers/api/v1/accounts/agents_controller.rb
index 4eff10127..01aa42620 100644
--- a/app/controllers/api/v1/accounts/agents_controller.rb
+++ b/app/controllers/api/v1/accounts/agents_controller.rb
@@ -24,7 +24,7 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
def update
@agent.update!(agent_params.slice(:name).compact)
- @agent.current_account_user.update!(agent_params.slice(:role, :availability, :auto_offline).compact)
+ @agent.current_account_user.update!(agent_params.slice(*account_user_attributes).compact)
end
def destroy
@@ -67,8 +67,16 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
@agent = agents.find(params[:id])
end
+ def account_user_attributes
+ [:role, :availability, :auto_offline]
+ end
+
+ def allowed_agent_params
+ [:name, :email, :name, :role, :availability, :auto_offline]
+ end
+
def agent_params
- params.require(:agent).permit(:name, :email, :name, :role, :availability, :auto_offline)
+ params.require(:agent).permit(allowed_agent_params)
end
def new_agent_params
@@ -101,3 +109,5 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
DeleteObjectJob.perform_later(agent) if agent.reload.account_users.blank?
end
end
+
+Api::V1::Accounts::AgentsController.prepend_mod_with('Api::V1::Accounts::AgentsController')
diff --git a/app/models/account_user.rb b/app/models/account_user.rb
index 176d52102..d559ab5b4 100644
--- a/app/models/account_user.rb
+++ b/app/models/account_user.rb
@@ -2,22 +2,24 @@
#
# Table name: account_users
#
-# id :bigint not null, primary key
-# active_at :datetime
-# auto_offline :boolean default(TRUE), not null
-# availability :integer default("online"), not null
-# role :integer default("agent")
-# created_at :datetime not null
-# updated_at :datetime not null
-# account_id :bigint
-# inviter_id :bigint
-# user_id :bigint
+# id :bigint not null, primary key
+# active_at :datetime
+# auto_offline :boolean default(TRUE), not null
+# availability :integer default("online"), not null
+# role :integer default("agent")
+# created_at :datetime not null
+# updated_at :datetime not null
+# account_id :bigint
+# custom_role_id :bigint
+# inviter_id :bigint
+# user_id :bigint
#
# Indexes
#
-# index_account_users_on_account_id (account_id)
-# index_account_users_on_user_id (user_id)
-# uniq_user_id_per_account_id (account_id,user_id) UNIQUE
+# index_account_users_on_account_id (account_id)
+# index_account_users_on_custom_role_id (custom_role_id)
+# index_account_users_on_user_id (user_id)
+# uniq_user_id_per_account_id (account_id,user_id) UNIQUE
#
class AccountUser < ApplicationRecord
@@ -77,4 +79,6 @@ class AccountUser < ApplicationRecord
end
end
+AccountUser.prepend_mod_with('AccountUser')
AccountUser.include_mod_with('Audit::AccountUser')
+AccountUser.include_mod_with('Concerns::AccountUser')
diff --git a/app/views/api/v1/models/_agent.json.jbuilder b/app/views/api/v1/models/_agent.json.jbuilder
index 02601d6b0..910670611 100644
--- a/app/views/api/v1/models/_agent.json.jbuilder
+++ b/app/views/api/v1/models/_agent.json.jbuilder
@@ -10,3 +10,4 @@ json.custom_attributes resource.custom_attributes if resource.custom_attributes.
json.name resource.name
json.role resource.role
json.thumbnail resource.avatar_url
+json.custom_role_id resource.current_account_user&.custom_role_id if ChatwootApp.enterprise?
diff --git a/config/routes.rb b/config/routes.rb
index f23def557..7006e8f4b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -74,6 +74,7 @@ Rails.application.routes.draw do
post :execute, on: :member
end
resources :sla_policies, only: [:index, :create, :show, :update, :destroy]
+ resources :custom_roles, only: [:index, :create, :show, :update, :destroy]
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy]
namespace :channels do
diff --git a/db/migrate/20240726220747_add_custom_roles.rb b/db/migrate/20240726220747_add_custom_roles.rb
new file mode 100644
index 000000000..07ef77043
--- /dev/null
+++ b/db/migrate/20240726220747_add_custom_roles.rb
@@ -0,0 +1,16 @@
+class AddCustomRoles < ActiveRecord::Migration[7.0]
+ def change
+ # Create the roles table
+ create_table :custom_roles do |t|
+ t.string :name
+ t.string :description
+ t.references :account, null: false
+ t.text :permissions, array: true, default: []
+ t.timestamps
+ end
+
+ # Associate the custom role with account user
+ # Add the custom_role_id column to the account_users table
+ add_reference :account_users, :custom_role, optional: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b894f98da..3ad9562b5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2024_05_16_003531) do
+ActiveRecord::Schema[7.0].define(version: 2024_07_26_220747) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -37,8 +37,10 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_16_003531) do
t.datetime "active_at", precision: nil
t.integer "availability", default: 0, null: false
t.boolean "auto_offline", default: true, null: false
+ t.bigint "custom_role_id"
t.index ["account_id", "user_id"], name: "uniq_user_id_per_account_id", unique: true
t.index ["account_id"], name: "index_account_users_on_account_id"
+ t.index ["custom_role_id"], name: "index_account_users_on_custom_role_id"
t.index ["user_id"], name: "index_account_users_on_user_id"
end
@@ -538,6 +540,16 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_16_003531) do
t.index ["user_id"], name: "index_custom_filters_on_user_id"
end
+ create_table "custom_roles", force: :cascade do |t|
+ t.string "name"
+ t.string "description"
+ t.bigint "account_id", null: false
+ t.text "permissions", default: [], array: true
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["account_id"], name: "index_custom_roles_on_account_id"
+ end
+
create_table "dashboard_apps", force: :cascade do |t|
t.string "title", null: false
t.jsonb "content", default: []
diff --git a/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb b/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb
new file mode 100644
index 000000000..01650a7de
--- /dev/null
+++ b/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb
@@ -0,0 +1,31 @@
+class Api::V1::Accounts::CustomRolesController < Api::V1::Accounts::EnterpriseAccountsController
+ before_action :fetch_custom_role, only: [:show, :update, :destroy]
+ before_action :check_authorization
+
+ def index
+ @custom_roles = Current.account.custom_roles
+ end
+
+ def create
+ @custom_role = Current.account.custom_roles.create!(permitted_params)
+ end
+
+ def show; end
+
+ def update
+ @custom_role.update!(permitted_params)
+ end
+
+ def destroy
+ @custom_role.destroy!
+ head :ok
+ end
+
+ def permitted_params
+ params.require(:custom_role).permit(:name, :description, permissions: [])
+ end
+
+ def fetch_custom_role
+ @custom_role = Current.account.custom_roles.find_by(id: params[:id])
+ end
+end
diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
new file mode 100644
index 000000000..c8a68c427
--- /dev/null
+++ b/enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
@@ -0,0 +1,9 @@
+module Enterprise::Api::V1::Accounts::AgentsController
+ def account_user_attributes
+ super + [:custom_role_id]
+ end
+
+ def allowed_agent_params
+ super + [:custom_role_id]
+ end
+end
diff --git a/enterprise/app/models/custom_role.rb b/enterprise/app/models/custom_role.rb
new file mode 100644
index 000000000..666f91378
--- /dev/null
+++ b/enterprise/app/models/custom_role.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: custom_roles
+#
+# id :bigint not null, primary key
+# description :string
+# name :string
+# permissions :text default([]), is an Array
+# created_at :datetime not null
+# updated_at :datetime not null
+# account_id :bigint not null
+#
+# Indexes
+#
+# index_custom_roles_on_account_id (account_id)
+#
+#
+
+# Available permissions for custom roles:
+# - 'conversation_manage': Can manage all conversations.
+# - 'conversation_unassigned_manage': Can manage unassigned conversations and assign to self.
+# - 'conversation_participating_manage': Can manage conversations they are participating in (assigned to or a participant).
+# - 'contact_manage': Can manage contacts.
+# - 'report_manage': Can manage reports.
+# - 'knowledge_base_manage': Can manage knowledge base portals.
+
+class CustomRole < ApplicationRecord
+ belongs_to :account
+ has_many :account_users, dependent: :nullify
+
+ PERMISSIONS = %w[
+ conversation_manage
+ conversation_unassigned_manage
+ conversation_participating_manage
+ contact_manage
+ report_manage
+ knowledge_base_manage
+ ].freeze
+
+ validates :name, presence: true
+ validates :permissions, inclusion: { in: PERMISSIONS }
+end
diff --git a/enterprise/app/models/enterprise/account_user.rb b/enterprise/app/models/enterprise/account_user.rb
new file mode 100644
index 000000000..c9726978f
--- /dev/null
+++ b/enterprise/app/models/enterprise/account_user.rb
@@ -0,0 +1,5 @@
+module Enterprise::AccountUser
+ def permissions
+ custom_role.present? ? (custom_role.permissions + ['custom_role']) : super
+ end
+end
diff --git a/enterprise/app/models/enterprise/concerns/account.rb b/enterprise/app/models/enterprise/concerns/account.rb
index b4f9d189f..37d5fcad4 100644
--- a/enterprise/app/models/enterprise/concerns/account.rb
+++ b/enterprise/app/models/enterprise/concerns/account.rb
@@ -4,6 +4,7 @@ module Enterprise::Concerns::Account
included do
has_many :sla_policies, dependent: :destroy_async
has_many :applied_slas, dependent: :destroy_async
+ has_many :custom_roles, dependent: :destroy_async
def self.add_response_related_associations
has_many :response_sources, dependent: :destroy_async
diff --git a/enterprise/app/models/enterprise/concerns/account_user.rb b/enterprise/app/models/enterprise/concerns/account_user.rb
new file mode 100644
index 000000000..0887d31d9
--- /dev/null
+++ b/enterprise/app/models/enterprise/concerns/account_user.rb
@@ -0,0 +1,7 @@
+module Enterprise::Concerns::AccountUser
+ extend ActiveSupport::Concern
+
+ included do
+ belongs_to :custom_role, optional: true
+ end
+end
diff --git a/enterprise/app/policies/custom_role_policy.rb b/enterprise/app/policies/custom_role_policy.rb
new file mode 100644
index 000000000..f16602488
--- /dev/null
+++ b/enterprise/app/policies/custom_role_policy.rb
@@ -0,0 +1,21 @@
+class CustomRolePolicy < ApplicationPolicy
+ def index?
+ @account_user.administrator?
+ end
+
+ def update?
+ @account_user.administrator?
+ end
+
+ def show?
+ @account_user.administrator?
+ end
+
+ def create?
+ @account_user.administrator?
+ end
+
+ def destroy?
+ @account_user.administrator?
+ end
+end
diff --git a/enterprise/app/views/api/v1/accounts/custom_roles/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/custom_roles/create.json.jbuilder
new file mode 100644
index 000000000..cb7226776
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/custom_roles/create.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'api/v1/models/custom_role', formats: [:json], custom_role: @custom_role
diff --git a/enterprise/app/views/api/v1/accounts/custom_roles/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/custom_roles/index.json.jbuilder
new file mode 100644
index 000000000..d60654620
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/custom_roles/index.json.jbuilder
@@ -0,0 +1,3 @@
+json.array! @custom_roles do |custom_role|
+ json.partial! 'api/v1/models/custom_role', formats: [:json], custom_role: custom_role
+end
diff --git a/enterprise/app/views/api/v1/accounts/custom_roles/show.json.jbuilder b/enterprise/app/views/api/v1/accounts/custom_roles/show.json.jbuilder
new file mode 100644
index 000000000..cb7226776
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/custom_roles/show.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'api/v1/models/custom_role', formats: [:json], custom_role: @custom_role
diff --git a/enterprise/app/views/api/v1/accounts/custom_roles/update.json.jbuilder b/enterprise/app/views/api/v1/accounts/custom_roles/update.json.jbuilder
new file mode 100644
index 000000000..cb7226776
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/custom_roles/update.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'api/v1/models/custom_role', formats: [:json], custom_role: @custom_role
diff --git a/enterprise/app/views/api/v1/models/_custom_role.json.jbuilder b/enterprise/app/views/api/v1/models/_custom_role.json.jbuilder
new file mode 100644
index 000000000..32f1d0563
--- /dev/null
+++ b/enterprise/app/views/api/v1/models/_custom_role.json.jbuilder
@@ -0,0 +1,6 @@
+json.id custom_role.id
+json.name custom_role.name
+json.description custom_role.description
+json.permissions custom_role.permissions
+json.created_at custom_role.created_at
+json.updated_at custom_role.updated_at
diff --git a/spec/enterprise/controllers/api/v1/accounts/custom_roles_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/custom_roles_controller_spec.rb
new file mode 100644
index 000000000..89af9d26c
--- /dev/null
+++ b/spec/enterprise/controllers/api/v1/accounts/custom_roles_controller_spec.rb
@@ -0,0 +1,174 @@
+require 'rails_helper'
+
+RSpec.describe 'Custom Roles API', type: :request do
+ let!(:account) { create(:account) }
+ let!(:administrator) { create(:user, account: account, role: :administrator) }
+ let!(:agent) { create(:user, account: account, role: :agent) }
+ let!(:custom_role) { create(:custom_role, account: account, name: 'Manager') }
+
+ describe 'GET #index' do
+ context 'when it is an authenticated administrator' do
+ it 'returns all custom roles in the account' do
+ get "/api/v1/accounts/#{account.id}/custom_roles",
+ headers: administrator.create_new_auth_token
+ expect(response).to have_http_status(:success)
+ body = JSON.parse(response.body)
+
+ expect(body[0]).to include('name' => custom_role.name)
+ end
+ end
+
+ context 'when the user is an agent and is authenticated' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/custom_roles",
+ headers: agent.create_new_auth_token
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/custom_roles"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'GET #show' do
+ context 'when it is an authenticated administrator' do
+ it 'returns the custom role details' do
+ get "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ headers: administrator.create_new_auth_token
+
+ expect(response).to have_http_status(:success)
+ body = JSON.parse(response.body)
+
+ expect(body).to include('name' => custom_role.name)
+ end
+ end
+
+ context 'when the user is an agent and is authenticated' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ headers: agent.create_new_auth_token
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'POST #create' do
+ let(:valid_params) do
+ { custom_role: { name: 'Support', description: 'Support role', permissions: CustomRole::PERMISSIONS.sample(SecureRandom.random_number(4)) } }
+ end
+
+ context 'when it is an authenticated administrator' do
+ it 'creates the custom role' do
+ expect do
+ post "/api/v1/accounts/#{account.id}/custom_roles",
+ params: valid_params,
+ headers: administrator.create_new_auth_token
+ end.to change(CustomRole, :count).by(1)
+
+ expect(response).to have_http_status(:success)
+ body = JSON.parse(response.body)
+
+ expect(body).to include('name' => 'Support')
+ end
+ end
+
+ context 'when the user is an agent and is authenticated' do
+ it 'returns unauthorized' do
+ post "/api/v1/accounts/#{account.id}/custom_roles",
+ params: valid_params,
+ headers: agent.create_new_auth_token
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ post "/api/v1/accounts/#{account.id}/custom_roles",
+ params: valid_params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ let(:update_params) { { custom_role: { name: 'Updated Role' } } }
+
+ context 'when it is an authenticated administrator' do
+ it 'updates the custom role' do
+ put "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ params: update_params,
+ headers: administrator.create_new_auth_token
+
+ expect(response).to have_http_status(:success)
+ body = JSON.parse(response.body)
+
+ expect(body).to include('name' => 'Updated Role')
+ end
+ end
+
+ context 'when the user is an agent and is authenticated' do
+ it 'returns unauthorized' do
+ put "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ params: update_params,
+ headers: agent.create_new_auth_token
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ put "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ params: update_params
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ context 'when it is an authenticated administrator' do
+ it 'deletes the custom role' do
+ delete "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ headers: administrator.create_new_auth_token
+
+ expect(response).to have_http_status(:success)
+ expect(CustomRole.count).to eq(0)
+ end
+ end
+
+ context 'when the user is an agent and is authenticated' do
+ it 'returns unauthorized' do
+ delete "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}",
+ headers: agent.create_new_auth_token
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ delete "/api/v1/accounts/#{account.id}/custom_roles/#{custom_role.id}"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb b/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb
new file mode 100644
index 000000000..aa1a99c5d
--- /dev/null
+++ b/spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+RSpec.describe 'Enterprise Agents API', type: :request do
+ let(:account) { create(:account) }
+ let(:admin) { create(:user, account: account, role: :administrator) }
+
+ describe 'PUT /api/v1/accounts/{account.id}/agents/:id' do
+ let(:other_agent) { create(:user, account: account, role: :agent) }
+ let!(:custom_role) { create(:custom_role, account: account) }
+
+ context 'when it is an authenticated administrator' do
+ it 'modified the custom role of the agent' do
+ put "/api/v1/accounts/#{account.id}/agents/#{other_agent.id}",
+ headers: admin.create_new_auth_token,
+ params: { custom_role_id: custom_role.id },
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(other_agent.account_users.first.reload.custom_role_id).to eq(custom_role.id)
+ expect(JSON.parse(response.body)['custom_role_id']).to eq(custom_role.id)
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/models/account_spec.rb b/spec/enterprise/models/account_spec.rb
index 67397a066..8e2681a9b 100644
--- a/spec/enterprise/models/account_spec.rb
+++ b/spec/enterprise/models/account_spec.rb
@@ -2,9 +2,15 @@
require 'rails_helper'
-RSpec.describe Account do
+RSpec.describe Account, type: :model do
include ActiveJob::TestHelper
+ describe 'associations' do
+ it { is_expected.to have_many(:sla_policies).dependent(:destroy_async) }
+ it { is_expected.to have_many(:applied_slas).dependent(:destroy_async) }
+ it { is_expected.to have_many(:custom_roles).dependent(:destroy_async) }
+ end
+
describe 'sla_policies' do
let!(:account) { create(:account) }
let!(:sla_policy) { create(:sla_policy, account: account) }
diff --git a/spec/enterprise/models/account_user_spec.rb b/spec/enterprise/models/account_user_spec.rb
index 3850cfa9e..fb572e86a 100644
--- a/spec/enterprise/models/account_user_spec.rb
+++ b/spec/enterprise/models/account_user_spec.rb
@@ -2,7 +2,33 @@
require 'rails_helper'
-RSpec.describe AccountUser do
+RSpec.describe AccountUser, type: :model do
+ describe 'associations' do
+ # option and dependant nullify
+ it { is_expected.to belong_to(:custom_role).optional }
+ end
+
+ describe 'permissions' do
+ context 'when custom role is assigned' do
+ it 'returns permissions of the custom role along with `custom_role` permission' do
+ account = create(:account)
+ custom_role = create(:custom_role, account: account)
+ account_user = create(:account_user, account: account, custom_role: custom_role)
+
+ expect(account_user.permissions).to eq(custom_role.permissions + ['custom_role'])
+ end
+ end
+
+ context 'when custom role is not assigned' do
+ it 'returns permissions of the default role' do
+ account = create(:account)
+ account_user = create(:account_user, account: account)
+
+ expect(account_user.permissions).to eq([account_user.role])
+ end
+ end
+ end
+
describe 'audit log' do
context 'when account user is created' do
it 'has associated audit log created' do
diff --git a/spec/enterprise/models/custom_role_spec.rb b/spec/enterprise/models/custom_role_spec.rb
new file mode 100644
index 000000000..f63f3c2dd
--- /dev/null
+++ b/spec/enterprise/models/custom_role_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+RSpec.describe CustomRole, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:account) }
+ it { is_expected.to have_many(:account_users).dependent(:nullify) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:name) }
+ end
+end
diff --git a/spec/factories/custom_roles.rb b/spec/factories/custom_roles.rb
new file mode 100644
index 000000000..7a3ca7f3f
--- /dev/null
+++ b/spec/factories/custom_roles.rb
@@ -0,0 +1,8 @@
+FactoryBot.define do
+ factory :custom_role do
+ account
+ name { Faker::Name.name }
+ description { Faker::Lorem.sentence }
+ permissions { CustomRole::PERMISSIONS.sample(SecureRandom.random_number(4)) }
+ end
+end
From 53d68868c69a23518a575c295fde0f832beb983f Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Fri, 23 Aug 2024 17:19:06 +0530
Subject: [PATCH 3/3] chore: Hide linear linked issues error toast messages
(#10020)
We are fetching linked Linear issues when opening a conversation if Linear integration is enabled. There may be some cases where the API call fails. We don't need to show an error message every time a user opens the conversation, as it's not critical. However, when someone clicks on the Linear icon, we can inform them that the integration is disabled. This PR will fix the issue.
---
.../components/widgets/conversation/linear/index.vue | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/app/javascript/dashboard/components/widgets/conversation/linear/index.vue b/app/javascript/dashboard/components/widgets/conversation/linear/index.vue
index 6d2c789c2..08f180c93 100644
--- a/app/javascript/dashboard/components/widgets/conversation/linear/index.vue
+++ b/app/javascript/dashboard/components/widgets/conversation/linear/index.vue
@@ -48,11 +48,7 @@ const loadLinkedIssue = async () => {
const issues = response.data;
linkedIssue.value = issues && issues.length ? issues[0] : null;
} catch (error) {
- const errorMessage = parseLinearAPIErrorResponse(
- error,
- t('INTEGRATION_SETTINGS.LINEAR.LOADING_ERROR')
- );
- useAlert(errorMessage);
+ // We don't want to show an error message here, as it's not critical. When someone clicks on the Linear icon, we can inform them that the integration is disabled.
}
};