Skip to content

Commit c6be797

Browse files
authored
DataGrid - AI Column: Fix minor issues (#31621)
Co-authored-by: Alyar <>
1 parent 6cabb52 commit c6be797

File tree

6 files changed

+197
-24
lines changed

6 files changed

+197
-24
lines changed

packages/devextreme/js/__internal/grids/grid_core/ai_column/ai_column.integration.test.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ describe('Options', () => {
164164
});
165165

166166
expect($(component.getDataCell(0, 3).getElement()).hasClass('custom-class')).toBe(true);
167+
expect($(component.getDataCell(0, 3).getElement()).hasClass(CLASSES.aiColumn)).toBe(true);
167168
});
168169
});
169170

@@ -826,6 +827,46 @@ describe('columnOption', () => {
826827
component.apiColumnOption('myColumn', 'type', 'ai');
827828
expect(component.apiColumnOption('myColumn').type).toBe('ai');
828829
});
830+
831+
describe('when prompt is reset', () => {
832+
it('should clear AI column values', async () => {
833+
const { component, instance } = await createDataGrid({
834+
keyExpr: 'id',
835+
dataSource: [
836+
{ id: 1, name: 'Name 1', value: 10 },
837+
],
838+
columns: [
839+
{ dataField: 'id', caption: 'ID' },
840+
{ dataField: 'name', caption: 'Name' },
841+
{ dataField: 'value', caption: 'Value' },
842+
{
843+
type: 'ai',
844+
caption: 'AI Column',
845+
name: 'myColumn',
846+
ai: {
847+
prompt: 'Initial Prompt',
848+
aiIntegration: new AIIntegration({
849+
sendRequest(): RequestResult {
850+
return {
851+
promise: new Promise<string>((resolve) => {
852+
resolve('{"1":"AI Value"}');
853+
}),
854+
abort: (): void => {},
855+
};
856+
},
857+
}),
858+
},
859+
},
860+
],
861+
});
862+
863+
expect(component.getDataCell(0, 3).getText()).toBe('AI Value');
864+
865+
instance.columnOption('myColumn', 'ai.prompt', '');
866+
867+
expect(component.getDataCell(0, 3).getText()).toBe(EMPTY_CELL_TEXT);
868+
});
869+
});
829870
});
830871

831872
describe('aiIntegration', () => {
@@ -4472,4 +4513,112 @@ describe('Load panel', () => {
44724513
expect(aiPromptEditor.getProgressBar().isVisible()).toBe(false);
44734514
});
44744515
});
4516+
4517+
describe('when AI column is cleared during request', () => {
4518+
it('should be hidden', async () => {
4519+
const { component, instance } = await createDataGrid({
4520+
dataSource: items,
4521+
keyExpr: 'id',
4522+
columns: [
4523+
{ dataField: 'id', caption: 'ID' },
4524+
{ dataField: 'name', caption: 'Name' },
4525+
{ dataField: 'value', caption: 'Value' },
4526+
{
4527+
type: 'ai',
4528+
caption: 'AI Column',
4529+
name: 'myColumn',
4530+
ai: {
4531+
aiIntegration: new AIIntegration({
4532+
sendRequest(): RequestResult {
4533+
return {
4534+
promise: new Promise<string>((resolve) => {
4535+
setTimeout(() => {
4536+
resolve('{"1":"AI Response 1","2":"AI Response 2"}');
4537+
}, 300);
4538+
}),
4539+
abort: (): void => {},
4540+
};
4541+
},
4542+
}),
4543+
},
4544+
},
4545+
],
4546+
});
4547+
4548+
component.apiColumnOption('myColumn', 'ai.prompt', 'Updated prompt');
4549+
4550+
expect(component.getLoadPanel().isVisible()).toBe(true);
4551+
4552+
instance.clearAIColumn('myColumn');
4553+
4554+
jest.runAllTimers(); // wait hidden load panel
4555+
4556+
expect(component.getLoadPanel().isVisible()).toBe(false);
4557+
});
4558+
});
4559+
4560+
describe('when AI Column response is cached', () => {
4561+
it('should be hidden', async () => {
4562+
const aiIntegration = new AIIntegration({
4563+
sendRequest(prompt): RequestResult {
4564+
return {
4565+
promise: new Promise<string>((resolve) => {
4566+
const result = {};
4567+
Object.entries(prompt.data?.data).forEach(([key, value]) => {
4568+
result[key] = `Response ${(value as any).name}`;
4569+
});
4570+
4571+
resolve(JSON.stringify(result));
4572+
}),
4573+
abort: (): void => {},
4574+
};
4575+
},
4576+
});
4577+
const { component, instance } = await createDataGrid({
4578+
dataSource: [
4579+
{ id: 1, name: 'Name 1', value: 10 },
4580+
{ id: 2, name: 'Name 2', value: 20 },
4581+
],
4582+
paging: {
4583+
pageSize: 1,
4584+
},
4585+
keyExpr: 'id',
4586+
columns: [
4587+
{ dataField: 'id', caption: 'ID' },
4588+
{ dataField: 'name', caption: 'Name' },
4589+
{ dataField: 'value', caption: 'Value' },
4590+
{
4591+
type: 'ai',
4592+
caption: 'AI Column',
4593+
name: 'myColumn',
4594+
ai: {
4595+
aiIntegration,
4596+
prompt: 'Test prompt',
4597+
},
4598+
},
4599+
],
4600+
});
4601+
4602+
expect(instance.getAIColumnText('myColumn', 1)).toEqual('Response Name 1');
4603+
4604+
instance.option('paging.pageIndex', 1);
4605+
jest.runAllTimers();
4606+
4607+
expect(component.getLoadPanel().isVisible()).toBe(true);
4608+
4609+
await Promise.resolve();
4610+
4611+
expect(instance.getAIColumnText('myColumn', 2)).toEqual('Response Name 2');
4612+
4613+
instance.option('paging.pageIndex', 0);
4614+
jest.runAllTimers();
4615+
4616+
expect(component.getLoadPanel().isVisible()).toBe(false);
4617+
4618+
await Promise.resolve();
4619+
4620+
expect(instance.getAIColumnText('myColumn', 1)).toEqual('Response Name 1');
4621+
expect(component.getLoadPanel().isVisible()).toBe(false);
4622+
});
4623+
});
44754624
});

packages/devextreme/js/__internal/grids/grid_core/ai_column/m_ai_column_controller.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
/* eslint-disable @typescript-eslint/no-unused-vars */
2-
import type { GenerateGridColumnCommandResult, RequestCallbacks } from '@js/common/ai-integration';
31
import type { Callback } from '@js/core/utils/callbacks';
42

53
import type { Column, ColumnsController } from '../columns_controller/m_columns_controller';
64
import type { DataController, HandleDataChangedArguments, UserData } from '../data_controller/m_data_controller';
75
import { Controller } from '../m_modules';
86
import { AIColumnIntegrationController } from './m_ai_column_integration_controller';
7+
import type { InternalRequestCallbacks } from './types';
98
import { getAICommandColumnDefaultOptions, isAIColumnAutoMode, isPromptOption } from './utils';
109

1110
export class AIColumnController extends Controller {
@@ -81,14 +80,6 @@ export class AIColumnController extends Controller {
8180
this.addAICommandColumn();
8281
}
8382

84-
private showResults(
85-
columnName: string,
86-
result: string,
87-
cachedData: Record<PropertyKey, string>,
88-
): void {
89-
// Update the results in the UI or internal state
90-
}
91-
9283
public getAIColumns(): Column[] {
9384
return this.columnsController.getColumns().filter((col) => col.type === 'ai') as Column[];
9485
}
@@ -133,13 +124,13 @@ export class AIColumnController extends Controller {
133124
needToShowLoadPanel = true,
134125
): void {
135126
const callbacks = this.getRequestCallbacks();
136-
const column = this.columnsController.columnOption(columnName);
137-
138-
if (needToShowLoadPanel && !!column?.ai?.prompt) {
139-
this.dataController.beginCustomLoading();
140-
}
141127

142-
this.aiColumnIntegrationController.sendRequest(columnName, useCache, callbacks);
128+
this.aiColumnIntegrationController.sendRequestCore({
129+
columnName,
130+
useCache,
131+
needToShowLoadPanel,
132+
callbacks,
133+
});
143134
}
144135

145136
public sendAIColumnRequest(
@@ -154,8 +145,13 @@ export class AIColumnController extends Controller {
154145
this.sendRequest(columnName, false);
155146
}
156147

157-
private getRequestCallbacks(): RequestCallbacks<GenerateGridColumnCommandResult> {
148+
private getRequestCallbacks(): InternalRequestCallbacks {
158149
return {
150+
onRequestSending: (needToShowLoadPanel: boolean): void => {
151+
if (needToShowLoadPanel) {
152+
this.dataController.beginCustomLoading();
153+
}
154+
},
159155
onComplete: (data): void => {
160156
this.dataController.endCustomLoading();
161157
this.aiRequestCompleted.fire(data);
@@ -169,7 +165,7 @@ export class AIColumnController extends Controller {
169165
}
170166

171167
public clearAIColumn(columnName: string): void {
172-
this.aiColumnIntegrationController.abortRequest(columnName);
168+
this.abortAIColumnRequest(columnName);
173169
this.aiColumnIntegrationController.clearAIColumn(columnName);
174170
this.columnsController.columnOption(columnName, 'ai.prompt', '');
175171
this.updateAICells();
@@ -188,6 +184,10 @@ export class AIColumnController extends Controller {
188184

189185
if (isPromptOptionName && column.name) {
190186
this.aiColumnIntegrationController.clearAIColumn(column.name);
187+
188+
if (!column.ai?.prompt) {
189+
this.updateAICells();
190+
}
191191
}
192192
}
193193

packages/devextreme/js/__internal/grids/grid_core/ai_column/m_ai_column_integration_controller.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { DataController } from '../data_controller/m_data_controller';
1010
import type { ErrorHandlingController } from '../error_handling/m_error_handling';
1111
import { Controller } from '../m_modules';
1212
import { AIColumnCacheController } from './m_ai_column_cache_controller';
13+
import type { InternalRequestCallbacks } from './types';
1314
import { getDataFromRowItems, reduceDataCachedKeys } from './utils';
1415

1516
export class AIColumnIntegrationController extends Controller {
@@ -35,11 +36,17 @@ export class AIColumnIntegrationController extends Controller {
3536
this.createAction('onAIColumnResponseReceived');
3637
}
3738

38-
public sendRequest(
39-
columnName: string,
40-
useCache: boolean,
41-
callbacks?: RequestCallbacks<GenerateGridColumnCommandResult>,
42-
): void {
39+
public sendRequestCore({
40+
columnName,
41+
useCache,
42+
needToShowLoadPanel,
43+
callbacks,
44+
}: {
45+
columnName: string;
46+
useCache: boolean;
47+
needToShowLoadPanel: boolean;
48+
callbacks: InternalRequestCallbacks;
49+
}): void {
4350
const aiIntegration = this.getAIIntegration(columnName);
4451
if (!aiIntegration) {
4552
return;
@@ -86,6 +93,8 @@ export class AIColumnIntegrationController extends Controller {
8693
return;
8794
}
8895

96+
callbacks.onRequestSending(needToShowLoadPanel);
97+
8998
const abort = aiIntegration.generateGridColumn(
9099
{
91100
text: prompt,

packages/devextreme/js/__internal/grids/grid_core/ai_column/m_ai_column_view.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const columnHeadersViewExtender = (
6767
showArrowIcon: false,
6868
icon: 'overflow',
6969
stylingMode: 'text',
70+
useItemTextAsTitle: false,
7071
items: this.getDropDownButtonItems(column),
7172
onItemClick: (e: ItemClickEvent): void => {
7273
const { key: actionName } = e.itemData;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type {
2+
GenerateGridColumnCommandResult,
3+
RequestCallbacks,
4+
} from '@js/common/ai-integration';
5+
6+
export type InternalRequestCallbacks = RequestCallbacks<GenerateGridColumnCommandResult> & {
7+
onRequestSending: (needToShowLoadPanel: boolean) => void;
8+
};

packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,13 @@ export const mergeColumns = (that: ColumnsController, columns, commandColumns, n
883883
{ fixed: isColumnFixing },
884884
commandColumns[commandColumnIndex],
885885
column,
886-
{ calculateCellValue: commandColumns[commandColumnIndex].calculateCellValue },
886+
{
887+
calculateCellValue: commandColumns[commandColumnIndex].calculateCellValue,
888+
cssClass: [
889+
commandColumns[commandColumnIndex].cssClass ?? '',
890+
column.cssClass ?? '',
891+
].join(' ').trim(),
892+
},
887893
);
888894
if (column.type !== GROUP_COMMAND_COLUMN_NAME) {
889895
defaultCommandColumns = defaultCommandColumns.filter(callbackFilter);

0 commit comments

Comments
 (0)