Skip to content

Commit

Permalink
Merge pull request #267 from actiontech/bugfix/issue-1499
Browse files Browse the repository at this point in the history
[fix]: support hight light sql and copy
  • Loading branch information
Rain-1214 authored May 17, 2023
2 parents 5b710be + a0057ec commit d232639
Show file tree
Hide file tree
Showing 18 changed files with 3,787 additions and 1,025 deletions.
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# <small>sqle-ui [依赖升级](https://github.com/actiontech/sqle-ui/pull/257)后的变更</small>

* [fix]: 修改 i18n 插值配置 <https://github.com/actiontech/sqle-ui/pull/258>
* [fix]: 修复同步数据源页面路由错误的问题 <https://github.com/actiontech/sqle-ui/pull/259>
* [fix]: 修复订阅审核失败消息表单数据错误的问题 <https://github.com/actiontech/sqle-ui/pull/260>
* [feature]: 添加数据源时根据数据源类型自动切换默认端口 <https://github.com/actiontech/sqle-ui/pull/263>
* [test]: 完成遗留的单元测试问题 <https://github.com/actiontech/sqle-ui/pull/264>
* [feature]: API服务里,工单执行完成后,可以提供一个回调地址去通知调用API的服务 <https://github.com/actiontech/sqle-ui/pull/266>
* [x] [fix]: 修改 i18n 插值配置 <https://github.com/actiontech/sqle-ui/pull/258>
* [] [fix]: 修复同步数据源页面路由错误的问题 <https://github.com/actiontech/sqle-ui/pull/259>
* [] [fix]: 修复订阅审核失败消息表单数据错误的问题 <https://github.com/actiontech/sqle-ui/pull/260>
* [] [feature]: 添加数据源时根据数据源类型自动切换默认端口 <https://github.com/actiontech/sqle-ui/pull/263>
* [] [test]: 完成遗留的单元测试问题 <https://github.com/actiontech/sqle-ui/pull/264>
* [] [feature]: API服务里,工单执行完成后,可以提供一个回调地址去通知调用API的服务 <https://github.com/actiontech/sqle-ui/pull/266>
* [] [fix]: 修复 sql 审核结果列表中的 执行语句未高亮的问题 <https://github.com/actiontech/sqle-ui/pull/267>
77 changes: 77 additions & 0 deletions src/components/CopyIcon/CopyIcon.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { fireEvent, render, screen } from '@testing-library/react';
import CopyIcon from './index';
import { getAllBySelector } from '../../testUtils/customQuery';
import Copy from '../../utils/Copy';
import { act } from 'react-dom/test-utils';

describe('test CopyIcon', () => {
let copySpy: jest.SpyInstance;
beforeEach(() => {
document.execCommand = jest.fn();
jest.spyOn(document, 'execCommand');
copySpy = jest.spyOn(Copy, 'copyText');
jest.useFakeTimers();
});
afterEach(() => {
jest.clearAllMocks();
jest.clearAllTimers();
jest.useRealTimers();
});
test('should match snapshot', () => {
const { baseElement } = render(<CopyIcon />);
expect(baseElement).toMatchSnapshot();
});

test('the first parameter of onCopy is the click event', async () => {
function onCopy(e?: React.MouseEvent<HTMLDivElement>) {
expect(e).not.toBeUndefined();
expect(copySpy).toBeCalledTimes(1);
expect(copySpy).nthCalledWith(1, 'text');
}

render(<CopyIcon onCopy={onCopy} text="text" />);
fireEvent.click(getAllBySelector('.actiontech-copy')[0]);

await act(() => {
jest.advanceTimersByTime(0);
});
expect(getAllBySelector('.actiontech-copy-success')[0]).not.toBeUndefined();

fireEvent.mouseEnter(getAllBySelector('.actiontech-copy-success')[0]);

await screen.findByText('common.copied');

await act(() => {
jest.advanceTimersByTime(3000);
});

expect(getAllBySelector('.actiontech-copy')[0]).not.toBeUndefined();
});

test('render custom title', async () => {
const { rerender } = render(<CopyIcon tooltips={false} />);

fireEvent.click(getAllBySelector('.actiontech-copy')[0]);
expect(copySpy).nthCalledWith(1, '');

await act(() => {
jest.advanceTimersByTime(0);
});
fireEvent.mouseEnter(getAllBySelector('.actiontech-copy-success')[0]);

expect(screen.queryByText('common.copied')).not.toBeInTheDocument();

rerender(<CopyIcon tooltips="custom title">text</CopyIcon>);

expect(screen.queryByText('custom title')).not.toBeInTheDocument();

fireEvent.click(getAllBySelector('.actiontech-copy')[0]);
expect(copySpy).nthCalledWith(2, 'text');
await act(() => {
jest.advanceTimersByTime(0);
});
fireEvent.mouseEnter(getAllBySelector('.actiontech-copy-success')[0]);

await screen.findByText('custom title');
});
});
64 changes: 64 additions & 0 deletions src/components/CopyIcon/CopyIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useTranslation } from 'react-i18next';
import React from 'react';
import Copy from '../../utils/Copy';
import { Tooltip } from 'antd';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { CopyIconProps } from '.';
import './index.less';

const CopyIcon: React.FC<CopyIconProps> = ({
text,
children,
tooltips = true,
onCopy,
}) => {
const { t } = useTranslation();
const [copied, setCopied] = React.useState(false);
const copyIdRef = React.useRef<number>();

const cleanCopyId = () => {
window.clearTimeout(copyIdRef.current!);
};

React.useEffect(() => cleanCopyId, []);

const onCopyClick = (e?: React.MouseEvent<HTMLDivElement>) => {
e?.preventDefault();
e?.stopPropagation();

Copy.copyText(text || (children && String(children)) || '');

setCopied(true);

cleanCopyId();
copyIdRef.current = window.setTimeout(() => {
setCopied(false);
}, 3000);

onCopy?.(e);
};

const tooltipsTitle = React.useMemo(() => {
if (!tooltips) {
return '';
}
if (tooltips === true) {
return copied ? t('common.copied') : '';
}

return copied ? tooltips : '';
}, [copied, t, tooltips]);

return (
<Tooltip key="copy" title={tooltipsTitle}>
<div
onClick={onCopyClick}
className={`actiontech-copy ${copied ? `actiontech-copy-success` : ''}`}
>
{copied ? <CheckOutlined /> : <CopyOutlined />}
</div>
</Tooltip>
);
};

export default CopyIcon;
31 changes: 31 additions & 0 deletions src/components/CopyIcon/__snapshots__/CopyIcon.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`test CopyIcon should match snapshot 1`] = `
<body>
<div>
<div
class="actiontech-copy "
>
<span
aria-label="copy"
class="anticon anticon-copy"
role="img"
>
<svg
aria-hidden="true"
data-icon="copy"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"
/>
</svg>
</span>
</div>
</div>
</body>
`;
13 changes: 13 additions & 0 deletions src/components/CopyIcon/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import '../../styles/_variable.less';

.actiontech-copy {
outline: none;
cursor: pointer;
transition: color 0.3s;
margin-left: 4px;
color: @bg-blue;
}

.actiontech-copy-success {
color: @text-green;
}
9 changes: 9 additions & 0 deletions src/components/CopyIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface CopyIconProps {
text?: string;
onCopy?: (event?: React.MouseEvent<HTMLDivElement>) => void;
tooltips?: boolean | React.ReactNode;
format?: 'text/plain' | 'text/html';
children?: React.ReactNode;
}

export { default } from './CopyIcon';
2 changes: 2 additions & 0 deletions src/locale/zh-CN/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export default {
unknownError: '未知错误...',
unknownStatus: '未知状态...',

copied: '复制成功',

true: '是',
false: '否',
ok: '确认',
Expand Down
30 changes: 30 additions & 0 deletions src/page/Order/AuditResult/RenderExecuteSql.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Space, Typography } from 'antd';
import HighlightCode from '../../../utils/HighlightCode';
import { RenderExecuteSqlProps } from './index.type';
import CopyIcon from '../../../components/CopyIcon';

const RenderExecuteSql: React.FC<RenderExecuteSqlProps> = ({ sql }) => {
if (!sql) {
return null;
}

return (
<Space>
<Typography.Paragraph
ellipsis={{
expandable: false,
tooltip: <pre className="pre-warp-break-all">{sql}</pre>,
rows: 10,
}}
className="margin-bottom-0"
>
<span
dangerouslySetInnerHTML={{ __html: HighlightCode.highlightSql(sql) }}
/>
</Typography.Paragraph>
<CopyIcon text={sql} />
</Space>
);
};

export default RenderExecuteSql;
15 changes: 15 additions & 0 deletions src/page/Order/AuditResult/__test__/RenderExecuteSql.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { render } from '@testing-library/react';
import RenderExecuteSql from '../RenderExecuteSql';

describe('test RenderExecuteSql', () => {
test('should match snapshot', () => {
const sql = `
select count(*) from (SELECT DISTINCT t.con_no AS con_no , CASE WHEN tgua.atime IS NOT NULL THEN tgua.atime WHEN tcc.CTIME IS NOT NULL THEN tcc.CTIME WHEN bb.CTIME IS NOT NULL THEN bb.CTIME END AS CTIME, t.PRO_METHOD AS pro_method FROM my_customer t LEFT OUTER JOIN first_ auth tgua ON t.con_no = tgua.con_no AND tgua.stat = 1 AND tgua.type = 'C' and (tgua.atime>= '2017-06-22' and tgua.atime< '2017-06-23') LEFT OUTER JOIN my_ contact tcc ON t.con_no = tcc.con_no AND tcc.NAME IS NOT NULL and (tcc.ctime>= '2017-06-22' and tcc.ctime< '2017-06-23') LEFT OUTER JOIN ( SELECT tcp.con_no AS con_no, tcai.CTIME AS CTIME FROM my_ person tcp INNER JOIN my_auth_info tcai ON tcp.CNO = tcai.CNO and (tcai.ctime>= '2017-06-22' and tcai.ctime< '2017-06-23') ) bb ON t.con_no = bb.con_no WHERE t.PRO_METHOD = 'uc' and (tgua.con_no IS NOT NULL OR tcc.con_no IS NOT NULL OR bb.con_no IS NOT NULL)) pp order by pp.con_nosql2:select count(*) from (SELECT DISTINCT t.con_no AS con_no , CASE WHEN tgua.atime IS NOT NULL THEN tgua.atime WHEN tcc.CTIME IS NOT NULL THEN tcc.CTIME WHEN bb.CTIME IS NOT NULL THEN bb.CTIME END AS CTIME, t.PRO_METHOD AS pro_method FROM my_customer t LEFT OUTER JOIN first_ auth tgua ON t.con_no = tgua.con_no AND tgua.status = 1 AND tgua.type = 'C' LEFT OUTER JOIN my_ contact tcc ON t.con_no = tcc.con_no AND tcc.NAME IS NOT NULL LEFT OUTER JOIN ( SELECT tcp.con_no AS con_no, tcai.CTIME AS CTIME FROM my_ person tcp INNER JOIN my_auth_info tcai ON tcp.CNO = tcai.CNO ) bb ON t.con_no = bb.con_no WHERE t.pro_method = 'uc' and (tgua.con_no IS NOT NULL OR tcc.con_no IS NOT NULL OR bb.con_no IS NOT NULL)) p where CTIME>= '2017-06-22' and CTIME< '2017-06-23' order by con_no `;

const { container, rerender } = render(<RenderExecuteSql />);
expect(container).toMatchSnapshot();

rerender(<RenderExecuteSql sql={sql} />);
expect(container).toMatchSnapshot();
});
});
Loading

0 comments on commit d232639

Please sign in to comment.