#15561: supported copy button in query editor (#15731)

* supported copy button in query editor

* fix sonar issue
This commit is contained in:
Ashish Gupta 2024-03-28 11:43:32 +05:30 committed by GitHub
parent 890820ed92
commit af47b856e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 151 additions and 41 deletions

View File

@ -326,7 +326,7 @@ const DataModelDetails = ({
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
readOnly: 'nocursor',
readOnly: true,
}}
value={dataModelData?.sql}
/>

View File

@ -99,9 +99,7 @@ const ParameterForm: React.FC<ParameterFormProps> = ({ definition, table }) => {
<SchemaEditor
className="custom-query-editor query-editor-h-200"
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
showCopyButton={false}
/>
);
} else {

View File

@ -161,9 +161,7 @@ const CustomMetricForm = ({
<SchemaEditor
className="custom-query-editor query-editor-h-200 custom-code-mirror-theme"
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
showCopyButton={false}
/>
</Form.Item>
</Form>

View File

@ -526,9 +526,6 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
className="custom-query-editor query-editor-h-200 custom-code-mirror-theme"
data-testid="profiler-setting-sql-editor"
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
value={state?.sqlQuery ?? ''}
onChange={handleCodeMirrorChange}
/>

View File

@ -0,0 +1,32 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CSMode } from '../../../enums/codemirror.enum';
type Mode = {
name: CSMode;
json?: boolean;
};
export interface SchemaEditorProps {
value?: string;
className?: string;
mode?: Mode;
readOnly?: boolean;
options?: {
[key: string]: string | boolean | Array<string>;
};
editorClass?: string;
showCopyButton?: boolean;
onChange?: (value: string) => void;
}

View File

@ -11,28 +11,88 @@
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import SchemaEditor from './SchemaEditor';
const mockOnChange = jest.fn();
const mockOnCopyToClipBoard = jest.fn();
jest.mock('../../../constants/constants', () => ({
JSON_TAB_SIZE: 25,
}));
jest.mock('../../../utils/SchemaEditor.utils', () => ({
getSchemaEditorValue: jest.fn().mockReturnValue('test SQL query'),
}));
jest.mock('../../../hooks/useClipBoard', () => ({
...jest.requireActual('../../../hooks/useClipBoard'),
useClipboard: jest
.fn()
.mockImplementation(() => ({ onCopyToClipBoard: mockOnCopyToClipBoard })),
}));
jest.mock('react-codemirror2', () => ({
...jest.requireActual('react-codemirror2'),
Controlled: jest.fn().mockImplementation(({ value, onChange }) => (
<div>
<span>{value}</span>
<input
data-testid="code-mirror-editor-input"
type="text"
onChange={onChange}
/>
</div>
)),
}));
const mockProps = {
value: 'test SQL query',
showCopyButton: true,
onChange: mockOnChange,
};
describe('SchemaEditor component test', () => {
it('Component should render properly', async () => {
render(<SchemaEditor value="" />);
render(<SchemaEditor {...mockProps} />);
expect(
await screen.findByTestId('code-mirror-container')
).toBeInTheDocument();
expect(await screen.findByTestId('query-copy-button')).toBeInTheDocument();
});
it('Value provided via props should be visible', async () => {
render(<SchemaEditor value="test SQL query" />);
render(<SchemaEditor {...mockProps} />);
expect(
(await screen.findByTestId('code-mirror-container')).textContent
).toBe('test SQL query');
});
it('Copy button should not be visible', async () => {
render(<SchemaEditor {...mockProps} showCopyButton={false} />);
expect(screen.queryByTestId('query-copy-button')).not.toBeInTheDocument();
});
it('Should call onCopyToClipBoard', async () => {
render(<SchemaEditor {...mockProps} />);
fireEvent.click(screen.getByTestId('query-copy-button'));
expect(mockOnCopyToClipBoard).toHaveBeenCalled();
});
it('Should call onChange handler', async () => {
render(<SchemaEditor {...mockProps} />);
fireEvent.change(screen.getByTestId('code-mirror-editor-input'), {
target: { value: 'new SQL query' },
});
expect(mockOnChange).toHaveBeenCalled();
});
});

View File

@ -11,6 +11,8 @@
* limitations under the License.
*/
import { Button, Tooltip } from 'antd';
import classNames from 'classnames';
import { Editor, EditorChange } from 'codemirror';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/matchbrackets.js';
@ -24,14 +26,14 @@ import 'codemirror/mode/sql/sql';
import { isUndefined } from 'lodash';
import React, { useEffect, useState } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import { useTranslation } from 'react-i18next';
import { ReactComponent as CopyIcon } from '../../../assets/svg/icon-copy.svg';
import { JSON_TAB_SIZE } from '../../../constants/constants';
import { CSMode } from '../../../enums/codemirror.enum';
import { useClipboard } from '../../../hooks/useClipBoard';
import { getSchemaEditorValue } from '../../../utils/SchemaEditor.utils';
type Mode = {
name: CSMode;
json?: boolean;
};
import './schema-editor.less';
import { SchemaEditorProps } from './SchemaEditor.interface';
const SchemaEditor = ({
value = '',
@ -42,18 +44,10 @@ const SchemaEditor = ({
},
options,
editorClass,
showCopyButton = true,
onChange,
}: {
value?: string;
className?: string;
mode?: Mode;
readOnly?: boolean;
options?: {
[key: string]: string | boolean | Array<string>;
};
editorClass?: string;
onChange?: (value: string) => void;
}) => {
}: SchemaEditorProps) => {
const { t } = useTranslation();
const defaultOptions = {
tabSize: JSON_TAB_SIZE,
indentUnit: JSON_TAB_SIZE,
@ -72,6 +66,8 @@ const SchemaEditor = ({
const [internalValue, setInternalValue] = useState<string>(
getSchemaEditorValue(value)
);
const { onCopyToClipBoard } = useClipboard(internalValue);
const handleEditorInputBeforeChange = (
_editor: Editor,
_data: EditorChange,
@ -94,7 +90,22 @@ const SchemaEditor = ({
}, [value]);
return (
<div className={className} data-testid="code-mirror-container">
<div
className={classNames('relative', className)}
data-testid="code-mirror-container">
{showCopyButton && (
<div className="query-editor-button">
<Tooltip title={t('message.copy-to-clipboard')}>
<Button
className="flex-center bg-white"
data-testid="query-copy-button"
icon={<CopyIcon height={16} width={16} />}
onClick={onCopyToClipBoard}
/>
</Tooltip>
</div>
)}
<CodeMirror
className={editorClass}
options={defaultOptions}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.query-editor-button {
position: absolute;
z-index: 10;
top: 14px;
right: 14px;
}

View File

@ -245,6 +245,7 @@ const QueryCard: FC<QueryCardProp> = ({
styleActiveLine: isEditMode,
readOnly: isEditMode ? false : 'nocursor',
}}
showCopyButton={false}
value={query.query ?? ''}
onChange={handleQueryChange}
/>

View File

@ -84,7 +84,7 @@ const StoredProcedureSummary = ({
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
readOnly: 'nocursor',
readOnly: true,
}}
value={
(

View File

@ -100,9 +100,7 @@ export const ModalWithQueryEditor = ({
<SchemaEditor
className="custom-query-editor query-editor-h-200 custom-code-mirror-theme"
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
showCopyButton={false}
/>
</Form.Item>
</Form>

View File

@ -530,9 +530,7 @@ export const PropertyValue: FC<PropertyValueProps> = ({
<SchemaEditor
className="custom-query-editor query-editor-h-200 custom-code-mirror-theme"
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
showCopyButton={false}
/>
</Form.Item>
</Form>

View File

@ -227,9 +227,7 @@ const AddQueryPage = () => {
<SchemaEditor
className="custom-query-editor query-editor-h-200 custom-code-mirror-theme"
mode={{ name: CSMode.SQL }}
options={{
readOnly: false,
}}
showCopyButton={false}
/>
</Form.Item>
<Form.Item

View File

@ -565,7 +565,7 @@ const StoredProcedurePage = () => {
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
readOnly: 'nocursor',
readOnly: true,
}}
value={code}
/>