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

Thanks

REXML 3.3.5 - 2024-08-12

Fixes

... (truncated)

Changelog

Sourced from rexml's changelog.

3.3.6 - 2024-08-22 {#version-3-3-6}

Improvements

Fixes

Thanks

3.3.5 - 2024-08-12 {#version-3-3-5}

Fixes

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rexml&package-manager=bundler&previous-version=3.3.4&new-version=3.3.6)](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. } };