diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js index fd57aac714e..b56e6b11e5b 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js @@ -95,7 +95,7 @@ describe('Tags page should work', () => { }); }); - it('Add new tag flow shoud work properly', () => { + it('Add new tag flow should work properly', () => { cy.get('[data-testid="side-panel-category"]') .contains(NEW_TAG_CATEGORY.name) .should('be.visible') diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx index d30fe34dfd5..522ac0881b8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx @@ -75,6 +75,7 @@ const DashboardDetailsProps = { chartUrl: 'http://localhost', chartType: 'Area', displayName: 'Test chart', + id: '1', }, ] as ChartType[], serviceType: '', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/dashboardVersion.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/dashboardVersion.mock.ts index 3bb0f9e6dcb..0f75b289c43 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/dashboardVersion.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/dashboardVersion.mock.ts @@ -38,7 +38,7 @@ export const dashboardVersionProp = { deleted: false, }, { - id: '0698ab5d-a122-4b86-a6e5-d10bf3550bd7', + id: '0698ab5d-a122-4b86-a6e5-d10bf3550bd6', type: 'chart', name: 'without_description', description: '', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx index a904869ffe9..ca57272ec43 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx @@ -40,7 +40,7 @@ import TabsPane from '../common/TabsPane/TabsPane'; import PageContainer from '../containers/PageContainer'; import EntityVersionTimeLine from '../EntityVersionTimeLine/EntityVersionTimeLine'; import Loader from '../Loader/Loader'; -import SchemaTab from '../SchemaTab/SchemaTab.component'; +import VersionTable from '../VersionTable/VersionTable.component'; import { DatasetVersionProp } from './DatasetVersion.interface'; const DatasetVersion: React.FC = ({ @@ -406,8 +406,7 @@ const DatasetVersion: React.FC = ({
- = ({ // TODO: Below we should have separate type for Dataset instead casting it to `Table` (currentVersionData as Table).joins as ColumnJoins[] } - tableConstraints={[]} />
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx index 8054f11b4c4..9f148b6cd61 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx @@ -13,7 +13,8 @@ import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Popover, Table } from 'antd'; +import { Popover, Table, Typography } from 'antd'; +import { ColumnsType } from 'antd/lib/table'; import classNames from 'classnames'; import { cloneDeep, isEmpty, isUndefined, lowerCase } from 'lodash'; import { EntityFieldThreads, EntityTags, TagOption } from 'Models'; @@ -25,20 +26,19 @@ import React, { useState, } from 'react'; import { useTranslation } from 'react-i18next'; -import { Link, useHistory } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; -import { getTableDetailsPath } from '../../constants/constants'; import { EntityField } from '../../constants/feed.constants'; import { SettledStatus } from '../../enums/axios.enum'; import { EntityType, FqnPart } from '../../enums/entity.enum'; -import { Column, JoinedWith } from '../../generated/entity/data/table'; +import { Column } from '../../generated/entity/data/table'; import { ThreadType } from '../../generated/entity/feed/thread'; import { LabelType, State, TagLabel } from '../../generated/type/tagLabel'; +import { getPartialNameFromTableFQN } from '../../utils/CommonUtils'; import { - getPartialNameFromTableFQN, - getTableFQNFromColumnFQN, -} from '../../utils/CommonUtils'; -import { ENTITY_LINK_SEPARATOR } from '../../utils/EntityUtils'; + ENTITY_LINK_SEPARATOR, + getFrequentlyJoinedColumns, +} from '../../utils/EntityUtils'; import { getFieldThreadElement } from '../../utils/FeedElementUtils'; import { fetchGlossaryTerms, @@ -62,8 +62,7 @@ import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPr import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; import TagsContainer from '../tags-container/tags-container'; import TagsViewer from '../tags-viewer/tags-viewer'; -import { TABLE_HEADERS_V1 } from './EntityTable.constants'; -import { EntityTableProps } from './EntityTable.interface'; +import { EntityTableProps, TableCellRendered } from './EntityTable.interface'; import './EntityTable.style.less'; const EntityTable = ({ @@ -252,21 +251,6 @@ const EntityTable = ({ setEditColumnTag(undefined); }; - const getFrequentlyJoinedWithColumns = ( - columnName: string - ): Array => { - return ( - joins.find((join) => join.columnName === columnName)?.joinedWith || [] - ); - }; - const checkIfJoinsAvailable = (columnName: string): boolean => { - return ( - joins && - Boolean(joins.length) && - Boolean(joins.find((join) => join.columnName === columnName)) - ); - }; - const searchInColumns = (table: Column[], searchText: string): Column[] => { const searchedValue: Column[] = table.reduce((searchedCols, column) => { const isContainData = @@ -438,46 +422,30 @@ const EntityTable = ({ ); }; - const getDataTypeDisplayCell = (record: Column | Column) => { + const renderDataTypeDisplay: TableCellRendered = ( + dataTypeDisplay + ) => { return ( <> - {record.dataTypeDisplay ? ( - <> - {isReadOnly ? ( -
- -
- ) : ( - <> - {record.dataTypeDisplay.length > 25 ? ( - - - {record.dataTypeDisplay.toLowerCase()} - - } - key="pop-over" - position="bottom" - theme="light" - trigger="click"> -
- -
-
-
- ) : ( - record.dataTypeDisplay.toLowerCase() - )} - - )} - + {dataTypeDisplay ? ( + isReadOnly || (dataTypeDisplay.length < 25 && !isReadOnly) ? ( + lowerCase(dataTypeDisplay) + ) : ( + + {lowerCase(dataTypeDisplay)} + + } + key="pop-over" + position="bottom" + theme="light" + trigger="click"> + + {dataTypeDisplay} + + + ) ) : ( '--' )} @@ -485,24 +453,28 @@ const EntityTable = ({ ); }; - const getDescriptionCell = (index: number, record: Column | Column) => { + const renderDescription: TableCellRendered = ( + description, + record, + index + ) => { return (
-
+
- {record?.description ? ( - + {description ? ( + ) : ( {t('label.no-description')} )}
-
+
{!isReadOnly ? ( {hasDescriptionEditAccess && ( @@ -550,189 +522,97 @@ const EntityTable = ({
- {checkIfJoinsAvailable(record?.name) && ( -
- - {t('label.frequently-joined-columns')}: - - - {getFrequentlyJoinedWithColumns(record?.name) - .slice(0, 3) - .map((columnJoin, index) => ( - - {index > 0 && ,} - - {getPartialNameFromTableFQN( - columnJoin.fullyQualifiedName, - [FqnPart.Database, FqnPart.Table, FqnPart.Column], - FQN_SEPARATOR_CHAR - )} - - - ))} - - {getFrequentlyJoinedWithColumns(record?.name).length > 3 && ( - - {getFrequentlyJoinedWithColumns(record?.name) - ?.slice(3) - .map((columnJoin, index) => ( - - - {getPartialNameFromTableFQN( - columnJoin?.fullyQualifiedName, - [ - FqnPart.Database, - FqnPart.Table, - FqnPart.Column, - ] - )} - - - ))} -
- } - position="bottom" - theme="light" - trigger="click"> - ... - - )} - -
+ {getFrequentlyJoinedColumns( + record?.name, + joins, + t('label.frequently-joined-columns') )}
); }; - const getTagsCell = (index: number, record: Column | Column) => { - return ( -
- {isReadOnly ? ( -
- -
- ) : ( -
{ - if (!editColumnTag) { - handleEditColumnTag(record, index); - // Fetch tags and terms only once - if (allTags.length === 0 || tagFetchFailed) { - fetchTagsAndGlossaryTerms(); - } - } - }}> - { - handleTagSelection(); - }} - onSelectionChange={(tags) => { - handleTagSelection(tags, record?.name); - }} - /> - -
- {getRequestTagsElement(record)} - {getFieldThreadElement( - getColumnName(record), - 'tags', - entityFieldThreads as EntityFieldThreads[], - onThreadLinkSelect, - EntityType.TABLE, - entityFqn, - `columns${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}tags`, - Boolean(record?.name?.length) - )} - {getFieldThreadElement( - getColumnName(record), - EntityField.TAGS, - entityFieldTasks as EntityFieldThreads[], - onThreadLinkSelect, - EntityType.TABLE, - entityFqn, - `${EntityField.COLUMNS}${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}${EntityField.TAGS}`, - Boolean(record?.name), - ThreadType.Task - )} + const renderTags: TableCellRendered = useCallback( + (tags, record: Column, index: number) => { + return ( +
+ {isReadOnly ? ( +
+
-
- )} -
- ); - }; - - const renderCell = useCallback( - (key: string, record: Column | Column, index: number) => { - switch (key) { - case TABLE_HEADERS_V1.dataTypeDisplay: - return getDataTypeDisplayCell(record); - - case TABLE_HEADERS_V1.description: - return getDescriptionCell(index, record); - - case TABLE_HEADERS_V1.tags: - return getTagsCell(index, record); - - default: - return ( - - {isReadOnly ? ( -
- -
- ) : ( - - {prepareConstraintIcon(record.name, record.constraint)} - {record.name} - + ) : ( +
- ); - } + data-testid="tags-wrapper" + onClick={() => { + if (!editColumnTag) { + handleEditColumnTag(record, index); + // Fetch tags and terms only once + if (allTags.length === 0 || tagFetchFailed) { + fetchTagsAndGlossaryTerms(); + } + } + }}> + { + handleTagSelection(); + }} + onSelectionChange={(selectedTags) => { + handleTagSelection(selectedTags, record?.name); + }} + /> + +
+ {getRequestTagsElement(record)} + {getFieldThreadElement( + getColumnName(record), + 'tags', + entityFieldThreads as EntityFieldThreads[], + onThreadLinkSelect, + EntityType.TABLE, + entityFqn, + `columns${ENTITY_LINK_SEPARATOR}${getColumnName( + record + )}${ENTITY_LINK_SEPARATOR}tags`, + Boolean(record?.name?.length) + )} + {getFieldThreadElement( + getColumnName(record), + EntityField.TAGS, + entityFieldTasks as EntityFieldThreads[], + onThreadLinkSelect, + EntityType.TABLE, + entityFqn, + `${ + EntityField.COLUMNS + }${ENTITY_LINK_SEPARATOR}${getColumnName( + record + )}${ENTITY_LINK_SEPARATOR}${EntityField.TAGS}`, + Boolean(record?.name), + ThreadType.Task + )} +
+
+ )} +
+ ); }, - [editColumnTag, isTagLoading, handleUpdate, handleTagSelection] + [isReadOnly, editColumnTag, hasTagEditAccess, isTagLoading] ); - const columns = useMemo( + const columns: ColumnsType = useMemo( () => [ { title: t('label.name'), @@ -741,8 +621,12 @@ const EntityTable = ({ accessor: 'name', ellipsis: true, width: 180, - render: (_: Array, record: Column, index: number) => - renderCell(TABLE_HEADERS_V1.name, record, index), + render: (name: Column['name'], record: Column) => ( + + {prepareConstraintIcon(name, record.constraint)} + {name} + + ), }, { title: t('label.type'), @@ -751,17 +635,14 @@ const EntityTable = ({ accessor: 'dataTypeDisplay', ellipsis: true, width: 200, - render: (_: Array, record: Column, index: number) => { - return renderCell(TABLE_HEADERS_V1.dataTypeDisplay, record, index); - }, + render: renderDataTypeDisplay, }, { title: t('label.description'), dataIndex: 'description', key: 'description', accessor: 'description', - render: (_: Array, record: Column, index: number) => - renderCell(TABLE_HEADERS_V1.description, record, index), + render: renderDescription, }, { title: t('label.tags'), @@ -769,11 +650,10 @@ const EntityTable = ({ key: 'tags', accessor: 'tags', width: 272, - render: (_: Array, record: Column | Column, index: number) => - renderCell(TABLE_HEADERS_V1.tags, record, index), + render: renderTags, }, ], - [editColumnTag, isTagLoading, renderCell] + [editColumnTag, isTagLoading, handleUpdate, handleTagSelection] ); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts index b618e998329..b102cbd58a3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts @@ -12,6 +12,7 @@ */ import { EntityFieldThreads } from 'Models'; +import { ReactNode } from 'react'; import { ThreadType } from '../../generated/api/feed/createThread'; import { Column, ColumnJoins, Table } from '../../generated/entity/data/table'; @@ -31,3 +32,13 @@ export interface EntityTableProps { onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; onEntityFieldSelect?: (value: string) => void; } + +export type TableCellRendered = ( + value: T[K], + record: T, + index: number +) => ReactNode; + +export interface DataTypeDisplayCellProps { + dataTypeDisplay: Column['dataTypeDisplay']; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx index 3e86575e81a..c4bb45c21c3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx @@ -20,7 +20,6 @@ import { Column } from '../../generated/api/data/createTable'; import { Table } from '../../generated/entity/data/table'; import { TagCategory, TagClass } from '../../generated/entity/tags/tagCategory'; import EntityTableV1 from './EntityTable.component'; -import type { ColumnsType } from 'antd/es/table'; const onEntityFieldSelect = jest.fn(); const onThreadLinkSelect = jest.fn(); @@ -32,15 +31,6 @@ const mockTableConstraints = [ }, ] as Table['tableConstraints']; -type ColumnDataType = { - key: string; - name: string; - dataTypeDisplay: string; - columnTests: string; - description: string; - tags: string; -}; - const mockEntityTableProp = { tableColumns: [ { @@ -257,35 +247,6 @@ jest.mock('../../utils/TagsUtils', () => ({ }), })); -jest.mock('antd', () => ({ - Popover: jest - .fn() - .mockImplementation(({ children }) =>
{children}
), - - Table: jest.fn().mockImplementation(({ columns, dataSource }) => ( - - - - {(columns as ColumnsType).map((col) => ( - - ))} - - - - {dataSource.map((row: Column, i: number) => ( - - {(columns as ColumnsType).map((col, index) => ( - - ))} - - ))} - -
{col.title}
- {col.render ? col.render(row, dataSource, index) : 'alt'} -
- )), -})); - describe('Test EntityTable Component', () => { it('Initially, Table should load', async () => { render(, { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.component.tsx index b325499c43f..cf6d3b67c20 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.component.tsx @@ -12,33 +12,10 @@ */ import { lowerCase } from 'lodash'; -import { EntityFieldThreads } from 'Models'; import React, { Fragment, FunctionComponent, useState } from 'react'; -import { - ColumnJoins, - Table, - TableData, -} from '../../generated/entity/data/table'; -import { ThreadType } from '../../generated/entity/feed/thread'; import Searchbar from '../common/searchbar/Searchbar'; import EntityTableV1 from '../EntityTable/EntityTable.component'; - -type Props = { - columns: Table['columns']; - joins: Array; - columnName: string; - tableConstraints: Table['tableConstraints']; - sampleData?: TableData; - hasDescriptionEditAccess?: boolean; - hasTagEditAccess?: boolean; - isReadOnly?: boolean; - entityFqn?: string; - entityFieldThreads?: EntityFieldThreads[]; - entityFieldTasks?: EntityFieldThreads[]; - onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; - onEntityFieldSelect?: (value: string) => void; - onUpdate?: (columns: Table['columns']) => Promise; -}; +import { Props } from './SchemaTab.interfaces'; const SchemaTab: FunctionComponent = ({ columns, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts new file mode 100644 index 00000000000..659bf7a2d8c --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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 { EntityFieldThreads } from 'Models'; +import { ThreadType } from '../../generated/api/feed/createThread'; +import { + ColumnJoins, + Table, + TableData, +} from '../../generated/entity/data/table'; + +export type Props = { + columns: Table['columns']; + joins: Array; + columnName: string; + tableConstraints: Table['tableConstraints']; + sampleData?: TableData; + hasDescriptionEditAccess?: boolean; + hasTagEditAccess?: boolean; + isReadOnly?: boolean; + entityFqn?: string; + entityFieldThreads?: EntityFieldThreads[]; + entityFieldTasks?: EntityFieldThreads[]; + onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; + onEntityFieldSelect?: (value: string) => void; + onUpdate?: (columns: Table['columns']) => Promise; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/VersionTable/VersionTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/VersionTable/VersionTable.component.tsx new file mode 100644 index 00000000000..30e40cccbe0 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/VersionTable/VersionTable.component.tsx @@ -0,0 +1,155 @@ +/* + * Copyright 2022 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 { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Col, Row, Table } from 'antd'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Column } from '../../generated/entity/data/table'; +import { + getFrequentlyJoinedColumns, + searchInColumns, +} from '../../utils/EntityUtils'; +import { makeData } from '../../utils/TableUtils'; +import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer'; +import Searchbar from '../common/searchbar/Searchbar'; +import TagsViewer from '../tags-viewer/tags-viewer'; +import { VersionTableProps } from './VersionTable.interfaces'; + +const VersionTable = ({ columnName, columns, joins }: VersionTableProps) => { + const [searchedColumns, setSearchedColumns] = useState([]); + const { t } = useTranslation(); + + const [searchText, setSearchText] = useState(''); + + const data = useMemo(() => makeData(searchedColumns), [searchedColumns]); + + const versionTableColumns = useMemo( + () => [ + { + title: t('label.name'), + dataIndex: 'name', + key: 'name', + accessor: 'name', + ellipsis: true, + width: 180, + render: (name: Column['name']) => ( +
+ +
+ ), + }, + { + title: t('label.type'), + dataIndex: 'dataTypeDisplay', + key: 'dataTypeDisplay', + accessor: 'dataTypeDisplay', + ellipsis: true, + width: 200, + render: (dataTypeDisplay: Column['dataTypeDisplay']) => + dataTypeDisplay ? ( + + ) : ( + '--' + ), + }, + { + title: t('label.description'), + dataIndex: 'description', + key: 'description', + accessor: 'description', + render: (description: Column['description']) => + description ? ( + <> + + {getFrequentlyJoinedColumns( + columnName, + joins, + t('label.frequently-joined-columns') + )} + + ) : ( + + {t('label.no-description')} + + ), + }, + { + title: t('label.tags'), + dataIndex: 'tags', + key: 'tags', + accessor: 'tags', + width: 272, + render: (tags: Column['tags']) => ( +
+ +
+ ), + }, + ], + [] + ); + + const handleSearchAction = (searchValue: string) => { + setSearchText(searchValue); + }; + + useEffect(() => { + if (!searchText) { + setSearchedColumns(columns); + } else { + const searchCols = searchInColumns(columns, searchText); + setSearchedColumns(searchCols); + } + }, [searchText, columns]); + + return ( + + + + + + + record.children ? ( + + onExpand( + record, + e as unknown as React.MouseEvent + ) + } + /> + ) : null, + }} + pagination={false} + size="small" + /> + + + ); +}; + +export default VersionTable; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/VersionTable/VersionTable.interfaces.ts b/openmetadata-ui/src/main/resources/ui/src/components/VersionTable/VersionTable.interfaces.ts new file mode 100644 index 00000000000..72d5ec8ba5e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/VersionTable/VersionTable.interfaces.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2022 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 { ColumnJoins, Table } from '../../generated/entity/data/table'; + +export interface VersionTableProps { + columnName: string; + columns: Table['columns']; + joins: Array; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CronEditor/CronEditor.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CronEditor/CronEditor.test.tsx index b59f7d77ac9..a4f958da4cc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CronEditor/CronEditor.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CronEditor/CronEditor.test.tsx @@ -129,12 +129,12 @@ describe('Test CronEditor component', () => { render(); const cronType = await screen.findByTestId('cron-type'); - act(async () => { - await userEvent.selectOptions(cronType, ''); - - expect( - await screen.findByTestId('manual-segment-container') - ).toBeInTheDocument(); + await act(async () => { + userEvent.selectOptions(cronType, ''); }); + + expect( + await screen.findByTestId('manual-segment-container') + ).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index d328c59e19c..604593dfe80 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -149,7 +149,6 @@ "bot": "bot", "for": "for", "are-you-sure": "Are you sure?", - "email": "Email", "select-token-expiration": "Select Token Expiration", "token-expiration": "Token Expiration", "select-auth-mechanism": "Select Auth Mechanism", @@ -172,6 +171,7 @@ "sql-query": "SQL Query", "sql-query-tooltip": "Queries returning 1 or more rows will result in the test failing.", "scopes-comma-separated": "Scopes value comma separated", + "find-in-table": "Find in table", "data-insight-summary": "OpenMetadata health at a glance", "data-insight-description-summary": "Percentage of Entities With Description", "data-insight-owner-summary": "Percentage of Entities With Owners", diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.test.tsx index ef7cdbc032b..f8f9deea792 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.test.tsx @@ -32,12 +32,6 @@ jest.mock('react-lazylog', () => ({ LazyLog: jest.fn().mockImplementation(() =>
LazyLog
), })); -jest.mock('react-i18next', () => ({ - useTranslation: jest.fn().mockReturnValue({ - t: (key: string) => key, - }), -})); - jest.mock('../../axiosAPIs/ingestionPipelineAPI', () => ({ getIngestionPipelineLogById: jest .fn() diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/teams/AddUsersModalV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/teams/AddUsersModalV1.tsx index b380f6cafdf..5ab3fd1041a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/teams/AddUsersModalV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/teams/AddUsersModalV1.tsx @@ -197,7 +197,7 @@ const AddUsersModalV1 = ({ className="user-list" data={uniqueUser} height={ADD_USER_CONTAINER_HEIGHT} - itemKey="user" + itemKey="id" onScroll={onScroll}> {(User) => ( ({ observe: jest.fn(), unobserve: jest.fn(), })); + +/** + * mock react-i18next + */ +jest.mock('react-i18next', () => ({ + useTranslation: jest.fn().mockReturnValue({ + t: (key) => key, + }), +})); diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index 04cc5e5f555..8b9ed2a78fb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -62,6 +62,9 @@ text-align: right; } +.text-underline { + text-decoration: underline; +} // Width .w-8 { width: 32px; @@ -177,6 +180,9 @@ align-items: center; justify-content: center; } +.flex-wrap { + flex-wrap: wrap; +} .break-word { word-break: break-word; @@ -252,10 +258,6 @@ } } -.underline { - text-decoration: underline; -} - .no-underline { text-decoration: none; } diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/position.less b/openmetadata-ui/src/main/resources/ui/src/styles/position.less index 39de4d69d56..5036cab2e1f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/position.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/position.less @@ -32,7 +32,12 @@ .d-flex { display: flex; } - +.d-inline-block { + display: inline-block; +} +.d-block { + display: block; +} .flex-1 { flex: 1; } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx index 5ff0a346e3c..29bf37904af 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx @@ -11,15 +11,18 @@ * limitations under the License. */ -import { isEmpty, isNil, isUndefined, startCase } from 'lodash'; +import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash'; import { Bucket, LeafNodes, LineagePos } from 'Models'; -import React from 'react'; +import React, { Fragment } from 'react'; +import { Link } from 'react-router-dom'; +import PopOver from '../components/common/popover/PopOver'; import { EntityData } from '../components/common/PopOverCard/EntityPopOverCard'; import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { getDatabaseDetailsPath, getServiceDetailsPath, + getTableDetailsPath, getTeamAndUserDetailsPath, } from '../constants/constants'; import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum'; @@ -28,12 +31,21 @@ import { ServiceCategory } from '../enums/service.enum'; import { PrimaryTableDataTypes } from '../enums/table.enum'; import { Dashboard } from '../generated/entity/data/dashboard'; import { Pipeline } from '../generated/entity/data/pipeline'; -import { Table } from '../generated/entity/data/table'; +import { + Column, + ColumnJoins, + JoinedWith, + Table, +} from '../generated/entity/data/table'; import { Topic } from '../generated/entity/data/topic'; import { Edge, EntityLineage } from '../generated/type/entityLineage'; import { EntityReference } from '../generated/type/entityUsage'; import { TagLabel } from '../generated/type/tagLabel'; -import { getEntityName, getPartialNameFromTableFQN } from './CommonUtils'; +import { + getEntityName, + getPartialNameFromTableFQN, + getTableFQNFromColumnFQN, +} from './CommonUtils'; import { getDataTypeString, getTierFromTableTags, @@ -400,3 +412,146 @@ export const getResourceEntityFromEntityType = (entityType: string) => { return ResourceEntity.ALL; }; + +/** + * It searches for a given text in a given table and returns a new table with only the columns that + * contain the given text + * @param {Column[]} table - Column[] - the table to search in + * @param {string} searchText - The text to search for. + * @returns An array of columns that have been searched for a specific string. + */ +export const searchInColumns = ( + table: Column[], + searchText: string +): Column[] => { + const searchedValue: Column[] = table.reduce((searchedCols, column) => { + const searchLowerCase = lowerCase(searchText); + const isContainData = + lowerCase(column.name).includes(searchLowerCase) || + lowerCase(column.description).includes(searchLowerCase) || + lowerCase(getDataTypeString(column.dataType)).includes(searchLowerCase); + + if (isContainData) { + return [...searchedCols, column]; + } else if (!isUndefined(column.children)) { + const searchedChildren = searchInColumns(column.children, searchText); + if (searchedChildren.length > 0) { + return [ + ...searchedCols, + { + ...column, + children: searchedChildren, + }, + ]; + } + } + + return searchedCols; + }, [] as Column[]); + + return searchedValue; +}; + +/** + * It checks if a column has a join + * @param {string} columnName - The name of the column you want to check if joins are available for. + * @param joins - Array + * @returns A boolean value. + */ +export const checkIfJoinsAvailable = ( + columnName: string, + joins: Array +): boolean => { + return ( + joins && + Boolean(joins.length) && + Boolean(joins.find((join) => join.columnName === columnName)) + ); +}; + +/** + * It takes a column name and a list of joins and returns the list of joinedWith for the column name + * @param {string} columnName - The name of the column you want to get the frequently joined with + * columns for. + * @param joins - Array + * @returns An array of joinedWith objects + */ +export const getFrequentlyJoinedWithColumns = ( + columnName: string, + joins: Array +): Array => { + return joins.find((join) => join.columnName === columnName)?.joinedWith || []; +}; + +export const getFrequentlyJoinedColumns = ( + columnName: string, + joins: Array, + columnLabel: string +) => { + const frequentlyJoinedWithColumns = getFrequentlyJoinedWithColumns( + columnName, + joins + ); + + return checkIfJoinsAvailable(columnName, joins) ? ( +
+ {columnLabel}: + + {frequentlyJoinedWithColumns.slice(0, 3).map((columnJoin, index) => ( + + {index > 0 && ,} + + {getPartialNameFromTableFQN( + columnJoin.fullyQualifiedName, + [FqnPart.Database, FqnPart.Table, FqnPart.Column], + FQN_SEPARATOR_CHAR + )} + + + ))} + + {frequentlyJoinedWithColumns.length > 3 && ( + + {frequentlyJoinedWithColumns + ?.slice(3) + .map((columnJoin, index) => ( + + + {getPartialNameFromTableFQN( + columnJoin?.fullyQualifiedName, + [FqnPart.Database, FqnPart.Table, FqnPart.Column] + )} + + + ))} +
+ } + position="bottom" + theme="light" + trigger="click"> + ... + + )} + + + ) : null; +};