From 6fcc0993b52d2fabd13d58c398d1a1e96ddda09b Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:30:00 +0530 Subject: [PATCH] feat(ui): displayName support for columns inside the table (#13090) * feat(ui): displayName support for columns inside the table * fix unit tests * update styling and unit tests added * address comments --- .../ui/src/assets/svg/edit-black.svg | 1 - .../resources/ui/src/assets/svg/edit-new.svg | 17 +- .../SchemaTable/SchemaTable.component.tsx | 209 ++++++++++++------ .../SchemaTable/SchemaTable.test.tsx | 76 +++++++ .../OwnerLabel/OwnerLabel.component.tsx | 23 +- .../resources/ui/src/utils/SvgUtils.test.tsx | 4 +- .../main/resources/ui/src/utils/SvgUtils.tsx | 6 - 7 files changed, 239 insertions(+), 97 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-black.svg diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-black.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-black.svg deleted file mode 100644 index b9b5273c559..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-black.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new.svg index c51d4a93793..9d59d99d96b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/edit-new.svg @@ -1,13 +1,4 @@ - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx index e36edda1af3..8f6175ef2be 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx @@ -11,10 +11,14 @@ * limitations under the License. */ +import Icon from '@ant-design/icons'; import { Space, Table, Tooltip, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { ExpandableConfig } from 'antd/lib/table/interface'; +import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg'; import FilterTablePlaceHolder from 'components/common/error-with-placeholder/FilterTablePlaceHolder'; +import EntityNameModal from 'components/Modals/EntityNameModal/EntityNameModal.component'; +import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface'; import TableDescription from 'components/TableDescription/TableDescription.component'; import TableTags from 'components/TableTags/TableTags.component'; import { TABLE_SCROLL_VALUE } from 'constants/Table.constants'; @@ -26,6 +30,7 @@ import { lowerCase, map, reduce, + set, sortBy, toLower, } from 'lodash'; @@ -65,6 +70,10 @@ const SchemaTable = ({ const [searchedColumns, setSearchedColumns] = useState([]); const [expandedRowKeys, setExpandedRowKeys] = useState([]); + const [editColumn, setEditColumn] = useState(); + + const [editColumnDisplayName, setEditColumnDisplayName] = useState(); + const sortByOrdinalPosition = useMemo( () => sortBy(tableColumns, 'ordinalPosition'), [tableColumns] @@ -75,32 +84,34 @@ const SchemaTable = ({ [searchedColumns] ); - const [editColumn, setEditColumn] = useState<{ - column: Column; - index: number; - }>(); - - const handleEditColumn = (column: Column, index: number): void => { - setEditColumn({ column, index }); + const handleEditColumn = (column: Column): void => { + setEditColumn(column); }; const closeEditColumnModal = (): void => { setEditColumn(undefined); }; - const updateColumnDescription = ( - tableCols: Column[], - changedColFQN: string, - description: string - ) => { - tableCols?.forEach((col) => { - if (col.fullyQualifiedName === changedColFQN) { - col.description = description; + const updateColumnFields = ({ + fqn, + field, + value, + columns, + }: { + fqn: string; + field: keyof Column; + value?: string; + columns: Column[]; + }) => { + columns?.forEach((col) => { + if (col.fullyQualifiedName === fqn) { + set(col, field, value); } else { - updateColumnDescription( - col?.children as Column[], - changedColFQN, - description - ); + updateColumnFields({ + fqn, + field, + value, + columns: col.children as Column[], + }); } }); }; @@ -153,13 +164,14 @@ const SchemaTable = ({ }; const handleEditColumnChange = async (columnDescription: string) => { - if (editColumn && editColumn.column.fullyQualifiedName) { + if (editColumn && editColumn.fullyQualifiedName) { const tableCols = cloneDeep(tableColumns); - updateColumnDescription( - tableCols, - editColumn.column.fullyQualifiedName, - columnDescription - ); + updateColumnFields({ + fqn: editColumn.fullyQualifiedName, + value: columnDescription, + field: 'description', + columns: tableCols, + }); await onUpdate(tableCols); setEditColumn(undefined); } else { @@ -214,8 +226,8 @@ const SchemaTable = ({ return searchedValue; }; - const handleUpdate = (column: Column, index: number) => { - handleEditColumn(column, index); + const handleUpdate = (column: Column) => { + handleEditColumn(column); }; const renderDataTypeDisplay: TableCellRendered = ( @@ -258,7 +270,7 @@ const SchemaTable = ({ hasEditPermission={hasDescriptionEditAccess} index={index} isReadOnly={isReadOnly} - onClick={() => handleUpdate(record, index)} + onClick={() => handleUpdate(record)} onThreadLinkSelect={onThreadLinkSelect} /> {getFrequentlyJoinedColumns( @@ -270,6 +282,52 @@ const SchemaTable = ({ ); }; + const expandableConfig: ExpandableConfig = useMemo( + () => ({ + ...getTableExpandableConfig(), + rowExpandable: (record) => !isEmpty(record.children), + expandedRowKeys, + onExpand: (expanded, record) => { + setExpandedRowKeys( + expanded + ? [...expandedRowKeys, record.fullyQualifiedName ?? ''] + : expandedRowKeys.filter((key) => key !== record.fullyQualifiedName) + ); + }, + }), + [expandedRowKeys] + ); + + useEffect(() => { + if (!searchText) { + setSearchedColumns(sortByOrdinalPosition); + } else { + const searchCols = searchInColumns(sortByOrdinalPosition, searchText); + setSearchedColumns(searchCols); + } + }, [searchText, sortByOrdinalPosition]); + + const handleEditDisplayNameClick = (record: Column) => { + setEditColumnDisplayName(record); + }; + + const handleEditDisplayName = ({ displayName }: EntityName) => { + if (editColumnDisplayName && editColumnDisplayName.fullyQualifiedName) { + const tableCols = cloneDeep(tableColumns); + updateColumnFields({ + fqn: editColumnDisplayName.fullyQualifiedName, + value: isEmpty(displayName) ? undefined : displayName, + field: 'displayName', + columns: tableCols, + }); + onUpdate(tableCols).then(() => { + setEditColumnDisplayName(undefined); + }); + } else { + setEditColumnDisplayName(undefined); + } + }; + const columns: ColumnsType = useMemo( () => [ { @@ -279,19 +337,47 @@ const SchemaTable = ({ accessor: 'name', width: 180, fixed: 'left', - render: (name: Column['name'], record: Column) => ( - - {prepareConstraintIcon({ - columnName: name, - columnConstraint: record.constraint, - tableConstraints, - })} - {getEntityName(record)} - - ), + render: (name: Column['name'], record: Column) => { + const { displayName } = record; + + return ( +
+ + {prepareConstraintIcon({ + columnName: name, + columnConstraint: record.constraint, + tableConstraints, + })} +
+ {/* If we do not have displayName name only be shown in the bold from the below code */} + {!isEmpty(displayName) ? ( + + {name} + + ) : null} + + {/* It will render displayName fallback to name */} + + {getEntityName(record)} + +
+
+ handleEditDisplayNameClick(record)} + /> +
+ ); + }, }, { title: t('label.type'), @@ -366,30 +452,6 @@ const SchemaTable = ({ onThreadLinkSelect, ] ); - const expandableConfig: ExpandableConfig = useMemo( - () => ({ - ...getTableExpandableConfig(), - rowExpandable: (record) => !isEmpty(record.children), - expandedRowKeys, - onExpand: (expanded, record) => { - setExpandedRowKeys( - expanded - ? [...expandedRowKeys, record.fullyQualifiedName ?? ''] - : expandedRowKeys.filter((key) => key !== record.fullyQualifiedName) - ); - }, - }), - [expandedRowKeys] - ); - - useEffect(() => { - if (!searchText) { - setSearchedColumns(sortByOrdinalPosition); - } else { - const searchCols = searchInColumns(sortByOrdinalPosition, searchText); - setSearchedColumns(searchCols); - } - }, [searchText, sortByOrdinalPosition]); return ( <> @@ -412,14 +474,25 @@ const SchemaTable = ({ )} + {editColumnDisplayName && ( + + )} ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx index 7286d1fbdf0..d73e62c7571 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx @@ -83,6 +83,45 @@ const mockEntityTableProp = { onUpdate, }; +const columnsWithDisplayName = [ + { + name: 'comments', + displayName: 'Comments', + dataType: 'STRING', + dataLength: 1, + dataTypeDisplay: 'string', + fullyQualifiedName: + 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.comments', + tags: [], + constraint: 'NULL', + ordinalPosition: 1, + }, + { + name: 'products', + dataType: 'ARRAY', + arrayDataType: 'STRUCT', + dataLength: 1, + dataTypeDisplay: + 'array>', + fullyQualifiedName: + 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.products', + tags: [], + constraint: 'NULL', + ordinalPosition: 2, + }, + { + name: 'platform', + dataType: 'STRING', + dataLength: 1, + dataTypeDisplay: 'string', + fullyQualifiedName: + 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.platform', + tags: [], + constraint: 'NULL', + ordinalPosition: 3, + }, +] as Column[]; + jest.mock('../../hooks/authHooks', () => { return { useAuth: jest.fn().mockReturnValue({ @@ -173,4 +212,41 @@ describe('Test EntityTable Component', () => { expect(emptyPlaceholder).toBeInTheDocument(); }); + + it('should render column name only if displayName is not present', async () => { + render(, { + wrapper: MemoryRouter, + }); + + const columnNames = await screen.findAllByTestId('column-display-name'); + + expect(columnNames).toHaveLength(3); + + expect(columnNames[0].textContent).toBe('comments'); + expect(columnNames[1].textContent).toBe('products'); + expect(columnNames[2].textContent).toBe('platform'); + }); + + it('should render column name & displayName for column if both presents', async () => { + render( + , + { + wrapper: MemoryRouter, + } + ); + + const columnDisplayName = await screen.findAllByTestId( + 'column-display-name' + ); + const columnName = await screen.findByTestId('column-name'); + + expect(columnDisplayName[0]).toBeInTheDocument(); + expect(columnName).toBeInTheDocument(); + + expect(columnDisplayName[0].textContent).toBe('Comments'); + expect(columnName.textContent).toBe('comments'); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx index 11dbfb4d126..883d649e0b7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx @@ -10,7 +10,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Space, Typography } from 'antd'; +import Icon from '@ant-design/icons'; +import { Typography } from 'antd'; import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg'; import { ReactComponent as IconUser } from 'assets/svg/user.svg'; import { getTeamAndUserDetailsPath, getUserPath } from 'constants/constants'; @@ -40,11 +41,21 @@ export const OwnerLabel = ({ const profilePicture = useMemo(() => { if (isUndefined(owner)) { - return ; + return ( + + ); } return owner.type === OwnerType.TEAM ? ( - + ) : ( ); }, [owner]); return ( - +
{profilePicture} {displayName ? ( @@ -86,6 +97,6 @@ export const OwnerLabel = ({ onUpdate={onUpdate} /> )} - +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.test.tsx index 83285fa9a03..7b526d48857 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.test.tsx @@ -17,9 +17,7 @@ import SVGIcons from './SvgUtils'; describe('Test SVGIcons component', () => { it('Component should render', async () => { - const { container } = render( - - ); + const { container } = render(); const image = await findByTestId(container, 'image'); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.tsx index c751168be84..55f6b374519 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SvgUtils.tsx @@ -60,7 +60,6 @@ import IconDocPrimary from '../assets/svg/doc-primary.svg'; import IconDocWhite from '../assets/svg/doc-white.svg'; import IconDoc from '../assets/svg/doc.svg'; import IconDrag from '../assets/svg/drag.svg'; -import IconEditBlack from '../assets/svg/edit-black.svg'; import IconEditOutlinePrimary from '../assets/svg/edit-outline-primery.svg'; import IconEditPrimary from '../assets/svg/edit-primary.svg'; import IconError from '../assets/svg/error.svg'; @@ -216,7 +215,6 @@ export const Icons = { GITHUB_ICON: 'github-icon', AUTH0_ICON: 'auth0-icon', EDIT: 'icon-edit', - EDIT_BLACK: 'icon-edit-black', EDIT_PRIMARY: 'icon-edit-primary', EDIT_OUTLINE_PRIMARY: 'icon-edit-outline-primary', EXPLORE: 'icon-explore', @@ -835,10 +833,6 @@ const SVGIcons: FunctionComponent = ({ icon, ...props }: Props) => { break; - case Icons.EDIT_BLACK: - IconComponent = IconEditBlack; - - break; case Icons.EDIT_PRIMARY: IconComponent = IconEditPrimary;