Fix #3523 Usernames in queries tab have clickable action but doesn't redirect them to user profile page (#3556)

This commit is contained in:
Sachin Chaurasiya 2022-03-21 22:41:42 +05:30 committed by GitHub
parent 920491e355
commit c2f71a497c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 343 additions and 101 deletions

View File

@ -0,0 +1,50 @@
/*
* Copyright 2021 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 { findByTestId, findByText, render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import QueryCard from './QueryCard';
const mockQueryData = {
query: 'select products from raw_product_catalog',
duration: 0.309,
user: {
id: 'd4785e53-bbdb-4dbd-b368-009fdb50c2c6',
type: 'user',
name: 'aaron_johnson0',
displayName: 'Aaron Johnson',
href: 'http://localhost:8585/api/v1/users/d4785e53-bbdb-4dbd-b368-009fdb50c2c6',
},
vote: 1,
checksum: '0232b0368458aadb29230ccc531462c9',
};
jest.mock('../schema-editor/SchemaEditor', () => {
return jest.fn().mockReturnValue(<p>SchemaEditor</p>);
});
describe('Test QueryCard Component', () => {
it('Check if QueryCard has all child elements', async () => {
const { container } = render(<QueryCard query={mockQueryData} />, {
wrapper: MemoryRouter,
});
const queryHeader = await findByTestId(container, 'query-header');
const query = await findByText(container, /SchemaEditor/i);
const copyQueryButton = await findByTestId(container, 'copy-query');
expect(queryHeader).toBeInTheDocument();
expect(query).toBeInTheDocument();
expect(copyQueryButton).toBeInTheDocument();
});
});

View File

@ -0,0 +1,122 @@
/*
* Copyright 2021 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 classNames from 'classnames';
import React, { FC, HTMLAttributes, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Link } from 'react-router-dom';
import { getUserPath } from '../../constants/constants';
import { CSMode } from '../../enums/codemirror.enum';
import { SQLQuery } from '../../generated/entity/data/table';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { Button } from '../buttons/Button/Button';
import SchemaEditor from '../schema-editor/SchemaEditor';
interface QueryCardProp extends HTMLAttributes<HTMLDivElement> {
query: SQLQuery;
}
const QueryCard: FC<QueryCardProp> = ({ className, query }) => {
const [expanded, setExpanded] = useState<boolean>(false);
const [, setIsCopied] = useState<boolean>(false);
const [showCopiedText, setShowCopiedText] = useState<boolean>(false);
const copiedTextHandler = () => {
setShowCopiedText(true);
setTimeout(() => {
setShowCopiedText(false);
}, 1000);
};
return (
<div className={classNames('tw-bg-white tw-py-3 tw-mb-3', className)}>
<div
className="tw-cursor-pointer"
onClick={() => setExpanded((pre) => !pre)}>
<div
className="tw-flex tw-py-1 tw-justify-between"
data-testid="query-header">
<p>
Last run by{' '}
<Link
className="button-comp"
to={getUserPath(query.user?.name as string)}>
<button className="tw-font-medium tw-text-grey-body ">
{query.user?.displayName ?? query.user?.name}
</button>{' '}
</Link>
and took{' '}
<span className="tw-font-medium">{query.duration} seconds</span>
</p>
<button>
{expanded ? (
<SVGIcons
alt="copy"
className="tw-mr-4"
icon={Icons.ICON_UP}
width="16px"
/>
) : (
<SVGIcons
alt="copy"
className="tw-mr-4"
icon={Icons.ICON_DOWN}
width="16px"
/>
)}
</button>
</div>
</div>
<div className="tw-border tw-border-main tw-rounded-md tw-p-px">
<div
className={classNames('tw-overflow-hidden tw-relative', {
'tw-max-h-10': !expanded,
})}>
<CopyToClipboard
text={query.query ?? ''}
onCopy={(_text, result) => {
setIsCopied(result);
if (result) copiedTextHandler();
}}>
<Button
className="tw-h-8 tw-ml-4 tw-absolute tw-right-4 tw-z-9999 tw--mt-px"
data-testid="copy-query"
size="custom"
theme="default"
title="Copy"
variant="text">
{showCopiedText ? (
<span
className="tw-mr-1 tw-text-success tw-bg-success-lite tw-px-1 tw-rounded-md"
data-testid="copy-success">
Copied to the clipboard
</span>
) : null}
<SVGIcons alt="copy" icon={Icons.COPY} width="16px" />
</Button>
</CopyToClipboard>
<SchemaEditor
editorClass={classNames('table-query-editor')}
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
}}
value={query.query ?? ''}
/>
</div>
</div>
</div>
);
};
export default QueryCard;

View File

@ -0,0 +1,162 @@
/*
* Copyright 2021 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 {
findAllByText,
findByTestId,
queryAllByText,
render,
} from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import TableQueries from './TableQueries';
const mockQueriesData = [
{
query: 'select products from raw_product_catalog',
duration: 0.309,
user: {
id: 'd4785e53-bbdb-4dbd-b368-009fdb50c2c6',
type: 'user',
name: 'aaron_johnson0',
displayName: 'Aaron Johnson',
href: 'http://localhost:8585/api/v1/users/d4785e53-bbdb-4dbd-b368-009fdb50c2c6',
},
vote: 1,
checksum: '0232b0368458aadb29230ccc531462c9',
},
{
query: 'select platform from raw_product_catalog',
duration: 0.278,
user: {
id: 'e3a25263-2998-4c2c-9c94-ac278dbf5423',
type: 'user',
name: 'adam_matthews2',
displayName: 'Adam Matthews',
href: 'http://localhost:8585/api/v1/users/e3a25263-2998-4c2c-9c94-ac278dbf5423',
},
vote: 1,
checksum: 'c54eb291adfb4a26a9816c83ba025711',
},
{
query: 'select store_address from raw_product_catalog',
duration: 0.333,
user: {
id: 'c61ce751-b11d-43c1-917a-5a2530d5ba3f',
type: 'user',
name: 'aaron_warren5',
displayName: 'Aaron Warren',
href: 'http://localhost:8585/api/v1/users/c61ce751-b11d-43c1-917a-5a2530d5ba3f',
},
vote: 1,
checksum: 'e4aa67aec0fc9727b45d2d4e0fbaeef6',
},
{
query: 'select last_order_date from raw_product_catalog',
duration: 0.381,
user: {
id: 'c61ce751-b11d-43c1-917a-5a2530d5ba3f',
type: 'user',
name: 'aaron_warren5',
displayName: 'Aaron Warren',
href: 'http://localhost:8585/api/v1/users/c61ce751-b11d-43c1-917a-5a2530d5ba3f',
},
vote: 1,
checksum: 'de2d10bb7f14cb3f679984dbfa986af2',
},
{
query: 'select first_order_date from raw_product_catalog',
duration: 0.125,
user: {
id: 'e3a25263-2998-4c2c-9c94-ac278dbf5423',
type: 'user',
name: 'adam_matthews2',
displayName: 'Adam Matthews',
href: 'http://localhost:8585/api/v1/users/e3a25263-2998-4c2c-9c94-ac278dbf5423',
},
vote: 1,
checksum: '74faee70cbae02b5b412e8258a3aa1a4',
},
{
query: 'select comments from raw_product_catalog',
duration: 0.845,
user: {
id: '66d521c8-837b-49fe-bfc1-1178bbae2e83',
type: 'user',
name: 'aaron_singh2',
displayName: 'Aaron Singh',
href: 'http://localhost:8585/api/v1/users/66d521c8-837b-49fe-bfc1-1178bbae2e83',
},
vote: 1,
checksum: '741f958a2762170708f29f96d1639601',
},
];
const mockTableQueriesProp = {
queries: mockQueriesData,
};
jest.mock('./ QueryCard', () => {
return jest.fn().mockReturnValue(<p>QueryCard</p>);
});
describe('Test TableQueries Component', () => {
it('Check if TableQueries component has all child elements', async () => {
const { container } = render(<TableQueries {...mockTableQueriesProp} />, {
wrapper: MemoryRouter,
});
const queriesContainer = await findByTestId(container, 'queries-container');
expect(queriesContainer).toBeInTheDocument();
});
it('Check if TableQueries component has n query card', async () => {
const queriesLength = mockQueriesData.length;
const { container } = render(<TableQueries {...mockTableQueriesProp} />, {
wrapper: MemoryRouter,
});
const queriesContainer = await findByTestId(container, 'queries-container');
const queryCards = await findAllByText(queriesContainer, /QueryCard/i);
expect(queriesContainer).toBeInTheDocument();
expect(queryCards).toHaveLength(queriesLength);
});
it('Check if TableQueries component has queries as undefined', async () => {
const { container } = render(
<TableQueries {...mockTableQueriesProp} queries={undefined} />,
{
wrapper: MemoryRouter,
}
);
const queryCards = queryAllByText(container, /QueryCard/i);
const noQueries = await findByTestId(container, 'no-queries');
expect(queryCards).toHaveLength(0);
expect(noQueries).toBeInTheDocument();
});
it('Check if TableQueries component has queries as empty list', async () => {
const { container } = render(
<TableQueries {...mockTableQueriesProp} queries={[]} />,
{
wrapper: MemoryRouter,
}
);
const queryCards = queryAllByText(container, /QueryCard/i);
const noQueries = await findByTestId(container, 'no-queries');
expect(queryCards).toHaveLength(0);
expect(noQueries).toBeInTheDocument();
});
});

View File

@ -11,118 +11,26 @@
* limitations under the License. * limitations under the License.
*/ */
import classNames from 'classnames'; import { isUndefined } from 'lodash';
import React, { FC, HTMLAttributes, useState } from 'react'; import React, { FC, HTMLAttributes } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard'; import { Table } from '../../generated/entity/data/table';
import { CSMode } from '../../enums/codemirror.enum';
import { SQLQuery, Table } from '../../generated/entity/data/table';
import { withLoader } from '../../hoc/withLoader'; import { withLoader } from '../../hoc/withLoader';
import SVGIcons, { Icons } from '../../utils/SvgUtils'; import QueryCard from './QueryCard';
import { Button } from '../buttons/Button/Button';
import SchemaEditor from '../schema-editor/SchemaEditor';
interface TableQueriesProp extends HTMLAttributes<HTMLDivElement> { interface TableQueriesProp extends HTMLAttributes<HTMLDivElement> {
queries: Table['tableQueries']; queries: Table['tableQueries'];
} }
interface QueryCardProp extends HTMLAttributes<HTMLDivElement> {
query: SQLQuery;
}
const QueryCard: FC<QueryCardProp> = ({ className, query }) => {
const [expanded, setExpanded] = useState<boolean>(false);
const [, setIsCopied] = useState<boolean>(false);
const [showCopiedText, setShowCopiedText] = useState<boolean>(false);
const copiedTextHandler = () => {
setShowCopiedText(true);
setTimeout(() => {
setShowCopiedText(false);
}, 1000);
};
return (
<div className={classNames('tw-bg-white tw-py-3 tw-mb-3', className)}>
<div
className="tw-cursor-pointer"
onClick={() => setExpanded((pre) => !pre)}>
<div className="tw-flex tw-py-1 tw-justify-between">
<p>
Last run by{' '}
<span className="tw-font-medium">
{query.user?.displayName ?? query.user?.name}
</span>{' '}
and took{' '}
<span className="tw-font-medium">{query.duration} seconds</span>
</p>
<button>
{expanded ? (
<SVGIcons
alt="copy"
className="tw-mr-4"
icon={Icons.ICON_UP}
width="16px"
/>
) : (
<SVGIcons
alt="copy"
className="tw-mr-4"
icon={Icons.ICON_DOWN}
width="16px"
/>
)}
</button>
</div>
</div>
<div className="tw-border tw-border-main tw-rounded-md tw-p-px">
<div
className={classNames('tw-overflow-hidden tw-relative', {
'tw-max-h-10': !expanded,
})}>
<CopyToClipboard
text={query.query ?? ''}
onCopy={(_text, result) => {
setIsCopied(result);
if (result) copiedTextHandler();
}}>
<Button
className="tw-h-8 tw-ml-4 tw-absolute tw-right-4 tw-z-9999 tw--mt-px"
data-testid="copy-query"
size="custom"
theme="default"
title="Copy"
variant="text">
{showCopiedText ? (
<span className="tw-mr-1 tw-text-success tw-bg-success-lite tw-px-1 tw-rounded-md">
Copied to the clipboard
</span>
) : null}
<SVGIcons alt="copy" icon={Icons.COPY} width="16px" />
</Button>
</CopyToClipboard>
<SchemaEditor
editorClass={classNames('table-query-editor')}
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
}}
value={query.query ?? ''}
/>
</div>
</div>
</div>
);
};
const TableQueries: FC<TableQueriesProp> = ({ queries, className }) => { const TableQueries: FC<TableQueriesProp> = ({ queries, className }) => {
return ( return (
<div className={className}> <div className={className}>
<div className="tw-my-6"> <div className="tw-my-6" data-testid="queries-container">
{queries ? ( {!isUndefined(queries) && queries.length > 0 ? (
queries.map((query, index) => <QueryCard key={index} query={query} />) queries.map((query, index) => <QueryCard key={index} query={query} />)
) : ( ) : (
<div className="tw-mt-4 tw-ml-4 tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8"> <div
className="tw-mt-4 tw-ml-4 tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8"
data-testid="no-queries">
<span>No queries data available.</span> <span>No queries data available.</span>
</div> </div>
)} )}