Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/live/ContactChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,9 @@ export class ContactChat extends ContactStoreElement {
@property({ type: String })
errorMessage: string;

// track if we're changing topic to preserve assignee
private changingTopic = false;

// http promise to monitor for completeness
public httpComplete: Promise<void>;
private chat: Chat;
Expand Down Expand Up @@ -1043,6 +1046,7 @@ export class ContactChat extends ContactStoreElement {
const topic = select.values[0];

if (this.currentTicket.topic.uuid !== topic.uuid) {
this.changingTopic = true;
postJSON(`/api/v2/ticket_actions.json`, {
tickets: [this.currentTicket.uuid],
action: 'change_topic',
Expand All @@ -1053,6 +1057,7 @@ export class ContactChat extends ContactStoreElement {
})
.catch((response: any) => {
console.error(response);
this.changingTopic = false;
});
}
}
Expand Down Expand Up @@ -1083,15 +1088,29 @@ export class ContactChat extends ContactStoreElement {

public refreshTicket() {
if (this.currentTicket) {
// preserve the current assignee if we're changing topic
const preservedAssignee = this.changingTopic
? this.currentTicket.assignee
: undefined;

fetchResults(`/api/v2/tickets.json?uuid=${this.currentTicket.uuid}`).then(
(values) => {
this.store.resolveUsers(values, ['assignee']).then(() => {
if (values.length > 0) {
const updatedTicket = values[0];

// if we were changing topic, restore the original assignee
// to prevent auto-assignment from server-side rules
if (this.changingTopic && preservedAssignee !== undefined) {
updatedTicket.assignee = preservedAssignee;
this.changingTopic = false;
}

this.fireCustomEvent(CustomEventType.TicketUpdated, {
ticket: values[0],
ticket: updatedTicket,
previous: this.currentTicket
});
this.currentTicket = values[0];
this.currentTicket = updatedTicket;
}
});
}
Expand Down
176 changes: 176 additions & 0 deletions test/temba-contact-chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,180 @@ describe('temba-contact-chat', () => {

await assertScreenshot('contacts/chat-failure', getClip(chat));
});

it('should not change assignee when topic changes and server auto-assigns', async () => {
await loadStore();

// Create a ticket with no assignee
const unassignedTicket = {
uuid: 'test-ticket-uuid',
contact: {
uuid: 'contact-dave-active',
name: 'Dave Matthews'
},
status: 'open',
subject: 'Test Ticket',
topic: {
uuid: 'topic-1',
name: 'Support'
},
assignee: null,
opened_on: '2021-03-31T00:00:00.000Z',
closed_on: null
};

// After topic change, server auto-assigns the ticket
const autoAssignedTicket = {
...unassignedTicket,
topic: {
uuid: 'topic-2',
name: 'Billing'
},
assignee: {
email: '[email protected]',
name: 'Adam McAdmin'
}
};

// Mock the ticket endpoint to return assigned ticket after refresh
mockGET(
/\/api\/v2\/tickets\.json\?uuid=test-ticket-uuid/,
'/test-assets/api/tickets.json',
{ results: [autoAssignedTicket] }
);

// Mock topic change action
mockPOST(/\/api\/v2\/ticket_actions\.json/, {}, {});

// Create chat with the ticket
const chat: ContactChat = await getContactChat({
contact: 'contact-dave-active'
});
chat.currentTicket = unassignedTicket;
await chat.requestUpdate();
await clock.tick(0);

// Get the user select component
const userSelect = chat.shadowRoot.querySelector('temba-user-select');
expect(userSelect).to.exist;

// Verify it starts empty (no assignee)
expect((userSelect as any).values.length).to.equal(0);

// Listen for ticket updated event
const ticketUpdatedPromise = oneEvent(
chat,
CustomEventType.TicketUpdated,
false
);

// Simulate topic change
const topicSelect = chat.shadowRoot.querySelector('temba-select');
expect(topicSelect).to.exist;

// Change to a new topic
const newTopic = { uuid: 'topic-2', name: 'Billing' };
(topicSelect as any).values = [newTopic];

// Fire change event - this triggers handleTopicChanged
topicSelect.dispatchEvent(new CustomEvent('change', { bubbles: true }));

// Wait for the ticket to be updated
await ticketUpdatedPromise;
await chat.requestUpdate();
await clock.tick(100);

// After the fix, the assignee widget should remain empty
// even though the server auto-assigned the ticket
expect((userSelect as any).values.length).to.equal(
0,
'Assignee should remain null after topic change'
);
expect(chat.currentTicket.assignee).to.be.null;
});

it('should preserve existing assignee when topic changes', async () => {
await loadStore();

// Create a ticket with an existing assignee
const assignedTicket = {
uuid: 'test-ticket-uuid-2',
contact: {
uuid: 'contact-dave-active',
name: 'Dave Matthews'
},
status: 'open',
subject: 'Test Ticket 2',
topic: {
uuid: 'topic-1',
name: 'Support'
},
assignee: {
email: '[email protected]',
name: 'Agnes McAgent'
},
opened_on: '2021-03-31T00:00:00.000Z',
closed_on: null
};

// After topic change, server tries to auto-assign to different user
const serverAssignedTicket = {
...assignedTicket,
topic: {
uuid: 'topic-2',
name: 'Billing'
},
assignee: {
email: '[email protected]',
name: 'Adam McAdmin'
}
};

// Mock the ticket endpoint
mockGET(
/\/api\/v2\/tickets\.json\?uuid=test-ticket-uuid-2/,
'/test-assets/api/tickets.json',
{ results: [serverAssignedTicket] }
);

mockPOST(/\/api\/v2\/ticket_actions\.json/, {}, {});

const chat: ContactChat = await getContactChat({
contact: 'contact-dave-active'
});
chat.currentTicket = assignedTicket;
await chat.requestUpdate();
await clock.tick(0);

const userSelect = chat.shadowRoot.querySelector('temba-user-select');
expect(userSelect).to.exist;

// Verify it starts with the original assignee
expect((userSelect as any).values.length).to.equal(1);
expect((userSelect as any).values[0].email).to.equal('[email protected]');

const ticketUpdatedPromise = oneEvent(
chat,
CustomEventType.TicketUpdated,
false
);

// Change topic
const topicSelect = chat.shadowRoot.querySelector('temba-select');
const newTopic = { uuid: 'topic-2', name: 'Billing' };
(topicSelect as any).values = [newTopic];
topicSelect.dispatchEvent(new CustomEvent('change', { bubbles: true }));

await ticketUpdatedPromise;
await chat.requestUpdate();
await clock.tick(100);

// Assignee should still be the original one
expect((userSelect as any).values.length).to.equal(1);
expect((userSelect as any).values[0].email).to.equal(
'[email protected]',
'Original assignee should be preserved'
);
expect(chat.currentTicket.assignee.email).to.equal('[email protected]');
});
});