Skip to content

Commit 1688596

Browse files
authored
DataGrid - AI Assistant: update AI Chat regenerate button behavior and move some AIChat utility methods to AIAssistant utils (#33571)
Co-authored-by: Alyar <>
1 parent 3c93f5b commit 1688596

5 files changed

Lines changed: 94 additions & 222 deletions

File tree

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/__tests__/utils.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { Message } from '@js/ui/chat';
66
import { AI_ASSISTANT_AUTHOR_ID, MessageStatus } from '../const';
77
import {
88
getMessageStatus,
9+
hasAbortedCommands,
10+
hasCommandErrors,
911
isAIMessage,
1012
isChatOptions,
1113
isEnabledOption,
@@ -119,6 +121,61 @@ describe('isChatOptions', () => {
119121
});
120122
});
121123

124+
describe('hasCommandErrors', () => {
125+
it('should return true when commands contain failure status', () => {
126+
const commands = [
127+
{ status: 'success' as const, message: 'OK' },
128+
{ status: 'failure' as const, message: 'Failed' },
129+
];
130+
131+
expect(hasCommandErrors(commands)).toBe(true);
132+
});
133+
134+
it('should return false when all commands are successful', () => {
135+
const commands = [
136+
{ status: 'success' as const, message: 'OK' },
137+
];
138+
139+
expect(hasCommandErrors(commands)).toBe(false);
140+
});
141+
142+
it('should return false when commands is undefined', () => {
143+
expect(hasCommandErrors(undefined)).toBe(false);
144+
});
145+
146+
it('should return false when commands contain only aborted status', () => {
147+
const commands = [
148+
{ status: 'aborted' as const, message: 'Aborted' },
149+
];
150+
151+
expect(hasCommandErrors(commands)).toBe(false);
152+
});
153+
});
154+
155+
describe('hasAbortedCommands', () => {
156+
it('should return true when commands contain aborted status', () => {
157+
const commands = [
158+
{ status: 'success' as const, message: 'OK' },
159+
{ status: 'aborted' as const, message: 'Aborted' },
160+
];
161+
162+
expect(hasAbortedCommands(commands)).toBe(true);
163+
});
164+
165+
it('should return false when no commands are aborted', () => {
166+
const commands = [
167+
{ status: 'success' as const, message: 'OK' },
168+
{ status: 'failure' as const, message: 'Failed' },
169+
];
170+
171+
expect(hasAbortedCommands(commands)).toBe(false);
172+
});
173+
174+
it('should return false when commands is undefined', () => {
175+
expect(hasAbortedCommands(undefined)).toBe(false);
176+
});
177+
});
178+
122179
describe('getMessageStatus', () => {
123180
it('should return Success when all commands are successful', () => {
124181
const commands = [

packages/devextreme/js/__internal/grids/grid_core/ai_assistant/utils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { isObject } from '@js/core/utils/type';
22
import type { Message } from '@js/ui/chat';
33

4-
import { hasAbortedCommands, hasCommandErrors } from '../ai_chat/utils';
54
import { AI_ASSISTANT_AUTHOR_ID, MessageStatus } from './const';
6-
import type { AIMessage, CommandResults } from './types';
5+
import type { AIMessage, CommandResult } from './types';
76

87
export const isAIMessage = (
98
message: Message,
@@ -21,7 +20,15 @@ export const isPopupOptions = (optionName: string, value: unknown): boolean => o
2120
export const isChatOptions = (optionName: string, value: unknown): boolean => optionName.startsWith('aiAssistant.chat')
2221
|| (optionName === 'aiAssistant' && isObject(value) && 'chat' in value);
2322

24-
export const getMessageStatus = (commands: CommandResults): MessageStatus => {
23+
export const hasCommandErrors = (
24+
commands: CommandResult[] | undefined,
25+
): boolean => !!commands?.some(({ status }) => status === 'failure');
26+
27+
export const hasAbortedCommands = (
28+
commands: CommandResult[] | undefined,
29+
): boolean => !!commands?.some(({ status }) => status === 'aborted');
30+
31+
export const getMessageStatus = (commands: CommandResult[]): MessageStatus => {
2532
if (hasCommandErrors(commands) || hasAbortedCommands(commands)) {
2633
return MessageStatus.Failure;
2734
}

packages/devextreme/js/__internal/grids/grid_core/ai_chat/ai_chat.test.ts

Lines changed: 0 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -394,26 +394,6 @@ describe('AIChat', () => {
394394
expect(icons[2].textContent).toBe(ABORTED_ITEM_EMOJI);
395395
});
396396

397-
it('should render error icon when commands contain errors', () => {
398-
createAIChat();
399-
triggerContentTemplate();
400-
401-
const chatConfig = getChatConfig();
402-
const container = document.createElement('div');
403-
404-
renderMessageTemplate(chatConfig, {
405-
author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' },
406-
text: 'Mixed',
407-
status: 'success',
408-
commands: [
409-
{ status: 'success', message: 'OK' },
410-
{ status: 'failure', message: 'Failed' },
411-
],
412-
}, container);
413-
414-
expect(container.querySelector(`.${CLASSES.messageIcon}`)?.classList.contains('dx-icon-errorcircle')).toBe(true);
415-
});
416-
417397
it('should not render regenerate button when all commands succeed', () => {
418398
const onRegenerate = jest.fn();
419399
createAIChat({ onRegenerate });
@@ -432,30 +412,6 @@ describe('AIChat', () => {
432412
expect(container.querySelector(`.${CLASSES.messageRegenerateButton}`)).toBeNull();
433413
});
434414

435-
it('should render regenerate button when commands contain errors', () => {
436-
const onRegenerate = jest.fn();
437-
createAIChat({ onRegenerate });
438-
triggerContentTemplate();
439-
440-
const chatConfig = getChatConfig();
441-
const container = document.createElement('div');
442-
443-
renderMessageTemplate(chatConfig, {
444-
author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' },
445-
text: 'Mixed results',
446-
status: 'success',
447-
commands: [
448-
{ status: 'success', message: 'OK' },
449-
{ status: 'failure', message: 'Failed' },
450-
],
451-
}, container);
452-
453-
const regenerateButton = container.querySelector(`.${CLASSES.messageRegenerateButton}`);
454-
455-
expect(regenerateButton).not.toBeNull();
456-
expect(regenerateButton?.classList.contains(`dx-icon-${REGENERATE_ICON}`)).toBe(true);
457-
});
458-
459415
it('should not render command list when commands array is empty', () => {
460416
createAIChat();
461417
triggerContentTemplate();
@@ -632,50 +588,6 @@ describe('AIChat', () => {
632588
expect(icons).toHaveLength(1);
633589
expect(icons[0].textContent).toBe(ABORTED_ITEM_EMOJI);
634590
});
635-
636-
it('should render error icon when commands contain aborted items', () => {
637-
createAIChat();
638-
triggerContentTemplate();
639-
640-
const chatConfig = getChatConfig();
641-
const container = document.createElement('div');
642-
643-
renderMessageTemplate(chatConfig, {
644-
author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' },
645-
text: 'Aborted',
646-
status: 'success',
647-
commands: [
648-
{ status: 'success', message: 'OK' },
649-
{ status: 'aborted', message: 'Aborted' },
650-
],
651-
}, container);
652-
653-
expect(container.querySelector(`.${CLASSES.messageIcon}`)?.classList.contains('dx-icon-errorcircle')).toBe(true);
654-
});
655-
656-
it('should render regenerate button when commands contain aborted items', () => {
657-
const onRegenerate = jest.fn();
658-
createAIChat({ onRegenerate });
659-
triggerContentTemplate();
660-
661-
const chatConfig = getChatConfig();
662-
const container = document.createElement('div');
663-
664-
renderMessageTemplate(chatConfig, {
665-
author: { id: AI_ASSISTANT_AUTHOR_ID, name: 'AI Assistant' },
666-
text: 'Aborted results',
667-
status: 'success',
668-
commands: [
669-
{ status: 'success', message: 'OK' },
670-
{ status: 'aborted', message: 'Aborted' },
671-
],
672-
}, container);
673-
674-
const regenerateButton = container.querySelector(`.${CLASSES.messageRegenerateButton}`);
675-
676-
expect(regenerateButton).not.toBeNull();
677-
expect(regenerateButton?.classList.contains(`dx-icon-${REGENERATE_ICON}`)).toBe(true);
678-
});
679591
});
680592

681593
describe('general', () => {

packages/devextreme/js/__internal/grids/grid_core/ai_chat/utils.test.ts

Lines changed: 17 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -10,92 +10,17 @@ import {
1010
import {
1111
getCommandItemStyle,
1212
getMessageIconName,
13-
hasAbortedCommands,
14-
hasCommandErrors,
1513
needToRenderCommandList,
1614
needToShowRegenerateButton,
1715
} from './utils';
1816

19-
describe('hasCommandErrors', () => {
20-
it('should return true when commands contain failure status', () => {
21-
const commands = [
22-
{ status: 'success' as const, message: 'OK' },
23-
{ status: 'failure' as const, message: 'Failed' },
24-
];
25-
26-
expect(hasCommandErrors(commands)).toBe(true);
27-
});
28-
29-
it('should return false when all commands are successful', () => {
30-
const commands = [
31-
{ status: 'success' as const, message: 'OK' },
32-
];
33-
34-
expect(hasCommandErrors(commands)).toBe(false);
35-
});
36-
37-
it('should return false when commands is undefined', () => {
38-
expect(hasCommandErrors(undefined)).toBe(false);
39-
});
40-
41-
it('should return false when commands contain only aborted status', () => {
42-
const commands = [
43-
{ status: 'aborted' as const, message: 'Aborted' },
44-
];
45-
46-
expect(hasCommandErrors(commands)).toBe(false);
47-
});
48-
});
49-
50-
describe('hasAbortedCommands', () => {
51-
it('should return true when commands contain aborted status', () => {
52-
const commands = [
53-
{ status: 'success' as const, message: 'OK' },
54-
{ status: 'aborted' as const, message: 'Aborted' },
55-
];
56-
57-
expect(hasAbortedCommands(commands)).toBe(true);
58-
});
59-
60-
it('should return false when no commands are aborted', () => {
61-
const commands = [
62-
{ status: 'success' as const, message: 'OK' },
63-
{ status: 'failure' as const, message: 'Failed' },
64-
];
65-
66-
expect(hasAbortedCommands(commands)).toBe(false);
67-
});
68-
69-
it('should return false when commands is undefined', () => {
70-
expect(hasAbortedCommands(undefined)).toBe(false);
71-
});
72-
});
73-
7417
describe('getMessageIconName', () => {
7518
it('should return errorcircle when message status is failure', () => {
7619
const message = { status: MessageStatus.Failure } as Message;
7720

7821
expect(getMessageIconName(message)).toBe('errorcircle');
7922
});
8023

81-
it('should return errorcircle when commands contain errors', () => {
82-
const message = {
83-
status: MessageStatus.Success,
84-
commands: [{ status: 'failure', message: 'Failed' }],
85-
} as Message;
86-
87-
expect(getMessageIconName(message)).toBe('errorcircle');
88-
});
89-
90-
it('should return errorcircle when commands contain aborted items', () => {
91-
const message = {
92-
status: MessageStatus.Success,
93-
commands: [{ status: 'aborted', message: 'Aborted' }],
94-
} as Message;
95-
96-
expect(getMessageIconName(message)).toBe('errorcircle');
97-
});
98-
9924
it('should return checkmarkcirclefilled when message status is success', () => {
10025
const message = { status: MessageStatus.Success } as Message;
10126

@@ -110,31 +35,22 @@ describe('getMessageIconName', () => {
11035
});
11136

11237
describe('needToShowRegenerateButton', () => {
113-
it('should return true when message status is failure', () => {
38+
it('should return true when message status is failure and has no commands', () => {
11439
const message = { status: MessageStatus.Failure } as Message;
11540

11641
expect(needToShowRegenerateButton(message)).toBe(true);
11742
});
11843

119-
it('should return true when commands contain errors', () => {
44+
it('should return false when message status is failure and has commands', () => {
12045
const message = {
121-
status: MessageStatus.Success,
46+
status: MessageStatus.Failure,
12247
commands: [{ status: 'failure', message: 'Failed' }],
12348
} as Message;
12449

125-
expect(needToShowRegenerateButton(message)).toBe(true);
126-
});
127-
128-
it('should return true when commands contain aborted items', () => {
129-
const message = {
130-
status: MessageStatus.Success,
131-
commands: [{ status: 'aborted', message: 'Aborted' }],
132-
} as Message;
133-
134-
expect(needToShowRegenerateButton(message)).toBe(true);
50+
expect(needToShowRegenerateButton(message)).toBe(false);
13551
});
13652

137-
it('should return false when message is successful without errors', () => {
53+
it('should return false when message is successful', () => {
13854
const message = {
13955
status: MessageStatus.Success,
14056
commands: [{ status: 'success', message: 'OK' }],
@@ -174,38 +90,35 @@ describe('getCommandItemStyle', () => {
17490
});
17591

17692
describe('needToRenderCommandList', () => {
177-
it('should return true when message status is success', () => {
178-
const message = { status: MessageStatus.Success } as Message;
179-
180-
expect(needToRenderCommandList(message)).toBe(true);
181-
});
182-
183-
it('should return true when commands contain errors', () => {
93+
it('should return true when message has commands', () => {
18494
const message = {
185-
status: MessageStatus.Failure,
186-
commands: [{ status: 'failure', message: 'Failed' }],
95+
status: MessageStatus.Success,
96+
commands: [{ status: 'success', message: 'OK' }],
18797
} as Message;
18898

18999
expect(needToRenderCommandList(message)).toBe(true);
190100
});
191101

192-
it('should return true when commands contain aborted items', () => {
102+
it('should return true when message has failed commands', () => {
193103
const message = {
194104
status: MessageStatus.Failure,
195-
commands: [{ status: 'aborted', message: 'Aborted' }],
105+
commands: [{ status: 'failure', message: 'Failed' }],
196106
} as Message;
197107

198108
expect(needToRenderCommandList(message)).toBe(true);
199109
});
200110

201-
it('should return false when message is pending without commands', () => {
202-
const message = { status: MessageStatus.Pending } as Message;
111+
it('should return false when message has no commands', () => {
112+
const message = { status: MessageStatus.Success } as Message;
203113

204114
expect(needToRenderCommandList(message)).toBe(false);
205115
});
206116

207-
it('should return false when message is failure without commands', () => {
208-
const message = { status: MessageStatus.Failure } as Message;
117+
it('should return false when commands is empty array', () => {
118+
const message = {
119+
status: MessageStatus.Success,
120+
commands: [],
121+
} as unknown as Message;
209122

210123
expect(needToRenderCommandList(message)).toBe(false);
211124
});

0 commit comments

Comments
 (0)