From 7b8a3fcae05088af5ec7fb88303630a04c880b06 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Wed, 16 Aug 2023 08:25:19 +0530 Subject: [PATCH] feat: auditlog for team and inbox member updates (#7516) - adds an audit log when an agent is added or removed from a team - adds an audit log when an agent is added or removed from an inbox Co-authored-by: Sojan Jose --- .../dashboard/helper/auditlogHelper.js | 83 +++++++++++++++---- .../helper/specs/auditlogHelper.spec.js | 42 ++++++++++ .../dashboard/i18n/locale/en/auditLogs.json | 10 ++- app/models/inbox_member.rb | 2 + app/models/team_member.rb | 2 + .../models/enterprise/audit/inbox_member.rb | 31 +++++++ .../models/enterprise/audit/team_member.rb | 31 +++++++ spec/enterprise/models/inbox_member_spec.rb | 31 +++++++ spec/enterprise/models/team_member_spec.rb | 31 +++++++ 9 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 enterprise/app/models/enterprise/audit/inbox_member.rb create mode 100644 enterprise/app/models/enterprise/audit/team_member.rb create mode 100644 spec/enterprise/models/inbox_member_spec.rb create mode 100644 spec/enterprise/models/team_member_spec.rb diff --git a/app/javascript/dashboard/helper/auditlogHelper.js b/app/javascript/dashboard/helper/auditlogHelper.js index 467b416f9..0812ba7f6 100644 --- a/app/javascript/dashboard/helper/auditlogHelper.js +++ b/app/javascript/dashboard/helper/auditlogHelper.js @@ -30,6 +30,10 @@ const translationKeys = { 'accountuser:create': `AUDIT_LOGS.ACCOUNT_USER.ADD`, 'accountuser:update:self': `AUDIT_LOGS.ACCOUNT_USER.EDIT.SELF`, 'accountuser:update:other': `AUDIT_LOGS.ACCOUNT_USER.EDIT.OTHER`, + 'inboxmember:create': `AUDIT_LOGS.INBOX_MEMBER.ADD`, + 'inboxmember:destroy': `AUDIT_LOGS.INBOX_MEMBER.REMOVE`, + 'teammember:create': `AUDIT_LOGS.TEAM_MEMBER.ADD`, + 'teammember:destroy': `AUDIT_LOGS.TEAM_MEMBER.REMOVE`, 'account:update': `AUDIT_LOGS.ACCOUNT.EDIT`, }; @@ -102,6 +106,58 @@ function handleAccountUserUpdate(auditLogItem, translationPayload, agentList) { return translationPayload; } +function setUserInPayload(auditLogItem, translationPayload, agentList) { + const userIdChange = auditLogItem.audited_changes.user_id; + if (userIdChange && userIdChange !== undefined) { + translationPayload.user = getAgentName(userIdChange, agentList); + } + return translationPayload; +} + +function setTeamIdInPayload(auditLogItem, translationPayload) { + if (auditLogItem.audited_changes.team_id) { + translationPayload.team_id = auditLogItem.audited_changes.team_id; + } + return translationPayload; +} + +function setInboxIdInPayload(auditLogItem, translationPayload) { + if (auditLogItem.audited_changes.inbox_id) { + translationPayload.inbox_id = auditLogItem.audited_changes.inbox_id; + } + return translationPayload; +} + +function handleInboxTeamMember(auditLogItem, translationPayload, agentList) { + if (auditLogItem.audited_changes) { + translationPayload = setUserInPayload( + auditLogItem, + translationPayload, + agentList + ); + translationPayload = setTeamIdInPayload(auditLogItem, translationPayload); + translationPayload = setInboxIdInPayload(auditLogItem, translationPayload); + } + return translationPayload; +} + +function handleAccountUser( + auditLogItem, + translationPayload, + agentList, + action +) { + if (action === 'create') { + return handleAccountUserCreate(auditLogItem, translationPayload, agentList); + } + + if (action === 'update') { + return handleAccountUserUpdate(auditLogItem, translationPayload, agentList); + } + + return translationPayload; +} + export function generateTranslationPayload(auditLogItem, agentList) { let translationPayload = { agentName: getAgentName(auditLogItem.user_id, agentList), @@ -112,21 +168,20 @@ export function generateTranslationPayload(auditLogItem, agentList) { const action = auditLogItem.action.toLowerCase(); if (auditableType === 'accountuser') { - if (action === 'create') { - translationPayload = handleAccountUserCreate( - auditLogItem, - translationPayload, - agentList - ); - } + translationPayload = handleAccountUser( + auditLogItem, + translationPayload, + agentList, + action + ); + } - if (action === 'update') { - translationPayload = handleAccountUserUpdate( - auditLogItem, - translationPayload, - agentList - ); - } + if (auditableType === 'inboxmember' || auditableType === 'teammember') { + translationPayload = handleInboxTeamMember( + auditLogItem, + translationPayload, + agentList + ); } return translationPayload; diff --git a/app/javascript/dashboard/helper/specs/auditlogHelper.spec.js b/app/javascript/dashboard/helper/specs/auditlogHelper.spec.js index 2545668b4..a04707010 100644 --- a/app/javascript/dashboard/helper/specs/auditlogHelper.spec.js +++ b/app/javascript/dashboard/helper/specs/auditlogHelper.spec.js @@ -98,6 +98,48 @@ describe('Helper functions', () => { }); }); + it('should handle InboxMember or TeamMember', () => { + const auditLogItemInboxMember = { + auditable_type: 'InboxMember', + action: 'create', + audited_changes: { + user_id: 2, + }, + user_id: 1, + auditable_id: 789, + }; + + const payloadInboxMember = generateTranslationPayload( + auditLogItemInboxMember, + agentList + ); + expect(payloadInboxMember).toEqual({ + agentName: 'Agent 1', + id: 789, + user: 'Agent 2', + }); + + const auditLogItemTeamMember = { + auditable_type: 'TeamMember', + action: 'create', + audited_changes: { + user_id: 3, + }, + user_id: 1, + auditable_id: 789, + }; + + const payloadTeamMember = generateTranslationPayload( + auditLogItemTeamMember, + agentList + ); + expect(payloadTeamMember).toEqual({ + agentName: 'Agent 1', + id: 789, + user: 'Agent 3', + }); + }); + it('should handle generic case like Team create', () => { const auditLogItem = { auditable_type: 'Team', diff --git a/app/javascript/dashboard/i18n/locale/en/auditLogs.json b/app/javascript/dashboard/i18n/locale/en/auditLogs.json index f3e8edc2c..8a3fa7715 100644 --- a/app/javascript/dashboard/i18n/locale/en/auditLogs.json +++ b/app/javascript/dashboard/i18n/locale/en/auditLogs.json @@ -56,8 +56,16 @@ "EDIT": "%{agentName} updated a macro (#%{id})", "DELETE": "%{agentName} deleted a macro (#%{id})" }, + "INBOX_MEMBER": { + "ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})", + "REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})" + }, + "TEAM_MEMBER": { + "ADD": "%{agentName} added %{user} to the team(#%{team_id})", + "REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})" + }, "ACCOUNT": { "EDIT": "%{agentName} updated the account configuration (#%{id})" } - } +} } diff --git a/app/models/inbox_member.rb b/app/models/inbox_member.rb index 178c33642..d4a36ccc8 100644 --- a/app/models/inbox_member.rb +++ b/app/models/inbox_member.rb @@ -35,3 +35,5 @@ class InboxMember < ApplicationRecord ::AutoAssignment::InboxRoundRobinService.new(inbox: inbox).remove_agent_from_queue(user_id) if inbox.present? end end + +InboxMember.include_mod_with('Audit::InboxMember') diff --git a/app/models/team_member.rb b/app/models/team_member.rb index f5d364007..f99af264b 100644 --- a/app/models/team_member.rb +++ b/app/models/team_member.rb @@ -19,3 +19,5 @@ class TeamMember < ApplicationRecord belongs_to :team validates :user_id, uniqueness: { scope: :team_id } end + +TeamMember.include_mod_with('Audit::TeamMember') diff --git a/enterprise/app/models/enterprise/audit/inbox_member.rb b/enterprise/app/models/enterprise/audit/inbox_member.rb new file mode 100644 index 000000000..d5f3362b2 --- /dev/null +++ b/enterprise/app/models/enterprise/audit/inbox_member.rb @@ -0,0 +1,31 @@ +module Enterprise::Audit::InboxMember + extend ActiveSupport::Concern + + included do + after_commit :create_audit_log_entry_on_create, on: :create + after_commit :create_audit_log_entry_on_delete, on: :destroy + end + + private + + def create_audit_log_entry_on_create + create_audit_log_entry('create') + end + + def create_audit_log_entry_on_delete + create_audit_log_entry('destroy') + end + + def create_audit_log_entry(action) + return if inbox.blank? + + Enterprise::AuditLog.create( + auditable_id: id, + auditable_type: 'InboxMember', + action: action, + associated_id: inbox&.account_id, + audited_changes: attributes.except('updated_at', 'created_at'), + associated_type: 'Account' + ) + end +end diff --git a/enterprise/app/models/enterprise/audit/team_member.rb b/enterprise/app/models/enterprise/audit/team_member.rb new file mode 100644 index 000000000..9be448b6c --- /dev/null +++ b/enterprise/app/models/enterprise/audit/team_member.rb @@ -0,0 +1,31 @@ +module Enterprise::Audit::TeamMember + extend ActiveSupport::Concern + + included do + after_commit :create_audit_log_entry_on_create, on: :create + after_commit :create_audit_log_entry_on_delete, on: :destroy + end + + private + + def create_audit_log_entry_on_create + create_audit_log_entry('create') + end + + def create_audit_log_entry_on_delete + create_audit_log_entry('destroy') + end + + def create_audit_log_entry(action) + return if team.blank? + + Enterprise::AuditLog.create( + auditable_id: id, + auditable_type: 'TeamMember', + action: action, + associated_id: team&.account_id, + audited_changes: attributes.except('updated_at', 'created_at'), + associated_type: 'Account' + ) + end +end diff --git a/spec/enterprise/models/inbox_member_spec.rb b/spec/enterprise/models/inbox_member_spec.rb new file mode 100644 index 000000000..b4559bfc6 --- /dev/null +++ b/spec/enterprise/models/inbox_member_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe InboxMember, type: :model do + let(:user) { create(:user) } + let(:inbox) { create(:inbox) } + let!(:inbox_member) { create(:inbox_member, inbox: inbox, user: user) } + + describe 'audit log' do + context 'when inbox member is created' do + it 'has associated audit log created' do + expect(Audited::Audit.where(auditable: inbox_member, action: 'create').count).to eq(1) + end + + it 'has user_id in audited_changes matching user.id' do + audit_log = Audited::Audit.find_by(auditable: inbox_member, action: 'create') + expect(audit_log.audited_changes['user_id']).to eq(user.id) + end + end + + context 'when inbox member is destroyed' do + it 'has associated audit log created' do + inbox_member.destroy + audit_log = Audited::Audit.find_by(auditable: inbox_member, action: 'destroy') + expect(audit_log).to be_present + expect(audit_log.audited_changes['inbox_id']).to eq(inbox.id) + end + end + end +end diff --git a/spec/enterprise/models/team_member_spec.rb b/spec/enterprise/models/team_member_spec.rb new file mode 100644 index 000000000..9ce2bd90f --- /dev/null +++ b/spec/enterprise/models/team_member_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TeamMember, type: :model do + let(:user) { create(:user) } + let(:team) { create(:team) } + let!(:team_member) { create(:team_member, user: user, team: team) } + + describe 'audit log' do + context 'when team member is created' do + it 'has associated audit log created' do + expect(Audited::Audit.where(auditable: team_member, action: 'create').count).to eq(1) + end + + it 'has user_id in audited_changes matching user.id' do + audit_log = Audited::Audit.find_by(auditable: team_member, action: 'create') + expect(audit_log.audited_changes['user_id']).to eq(user.id) + end + end + + context 'when team member is destroyed' do + it 'has associated audit log created' do + team_member.destroy + audit_log = Audited::Audit.find_by(auditable: team_member, action: 'destroy') + expect(audit_log).to be_present + expect(audit_log.audited_changes['team_id']).to eq(team.id) + end + end + end +end