mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-22 07:58:06 +00:00
Fix #3523 Usernames in queries tab have clickable action but doesn't redirect them to user profile page (#3556)
This commit is contained in:
parent
920491e355
commit
c2f71a497c
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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;
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user