diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx index 9c76109b948cf..03e633e68eb86 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx @@ -53,6 +53,7 @@ function ChatInfo({ id, route }: ChatInfoProps) { const { data: room } = useOmnichannelRoomInfo(id); // FIXME: `room` is serialized, but we need to deserialize it const { + _id: roomId, ts, tags, closedAt, @@ -121,15 +122,17 @@ function ChatInfo({ id, route }: ChatInfoProps) { {departmentId && } {tags && tags.length > 0 && ( - {t('Tags')} + {t('Tags')} - {tags.map((tag) => ( - - - {tag} - - - ))} +
    + {tags.map((tag) => ( + + + {tag} + + + ))} +
)} diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-assign-room-tags.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-assign-room-tags.spec.ts new file mode 100644 index 0000000000000..a76f3e1a67813 --- /dev/null +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-assign-room-tags.spec.ts @@ -0,0 +1,135 @@ +import { createFakeVisitor } from '../../mocks/data'; +import { IS_EE } from '../config/constants'; +import { Users } from '../fixtures/userStates'; +import { HomeOmnichannel } from '../page-objects'; +import { createAgent } from '../utils/omnichannel/agents'; +import { addAgentToDepartment, createDepartment } from '../utils/omnichannel/departments'; +import { createConversation } from '../utils/omnichannel/rooms'; +import { createTag } from '../utils/omnichannel/tags'; +import { test, expect } from '../utils/test'; + +const visitorA = createFakeVisitor(); +const visitorB = createFakeVisitor(); + +test.use({ storageState: Users.user1.state }); + +test.describe('OC - Tags Visibility', () => { + test.skip(!IS_EE, 'OC - Tags Visibility > Enterprise Edition Only'); + + let poOmnichannel: HomeOmnichannel; + let conversations: Awaited>[] = []; + let departmentA: Awaited>; + let departmentB: Awaited>; + let agent: Awaited>; + let tags: Awaited>[] = []; + + test.beforeAll('Create departments', async ({ api }) => { + departmentA = await createDepartment(api, { name: 'Department A' }); + departmentB = await createDepartment(api, { name: 'Department B' }); + }); + + test.beforeAll('Create agent', async ({ api }) => { + agent = await createAgent(api, 'user1'); + }); + + test.beforeAll('Add agents to departments', async ({ api }) => { + await Promise.all([ + addAgentToDepartment(api, { department: departmentA.data, agentId: 'user1' }), + addAgentToDepartment(api, { department: departmentB.data, agentId: 'user1' }), + ]); + }); + + test.beforeAll('Create tags', async ({ api }) => { + tags = await Promise.all([ + createTag(api, { name: 'TagA', description: 'tag A', departments: [departmentA.data._id] }), + createTag(api, { name: 'TagB', description: 'tag B', departments: [departmentB.data._id] }), + createTag(api, { name: 'GlobalTag', description: 'public tag', departments: [] }), + createTag(api, { + name: 'SharedTag', + description: 'tag for both departments', + departments: [departmentA.data._id, departmentB.data._id], + }), + ]); + }); + + test.beforeAll('Create conversations', async ({ api }) => { + conversations = await Promise.all([ + createConversation(api, { visitorName: visitorA.name, agentId: 'user1', departmentId: departmentA.data._id }), + createConversation(api, { visitorName: visitorB.name, agentId: 'user1', departmentId: departmentB.data._id }), + ]); + }); + + test.beforeEach(async ({ page }) => { + poOmnichannel = new HomeOmnichannel(page); + await page.goto('/'); + }); + + test.afterAll(async () => { + await Promise.all(conversations.map((conversation) => conversation.delete())); + await Promise.all(tags.map((tag) => tag.delete())); + await agent.delete(); + await departmentA.delete(); + await departmentB.delete(); + }); + + test('Verify agent should see correct tags based on department association', async () => { + await test.step('Agent opens room', async () => { + await poOmnichannel.sidenav.getSidebarItemByName(visitorA.name).click(); + }); + + await test.step('should not be able to see tags field', async () => { + await expect(poOmnichannel.roomInfo.getLabel('Tags')).not.toBeVisible(); + }); + + await test.step('check available tags', async () => { + await poOmnichannel.roomInfo.btnEditRoomInfo.click(); + await expect(poOmnichannel.roomInfo.dialogEditRoom).toBeVisible(); + await poOmnichannel.roomInfo.inputTags.click(); + }); + + await test.step('Should see TagA (department A specific)', async () => { + await expect(poOmnichannel.roomInfo.optionTags('TagA')).toBeVisible(); + }); + + await test.step('Should see SharedTag (both departments)', async () => { + await expect(poOmnichannel.roomInfo.optionTags('SharedTag')).toBeVisible(); + }); + + await test.step('Should see Public Tags for all chats (no department restriction)', async () => { + await expect(poOmnichannel.roomInfo.optionTags('GlobalTag')).toBeVisible(); + }); + + await test.step('Should not see TagB (department B specific)', async () => { + await expect(poOmnichannel.roomInfo.optionTags('TagB')).not.toBeVisible(); + }); + + await test.step('add tags and save', async () => { + await poOmnichannel.roomInfo.selectTag('TagA'); + await poOmnichannel.roomInfo.selectTag('GlobalTag'); + await poOmnichannel.roomInfo.btnSaveEditRoom.click(); + }); + + await test.step('verify selected tags are displayed under room information', async () => { + await expect(poOmnichannel.roomInfo.getLabel('Tags')).toBeVisible(); + await expect(poOmnichannel.roomInfo.getTagInfoByLabel('TagA')).toBeVisible(); + await expect(poOmnichannel.roomInfo.getTagInfoByLabel('GlobalTag')).toBeVisible(); + }); + }); + + test('Verify tags visibility for agent associated with multiple departments', async () => { + await test.step('Open room info', async () => { + await poOmnichannel.sidenav.getSidebarItemByName(visitorB.name).click(); + await poOmnichannel.roomInfo.btnEditRoomInfo.click(); + await expect(poOmnichannel.roomInfo.dialogEditRoom).toBeVisible(); + await poOmnichannel.roomInfo.inputTags.click(); + }); + + await test.step('Agent associated with DepartmentB should be able to see tags for Department B', async () => { + await expect(poOmnichannel.roomInfo.optionTags('TagB')).toBeVisible(); + }); + + await test.step('Agent associated with DepartmentB should not be able to see tags for DepartmentA', async () => { + await expect(poOmnichannel.roomInfo.optionTags('TagA')).not.toBeVisible(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-room-info.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-room-info.ts index 84a49c01c13b2..7c415faccdb70 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-room-info.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-room-info.ts @@ -53,6 +53,22 @@ export class OmnichannelRoomInfo { return this.page.getByRole('option', { name, exact: true }).click(); } + get inputTags(): Locator { + return this.page.getByRole('textbox', { name: 'Select an option' }); + } + + optionTags(name: string): Locator { + return this.page.getByRole('option', { name, exact: true }); + } + + async selectTag(name: string): Promise { + await this.optionTags(name).click(); + } + + getTagInfoByLabel(label: string): Locator { + return this.dialogRoomInfo.getByRole('list', { name: 'Tags' }).getByText(label, { exact: true }); + } + getBadgeIndicator(name: string, title: string): Locator { return this.homeSidenav.getSidebarItemByName(name).getByTitle(title); }