diff --git a/frontend/src/components/DataGrid.ddl.test.tsx b/frontend/src/components/DataGrid.ddl.test.tsx index 3bb9a496..b2493968 100644 --- a/frontend/src/components/DataGrid.ddl.test.tsx +++ b/frontend/src/components/DataGrid.ddl.test.tsx @@ -239,6 +239,7 @@ vi.mock('@ant-design/icons', () => { DatabaseOutlined: Icon, NodeIndexOutlined: Icon, ThunderboltOutlined: Icon, + FormatPainterOutlined: Icon, }; }); @@ -2170,6 +2171,61 @@ describe('DataGrid DDL interactions', () => { renderer!.unmount(); }); + it('formats a writable JSON cell from the toolbar before saving the draft', async () => { + storeState.appearance.uiVersion = 'v2'; + const compactJson = '{"billType":"YDApp","data":{"items":[{"count":100}]}}'; + const formattedJson = JSON.stringify(JSON.parse(compactJson), null, 2); + const rows = [{ __gonavi_row_key__: 'row-1', id: 1, payload: compactJson }]; + + let renderer: ReactTestRenderer; + await act(async () => { + renderer = create( + , + ); + }); + await waitForEffects(); + + const doubleClickSurface = renderer!.root.findAll( + (node) => typeof node.props.onDoubleClickCapture === 'function', + )[0]; + await act(async () => { + doubleClickSurface.props.onDoubleClickCapture({ + target: createRenderedCellTarget('row-1', 'payload'), + preventDefault: vi.fn(), + stopPropagation: vi.fn(), + }); + }); + + const editorTitle = t('data_grid.cell_editor.title_with_column', { column: 'payload' }); + const editor = renderer!.root.findByProps({ 'data-modal-title': editorTitle }); + const toolbar = editor.findByProps({ 'data-grid-cell-editor-toolbar': 'true' }); + const formatButton = toolbar.findByProps({ 'data-grid-cell-editor-format': 'true' }); + expect(formatButton.props.disabled).not.toBe(true); + expect(textContent(editor.findByProps({ 'data-monaco-editor': 'true' }))).toBe(compactJson); + + await act(async () => { + formatButton.props.onClick(); + }); + + expect(textContent(editor.findByProps({ 'data-monaco-editor': 'true' }))).toBe(formattedJson); + expect(testRenderState.latestTableProps.dataSource[0].payload).toBe(compactJson); + + await act(async () => { + findButton(renderer!, t('common.save')).props.onClick(); + }); + + expect(testRenderState.latestTableProps.dataSource[0].payload).toBe(formattedJson); + renderer!.unmount(); + }); + it('preserves spaced and empty quoted column aliases when resolving a read-only cell', async () => { storeState.appearance.uiVersion = 'v2'; const columnNames = [' payload ', '']; diff --git a/frontend/src/components/DataGridModals.tsx b/frontend/src/components/DataGridModals.tsx index cd837b5e..be4d1653 100644 --- a/frontend/src/components/DataGridModals.tsx +++ b/frontend/src/components/DataGridModals.tsx @@ -1,8 +1,8 @@ import Modal from './common/ResizableDraggableModal'; import React from 'react'; -import { Button, Checkbox, DatePicker, Form, Input, TimePicker } from 'antd'; +import { Button, Checkbox, DatePicker, Form, Input, TimePicker, Tooltip } from 'antd'; import dayjs from 'dayjs'; -import { CopyOutlined } from '@ant-design/icons'; +import { CopyOutlined, FormatPainterOutlined } from '@ant-design/icons'; import Editor from './MonacoEditor'; import { TEMPORAL_FORMATS, @@ -208,13 +208,38 @@ const DataGridModals: React.FC = ({ , ] : [ - , , , ]} > -
- {cellEditorMeta ? `${tableName || ''}${tableName ? '.' : ''}${cellEditorMeta.dataIndex}` : ''} +
+ + {cellEditorMeta ? `${tableName || ''}${tableName ? '.' : ''}${cellEditorMeta.dataIndex}` : ''} + + + {!cellEditorReadOnly && cellEditorIsJson && ( + + + + )}
{cellEditorOpen && (