ui: fixed glossary csv preview issue and added remove owner functionality (#9950)

* Fixed: Glossary csv parser issue and other ui issue

* content overflow fix

* fixed failing unit test

* added test case for special character
This commit is contained in:
Shailesh Parmar 2023-01-27 16:37:50 +05:30 committed by GitHub
parent d8674bd7f8
commit b024ada0f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 151 additions and 40 deletions

View file

@ -80,6 +80,7 @@
"react-i18next": "^11.18.6",
"react-lazylog": "^4.5.3",
"react-oidc": "^1.0.3",
"react-papaparse": "^4.1.0",
"react-quill": "^2.0.0",
"react-router-dom": "^5.2.0",
"react-toastify": "^8.2.0",

View file

@ -200,6 +200,14 @@ const GlossaryHeader = ({
}
setListVisible(false);
};
const onRemoveOwner = () => {
const updatedData = {
...selectedData,
owner: undefined,
};
onUpdate(updatedData);
setListVisible(false);
};
const handleReviewerSave = (data: Array<EntityReference>) => {
if (!isEqual(data, selectedData.reviewers)) {
@ -335,6 +343,7 @@ const GlossaryHeader = ({
horzPosRight={false}
isLoading={isUserLoading}
listGroups={['Teams', 'Users']}
removeOwner={onRemoveOwner}
showSearchBar={isCurrentUserAdmin()}
value={selectedData.owner?.id || ''}
onSearchTextChange={handleOwnerSearch}

View file

@ -12,7 +12,7 @@
*/
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Col, Dropdown, Row, Space, Tooltip, Typography } from 'antd';
import { Col, Dropdown, Row, Tooltip, Typography } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
@ -253,16 +253,17 @@ const GlossaryV1 = ({
: []),
{
label: (
<Space
<Row
className="tw-cursor-pointer manage-button"
size={8}
onClick={(e) => {
e.stopPropagation();
setIsDelete(true);
setShowActions(false);
}}>
<SVGIcons alt="Delete" icon={Icons.DELETE} />
<div className="tw-text-left" data-testid="delete-button">
<Col span={3}>
<SVGIcons alt="Delete" icon={Icons.DELETE} />
</Col>
<Col className="tw-text-left" data-testid="delete-button" span={21}>
<p className="tw-font-medium" data-testid="delete-button-title">
Delete
</p>
@ -270,8 +271,8 @@ const GlossaryV1 = ({
Deleting this {isGlossaryActive ? 'Glossary' : 'GlossaryTerm'}{' '}
will permanently remove its metadata from OpenMetadata.
</p>
</div>
</Space>
</Col>
</Row>
),
key: 'delete-button',
},

View file

@ -28,13 +28,9 @@ const mockCsvImportResult = {
numberOfRowsFailed: 0,
importResultsCsv: `status,details,parent,name*,displayName,description*,synonyms,relatedTerms,references,tags\r
success,Entity updated,,Glossary2 Term,Glossary2 Term displayName,Description for Glossary2 Term,,,,\r
success,Entity updated,,Glossary2 term2,Glossary2 term2,Description data.,,,,\r`,
success,Entity updated,,Glossary2 term2,Glossary2 term2 displayname,"Description, data.","ter1,term2",,,\r`,
};
jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () =>
jest.fn().mockReturnValue(<div>RichTextViewer</div>)
);
describe('Import Results', () => {
it('Should render the results', async () => {
render(
@ -70,7 +66,10 @@ describe('Import Results', () => {
firstRow,
'Glossary2 Term displayName'
);
const rowDescription = await findByText(firstRow, 'RichTextViewer');
const rowDescription = await findByText(
firstRow,
'Description for Glossary2 Term'
);
expect(rowStatus).toBeInTheDocument();
expect(rowDetails).toBeInTheDocument();
@ -78,4 +77,30 @@ describe('Import Results', () => {
expect(rowDisplayName).toBeInTheDocument();
expect(rowDescription).toBeInTheDocument();
});
it('Should render the parsed result properly with special character', async () => {
const { container } = render(
<ImportResult csvImportResult={mockCsvImportResult as CSVImportResult} />
);
const tableRows = getAllByRole(container, 'row');
expect(tableRows).toHaveLength(3);
const secondRow = tableRows[2];
const rowStatus = await findByText(secondRow, 'success');
const rowDetails = await findByText(secondRow, 'Entity updated');
const rowName = await findByText(secondRow, 'Glossary2 term2 displayname');
const rowDisplayName = await findByText(secondRow, 'Glossary2 term2');
const rowDescription = await findByText(secondRow, 'Description, data.');
const synonym = await findByText(secondRow, 'ter1,term2');
expect(rowStatus).toBeInTheDocument();
expect(rowDetails).toBeInTheDocument();
expect(rowName).toBeInTheDocument();
expect(rowDisplayName).toBeInTheDocument();
expect(rowDescription).toBeInTheDocument();
expect(synonym).toBeInTheDocument();
});
});

View file

@ -13,11 +13,11 @@
import { Col, Row, Space, Typography } from 'antd';
import Table, { ColumnsType } from 'antd/lib/table';
import classNames from 'classnames';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import { CSVImportResult, Status } from 'generated/type/csvImportResult';
import React, { FC, useMemo } from 'react';
import { isEmpty } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { usePapaParse } from 'react-papaparse';
import { parseCSV } from 'utils/GlossaryUtils';
import { GlossaryCSVRecord } from '../ImportGlossary/ImportGlossary.interface';
@ -26,17 +26,9 @@ interface Props {
}
const ImportResult: FC<Props> = ({ csvImportResult }) => {
const { readString } = usePapaParse();
const { t } = useTranslation();
const parsedRecords: GlossaryCSVRecord[] = useMemo(() => {
const importResult = csvImportResult?.importResultsCsv;
if (importResult) {
return parseCSV(importResult);
} else {
return [];
}
}, [csvImportResult]);
const [parsedRecords, setParsedRecords] = useState<GlossaryCSVRecord[]>([]);
const columns: ColumnsType<GlossaryCSVRecord> = useMemo(
() => [
@ -63,7 +55,11 @@ const ImportResult: FC<Props> = ({ csvImportResult }) => {
dataIndex: 'details',
key: 'details',
render: (details: GlossaryCSVRecord['details']) => {
return <Typography.Text>{details ?? '--'}</Typography.Text>;
return (
<Typography.Text>
{isEmpty(details) ? '--' : details}
</Typography.Text>
);
},
},
{
@ -71,7 +67,13 @@ const ImportResult: FC<Props> = ({ csvImportResult }) => {
dataIndex: 'parent',
key: 'parent',
render: (parent: GlossaryCSVRecord['parent']) => {
return <Typography.Text>{parent ?? '--'}</Typography.Text>;
return (
<Typography.Text
ellipsis={{ tooltip: parent }}
style={{ maxWidth: 100 }}>
{isEmpty(parent) ? '--' : parent}
</Typography.Text>
);
},
},
{
@ -87,15 +89,29 @@ const ImportResult: FC<Props> = ({ csvImportResult }) => {
dataIndex: 'displayName',
key: 'displayName',
render: (displayName: GlossaryCSVRecord['displayName']) => {
return <Typography.Text>{displayName ?? '--'}</Typography.Text>;
return (
<Typography.Text>
{isEmpty(displayName) ? '--' : displayName}
</Typography.Text>
);
},
},
{
title: t('label.description'),
dataIndex: 'description*',
key: 'description',
width: 300,
render: (description: GlossaryCSVRecord['description*']) => {
return <RichTextEditorPreviewer markdown={description ?? '--'} />;
return (
<Typography.Paragraph
ellipsis={{
rows: 2,
}}
style={{ width: 300 }}
title={description}>
{description}
</Typography.Paragraph>
);
},
},
{
@ -103,7 +119,27 @@ const ImportResult: FC<Props> = ({ csvImportResult }) => {
dataIndex: 'synonyms',
key: 'synonyms',
render: (synonyms: GlossaryCSVRecord['synonyms']) => {
return <Typography.Text>{synonyms ?? '--'}</Typography.Text>;
return (
<Typography.Text
ellipsis={{ tooltip: synonyms }}
style={{ maxWidth: 100 }}>
{isEmpty(synonyms) ? '--' : synonyms}
</Typography.Text>
);
},
},
{
title: t('label.related-term-plural'),
dataIndex: 'relatedTerms',
key: 'relatedTerms',
render: (relatedTerms: GlossaryCSVRecord['relatedTerms']) => {
return (
<Typography.Text
ellipsis={{ tooltip: relatedTerms }}
style={{ maxWidth: 100 }}>
{isEmpty(relatedTerms) ? '--' : relatedTerms}
</Typography.Text>
);
},
},
{
@ -111,13 +147,35 @@ const ImportResult: FC<Props> = ({ csvImportResult }) => {
dataIndex: 'tags',
key: 'tags',
render: (tags: GlossaryCSVRecord['tags']) => {
return <Typography.Text>{tags ?? '--'}</Typography.Text>;
return (
<Typography.Text
ellipsis={{ tooltip: tags }}
style={{ maxWidth: 100 }}>
{isEmpty(tags) ? '--' : tags}
</Typography.Text>
);
},
},
],
[]
);
const parseCsvFile = () => {
if (csvImportResult.importResultsCsv) {
readString(csvImportResult.importResultsCsv, {
worker: true,
complete: (results) => {
// results.data is returning data with unknown type
setParsedRecords(parseCSV(results.data as string[][]));
},
});
}
};
useEffect(() => {
parseCsvFile();
}, [csvImportResult.importResultsCsv]);
return (
<Row data-testid="import-results" gutter={[16, 16]}>
<Col span={24}>

View file

@ -74,20 +74,17 @@ export const getEntityReferenceFromGlossary = (
};
};
export const parseCSV = (csvData: string) => {
export const parseCSV = (csvData: string[][]) => {
const recordList: GlossaryCSVRecord[] = [];
const lines = csvData.trim().split('\n').filter(Boolean);
if (!isEmpty(csvData)) {
const headers = csvData[0];
if (!isEmpty(lines)) {
const headers = lines[0].split(',').map((header) => header.trim());
lines.slice(1).forEach((line) => {
csvData.slice(1).forEach((line) => {
const record: GlossaryCSVRecord = {} as GlossaryCSVRecord;
const lineData = line.split(',');
headers.forEach((header, index) => {
record[header as keyof GlossaryCSVRecord] = lineData[index];
record[header as keyof GlossaryCSVRecord] = line[index];
});
recordList.push(record);

View file

@ -3688,6 +3688,13 @@
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.0.tgz#12ab4c19107528452e73ac99132c875ccd43bdfb"
integrity sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==
"@types/papaparse@^5.3.1":
version "5.3.7"
resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.7.tgz#8d3bf9e62ac2897df596f49d9ca59a15451aa247"
integrity sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==
dependencies:
"@types/node" "*"
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -10850,6 +10857,11 @@ packet-reader@1.0.0:
resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
papaparse@^5.3.1:
version "5.3.2"
resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.3.2.tgz#d1abed498a0ee299f103130a6109720404fbd467"
integrity sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw==
param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
@ -12167,6 +12179,14 @@ react-oidc@^1.0.3:
oidc-client "^1.5.2"
typescript "^2.9.2"
react-papaparse@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/react-papaparse/-/react-papaparse-4.1.0.tgz#09e1cdae55a0f3e36650aaf7ff9bb5ee2aed164a"
integrity sha512-sGJqK+OE2rVVQPxQUCCDW2prLIglv9kTdizhNe2awXvKo0gLShmhpRN3BwA+ujw5M2gSJ/KGNEwtgII0OsLgkg==
dependencies:
"@types/papaparse" "^5.3.1"
papaparse "^5.3.1"
react-property@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/react-property/-/react-property-1.0.1.tgz#4ae4211557d0a0ae050a71aa8ad288c074bea4e6"