diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java index 4c0f336241c..7d168b2da59 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java @@ -13,14 +13,24 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javax.json.JsonPatch; import org.openmetadata.schema.EntityInterface; import org.openmetadata.schema.entity.data.Container; import org.openmetadata.schema.entity.services.StorageService; -import org.openmetadata.schema.type.*; +import org.openmetadata.schema.type.Column; +import org.openmetadata.schema.type.ContainerFileFormat; +import org.openmetadata.schema.type.EntityReference; +import org.openmetadata.schema.type.Include; +import org.openmetadata.schema.type.Relationship; +import org.openmetadata.schema.type.TagLabel; +import org.openmetadata.schema.type.TaskDetails; import org.openmetadata.service.Entity; +import org.openmetadata.service.exception.CatalogExceptionMessage; +import org.openmetadata.service.resources.feeds.MessageParser; import org.openmetadata.service.resources.storages.ContainerResource; import org.openmetadata.service.util.EntityUtil; import org.openmetadata.service.util.FullyQualifiedName; +import org.openmetadata.service.util.JsonUtils; public class ContainerRepository extends EntityRepository { @@ -214,6 +224,36 @@ public class ContainerRepository extends EntityRepository { return allTags; } + @Override + public void update(TaskDetails task, MessageParser.EntityLink entityLink, String newValue, String user) + throws IOException { + // TODO move this as the first check + if (entityLink.getFieldName().equals("dataModel")) { + Container container = getByName(null, entityLink.getEntityFQN(), getFields("dataModel,tags"), Include.ALL); + Column column = + container.getDataModel().getColumns().stream() + .filter(c -> c.getName().equals(entityLink.getArrayFieldName())) + .findFirst() + .orElseThrow( + () -> + new IllegalArgumentException( + CatalogExceptionMessage.invalidFieldName("column", entityLink.getArrayFieldName()))); + + String origJson = JsonUtils.pojoToJson(container); + if (EntityUtil.isDescriptionTask(task.getType())) { + column.setDescription(newValue); + } else if (EntityUtil.isTagTask(task.getType())) { + List tags = JsonUtils.readObjects(newValue, TagLabel.class); + column.setTags(tags); + } + String updatedEntityJson = JsonUtils.pojoToJson(container); + JsonPatch patch = JsonUtils.getJsonPatch(origJson, updatedEntityJson); + patch(null, container.getId(), user, patch); + return; + } + super.update(task, entityLink, newValue, user); + } + private void addDerivedColumnTags(List columns) { if (nullOrEmpty(columns)) { return; diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java index 8b9987766f3..de030b8e7ba 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/MlModelRepository.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import javax.json.JsonPatch; import lombok.extern.slf4j.Slf4j; import org.openmetadata.schema.EntityInterface; import org.openmetadata.schema.entity.data.MlModel; @@ -37,11 +38,15 @@ import org.openmetadata.schema.type.MlFeatureSource; import org.openmetadata.schema.type.MlHyperParameter; import org.openmetadata.schema.type.Relationship; import org.openmetadata.schema.type.TagLabel; +import org.openmetadata.schema.type.TaskDetails; import org.openmetadata.service.Entity; +import org.openmetadata.service.exception.CatalogExceptionMessage; +import org.openmetadata.service.resources.feeds.MessageParser; import org.openmetadata.service.resources.mlmodels.MlModelResource; import org.openmetadata.service.util.EntityUtil; import org.openmetadata.service.util.EntityUtil.Fields; import org.openmetadata.service.util.FullyQualifiedName; +import org.openmetadata.service.util.JsonUtils; @Slf4j public class MlModelRepository extends EntityRepository { @@ -221,6 +226,35 @@ public class MlModelRepository extends EntityRepository { return allTags; } + @Override + public void update(TaskDetails task, MessageParser.EntityLink entityLink, String newValue, String user) + throws IOException { + if (entityLink.getFieldName().equals("mlFeatures")) { + MlModel mlModel = getByName(null, entityLink.getEntityFQN(), getFields("tags"), Include.ALL); + MlFeature mlFeature = + mlModel.getMlFeatures().stream() + .filter(c -> c.getName().equals(entityLink.getArrayFieldName())) + .findFirst() + .orElseThrow( + () -> + new IllegalArgumentException( + CatalogExceptionMessage.invalidFieldName("chart", entityLink.getArrayFieldName()))); + + String origJson = JsonUtils.pojoToJson(mlModel); + if (EntityUtil.isDescriptionTask(task.getType())) { + mlFeature.setDescription(newValue); + } else if (EntityUtil.isTagTask(task.getType())) { + List tags = JsonUtils.readObjects(newValue, TagLabel.class); + mlFeature.setTags(tags); + } + String updatedEntityJson = JsonUtils.pojoToJson(mlModel); + JsonPatch patch = JsonUtils.getJsonPatch(origJson, updatedEntityJson); + patch(null, mlModel.getId(), user, patch); + return; + } + super.update(task, entityLink, newValue, user); + } + private void populateService(MlModel mlModel) throws IOException { MlModelService service = Entity.getEntity(mlModel.getService(), "", Include.NON_DELETED); mlModel.setService(service.getEntityReference()); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java index 781a317316d..80e7a5998c8 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TableRepository.java @@ -695,16 +695,29 @@ public class TableRepository extends EntityRepository { public void update(TaskDetails task, EntityLink entityLink, String newValue, String user) throws IOException { validateEntityLinkFieldExists(entityLink, task.getType()); if (entityLink.getFieldName().equals("columns")) { + String columnName = entityLink.getArrayFieldName(); + String childrenName = ""; + if (entityLink.getArrayFieldName().contains(".")) { + String fieldNameWithoutQuotes = + entityLink.getArrayFieldName().substring(1, entityLink.getArrayFieldName().length() - 1); + columnName = fieldNameWithoutQuotes.substring(0, fieldNameWithoutQuotes.indexOf(".")); + childrenName = fieldNameWithoutQuotes.substring(fieldNameWithoutQuotes.lastIndexOf(".") + 1); + } Table table = getByName(null, entityLink.getEntityFQN(), getFields("columns,tags"), Include.ALL); - Column column = - table.getColumns().stream() - .filter(c -> c.getName().equals(entityLink.getArrayFieldName())) - .findFirst() - .orElseThrow( - () -> - new IllegalArgumentException( - CatalogExceptionMessage.invalidFieldName("column", entityLink.getArrayFieldName()))); - + Column column = null; + for (Column c : table.getColumns()) { + if (c.getName().equals(columnName)) { + column = c; + break; + } + } + if (childrenName != "" && column != null) { + column = getChildrenColumn(column.getChildren(), childrenName); + } + if (column == null) { + throw new IllegalArgumentException( + CatalogExceptionMessage.invalidFieldName("column", entityLink.getArrayFieldName())); + } String origJson = JsonUtils.pojoToJson(table); if (EntityUtil.isDescriptionTask(task.getType())) { column.setDescription(newValue); @@ -720,6 +733,27 @@ public class TableRepository extends EntityRepository
{ super.update(task, entityLink, newValue, user); } + private static Column getChildrenColumn(List column, String childrenName) { + Column childrenColumn = null; + for (Column col : column) { + if (col.getName().equals(childrenName)) { + childrenColumn = col; + break; + } + } + if (childrenColumn == null) { + for (int i = 0; i < column.size(); i++) { + if (column.get(i).getChildren() != null) { + childrenColumn = getChildrenColumn(column.get(i).getChildren(), childrenName); + if (childrenColumn != null) { + break; + } + } + } + } + return childrenColumn; + } + private void getColumnTags(boolean setTags, List columns) { for (Column c : listOrEmpty(columns)) { c.setTags(setTags ? getTags(c.getFullyQualifiedName()) : null); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java index 5ffc6df6bd5..7ec373de9a1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TopicRepository.java @@ -15,7 +15,10 @@ package org.openmetadata.service.jdbi3; import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; -import static org.openmetadata.service.Entity.*; +import static org.openmetadata.service.Entity.FIELD_DESCRIPTION; +import static org.openmetadata.service.Entity.FIELD_DISPLAY_NAME; +import static org.openmetadata.service.Entity.FIELD_FOLLOWERS; +import static org.openmetadata.service.Entity.FIELD_TAGS; import static org.openmetadata.service.util.EntityUtil.getSchemaField; import com.fasterxml.jackson.core.JsonProcessingException; @@ -29,6 +32,7 @@ import java.util.UUID; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.stream.Collectors; +import javax.json.JsonPatch; import org.jdbi.v3.sqlobject.transaction.Transaction; import org.openmetadata.schema.EntityInterface; import org.openmetadata.schema.entity.data.Topic; @@ -38,9 +42,12 @@ import org.openmetadata.schema.type.Field; import org.openmetadata.schema.type.Include; import org.openmetadata.schema.type.Relationship; import org.openmetadata.schema.type.TagLabel; +import org.openmetadata.schema.type.TaskDetails; import org.openmetadata.schema.type.topic.CleanupPolicy; import org.openmetadata.schema.type.topic.TopicSampleData; import org.openmetadata.service.Entity; +import org.openmetadata.service.exception.CatalogExceptionMessage; +import org.openmetadata.service.resources.feeds.MessageParser; import org.openmetadata.service.resources.topics.TopicResource; import org.openmetadata.service.security.mask.PIIMasker; import org.openmetadata.service.util.EntityUtil; @@ -264,6 +271,70 @@ public class TopicRepository extends EntityRepository { return allTags; } + @Override + public void update(TaskDetails task, MessageParser.EntityLink entityLink, String newValue, String user) + throws IOException { + if (entityLink.getFieldName().equals("messageSchema")) { + String schemaName = entityLink.getArrayFieldName(); + String childrenSchemaName = ""; + if (entityLink.getArrayFieldName().contains(".")) { + String fieldNameWithoutQuotes = + entityLink.getArrayFieldName().substring(1, entityLink.getArrayFieldName().length() - 1); + schemaName = fieldNameWithoutQuotes.substring(0, fieldNameWithoutQuotes.indexOf(".")); + childrenSchemaName = fieldNameWithoutQuotes.substring(fieldNameWithoutQuotes.lastIndexOf(".") + 1); + } + Topic topic = getByName(null, entityLink.getEntityFQN(), getFields("tags"), Include.ALL); + Field schemaField = null; + for (Field field : topic.getMessageSchema().getSchemaFields()) { + if (field.getName().equals(schemaName)) { + schemaField = field; + break; + } + } + if (childrenSchemaName != "" && schemaField != null) { + schemaField = getchildrenSchemaField(schemaField.getChildren(), childrenSchemaName); + } + if (schemaField == null) { + throw new IllegalArgumentException( + CatalogExceptionMessage.invalidFieldName("schema", entityLink.getArrayFieldName())); + } + + String origJson = JsonUtils.pojoToJson(topic); + if (EntityUtil.isDescriptionTask(task.getType())) { + schemaField.setDescription(newValue); + } else if (EntityUtil.isTagTask(task.getType())) { + List tags = JsonUtils.readObjects(newValue, TagLabel.class); + schemaField.setTags(tags); + } + String updatedEntityJson = JsonUtils.pojoToJson(topic); + JsonPatch patch = JsonUtils.getJsonPatch(origJson, updatedEntityJson); + patch(null, topic.getId(), user, patch); + return; + } + super.update(task, entityLink, newValue, user); + } + + private static Field getchildrenSchemaField(List fields, String childrenSchemaName) { + Field childrenSchemaField = null; + for (Field field : fields) { + if (field.getName().equals(childrenSchemaName)) { + childrenSchemaField = field; + break; + } + } + if (childrenSchemaField == null) { + for (int i = 0; i < fields.size(); i++) { + if (fields.get(i).getChildren() != null) { + childrenSchemaField = getchildrenSchemaField(fields.get(i).getChildren(), childrenSchemaName); + if (childrenSchemaField != null) { + break; + } + } + } + } + return childrenSchemaField; + } + public static Set getAllFieldTags(Field field) { Set tags = new HashSet<>(); if (!listOrEmpty(field.getTags()).isEmpty()) { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts index f7f2e3cc904..81bc4e68721 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts @@ -10,7 +10,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ThreadType } from 'generated/api/feed/createThread'; import { Container } from 'generated/entity/data/container'; +import { EntityFieldThreads } from 'interface/feed.interface'; import { ReactNode } from 'react'; export type CellRendered = ( @@ -24,5 +26,8 @@ export interface ContainerDataModelProps { hasDescriptionEditAccess: boolean; hasTagEditAccess: boolean; isReadOnly: boolean; + entityFqn: string; + entityFieldThreads: EntityFieldThreads[]; + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; onUpdate: (updatedDataModel: Container['dataModel']) => Promise; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx index 6baf02ebd07..e2444c01b7a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx @@ -73,6 +73,16 @@ const props = { hasTagEditAccess: true, isReadOnly: false, onUpdate: jest.fn(), + entityFqn: 's3_storage_sample.departments', + entityFieldThreads: [ + { + entityLink: + '<#E::container::s3_storage_sample.departments.finance.expenditures::dataModel::columns::department_id::description>', + count: 2, + entityField: 'dataModel::columns::department_id::description', + }, + ], + onThreadLinkSelect: jest.fn(), }; jest.mock('utils/TagsUtils', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx index 06496a86360..510b144da38 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx @@ -10,14 +10,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, Popover, Space, Typography } from 'antd'; +import { Popover, Typography } from 'antd'; import Table, { ColumnsType } from 'antd/lib/table'; -import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; -import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; import { ModalWithMarkdownEditor } from 'components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; +import TableDescription from 'components/TableDescription/TableDescription.component'; import TableTags from 'components/TableTags/TableTags.component'; import { TABLE_SCROLL_VALUE } from 'constants/Table.constants'; +import { EntityType } from 'enums/entity.enum'; import { Column, TagLabel } from 'generated/entity/data/container'; import { TagSource } from 'generated/type/tagLabel'; import { cloneDeep, isEmpty, isUndefined, map, toLower } from 'lodash'; @@ -30,10 +30,7 @@ import { } from 'utils/ContainerDetailUtils'; import { getEntityName } from 'utils/EntityUtils'; import { getTableExpandableConfig } from 'utils/TableUtils'; -import { - CellRendered, - ContainerDataModelProps, -} from './ContainerDataModel.interface'; +import { ContainerDataModelProps } from './ContainerDataModel.interface'; const ContainerDataModel: FC = ({ dataModel, @@ -41,6 +38,9 @@ const ContainerDataModel: FC = ({ hasTagEditAccess, isReadOnly, onUpdate, + entityFqn, + entityFieldThreads, + onThreadLinkSelect, }) => { const { t } = useTranslation(); @@ -84,38 +84,6 @@ const ContainerDataModel: FC = ({ setEditContainerColumnDescription(undefined); }; - const renderContainerColumnDescription: CellRendered = - (description, record, index) => { - return ( - - <> - {description ? ( - - ) : ( - - {t('label.no-entity', { - entity: t('label.description'), - })} - - )} - - {isReadOnly || !hasDescriptionEditAccess ? null : ( - - - )} - - ); - }, - [chartsPermissionsArray, handleUpdateChart] - ); - const hasEditTagAccess = (record: ChartType) => { const permissionsObject = chartsPermissionsArray?.find( (chart) => chart.id === record.id @@ -464,6 +414,12 @@ const DashboardDetails = ({ } }; + const entityFieldThreads = useMemo( + () => + getEntityFieldThreadCounts(EntityField.CHARTS, entityFieldThreadCount), + [entityFieldThreadCount, getEntityFieldThreadCounts] + ); + const tableColumn: ColumnsType = useMemo( () => [ { @@ -501,7 +457,35 @@ const DashboardDetails = ({ dataIndex: 'description', key: 'description', width: 350, - render: renderDescription, + render: (_, record, index) => { + const permissionsObject = chartsPermissionsArray?.find( + (chart) => chart.id === record.id + )?.permissions; + + const editDescriptionPermissions = + !isUndefined(permissionsObject) && + (permissionsObject.EditDescription || permissionsObject.EditAll); + + return ( + handleUpdateChart(record, index)} + onThreadLinkSelect={onThreadLinkSelect} + /> + ); + }, }, { title: t('label.tag-plural'), @@ -512,6 +496,9 @@ const DashboardDetails = ({ render: (tags: TagLabel[], record: ChartType, index: number) => { return ( + entityFieldThreads={entityFieldThreads} + entityFqn={entityFqn} + entityType={EntityType.DASHBOARD} handleTagSelection={handleChartTagSelection} hasTagEditAccess={hasEditTagAccess(record)} index={index} @@ -519,6 +506,7 @@ const DashboardDetails = ({ record={record} tags={tags} type={TagSource.Classification} + onThreadLinkSelect={onThreadLinkSelect} /> ); }, @@ -531,6 +519,9 @@ const DashboardDetails = ({ width: 300, render: (tags: TagLabel[], record: ChartType, index: number) => ( + entityFieldThreads={entityFieldThreads} + entityFqn={entityFqn} + entityType={EntityType.DASHBOARD} handleTagSelection={handleChartTagSelection} hasTagEditAccess={hasEditTagAccess(record)} index={index} @@ -538,11 +529,23 @@ const DashboardDetails = ({ record={record} tags={tags} type={TagSource.Glossary} + onThreadLinkSelect={onThreadLinkSelect} /> ), }, ], - [deleted, renderDescription, handleChartTagSelection, hasEditTagAccess] + [ + deleted, + entityFqn, + entityFieldThreads, + entityFieldThreadCount, + chartsPermissionsArray, + onThreadLinkSelect, + hasEditTagAccess, + handleUpdateChart, + handleChartTagSelection, + getEntityFieldThreadCounts, + ] ); const tabs = useMemo( @@ -649,6 +652,7 @@ const DashboardDetails = ({ entityType={EntityType.DASHBOARD} fqn={dashboardDetails?.fullyQualifiedName ?? ''} onFeedUpdate={getEntityFeedCount} + onUpdateEntityDetails={fetchDashboard} /> ), @@ -699,7 +703,6 @@ const DashboardDetails = ({ tableColumn, dashboardDetails, charts, - entityFieldTaskCount, entityFieldThreadCount, entityName, dashboardPermissions, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.interface.ts index 05be62cf3d4..d7d2e258c04 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.interface.ts @@ -28,6 +28,7 @@ export interface ChartsPermissions { export interface DashboardDetailsProps { charts: Array; dashboardDetails: Dashboard; + fetchDashboard: () => void; createThread: (data: CreateThread) => void; followDashboardHandler: () => Promise; unFollowDashboardHandler: () => Promise; 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 ed50c1d3d62..90d05896a5e 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 @@ -66,6 +66,7 @@ const dashboardDetailsProps: DashboardDetailsProps = { onDashboardUpdate: jest.fn(), versionHandler: jest.fn(), createThread: jest.fn(), + fetchDashboard: jest.fn(), }; const mockEntityPermissions = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx index 83d16d2ccc2..9724f06d89e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx @@ -31,7 +31,7 @@ import { CSMode } from 'enums/codemirror.enum'; import { EntityTabs, EntityType } from 'enums/entity.enum'; import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel'; import { EntityFieldThreadCount } from 'interface/feed.interface'; -import { isUndefined, noop, toString } from 'lodash'; +import { isUndefined, toString } from 'lodash'; import { EntityTags } from 'Models'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -46,6 +46,7 @@ import ModelTab from './ModelTab/ModelTab.component'; const DataModelDetails = ({ dataModelData, dataModelPermissions, + fetchDataModel, createThread, handleFollowDataModel, handleUpdateTags, @@ -100,7 +101,6 @@ const DataModelDetails = ({ EntityType.DASHBOARD_DATA_MODEL, dashboardDataModelFQN, setEntityFieldThreadCount, - noop, setFeedCount ); }; @@ -183,9 +183,15 @@ const DataModelDetails = ({ /> @@ -231,7 +237,6 @@ const DataModelDetails = ({ entityName, handleTagSelection, onThreadLinkSelect, - onThreadLinkSelect, handleColumnUpdateDataModel, handleUpdateDescription, getEntityFieldThreadCounts, @@ -266,6 +271,7 @@ const DataModelDetails = ({ entityType={EntityType.DASHBOARD_DATA_MODEL} fqn={dataModelData?.fullyQualifiedName ?? ''} onFeedUpdate={getEntityFeedCount} + onUpdateEntityDetails={fetchDataModel} /> ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.interface.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.interface.tsx index 94b22ac03fa..67482b098cb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.interface.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.interface.tsx @@ -21,6 +21,7 @@ import { EntityTags } from 'Models'; export interface DataModelDetailsProps { dataModelData: DashboardDataModel; dataModelPermissions: OperationPermission; + fetchDataModel: () => void; createThread: (data: CreateThread) => void; handleFollowDataModel: () => Promise; handleUpdateTags: (selectedTags?: EntityTags[]) => void; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx index 9cda0348b90..3bc8c80b476 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx @@ -10,13 +10,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, Space, Table, Typography } from 'antd'; +import { Table, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; -import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; -import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; -import { CellRendered } from 'components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface'; import { ModalWithMarkdownEditor } from 'components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; +import TableDescription from 'components/TableDescription/TableDescription.component'; import TableTags from 'components/TableTags/TableTags.component'; +import { EntityType } from 'enums/entity.enum'; import { Column } from 'generated/entity/data/dashboardDataModel'; import { TagLabel, TagSource } from 'generated/type/tagLabel'; import { cloneDeep, isUndefined, map } from 'lodash'; @@ -36,6 +35,9 @@ const ModelTab = ({ hasEditDescriptionPermission, hasEditTagsPermission, onUpdate, + entityFqn, + entityFieldThreads, + onThreadLinkSelect, }: ModelTabProps) => { const { t } = useTranslation(); const [editColumnDescription, setEditColumnDescription] = useState(); @@ -76,41 +78,6 @@ const ModelTab = ({ [editColumnDescription, data] ); - const renderColumnDescription: CellRendered = - useCallback( - (description, record, index) => { - return ( - - <> - {description ? ( - - ) : ( - - {t('label.no-entity', { - entity: t('label.description'), - })} - - )} - - {isReadOnly && !hasEditDescriptionPermission ? null : ( - showInlineEditTagButton + entityFieldThreads={entityFieldThreads} + entityFqn={entityFqn} + entityType={EntityType.MLMODEL} handleTagSelection={handleTagsChange} hasTagEditAccess={hasEditPermission} index={index} @@ -160,6 +166,7 @@ const MlModelFeaturesList = ({ record={feature} tags={feature.tags ?? []} type={TagSource.Glossary} + onThreadLinkSelect={onThreadLinkSelect} /> @@ -175,6 +182,9 @@ const MlModelFeaturesList = ({ showInlineEditTagButton + entityFieldThreads={entityFieldThreads} + entityFqn={entityFqn} + entityType={EntityType.MLMODEL} handleTagSelection={handleTagsChange} hasTagEditAccess={hasEditPermission} index={index} @@ -182,6 +192,7 @@ const MlModelFeaturesList = ({ record={feature} tags={feature.tags ?? []} type={TagSource.Classification} + onThreadLinkSelect={onThreadLinkSelect} /> @@ -195,31 +206,25 @@ const MlModelFeaturesList = ({ - - {feature.description ? ( - - ) : ( - - {t('label.no-entity', { - entity: t('label.description'), - })} - - )} - {(permissions.EditAll || - permissions.EditDescription) && ( - - + render: (_, record, index) => ( + + entityFqn={entityFqn} + entityType={EntityType.PIPELINE} + hasEditPermission={ + pipelinePermissions.EditDescription || pipelinePermissions.EditAll + } + index={index} + isReadOnly={deleted} + onClick={() => setEditTask({ task: record, index })} + onThreadLinkSelect={onThreadLinkSelect} + /> ), }, { @@ -436,6 +420,12 @@ const PipelineDetails = ({ width: 300, render: (tags, record, index) => ( + entityFieldThreads={getEntityFieldThreadCounts( + EntityField.TASKS, + entityFieldThreadCount + )} + entityFqn={entityFqn} + entityType={EntityType.PIPELINE} handleTagSelection={handleTableTagSelection} hasTagEditAccess={hasTagEditAccess} index={index} @@ -443,6 +433,7 @@ const PipelineDetails = ({ record={record} tags={tags} type={TagSource.Classification} + onThreadLinkSelect={onThreadLinkSelect} /> ), }, @@ -454,6 +445,12 @@ const PipelineDetails = ({ width: 300, render: (tags, record, index) => ( + entityFieldThreads={getEntityFieldThreadCounts( + EntityField.TASKS, + entityFieldThreadCount + )} + entityFqn={entityFqn} + entityType={EntityType.PIPELINE} handleTagSelection={handleTableTagSelection} hasTagEditAccess={hasTagEditAccess} index={index} @@ -461,6 +458,7 @@ const PipelineDetails = ({ record={record} tags={tags} type={TagSource.Glossary} + onThreadLinkSelect={onThreadLinkSelect} /> ), }, @@ -468,9 +466,14 @@ const PipelineDetails = ({ [ deleted, editTask, + entityFqn, hasTagEditAccess, pipelinePermissions, + entityFieldThreadCount, + getEntityName, + onThreadLinkSelect, handleTableTagSelection, + getEntityFieldThreadCounts, ] ); @@ -645,6 +648,7 @@ const PipelineDetails = ({ entityType={EntityType.PIPELINE} fqn={pipelineDetails?.fullyQualifiedName ?? ''} onFeedUpdate={getEntityFeedCount} + onUpdateEntityDetails={fetchPipeline} /> ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.interface.ts index 93fff0ee3a5..9e14946fbf8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.interface.ts @@ -21,6 +21,7 @@ export interface PipeLineDetailsProp { pipelineDetails: Pipeline; followers: Array; paging: Paging; + fetchPipeline: () => void; followPipelineHandler: (fetchCount: () => void) => Promise; unFollowPipelineHandler: (fetchCount: () => void) => Promise; settingsUpdateHandler: (updatedPipeline: Pipeline) => Promise; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx index fff2294b42e..af7f70b1f99 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx @@ -98,6 +98,7 @@ const PipelineDetailsProps = { pipelineTags: [], slashedPipelineName: [], taskUpdateHandler: mockTaskUpdateHandler, + fetchPipeline: jest.fn(), setActiveTabHandler: jest.fn(), followPipelineHandler: jest.fn(), unFollowPipelineHandler: jest.fn(), 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 e25540be3d6..cf60baff4dd 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 @@ -15,7 +15,7 @@ import { t } from 'i18next'; import { lowerCase } from 'lodash'; import React, { Fragment, FunctionComponent, useState } from 'react'; import Searchbar from '../common/searchbar/Searchbar'; -import EntityTableV1 from '../EntityTable/EntityTable.component'; +import SchemaTable from '../SchemaTable/SchemaTable.component'; import { Props } from './SchemaTab.interfaces'; const SchemaTab: FunctionComponent = ({ @@ -27,11 +27,9 @@ const SchemaTab: FunctionComponent = ({ hasTagEditAccess, entityFieldThreads, onThreadLinkSelect, - onEntityFieldSelect, isReadOnly = false, entityFqn, tableConstraints, - entityFieldTasks, }: Props) => { const [searchText, setSearchText] = useState(''); @@ -51,9 +49,8 @@ const SchemaTab: FunctionComponent = ({ /> - = ({ searchText={lowerCase(searchText)} tableColumns={columns} tableConstraints={tableConstraints} - onEntityFieldSelect={onEntityFieldSelect} onThreadLinkSelect={onThreadLinkSelect} onUpdate={onUpdate} /> 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 index 451913a54e7..f9120a12ab1 100644 --- 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 @@ -25,13 +25,11 @@ export type Props = { columnName: string; tableConstraints: Table['tableConstraints']; sampleData?: TableData; - hasDescriptionEditAccess?: boolean; + hasDescriptionEditAccess: boolean; hasTagEditAccess: boolean; isReadOnly?: boolean; - entityFqn?: string; - entityFieldThreads?: EntityFieldThreads[]; - entityFieldTasks?: EntityFieldThreads[]; - onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; - onEntityFieldSelect?: (value: string) => void; + entityFqn: string; + entityFieldThreads: EntityFieldThreads[]; + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; onUpdate: (columns: Table['columns']) => Promise; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.test.tsx index a69dac5336c..2dae44938bb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.test.tsx @@ -69,8 +69,8 @@ jest.mock('../SampleDataTable/SampleDataTable.component', () => { return jest.fn().mockReturnValue(

SampleDataTable

); }); -jest.mock('../EntityTable/EntityTable.component', () => { - return jest.fn().mockReturnValue(

EntityTableV1

); +jest.mock('../SchemaTable/SchemaTable.component', () => { + return jest.fn().mockReturnValue(

SchemaTable

); }); const mockTableConstraints = [ @@ -88,9 +88,20 @@ describe('Test SchemaTab Component', () => { hasTagEditAccess columnName="columnName" columns={mockColumns} + entityFieldThreads={[ + { + entityLink: + '<#E::table::sample_data.ecommerce_db.shopify.raw_customer::columns::comments::tags>', + count: 4, + entityField: 'columns::comments::tags', + }, + ]} + entityFqn="mlflow_svc.eta_predictions" + isReadOnly={false} joins={mockjoins} sampleData={mockSampleData} tableConstraints={mockTableConstraints} + onThreadLinkSelect={jest.fn()} onUpdate={mockUpdate} />, { @@ -101,7 +112,7 @@ describe('Test SchemaTab Component', () => { expect(searchBar).toBeInTheDocument(); - const schemaTable = getByText(container, /EntityTable/i); + const schemaTable = getByText(container, /SchemaTable/i); expect(schemaTable).toBeInTheDocument(); expect(queryByTestId('sample-data-table')).toBeNull(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx similarity index 60% rename from openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx rename to openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx index ddd33dcdaa6..0e150ee3b97 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.component.tsx @@ -11,12 +11,11 @@ * limitations under the License. */ -import { Button, Popover, Space, Table, Typography } from 'antd'; +import { Popover, Space, Table, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; -import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg'; import FilterTablePlaceHolder from 'components/common/error-with-placeholder/FilterTablePlaceHolder'; +import TableDescription from 'components/TableDescription/TableDescription.component'; import TableTags from 'components/TableTags/TableTags.component'; -import { DE_ACTIVE_COLOR } from 'constants/constants'; import { TABLE_SCROLL_VALUE } from 'constants/Table.constants'; import { LabelType, State, TagSource } from 'generated/type/schema'; import { @@ -30,42 +29,25 @@ import { toLower, } from 'lodash'; import { EntityTags, TagOption } from 'Models'; -import React, { Fragment, useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useHistory } from 'react-router-dom'; -import { ReactComponent as IconRequest } from '../../assets/svg/request-icon.svg'; -import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; -import { EntityField } from '../../constants/Feeds.constants'; -import { EntityType, FqnPart } from '../../enums/entity.enum'; +import { EntityType } from '../../enums/entity.enum'; import { Column } from '../../generated/entity/data/table'; -import { ThreadType } from '../../generated/entity/feed/thread'; import { TagLabel } from '../../generated/type/tagLabel'; -import { EntityFieldThreads } from '../../interface/feed.interface'; -import { getPartialNameFromTableFQN } from '../../utils/CommonUtils'; import { - ENTITY_LINK_SEPARATOR, getEntityName, getFrequentlyJoinedColumns, } from '../../utils/EntityUtils'; -import { getFieldThreadElement } from '../../utils/FeedElementUtils'; import { getDataTypeString, getTableExpandableConfig, makeData, prepareConstraintIcon, } from '../../utils/TableUtils'; -import { - getRequestDescriptionPath, - getRequestTagsPath, - getUpdateDescriptionPath, - getUpdateTagsPath, -} from '../../utils/TasksUtils'; -import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer'; import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; -import { EntityTableProps, TableCellRendered } from './EntityTable.interface'; -import './EntityTable.style.less'; +import { SchemaTableProps, TableCellRendered } from './SchemaTable.interface'; -const EntityTable = ({ +const SchemaTable = ({ tableColumns, searchText, onUpdate, @@ -77,9 +59,7 @@ const EntityTable = ({ onThreadLinkSelect, entityFqn, tableConstraints, - entityFieldTasks, -}: EntityTableProps) => { - const history = useHistory(); +}: SchemaTableProps) => { const { t } = useTranslation(); const [searchedColumns, setSearchedColumns] = useState([]); @@ -233,96 +213,10 @@ const EntityTable = ({ return searchedValue; }; - const getColumnName = (cell: Column) => { - const fqn = cell?.fullyQualifiedName || ''; - const columnName = getPartialNameFromTableFQN(fqn, [FqnPart.NestedColumn]); - // wrap it in quotes if dot is present - - return columnName.includes(FQN_SEPARATOR_CHAR) - ? `"${columnName}"` - : columnName; - }; - - const onRequestDescriptionHandler = (cell: Column) => { - const field = EntityField.COLUMNS; - const value = getColumnName(cell); - history.push( - getRequestDescriptionPath( - EntityType.TABLE, - entityFqn as string, - field, - value - ) - ); - }; - - const onUpdateDescriptionHandler = (cell: Column) => { - const field = EntityField.COLUMNS; - const value = getColumnName(cell); - history.push( - getUpdateDescriptionPath( - EntityType.TABLE, - entityFqn as string, - field, - value - ) - ); - }; - - const onRequestTagsHandler = (cell: Column) => { - const field = EntityField.COLUMNS; - const value = getColumnName(cell); - history.push( - getRequestTagsPath(EntityType.TABLE, entityFqn as string, field, value) - ); - }; - - const onUpdateTagsHandler = (cell: Column) => { - const field = EntityField.COLUMNS; - const value = getColumnName(cell); - history.push( - getUpdateTagsPath(EntityType.TABLE, entityFqn as string, field, value) - ); - }; - const handleUpdate = (column: Column, index: number) => { handleEditColumn(column, index); }; - const getRequestDescriptionElement = (cell: Column) => { - const hasDescription = Boolean(cell?.description ?? ''); - - return ( - - ); - }; - const renderDataTypeDisplay: TableCellRendered = ( dataTypeDisplay, record @@ -355,92 +249,35 @@ const EntityTable = ({ }; const renderDescription: TableCellRendered = ( - description, + _, record, index ) => { return ( -
-
- -
- {description ? ( - - ) : ( - - {t('label.no-entity', { - entity: t('label.description'), - })} - - )} -
-
- {!isReadOnly ? ( - - {hasDescriptionEditAccess && ( - <> - - - )} - {getRequestDescriptionElement(record)} - {getFieldThreadElement( - getColumnName(record), - EntityField.DESCRIPTION, - entityFieldThreads as EntityFieldThreads[], - onThreadLinkSelect, - EntityType.TABLE, - entityFqn, - `columns${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}description`, - Boolean(record) - )} - {getFieldThreadElement( - getColumnName(record), - EntityField.DESCRIPTION, - entityFieldTasks as EntityFieldThreads[], - onThreadLinkSelect, - EntityType.TABLE, - entityFqn, - `columns${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}description`, - Boolean(record), - ThreadType.Task - )} - - ) : null} -
-
-
+ <> + handleUpdate(record, index)} + onThreadLinkSelect={onThreadLinkSelect} + /> {getFrequentlyJoinedColumns( record?.name, joins, t('label.frequently-joined-column-plural') )} -
+ ); }; - const getColumnFieldFQN = (record: Column) => - `${EntityField.COLUMNS}${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}${EntityField.TAGS}`; - const columns: ColumnsType = useMemo( () => [ { @@ -487,8 +324,7 @@ const EntityTable = ({ entityFieldThreads={entityFieldThreads} entityFqn={entityFqn} - getColumnFieldFQN={getColumnFieldFQN(record)} - getColumnName={getColumnName} + entityType={EntityType.TABLE} handleTagSelection={handleTagSelection} hasTagEditAccess={hasTagEditAccess} index={index} @@ -496,9 +332,7 @@ const EntityTable = ({ record={record} tags={tags} type={TagSource.Classification} - onRequestTagsHandler={onRequestTagsHandler} onThreadLinkSelect={onThreadLinkSelect} - onUpdateTagsHandler={onUpdateTagsHandler} /> ), }, @@ -512,8 +346,7 @@ const EntityTable = ({ entityFieldThreads={entityFieldThreads} entityFqn={entityFqn} - getColumnFieldFQN={getColumnFieldFQN(record)} - getColumnName={getColumnName} + entityType={EntityType.TABLE} handleTagSelection={handleTagSelection} hasTagEditAccess={hasTagEditAccess} index={index} @@ -521,9 +354,7 @@ const EntityTable = ({ record={record} tags={tags} type={TagSource.Glossary} - onRequestTagsHandler={onRequestTagsHandler} onThreadLinkSelect={onThreadLinkSelect} - onUpdateTagsHandler={onUpdateTagsHandler} /> ), }, @@ -531,7 +362,6 @@ const EntityTable = ({ [ entityFqn, isReadOnly, - entityFieldTasks, entityFieldThreads, tableConstraints, hasTagEditAccess, @@ -539,10 +369,7 @@ const EntityTable = ({ handleTagSelection, renderDataTypeDisplay, renderDescription, - getColumnName, handleTagSelection, - onRequestTagsHandler, - onUpdateTagsHandler, onThreadLinkSelect, ] ); @@ -592,4 +419,4 @@ const EntityTable = ({ ); }; -export default EntityTable; +export default SchemaTable; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.interface.ts similarity index 81% rename from openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts rename to openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.interface.ts index dfd2db1b413..db7a12f8874 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.interface.ts @@ -16,21 +16,19 @@ import { ThreadType } from '../../generated/api/feed/createThread'; import { Column, ColumnJoins, Table } from '../../generated/entity/data/table'; import { EntityFieldThreads } from '../../interface/feed.interface'; -export interface EntityTableProps { +export interface SchemaTableProps { tableColumns: Column[]; joins: Array; columnName: string; - hasDescriptionEditAccess?: boolean; + hasDescriptionEditAccess: boolean; hasTagEditAccess: boolean; tableConstraints: Table['tableConstraints']; searchText?: string; isReadOnly?: boolean; - entityFqn?: string; - entityFieldThreads?: EntityFieldThreads[]; - entityFieldTasks?: EntityFieldThreads[]; + entityFqn: string; + entityFieldThreads: EntityFieldThreads[]; onUpdate: (columns: Column[]) => Promise; - onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; - onEntityFieldSelect?: (value: string) => void; + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; } export type TableCellRendered = ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx similarity index 66% rename from openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx rename to openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx index 47beed65649..16a47c085ef 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTable/SchemaTable.test.tsx @@ -11,13 +11,13 @@ * limitations under the License. */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import { Column } from 'generated/entity/data/container'; import { TagOption } from 'Models'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { Column } from '../../generated/api/data/createTable'; import { Table } from '../../generated/entity/data/table'; -import EntityTableV1 from './EntityTable.component'; +import EntityTableV1 from './SchemaTable.component'; const onEntityFieldSelect = jest.fn(); const onThreadLinkSelect = jest.fn(); @@ -67,46 +67,12 @@ const mockEntityTableProp = { constraint: 'NULL', ordinalPosition: 3, }, - { - name: 'store_address', - dataType: 'ARRAY', - arrayDataType: 'STRUCT', - dataLength: 1, - dataTypeDisplay: - 'array>', - fullyQualifiedName: - 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.store_address', - tags: [], - constraint: 'NULL', - ordinalPosition: 4, - }, - { - name: 'first_order_date', - dataType: 'TIMESTAMP', - dataTypeDisplay: 'timestamp', - description: - 'The date (ISO 8601) and time (UTC) when the customer placed their first order. The format is YYYY-MM-DD HH:mm:ss (for example, 2016-02-05 17:04:01).', - fullyQualifiedName: - 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.first_order_date', - tags: [], - ordinalPosition: 5, - }, - { - name: 'last_order_date', - dataType: 'TIMESTAMP', - dataTypeDisplay: 'timestamp', - description: - 'The date (ISO 8601) and time (UTC) when the customer placed their most recent order. The format is YYYY-MM-DD HH:mm:ss (for example, 2016-02-05 17:04:01).', - fullyQualifiedName: - 'bigquery_gcp.ecommerce.shopify.raw_product_catalog.last_order_date', - tags: [], - ordinalPosition: 6, - }, ] as Column[], searchText: '', hasEditAccess: false, joins: [], entityFieldThreads: [], + hasDescriptionEditAccess: true, isReadOnly: false, entityFqn: 'bigquery_gcp.ecommerce.shopify.raw_product_catalog', owner: {} as Table['owner'], @@ -135,6 +101,13 @@ jest.mock('../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor', () => ({ ModalWithMarkdownEditor: jest.fn().mockReturnValue(

EditorModal

), })); +jest.mock( + 'components/common/error-with-placeholder/FilterTablePlaceHolder', + () => { + return jest.fn().mockReturnValue(

FilterTablePlaceHolder

); + } +); + jest.mock('components/Tag/TagsContainer/tags-container', () => { return jest.fn().mockImplementation(({ tagList }) => { return ( @@ -165,17 +138,22 @@ jest.mock('../../utils/GlossaryUtils', () => ({ getGlossaryTermHierarchy: jest.fn().mockReturnValue([]), })); -jest.mock( - 'components/common/error-with-placeholder/FilterTablePlaceHolder', - () => { - return jest.fn().mockReturnValue(

FilterTablePlaceHolder

); - } -); - jest.mock('components/TableTags/TableTags.component', () => { return jest.fn().mockReturnValue(

TableTags

); }); +jest.mock('components/TableDescription/TableDescription.component', () => { + return jest.fn().mockReturnValue(

TableDescription

); +}); + +const mockTableScrollValue = jest.fn(); + +jest.mock('constants/Table.constants', () => ({ + get TABLE_SCROLL_VALUE() { + return mockTableScrollValue(); + }, +})); + describe('Test EntityTable Component', () => { it('Initially, Table should load', async () => { render(, { @@ -184,45 +162,36 @@ describe('Test EntityTable Component', () => { const entityTable = await screen.findByTestId('entity-table'); + screen.debug(entityTable); + expect(entityTable).toBeInTheDocument(); }); - it('should render request description button', async () => { + it('Should render tags and description components', async () => { render(, { wrapper: MemoryRouter, }); + const tableTags = screen.getAllByText('TableTags'); + + expect(tableTags).toHaveLength(6); + + const tableDescription = screen.getAllByText('TableDescription'); + + expect(tableDescription).toHaveLength(3); + }); + + it('Table should load empty when no data present', async () => { + render(, { + wrapper: MemoryRouter, + }); + const entityTable = await screen.findByTestId('entity-table'); expect(entityTable).toBeInTheDocument(); - const requestDescriptionButton = await screen.findAllByTestId( - 'request-description' - ); + const emptyPlaceholder = screen.getByText('FilterTablePlaceHolder'); - expect(requestDescriptionButton[0]).toBeInTheDocument(); - }); - - it('Should render start thread button', async () => { - render(, { - wrapper: MemoryRouter, - }); - - const entityTable = await screen.findByTestId('entity-table'); - - expect(entityTable).toBeInTheDocument(); - - const startThreadButton = await screen.findAllByTestId( - 'start-field-thread' - ); - - expect(startThreadButton[0]).toBeInTheDocument(); - - fireEvent.click( - startThreadButton[0], - new MouseEvent('click', { bubbles: true, cancelable: true }) - ); - - expect(onThreadLinkSelect).toHaveBeenCalled(); + expect(emptyPlaceholder).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableDescription/TableDescription.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableDescription/TableDescription.component.tsx new file mode 100644 index 00000000000..97e9f6fd3f2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableDescription/TableDescription.component.tsx @@ -0,0 +1,78 @@ +/* + * Copyright 2023 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 { Space } from 'antd'; +import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; +import { DE_ACTIVE_COLOR } from 'constants/constants'; +import EntityTaskDescription from 'pages/TasksPage/EntityTaskDescription/EntityTaskDescription.component'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ReactComponent as EditIcon } from '../../assets/svg/edit-new.svg'; +import { TableDescriptionProps } from './TableDescription.interface'; + +const TableDescription = ({ + index, + columnData, + entityFqn, + isReadOnly, + onClick, + entityType, + hasEditPermission, + entityFieldThreads, + onThreadLinkSelect, +}: TableDescriptionProps) => { + const { t } = useTranslation(); + + return ( + + {columnData.description ? ( + + ) : ( + + {t('label.no-entity', { + entity: t('label.description'), + })} + + )} + {!isReadOnly ? ( + + {hasEditPermission && ( + + )} + + + + ) : null} + + ); +}; + +export default TableDescription; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableDescription/TableDescription.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/TableDescription/TableDescription.interface.ts new file mode 100644 index 00000000000..2304839161d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableDescription/TableDescription.interface.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2023 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 { EntityType } from 'enums/entity.enum'; +import { ThreadType } from 'generated/entity/feed/thread'; +import { EntityFieldThreads } from 'interface/feed.interface'; + +export interface TableDescriptionProps { + index: number; + columnData: { + fqn: string; + description?: string; + }; + entityFqn: string; + entityType: EntityType; + hasEditPermission: boolean; + entityFieldThreads: EntityFieldThreads[]; + isReadOnly?: boolean; + onClick: () => void; + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx index 9e4f8f1c7dd..cf12abf88f6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx @@ -11,19 +11,10 @@ * limitations under the License. */ -import { Button, Tooltip } from 'antd'; import classNames from 'classnames'; import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2'; -import { DE_ACTIVE_COLOR } from 'constants/constants'; -import { EntityField } from 'constants/Feeds.constants'; -import { EntityType } from 'enums/entity.enum'; -import { TagSource } from 'generated/type/tagLabel'; -import { EntityFieldThreads } from 'interface/feed.interface'; -import { isEmpty } from 'lodash'; -import React, { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { getFieldThreadElement } from 'utils/FeedElementUtils'; -import { ReactComponent as IconRequest } from '../../assets/svg/request-icon.svg'; +import EntityTaskTags from 'pages/TasksPage/EntityTaskTags/EntityTaskTags.component'; +import React from 'react'; import { TableTagsComponentProps, TableUnion } from './TableTags.interface'; const TableTags = ({ @@ -35,64 +26,11 @@ const TableTags = ({ isReadOnly, hasTagEditAccess, entityFieldThreads, - getColumnFieldFQN, showInlineEditTagButton, - getColumnName, - onUpdateTagsHandler, - onRequestTagsHandler, onThreadLinkSelect, handleTagSelection, + entityType, }: TableTagsComponentProps) => { - const { t } = useTranslation(); - - const hasTagOperationAccess = useMemo( - () => - getColumnFieldFQN && - getColumnName && - onUpdateTagsHandler && - onRequestTagsHandler, - [ - getColumnFieldFQN, - getColumnName, - onUpdateTagsHandler, - onRequestTagsHandler, - ] - ); - - const getRequestTagsElement = useMemo(() => { - const hasTags = !isEmpty(record.tags || []); - - return ( - -
- + + handleTagsTask(hasTags)} + /> + - ) : null; - }, [tags?.[tagType], handleUpdateTags, handleRequestTags]); + ); + }, [tags?.[tagType], handleTagsTask]); const conversationThreadElement = useMemo( () => ( - + + + onThreadLinkSelect?.( + entityThreadLink ?? + getEntityFeedLink(entityType, entityFqn, 'tags') + ) + } + /> + ), [ @@ -281,22 +267,23 @@ const TagsContainerV2 = ({ {isGlossaryType ? t('label.glossary-term') : t('label.tag-plural')} {permission && ( - + {!isEmpty(tags?.[tagType]) && !isEditTags && ( - + + )} - {permission && !isVersionView && ( - + {showTaskHandler && ( + <> {tagType === TagSource.Classification && requestTagElement} {onThreadLinkSelect && conversationThreadElement} - + )} )} @@ -309,7 +296,7 @@ const TagsContainerV2 = ({ showHeader, isEditTags, permission, - isVersionView, + showTaskHandler, isGlossaryType, requestTagElement, conversationThreadElement, @@ -318,19 +305,13 @@ const TagsContainerV2 = ({ const editTagButton = useMemo( () => permission && !isEmpty(tags?.[tagType]) ? ( - - ); - }; - - const renderFieldDescription: CellRendered = ( - description, - record, - index - ) => { - return ( - -
- {description ? ( - - ) : ( - - {t('label.no-entity', { - entity: t('label.description'), - })} - - )} -
-
- {!isReadOnly ? ( - - {hasDescriptionEditAccess && ( - <> - - - )} - {getRequestDescriptionElement(record)} - {getFieldThreadElement( - getColumnName(record), - EntityField.DESCRIPTION, - entityFieldThreads as EntityFieldThreads[], - onThreadLinkSelect, - EntityType.TOPIC, - entityFqn, - `columns${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}description`, - Boolean(record) - )} - {getFieldThreadElement( - getColumnName(record), - EntityField.DESCRIPTION, - entityFieldTasks as EntityFieldThreads[], - onThreadLinkSelect, - EntityType.TOPIC, - entityFqn, - `columns${ENTITY_LINK_SEPARATOR}${getColumnName( - record - )}${ENTITY_LINK_SEPARATOR}description`, - Boolean(record), - ThreadType.Task - )} - - ) : null} -
-
- ); - }; - const columns: ColumnsType = useMemo( () => [ { @@ -339,7 +178,22 @@ const TopicSchemaFields: FC = ({ dataIndex: 'description', key: 'description', width: 350, - render: renderFieldDescription, + render: (_, record, index) => ( + setEditFieldDescription(record)} + onThreadLinkSelect={onThreadLinkSelect} + /> + ), }, { title: t('label.tag-plural'), @@ -349,6 +203,9 @@ const TopicSchemaFields: FC = ({ width: 300, render: (tags: TagLabel[], record: Field, index: number) => ( + entityFieldThreads={entityFieldThreads} + entityFqn={entityFqn} + entityType={EntityType.TOPIC} handleTagSelection={handleFieldTagsChange} hasTagEditAccess={hasTagEditAccess} index={index} @@ -356,6 +213,7 @@ const TopicSchemaFields: FC = ({ record={record} tags={tags} type={TagSource.Classification} + onThreadLinkSelect={onThreadLinkSelect} /> ), }, @@ -367,6 +225,9 @@ const TopicSchemaFields: FC = ({ width: 300, render: (tags: TagLabel[], record: Field, index: number) => ( + entityFieldThreads={entityFieldThreads} + entityFqn={entityFqn} + entityType={EntityType.TOPIC} handleTagSelection={handleFieldTagsChange} hasTagEditAccess={hasTagEditAccess} index={index} @@ -374,6 +235,7 @@ const TopicSchemaFields: FC = ({ record={record} tags={tags} type={TagSource.Glossary} + onThreadLinkSelect={onThreadLinkSelect} /> ), }, @@ -453,26 +315,24 @@ const TopicSchemaFields: FC = ({ /> ) ) : ( - <> -
(), - rowExpandable: (record) => !isEmpty(record.children), - onExpandedRowsChange: handleExpandedRowsChange, - defaultExpandAllRows, - expandedRowKeys, - }} - pagination={false} - rowKey="name" - scroll={TABLE_SCROLL_VALUE} - size="small" - /> - +
(), + rowExpandable: (record) => !isEmpty(record.children), + onExpandedRowsChange: handleExpandedRowsChange, + defaultExpandAllRows, + expandedRowKeys, + }} + pagination={false} + rowKey="name" + scroll={TABLE_SCROLL_VALUE} + size="small" + /> )} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx index 6bf3c2591e5..69c3795422b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicVersion/TopicVersion.component.tsx @@ -26,6 +26,7 @@ import { EntityField } from 'constants/Feeds.constants'; import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum'; import { EntityTabs, EntityType } from 'enums/entity.enum'; import { TagSource } from 'generated/type/tagLabel'; +import { noop } from 'lodash'; import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory, useParams } from 'react-router-dom'; @@ -127,11 +128,13 @@ const TopicVersion: FC = ({ @@ -143,7 +146,6 @@ const TopicVersion: FC = ({ {Object.keys(TagSource).map((tagType) => ( { const [entityFieldThreadCount, setEntityFieldThreadCount] = useState< EntityFieldThreadCount[] >([]); - const [entityFieldTaskCount, setEntityFieldTaskCount] = useState< - EntityFieldThreadCount[] - >([]); const [threadLink, setThreadLink] = useState(''); const [threadType, setThreadType] = useState( @@ -238,6 +235,7 @@ const ContainerPage = () => { isUserFollowing, tags, tier, + entityFqn, } = useMemo(() => { return { deleted: containerData?.deleted, @@ -255,6 +253,7 @@ const ContainerPage = () => { size: containerData?.size || 0, numberOfObjects: containerData?.numberOfObjects || 0, partitioned: containerData?.dataModel?.isPartitioned, + entityFqn: containerData?.fullyQualifiedName ?? '', }; }, [containerData]); @@ -269,7 +268,6 @@ const ContainerPage = () => { EntityType.CONTAINER, containerName, setEntityFieldThreadCount, - setEntityFieldTaskCount, setFeedCount ); }; @@ -603,9 +601,15 @@ const ContainerPage = () => { @@ -748,7 +752,6 @@ const ContainerPage = () => { entityFieldThreadCount, tags, entityLineage, - entityFieldTaskCount, feedCount, containerChildrenData, handleAddLineage, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx index 04d0fef8fed..1085d06fb27 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx @@ -312,6 +312,7 @@ const DashboardDetailsPage = () => { charts={charts} createThread={createThread} dashboardDetails={dashboardDetails} + fetchDashboard={() => fetchDashboardDetail(dashboardFQN)} followDashboardHandler={followDashboard} unFollowDashboardHandler={unFollowDashboard} versionHandler={versionHandler} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx index f7d95741b40..964b9eee726 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx @@ -321,6 +321,7 @@ const DataModelsPage = () => { createThread={createThread} dataModelData={dataModelData} dataModelPermissions={dataModelPermissions} + fetchDataModel={() => fetchDataModelDetails(dashboardDataModelFQN)} handleColumnUpdateDataModel={handleColumnUpdateDataModel} handleFollowDataModel={handleFollowDataModel} handleUpdateDescription={handleUpdateDescription} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx index b8cffec76ed..f9f286bed1f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/MlModelPage/MlModelPage.component.tsx @@ -291,6 +291,7 @@ const MlModelPage = () => { fetchMlModelDetails(mlModelFqn)} followMlModelHandler={followMlModel} mlModelDetail={mlModelDetail} settingsUpdateHandler={settingsUpdateHandler} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx index 2592edbc6af..f0b4636b761 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx @@ -259,6 +259,7 @@ const PipelineDetailsPage = () => { return ( fetchPipelineDetail(pipelineFQN)} followPipelineHandler={followPipeline} followers={followers} paging={paging} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx index 5ea30859466..3150f511ed9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx @@ -103,9 +103,6 @@ const TableDetailsPageV1 = () => { const [entityFieldThreadCount, setEntityFieldThreadCount] = useState< EntityFieldThreadCount[] >([]); - const [entityFieldTaskCount, setEntityFieldTaskCount] = useState< - EntityFieldThreadCount[] - >([]); const [isEdit, setIsEdit] = useState(false); const [threadLink, setThreadLink] = useState(''); const [threadType, setThreadType] = useState( @@ -281,7 +278,6 @@ const TableDetailsPageV1 = () => { EntityType.TABLE, datasetFQN, setEntityFieldThreadCount, - setEntityFieldTaskCount, setFeedCount ); }; @@ -460,10 +456,6 @@ const TableDetailsPageV1 = () => { FQN_SEPARATOR_CHAR )} columns={tableDetails?.columns ?? []} - entityFieldTasks={getEntityFieldThreadCounts( - EntityField.COLUMNS, - entityFieldTaskCount - )} entityFieldThreads={getEntityFieldThreadCounts( EntityField.COLUMNS, entityFieldThreadCount diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskDescription/EntityTaskDescription.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskDescription/EntityTaskDescription.component.tsx new file mode 100644 index 00000000000..ecedf2e2a63 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskDescription/EntityTaskDescription.component.tsx @@ -0,0 +1,106 @@ +/* + * Copyright 2023 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 { Space, Tooltip } from 'antd'; +import { FQN_SEPARATOR_CHAR } from 'constants/char.constants'; +import { DE_ACTIVE_COLOR } from 'constants/constants'; +import { EntityField } from 'constants/Feeds.constants'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHistory } from 'react-router-dom'; +import { getPartialNameFromTableFQN } from 'utils/CommonUtils'; +import { ENTITY_LINK_SEPARATOR } from 'utils/EntityUtils'; +import { getFieldThreadElement } from 'utils/FeedElementUtils'; +import { + getEntityTaskDetails, + getRequestDescriptionPath, + getUpdateDescriptionPath, +} from 'utils/TasksUtils'; +import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg'; +import { EntityTaskDescriptionProps } from './entityTaskDescription.interface'; + +const EntityTaskDescription = ({ + entityFqn, + entityType, + data, + onThreadLinkSelect, + entityFieldThreads, +}: EntityTaskDescriptionProps) => { + const history = useHistory(); + const { t } = useTranslation(); + + const { fqnPart, entityField } = useMemo( + () => getEntityTaskDetails(entityType), + [entityType] + ); + + const columnName = useMemo(() => { + const columnName = getPartialNameFromTableFQN(data.fqn ?? '', fqnPart); + + return columnName.includes(FQN_SEPARATOR_CHAR) + ? `"${columnName}"` + : columnName; + }, [data.fqn]); + + const handleDescriptionTask = (hasDescription: boolean) => { + history.push( + (hasDescription ? getUpdateDescriptionPath : getRequestDescriptionPath)( + entityType, + entityFqn, + entityField, + columnName + ) + ); + }; + + const requestDescriptionElement = useMemo(() => { + const hasDescription = Boolean(data?.description); + + return ( + + handleDescriptionTask(hasDescription)} + /> + + ); + }, [data]); + + return ( + + {requestDescriptionElement} + {getFieldThreadElement( + columnName, + EntityField.DESCRIPTION, + entityFieldThreads, + onThreadLinkSelect, + entityType, + entityFqn, + `${entityField}${ENTITY_LINK_SEPARATOR}${columnName}${ENTITY_LINK_SEPARATOR}${EntityField.DESCRIPTION}` + )} + + ); +}; + +export default EntityTaskDescription; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.style.less b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskDescription/entityTaskDescription.interface.ts similarity index 54% rename from openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.style.less rename to openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskDescription/entityTaskDescription.interface.ts index 412f08cfbdb..04cdbeefa49 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.style.less +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskDescription/entityTaskDescription.interface.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * Copyright 2023 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 @@ -11,13 +11,17 @@ * limitations under the License. */ -.hover-icon-group { - .hover-cell-icon { - opacity: 0; - } - &:hover { - .hover-cell-icon { - opacity: 100; - } - } +import { EntityType } from 'enums/entity.enum'; +import { ThreadType } from 'generated/api/feed/createThread'; +import { EntityFieldThreads } from 'interface/feed.interface'; + +export interface EntityTaskDescriptionProps { + data: { + fqn?: string; + description?: string; + }; + entityFqn: string; + entityType: EntityType; + entityFieldThreads: EntityFieldThreads[]; + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskTags/EntityTaskTags.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskTags/EntityTaskTags.component.tsx new file mode 100644 index 00000000000..cb1a7350dff --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskTags/EntityTaskTags.component.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2023 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 { Space, Tooltip } from 'antd'; +import { FQN_SEPARATOR_CHAR } from 'constants/char.constants'; +import { DE_ACTIVE_COLOR } from 'constants/constants'; +import { EntityField } from 'constants/Feeds.constants'; +import { TagSource } from 'generated/type/tagLabel'; +import { isEmpty } from 'lodash'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHistory } from 'react-router-dom'; +import { getPartialNameFromTableFQN } from 'utils/CommonUtils'; +import { ENTITY_LINK_SEPARATOR } from 'utils/EntityUtils'; +import { getFieldThreadElement } from 'utils/FeedElementUtils'; +import { + getEntityTaskDetails, + getRequestTagsPath, + getUpdateTagsPath, +} from 'utils/TasksUtils'; +import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg'; +import { EntityTaskTagsProps } from './EntityTaskTags.interface'; + +const EntityTaskTags = ({ + data, + tagSource, + entityFqn, + entityType, + entityFieldThreads, + onThreadLinkSelect, +}: EntityTaskTagsProps) => { + const { t } = useTranslation(); + const history = useHistory(); + + const { fqnPart, entityField } = useMemo( + () => getEntityTaskDetails(entityType), + [entityType] + ); + + const columnName = useMemo(() => { + const columnName = getPartialNameFromTableFQN(data.fqn ?? '', fqnPart); + + return columnName.includes(FQN_SEPARATOR_CHAR) + ? `"${columnName}"` + : columnName; + }, [data.fqn]); + + const handleTagTask = (hasTags: boolean) => { + history.push( + (hasTags ? getUpdateTagsPath : getRequestTagsPath)( + entityType, + entityFqn, + entityField, + columnName + ) + ); + }; + + const getRequestTagsElement = useMemo(() => { + const hasTags = !isEmpty(data.tags); + + return ( + + handleTagTask(hasTags)} + /> + + ); + }, [data]); + + return ( + + {/* Request and Update tags */} + {tagSource === TagSource.Classification && getRequestTagsElement} + + {/* List Conversation */} + {getFieldThreadElement( + columnName, + EntityField.TAGS, + entityFieldThreads, + onThreadLinkSelect, + entityType, + entityFqn, + `${entityField}${ENTITY_LINK_SEPARATOR}${columnName}${ENTITY_LINK_SEPARATOR}${EntityField.TAGS}` + )} + + ); +}; + +export default EntityTaskTags; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskTags/entityTaskTags.interface.ts b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskTags/entityTaskTags.interface.ts new file mode 100644 index 00000000000..eb084cc9873 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/EntityTaskTags/entityTaskTags.interface.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2023 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 { EntityType } from 'enums/entity.enum'; +import { ThreadType } from 'generated/api/feed/createThread'; +import { TagLabel, TagSource } from 'generated/type/tagLabel'; +import { EntityFieldThreads } from 'interface/feed.interface'; + +export interface EntityTaskTagsProps { + data: { + fqn: string; + tags: TagLabel[]; + }; + tagSource: TagSource; + entityFqn: string; + entityType: EntityType; + entityFieldThreads: EntityFieldThreads[]; + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx index e25b97e17a7..f94eb456038 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateDescriptionPage/UpdateDescriptionPage.tsx @@ -34,7 +34,6 @@ import { TaskType, ThreadType, } from '../../../generated/api/feed/createThread'; -import { Table } from '../../../generated/entity/data/table'; import { ENTITY_LINK_SEPARATOR, getEntityFeedLink, @@ -45,6 +44,7 @@ import { fetchOptions, getBreadCrumbList, getColumnObject, + getEntityColumnsDetails, } from '../../../utils/TasksUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import Assignees from '../shared/Assignees'; @@ -86,8 +86,12 @@ const UpdateDescription = () => { const columnObject = useMemo(() => { const column = getSanitizeValue.split(FQN_SEPARATOR_CHAR).slice(-1); - return getColumnObject(column[0], (entityData as Table).columns || []); - }, [field, entityData as Table]); + return getColumnObject( + column[0], + getEntityColumnsDetails(entityType, entityData), + entityType as EntityType + ); + }, [field, entityData, entityType]); const getDescription = () => { if (!isEmpty(columnObject) && !isUndefined(columnObject)) { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx index d80d4b95662..8bd6cc47e07 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/UpdateTagPage/UpdateTagPage.tsx @@ -19,6 +19,7 @@ import ResizablePanels from 'components/common/ResizablePanels/ResizablePanels'; import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; import ExploreSearchCard from 'components/ExploreV1/ExploreSearchCard/ExploreSearchCard'; import { SearchedDataProps } from 'components/searched-data/SearchedData.interface'; +import { Chart } from 'generated/entity/data/chart'; import { isEmpty, isUndefined } from 'lodash'; import { observer } from 'mobx-react'; import React, { useEffect, useMemo, useState } from 'react'; @@ -34,7 +35,6 @@ import { CreateThread, TaskType, } from '../../../generated/api/feed/createThread'; -import { Table } from '../../../generated/entity/data/table'; import { ThreadType } from '../../../generated/entity/feed/thread'; import { TagLabel } from '../../../generated/type/tagLabel'; import { @@ -47,6 +47,7 @@ import { fetchOptions, getBreadCrumbList, getColumnObject, + getEntityColumnsDetails, } from '../../../utils/TasksUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import Assignees from '../shared/Assignees'; @@ -67,6 +68,8 @@ const UpdateTag = () => { const value = queryParams.get('value'); const [entityData, setEntityData] = useState({} as EntityData); + const [chartData, setChartData] = useState([] as Chart[]); + const [options, setOptions] = useState([]); const [assignees, setAssignees] = useState([]); const [currentTags, setCurrentTags] = useState([]); @@ -89,8 +92,13 @@ const UpdateTag = () => { const columnObject = useMemo(() => { const column = getSanitizeValue.split(FQN_SEPARATOR_CHAR).slice(-1); - return getColumnObject(column[0], (entityData as Table).columns || []); - }, [field, entityData]); + return getColumnObject( + column[0], + getEntityColumnsDetails(entityType, entityData), + entityType as EntityType, + chartData + ); + }, [field, entityData, chartData, entityType]); const getTags = () => { if (!isEmpty(columnObject) && !isUndefined(columnObject)) { @@ -151,7 +159,8 @@ const UpdateTag = () => { fetchEntityDetail( entityType as EntityType, entityFQN as string, - setEntityData + setEntityData, + setChartData ); }, [entityFQN, entityType]); @@ -174,7 +183,7 @@ const UpdateTag = () => { updatedTags: getTags(), assignees: defaultAssignee, }); - }, [entityData]); + }, [entityData, columnObject]); useEffect(() => { setCurrentTags(getTags()); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx index c3bd6ac0a35..439d24450c6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx @@ -237,6 +237,7 @@ const TopicDetailsPage: FunctionComponent = () => { return ( fetchTopicDetail(topicFQN)} followTopicHandler={followTopic} topicDetails={topicDetails} topicPermissions={topicPermissions} 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 98d37c4a3a1..f31c39a319f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -325,6 +325,18 @@ a[href].link-text-grey, } } +.hover-icon-group { + .hover-cell-icon { + opacity: 0; + transition: 0.3s ease-in; + } + &:hover { + .hover-cell-icon { + opacity: 100; + } + } +} + /* Group CSS End*/ .quick-filter-dropdown-trigger-btn { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx index 8ea348215cb..3b3c1cab7b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx @@ -72,7 +72,7 @@ import { import { SIZE } from '../enums/common.enum'; import { EntityTabs, EntityType, FqnPart } from '../enums/entity.enum'; import { FilterPatternEnum } from '../enums/filterPattern.enum'; -import { ThreadTaskStatus, ThreadType } from '../generated/entity/feed/thread'; +import { ThreadType } from '../generated/entity/feed/thread'; import { PipelineType } from '../generated/entity/services/ingestionPipelines/ingestionPipeline'; import { EntityReference } from '../generated/entity/teams/user'; import { Paging } from '../generated/type/paging'; @@ -144,6 +144,12 @@ export const getPartialNameFromTableFQN = ( return splitFqn.slice(4).join(FQN_SEPARATOR_CHAR); } + + if (fqnParts.includes(FqnPart.Topic)) { + // Remove the first 2 parts ( service, database) + return splitFqn.slice(2).join(FQN_SEPARATOR_CHAR); + } + const arrPartialName = []; if (splitFqn.length > 0) { if (fqnParts.includes(FqnPart.Service)) { @@ -551,7 +557,6 @@ export const getFeedCounts = ( conversationCallback: ( value: React.SetStateAction ) => void, - taskCallback: (value: React.SetStateAction) => void, entityCallback: (value: React.SetStateAction) => void ) => { // To get conversation count @@ -570,23 +575,6 @@ export const getFeedCounts = ( showErrorToast(err, t('server.entity-feed-fetch-error')); }); - // To get open tasks count - getFeedCount( - getEntityFeedLink(entityType, entityFQN), - ThreadType.Task, - ThreadTaskStatus.Open - ) - .then((res) => { - if (res) { - taskCallback(res.counts); - } else { - throw t('server.entity-feed-fetch-error'); - } - }) - .catch((err: AxiosError) => { - showErrorToast(err, t('server.entity-feed-fetch-error')); - }); - // To get all thread count (task + conversation) getFeedCount(getEntityFeedLink(entityType, entityFQN)) .then((res) => { @@ -977,7 +965,3 @@ export const getEntityDetailLink = ( return path; }; - -export const getPartialNameFromTopicFQN = (fqn: string): string => { - return Fqn.split(fqn).slice(2).join(FQN_SEPARATOR_CHAR); -}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts index 8beee4e7a2c..ec51ca3bc7d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts @@ -10,6 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { TabSpecificField } from 'enums/entity.enum'; import { Column, ContainerDataModel } from 'generated/entity/data/container'; import { LabelType, State, TagLabel } from 'generated/type/tagLabel'; import { isEmpty } from 'lodash'; @@ -94,3 +95,6 @@ export const updateContainerColumnDescription = ( } }); }; + +export const ContainerFields = `${TabSpecificField.TAGS}, ${TabSpecificField.OWNER}, +${TabSpecificField.FOLLOWERS},${TabSpecificField.DATAMODEL}`; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FeedElementUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FeedElementUtils.tsx index 2d27cb8f9ba..5e84ad3ffca 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FeedElementUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FeedElementUtils.tsx @@ -11,13 +11,12 @@ * limitations under the License. */ -import { Button, Space, Tooltip } from 'antd'; +import { Tooltip } from 'antd'; import { DE_ACTIVE_COLOR } from 'constants/constants'; import { t } from 'i18next'; -import { isEmpty, isEqual, isUndefined } from 'lodash'; -import React, { Fragment } from 'react'; +import { isEmpty, isUndefined } from 'lodash'; +import React from 'react'; import { ReactComponent as IconComments } from '../assets/svg/comment.svg'; -import { ReactComponent as IconTaskColor } from '../assets/svg/Task-ic.svg'; import { entityUrlMap } from '../constants/Feeds.constants'; import { ThreadType } from '../generated/entity/feed/thread'; import { EntityReference } from '../generated/entity/teams/user'; @@ -36,12 +35,10 @@ export const getFieldThreadElement = ( columnName: string, columnField: string, entityFieldThreads: EntityFieldThreads[], - onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void, + onThreadLinkSelect: (value: string, threadType?: ThreadType) => void, entityType?: string, entityFqn?: string, - entityField?: string, - flag = true, - threadType?: ThreadType + entityField?: string ) => { let threadValue: EntityFieldThreads = {} as EntityFieldThreads; @@ -52,61 +49,31 @@ export const getFieldThreadElement = ( } }); - const isTaskType = isEqual(threadType, ThreadType.Task); - - return !isEmpty(threadValue) ? ( - - ) : ( - - {entityType && entityFqn && entityField && flag && !isTaskType ? ( - - ) : null} - + return ( + + { + e.preventDefault(); + e.stopPropagation(); + isEmpty(threadValue) + ? onThreadLinkSelect( + getEntityFeedLink(entityType, entityFqn, entityField) + ) + : onThreadLinkSelect( + threadValue.entityLink, + ThreadType.Conversation + ); + }} + /> + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts index 96fa5dcf2cc..bf9f704da06 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts @@ -13,7 +13,15 @@ import { AxiosError } from 'axios'; import { ActivityFeedTabs } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface'; +import { EntityField } from 'constants/Feeds.constants'; import { Change, diffWordsWithSpace } from 'diff'; +import { Chart } from 'generated/entity/data/chart'; +import { Container } from 'generated/entity/data/container'; +import { Dashboard } from 'generated/entity/data/dashboard'; +import { MlFeature, Mlmodel } from 'generated/entity/data/mlmodel'; +import { Pipeline, Task } from 'generated/entity/data/pipeline'; +import { Field, Topic } from 'generated/entity/data/topic'; +import { TagLabel } from 'generated/type/tagLabel'; import i18Next from 'i18next'; import { isEqual, isUndefined } from 'lodash'; import { @@ -49,7 +57,11 @@ import { ServiceCategory } from '../enums/service.enum'; import { Column, Table } from '../generated/entity/data/table'; import { TaskType, Thread } from '../generated/entity/feed/thread'; import { getEntityDetailLink, getPartialNameFromTableFQN } from './CommonUtils'; -import { defaultFields as DashboardFields } from './DashboardDetailsUtils'; +import { ContainerFields } from './ContainerDetailUtils'; +import { + defaultFields as DashboardFields, + fetchCharts, +} from './DashboardDetailsUtils'; import { defaultFields as DatabaseSchemaFields } from './DatabaseSchemaDetailsUtils'; import { defaultFields as DataModelFields } from './DataModelsUtils'; import { defaultFields as TableFields } from './DatasetDetailsUtils'; @@ -178,19 +190,66 @@ export const fetchOptions = ( .catch((err: AxiosError) => showErrorToast(err)); }; +export const getEntityColumnsDetails = ( + entityType: string, + entityData: EntityData +) => { + switch (entityType) { + case EntityType.TOPIC: + return (entityData as Topic).messageSchema?.schemaFields ?? []; + + case EntityType.DASHBOARD: + return (entityData as Dashboard).charts ?? []; + + case EntityType.PIPELINE: + return (entityData as Pipeline).tasks ?? []; + + case EntityType.MLMODEL: + return (entityData as Mlmodel).mlFeatures ?? []; + + case EntityType.CONTAINER: + return (entityData as Container).dataModel?.columns ?? []; + + default: + return (entityData as Table).columns ?? []; + } +}; + +type EntityColumns = Column[] | Task[] | MlFeature[] | Field[]; + +interface EntityColumnProps { + description: string; + tags: TagLabel[]; +} + export const getColumnObject = ( columnName: string, - columns: Table['columns'] -): Column => { - let columnObject: Column = {} as Column; + columns: EntityColumns, + entityType: EntityType, + chartData?: Chart[] +): EntityColumnProps => { + let columnObject: EntityColumnProps = {} as EntityColumnProps; + for (let index = 0; index < columns.length; index++) { const column = columns[index]; if (isEqual(column.name, columnName)) { - columnObject = column; + columnObject = { + description: column.description ?? '', + tags: + column.tags ?? + (entityType === EntityType.DASHBOARD + ? chartData?.find((item) => item.name === columnName)?.tags ?? [] + : []), + }; break; } else { - columnObject = getColumnObject(columnName, column.children || []); + columnObject = getColumnObject( + columnName, + (column as Column).children || [], + entityType, + chartData + ); } } @@ -298,7 +357,8 @@ export const getBreadCrumbList = ( export const fetchEntityDetail = ( entityType: EntityType, entityFQN: string, - setEntityData: (value: React.SetStateAction) => void + setEntityData: (value: React.SetStateAction) => void, + setChartData?: (value: React.SetStateAction) => void ) => { switch (entityType) { case EntityType.TABLE: @@ -321,6 +381,11 @@ export const fetchEntityDetail = ( getDashboardByFqn(entityFQN, DashboardFields) .then((res) => { setEntityData(res); + fetchCharts(res.charts) + .then((chart) => { + setChartData?.(chart); + }) + .catch((err: AxiosError) => showErrorToast(err)); }) .catch((err: AxiosError) => showErrorToast(err)); @@ -361,7 +426,7 @@ export const fetchEntityDetail = ( break; case EntityType.CONTAINER: - getContainerByFQN(entityFQN, DataModelFields) + getContainerByFQN(entityFQN, ContainerFields) .then((res) => { setEntityData(res); }) @@ -401,3 +466,56 @@ export const isDescriptionTask = (taskType: TaskType) => export const isTagsTask = (taskType: TaskType) => [TaskType.RequestTag, TaskType.UpdateTag].includes(taskType); + +export const getEntityTaskDetails = ( + entityType: EntityType +): { + fqnPart: FqnPart[]; + entityField: string; +} => { + let fqnPartTypes: FqnPart; + let entityField: string; + switch (entityType) { + case EntityType.TABLE: + fqnPartTypes = FqnPart.NestedColumn; + entityField = EntityField.COLUMNS; + + break; + + case EntityType.TOPIC: + fqnPartTypes = FqnPart.Topic; + entityField = EntityField.MESSAGE_SCHEMA; + + break; + + case EntityType.DASHBOARD: + fqnPartTypes = FqnPart.Database; + entityField = EntityField.CHARTS; + + break; + + case EntityType.PIPELINE: + fqnPartTypes = FqnPart.Schema; + entityField = EntityField.TASKS; + + break; + + case EntityType.MLMODEL: + fqnPartTypes = FqnPart.Schema; + entityField = EntityField.ML_FEATURES; + + break; + + case EntityType.CONTAINER: + fqnPartTypes = FqnPart.Topic; + entityField = EntityField.DATA_MODEL; + + break; + + default: + fqnPartTypes = FqnPart.Table; + entityField = EntityField.COLUMNS; + } + + return { fqnPart: [fqnPartTypes], entityField }; +};