mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-22 16:08:13 +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.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { FC, HTMLAttributes, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { CSMode } from '../../enums/codemirror.enum';
|
||||
import { SQLQuery, Table } from '../../generated/entity/data/table';
|
||||
import { isUndefined } from 'lodash';
|
||||
import React, { FC, HTMLAttributes } from 'react';
|
||||
import { Table } from '../../generated/entity/data/table';
|
||||
import { withLoader } from '../../hoc/withLoader';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import SchemaEditor from '../schema-editor/SchemaEditor';
|
||||
import QueryCard from './QueryCard';
|
||||
|
||||
interface TableQueriesProp extends HTMLAttributes<HTMLDivElement> {
|
||||
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 }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="tw-my-6">
|
||||
{queries ? (
|
||||
<div className="tw-my-6" data-testid="queries-container">
|
||||
{!isUndefined(queries) && queries.length > 0 ? (
|
||||
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>
|
||||
</div>
|
||||
)}
|
||||
|
Loading…
x
Reference in New Issue
Block a user