diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 482d51febd7..a5070e8f1c0 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx index 65da2f8bf47..bd147f22f6c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx @@ -200,6 +200,14 @@ const GlossaryHeader = ({ } setListVisible(false); }; + const onRemoveOwner = () => { + const updatedData = { + ...selectedData, + owner: undefined, + }; + onUpdate(updatedData); + setListVisible(false); + }; const handleReviewerSave = (data: Array) => { 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} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx index 7ea89c1d1a7..1f3035c37fc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx @@ -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: ( - { e.stopPropagation(); setIsDelete(true); setShowActions(false); }}> - -
+ + + +

Delete

@@ -270,8 +271,8 @@ const GlossaryV1 = ({ Deleting this {isGlossaryActive ? 'Glossary' : 'GlossaryTerm'}{' '} will permanently remove its metadata from OpenMetadata.

-
-
+ + ), key: 'delete-button', }, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.test.tsx index 2fbf13950e0..2b05a530178 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.test.tsx @@ -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(
RichTextViewer
) -); - 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( + + ); + + 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(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.tsx index 0eaca3c5698..3cffdb36498 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/ImportResult/ImportResult.tsx @@ -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 = ({ 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([]); const columns: ColumnsType = useMemo( () => [ @@ -63,7 +55,11 @@ const ImportResult: FC = ({ csvImportResult }) => { dataIndex: 'details', key: 'details', render: (details: GlossaryCSVRecord['details']) => { - return {details ?? '--'}; + return ( + + {isEmpty(details) ? '--' : details} + + ); }, }, { @@ -71,7 +67,13 @@ const ImportResult: FC = ({ csvImportResult }) => { dataIndex: 'parent', key: 'parent', render: (parent: GlossaryCSVRecord['parent']) => { - return {parent ?? '--'}; + return ( + + {isEmpty(parent) ? '--' : parent} + + ); }, }, { @@ -87,15 +89,29 @@ const ImportResult: FC = ({ csvImportResult }) => { dataIndex: 'displayName', key: 'displayName', render: (displayName: GlossaryCSVRecord['displayName']) => { - return {displayName ?? '--'}; + return ( + + {isEmpty(displayName) ? '--' : displayName} + + ); }, }, { title: t('label.description'), dataIndex: 'description*', key: 'description', + width: 300, render: (description: GlossaryCSVRecord['description*']) => { - return ; + return ( + + {description} + + ); }, }, { @@ -103,7 +119,27 @@ const ImportResult: FC = ({ csvImportResult }) => { dataIndex: 'synonyms', key: 'synonyms', render: (synonyms: GlossaryCSVRecord['synonyms']) => { - return {synonyms ?? '--'}; + return ( + + {isEmpty(synonyms) ? '--' : synonyms} + + ); + }, + }, + { + title: t('label.related-term-plural'), + dataIndex: 'relatedTerms', + key: 'relatedTerms', + render: (relatedTerms: GlossaryCSVRecord['relatedTerms']) => { + return ( + + {isEmpty(relatedTerms) ? '--' : relatedTerms} + + ); }, }, { @@ -111,13 +147,35 @@ const ImportResult: FC = ({ csvImportResult }) => { dataIndex: 'tags', key: 'tags', render: (tags: GlossaryCSVRecord['tags']) => { - return {tags ?? '--'}; + return ( + + {isEmpty(tags) ? '--' : tags} + + ); }, }, ], [] ); + 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 ( diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.ts index 5e1fe2eff14..0c1a1ec7082 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlossaryUtils.ts @@ -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); diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index e334d317316..c53dd1e09e9 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -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"