mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-23 01:12:22 +00:00
Added Support tasks for all entity (#12289)
* working on support task for all entites * added support task for topic entity * added support task for all entities * added ui support for entity task of all the entities * solve issue for container and topic tasks * fix the task issue and ui improvements for tag icons * added support for tasks assigned to children * code improvement * ui improvement around tags for icons * changes as per comments * fix unit test --------- Co-authored-by: Ashish Gupta <ashish@getcollate.io>
This commit is contained in:
parent
e698589d55
commit
36f8cf6900
@ -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<Container> {
|
||||
|
||||
@ -214,6 +224,36 @@ public class ContainerRepository extends EntityRepository<Container> {
|
||||
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<TagLabel> 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<Column> columns) {
|
||||
if (nullOrEmpty(columns)) {
|
||||
return;
|
||||
|
@ -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<MlModel> {
|
||||
@ -221,6 +226,35 @@ public class MlModelRepository extends EntityRepository<MlModel> {
|
||||
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<TagLabel> 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());
|
||||
|
@ -695,16 +695,29 @@ public class TableRepository extends EntityRepository<Table> {
|
||||
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<Table> {
|
||||
super.update(task, entityLink, newValue, user);
|
||||
}
|
||||
|
||||
private static Column getChildrenColumn(List<Column> 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<Column> columns) {
|
||||
for (Column c : listOrEmpty(columns)) {
|
||||
c.setTags(setTags ? getTags(c.getFullyQualifiedName()) : null);
|
||||
|
@ -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<Topic> {
|
||||
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<TagLabel> 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<Field> 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<TagLabel> getAllFieldTags(Field field) {
|
||||
Set<TagLabel> tags = new HashSet<>();
|
||||
if (!listOrEmpty(field.getTags()).isEmpty()) {
|
||||
|
@ -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<T, K extends keyof T> = (
|
||||
@ -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<void>;
|
||||
}
|
||||
|
@ -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', () => ({
|
||||
|
@ -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<ContainerDataModelProps> = ({
|
||||
dataModel,
|
||||
@ -41,6 +38,9 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
hasTagEditAccess,
|
||||
isReadOnly,
|
||||
onUpdate,
|
||||
entityFqn,
|
||||
entityFieldThreads,
|
||||
onThreadLinkSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -84,38 +84,6 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
setEditContainerColumnDescription(undefined);
|
||||
};
|
||||
|
||||
const renderContainerColumnDescription: CellRendered<Column, 'description'> =
|
||||
(description, record, index) => {
|
||||
return (
|
||||
<Space
|
||||
className="custom-group w-full"
|
||||
data-testid="description"
|
||||
id={`field-description-${index}`}
|
||||
size={4}>
|
||||
<>
|
||||
{description ? (
|
||||
<RichTextEditorPreviewer markdown={description} />
|
||||
) : (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
{isReadOnly || !hasDescriptionEditAccess ? null : (
|
||||
<Button
|
||||
className="p-0 opacity-0 group-hover-opacity-100 flex-center"
|
||||
data-testid="edit-button"
|
||||
icon={<EditIcon width="16px" />}
|
||||
type="text"
|
||||
onClick={() => setEditContainerColumnDescription(record)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<Column> = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -168,7 +136,22 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
key: 'description',
|
||||
accessor: 'description',
|
||||
width: 350,
|
||||
render: renderContainerColumnDescription,
|
||||
render: (_, record, index) => (
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
description: record.description,
|
||||
}}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.CONTAINER}
|
||||
hasEditPermission={hasDescriptionEditAccess}
|
||||
index={index}
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => setEditContainerColumnDescription(record)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('label.tag-plural'),
|
||||
@ -178,6 +161,9 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
width: 300,
|
||||
render: (tags: TagLabel[], record: Column, index: number) => (
|
||||
<TableTags<Column>
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.CONTAINER}
|
||||
handleTagSelection={handleFieldTagsChange}
|
||||
hasTagEditAccess={hasTagEditAccess}
|
||||
index={index}
|
||||
@ -185,6 +171,7 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
record={record}
|
||||
tags={tags}
|
||||
type={TagSource.Classification}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -196,6 +183,9 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
width: 300,
|
||||
render: (tags: TagLabel[], record: Column, index: number) => (
|
||||
<TableTags<Column>
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.CONTAINER}
|
||||
handleTagSelection={handleFieldTagsChange}
|
||||
hasTagEditAccess={hasTagEditAccess}
|
||||
index={index}
|
||||
@ -203,15 +193,20 @@ const ContainerDataModel: FC<ContainerDataModelProps> = ({
|
||||
record={record}
|
||||
tags={tags}
|
||||
type={TagSource.Glossary}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
isReadOnly,
|
||||
entityFqn,
|
||||
hasTagEditAccess,
|
||||
entityFieldThreads,
|
||||
hasDescriptionEditAccess,
|
||||
editContainerColumnDescription,
|
||||
getEntityName,
|
||||
onThreadLinkSelect,
|
||||
handleFieldTagsChange,
|
||||
]
|
||||
);
|
||||
|
@ -11,9 +11,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Card, Col, Row, Space, Table, Tabs, Tooltip, Typography } from 'antd';
|
||||
import { Card, Col, Row, Space, Table, Tabs, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import ActivityFeedProvider, {
|
||||
useActivityFeedProvider,
|
||||
@ -57,7 +56,6 @@ import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||
import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable';
|
||||
import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
|
||||
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||
@ -68,11 +66,14 @@ import {
|
||||
DashboardDetailsProps,
|
||||
} from './DashboardDetails.interface';
|
||||
|
||||
import TableDescription from 'components/TableDescription/TableDescription.component';
|
||||
|
||||
const DashboardDetails = ({
|
||||
charts,
|
||||
dashboardDetails,
|
||||
fetchDashboard,
|
||||
followDashboardHandler,
|
||||
unFollowDashboardHandler,
|
||||
dashboardDetails,
|
||||
charts,
|
||||
chartDescriptionUpdateHandler,
|
||||
chartTagUpdateHandler,
|
||||
versionHandler,
|
||||
@ -94,10 +95,6 @@ const DashboardDetails = ({
|
||||
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
|
||||
const [threadLink, setThreadLink] = useState<string>('');
|
||||
|
||||
const [threadType, setThreadType] = useState<ThreadType>(
|
||||
@ -118,14 +115,16 @@ const DashboardDetails = ({
|
||||
deleted,
|
||||
dashboardTags,
|
||||
tier,
|
||||
entityFqn,
|
||||
} = useMemo(() => {
|
||||
const { tags = [] } = dashboardDetails;
|
||||
const { tags = [], fullyQualifiedName } = dashboardDetails;
|
||||
|
||||
return {
|
||||
...dashboardDetails,
|
||||
tier: getTierTags(tags),
|
||||
dashboardTags: getTagsWithoutTier(tags),
|
||||
entityName: getEntityName(dashboardDetails),
|
||||
entityFqn: fullyQualifiedName ?? '',
|
||||
};
|
||||
}, [dashboardDetails]);
|
||||
|
||||
@ -177,7 +176,6 @@ const DashboardDetails = ({
|
||||
EntityType.DASHBOARD,
|
||||
dashboardFQN,
|
||||
setEntityFieldThreadCount,
|
||||
setEntityFieldTaskCount,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
@ -390,54 +388,6 @@ const DashboardDetails = ({
|
||||
setThreadLink('');
|
||||
};
|
||||
|
||||
const renderDescription = useCallback(
|
||||
(text, record, index) => {
|
||||
const permissionsObject = chartsPermissionsArray?.find(
|
||||
(chart) => chart.id === record.id
|
||||
)?.permissions;
|
||||
|
||||
const editDescriptionPermissions =
|
||||
!isUndefined(permissionsObject) &&
|
||||
(permissionsObject.EditDescription || permissionsObject.EditAll);
|
||||
|
||||
return (
|
||||
<Space
|
||||
className="w-full tw-group cursor-pointer"
|
||||
data-testid="description">
|
||||
<div>
|
||||
{text ? (
|
||||
<RichTextEditorPreviewer markdown={text} />
|
||||
) : (
|
||||
<span className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!deleted && (
|
||||
<Tooltip
|
||||
title={
|
||||
editDescriptionPermissions
|
||||
? t('label.edit-entity', {
|
||||
entity: t('label.description'),
|
||||
})
|
||||
: t('message.no-permission-for-action')
|
||||
}>
|
||||
<button
|
||||
className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"
|
||||
disabled={!editDescriptionPermissions}
|
||||
onClick={() => handleUpdateChart(record, index)}>
|
||||
<EditIcon width={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
[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<ChartType> = 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 (
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
description: record.description,
|
||||
}}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.DASHBOARD}
|
||||
hasEditPermission={editDescriptionPermissions}
|
||||
index={index}
|
||||
isReadOnly={deleted}
|
||||
onClick={() => handleUpdateChart(record, index)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('label.tag-plural'),
|
||||
@ -512,6 +496,9 @@ const DashboardDetails = ({
|
||||
render: (tags: TagLabel[], record: ChartType, index: number) => {
|
||||
return (
|
||||
<TableTags<ChartType>
|
||||
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) => (
|
||||
<TableTags<ChartType>
|
||||
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}
|
||||
/>
|
||||
</ActivityFeedProvider>
|
||||
),
|
||||
@ -699,7 +703,6 @@ const DashboardDetails = ({
|
||||
tableColumn,
|
||||
dashboardDetails,
|
||||
charts,
|
||||
entityFieldTaskCount,
|
||||
entityFieldThreadCount,
|
||||
entityName,
|
||||
dashboardPermissions,
|
||||
|
@ -28,6 +28,7 @@ export interface ChartsPermissions {
|
||||
export interface DashboardDetailsProps {
|
||||
charts: Array<ChartType>;
|
||||
dashboardDetails: Dashboard;
|
||||
fetchDashboard: () => void;
|
||||
createThread: (data: CreateThread) => void;
|
||||
followDashboardHandler: () => Promise<void>;
|
||||
unFollowDashboardHandler: () => Promise<void>;
|
||||
|
@ -66,6 +66,7 @@ const dashboardDetailsProps: DashboardDetailsProps = {
|
||||
onDashboardUpdate: jest.fn(),
|
||||
versionHandler: jest.fn(),
|
||||
createThread: jest.fn(),
|
||||
fetchDashboard: jest.fn(),
|
||||
};
|
||||
|
||||
const mockEntityPermissions = {
|
||||
|
@ -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 = ({
|
||||
/>
|
||||
<ModelTab
|
||||
data={dataModelData?.columns || []}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.COLUMNS,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={dashboardDataModelFQN}
|
||||
hasEditDescriptionPermission={hasEditDescriptionPermission}
|
||||
hasEditTagsPermission={hasEditTagsPermission}
|
||||
isReadOnly={Boolean(deleted)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
onUpdate={handleColumnUpdateDataModel}
|
||||
/>
|
||||
</div>
|
||||
@ -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}
|
||||
/>
|
||||
</ActivityFeedProvider>
|
||||
),
|
||||
|
@ -21,6 +21,7 @@ import { EntityTags } from 'Models';
|
||||
export interface DataModelDetailsProps {
|
||||
dataModelData: DashboardDataModel;
|
||||
dataModelPermissions: OperationPermission;
|
||||
fetchDataModel: () => void;
|
||||
createThread: (data: CreateThread) => void;
|
||||
handleFollowDataModel: () => Promise<void>;
|
||||
handleUpdateTags: (selectedTags?: EntityTags[]) => void;
|
||||
|
@ -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<Column>();
|
||||
@ -76,41 +78,6 @@ const ModelTab = ({
|
||||
[editColumnDescription, data]
|
||||
);
|
||||
|
||||
const renderColumnDescription: CellRendered<Column, 'description'> =
|
||||
useCallback(
|
||||
(description, record, index) => {
|
||||
return (
|
||||
<Space
|
||||
className="custom-group w-full"
|
||||
data-testid="description"
|
||||
id={`field-description-${index}`}
|
||||
size={4}>
|
||||
<>
|
||||
{description ? (
|
||||
<RichTextEditorPreviewer markdown={description} />
|
||||
) : (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</>
|
||||
{isReadOnly && !hasEditDescriptionPermission ? null : (
|
||||
<Button
|
||||
className="p-0 opacity-0 group-hover-opacity-100"
|
||||
data-testid="edit-button"
|
||||
icon={<EditIcon width="16px" />}
|
||||
type="text"
|
||||
onClick={() => setEditColumnDescription(record)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
[isReadOnly, hasEditDescriptionPermission]
|
||||
);
|
||||
|
||||
const tableColumn: ColumnsType<Column> = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -139,7 +106,22 @@ const ModelTab = ({
|
||||
key: 'description',
|
||||
accessor: 'description',
|
||||
width: 350,
|
||||
render: renderColumnDescription,
|
||||
render: (_, record, index) => (
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
description: record.description,
|
||||
}}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.DASHBOARD_DATA_MODEL}
|
||||
hasEditPermission={hasEditDescriptionPermission}
|
||||
index={index}
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => setEditColumnDescription(record)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('label.tag-plural'),
|
||||
@ -149,6 +131,9 @@ const ModelTab = ({
|
||||
width: 300,
|
||||
render: (tags: TagLabel[], record: Column, index: number) => (
|
||||
<TableTags<Column>
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.DASHBOARD_DATA_MODEL}
|
||||
handleTagSelection={handleFieldTagsChange}
|
||||
hasTagEditAccess={hasEditTagsPermission}
|
||||
index={index}
|
||||
@ -156,6 +141,7 @@ const ModelTab = ({
|
||||
record={record}
|
||||
tags={tags}
|
||||
type={TagSource.Classification}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -167,6 +153,9 @@ const ModelTab = ({
|
||||
width: 300,
|
||||
render: (tags: TagLabel[], record: Column, index: number) => (
|
||||
<TableTags<Column>
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.DASHBOARD_DATA_MODEL}
|
||||
handleTagSelection={handleFieldTagsChange}
|
||||
hasTagEditAccess={hasEditTagsPermission}
|
||||
index={index}
|
||||
@ -174,15 +163,19 @@ const ModelTab = ({
|
||||
record={record}
|
||||
tags={tags}
|
||||
type={TagSource.Glossary}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
entityFqn,
|
||||
isReadOnly,
|
||||
entityFieldThreads,
|
||||
hasEditTagsPermission,
|
||||
editColumnDescription,
|
||||
hasEditDescriptionPermission,
|
||||
onThreadLinkSelect,
|
||||
handleFieldTagsChange,
|
||||
]
|
||||
);
|
||||
|
@ -10,12 +10,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ThreadType } from 'generated/api/feed/createThread';
|
||||
import { Column } from 'generated/entity/data/dashboardDataModel';
|
||||
import { EntityFieldThreads } from 'interface/feed.interface';
|
||||
|
||||
export interface ModelTabProps {
|
||||
data: Column[];
|
||||
entityFqn: string;
|
||||
isReadOnly: boolean;
|
||||
hasEditTagsPermission: boolean;
|
||||
hasEditDescriptionPermission: boolean;
|
||||
entityFieldThreads: EntityFieldThreads[];
|
||||
onThreadLinkSelect: (value: string, threadType?: ThreadType) => void;
|
||||
onUpdate: (updatedDataModel: Column[]) => Promise<void>;
|
||||
}
|
||||
|
@ -12,11 +12,16 @@
|
||||
*/
|
||||
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { ThreadType } from 'generated/api/feed/createThread';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { EntityFieldThreads } from 'interface/feed.interface';
|
||||
|
||||
export interface MlModelFeaturesListProp {
|
||||
mlFeatures: Mlmodel['mlFeatures'];
|
||||
permissions: OperationPermission;
|
||||
handleFeaturesUpdate: (features: Mlmodel['mlFeatures']) => Promise<void>;
|
||||
isDeleted?: boolean;
|
||||
entityFqn: string;
|
||||
entityFieldThreads: EntityFieldThreads[];
|
||||
onThreadLinkSelect: (value: string, threadType?: ThreadType) => void;
|
||||
}
|
||||
|
@ -142,6 +142,7 @@ const settingsUpdateHandler = jest.fn();
|
||||
const mockProp = {
|
||||
mlModelDetail: mockData as Mlmodel,
|
||||
activeTab: 1,
|
||||
fetchMlModel: jest.fn(),
|
||||
followMlModelHandler,
|
||||
unFollowMlModelHandler,
|
||||
descriptionUpdateHandler,
|
||||
|
@ -63,6 +63,7 @@ import MlModelFeaturesList from './MlModelFeaturesList';
|
||||
|
||||
const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
mlModelDetail,
|
||||
fetchMlModel,
|
||||
followMlModelHandler,
|
||||
unFollowMlModelHandler,
|
||||
descriptionUpdateHandler,
|
||||
@ -84,9 +85,6 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
|
||||
const [mlModelPermissions, setPipelinePermissions] = useState(
|
||||
DEFAULT_ENTITY_PERMISSION
|
||||
@ -126,7 +124,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
[AppState.nonSecureUserDetails, AppState.userDetails]
|
||||
);
|
||||
|
||||
const { mlModelTags, isFollowing, tier } = useMemo(() => {
|
||||
const { mlModelTags, isFollowing, tier, entityFqn } = useMemo(() => {
|
||||
return {
|
||||
...mlModelDetail,
|
||||
tier: getTierTags(mlModelDetail.tags ?? []),
|
||||
@ -135,6 +133,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
isFollowing: mlModelDetail.followers?.some(
|
||||
({ id }: { id: string }) => id === currentUser?.id
|
||||
),
|
||||
entityFqn: mlModelDetail.fullyQualifiedName ?? '',
|
||||
};
|
||||
}, [mlModelDetail]);
|
||||
|
||||
@ -143,7 +142,6 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
EntityType.MLMODEL,
|
||||
mlModelFqn,
|
||||
setEntityFieldThreadCount,
|
||||
setEntityFieldTaskCount,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
@ -408,10 +406,16 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
onThreadLinkSelect={handleThreadLinkSelect}
|
||||
/>
|
||||
<MlModelFeaturesList
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.ML_FEATURES,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={entityFqn}
|
||||
handleFeaturesUpdate={onFeaturesUpdate}
|
||||
isDeleted={mlModelDetail.deleted}
|
||||
mlFeatures={mlModelDetail.mlFeatures}
|
||||
permissions={mlModelPermissions}
|
||||
onThreadLinkSelect={handleThreadLinkSelect}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
@ -470,6 +474,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
entityType={EntityType.MLMODEL}
|
||||
fqn={mlModelDetail?.fullyQualifiedName ?? ''}
|
||||
onFeedUpdate={fetchEntityFeedCount}
|
||||
onUpdateEntityDetails={fetchMlModel}
|
||||
/>
|
||||
</ActivityFeedProvider>
|
||||
),
|
||||
@ -533,7 +538,6 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
mlModelPermissions,
|
||||
isEdit,
|
||||
entityFieldThreadCount,
|
||||
entityFieldTaskCount,
|
||||
getMlHyperParameters,
|
||||
getMlModelStore,
|
||||
onCancel,
|
||||
|
@ -18,6 +18,7 @@ import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||
export interface MlModelDetailProp extends HTMLAttributes<HTMLDivElement> {
|
||||
mlModelDetail: Mlmodel;
|
||||
version?: string;
|
||||
fetchMlModel: () => void;
|
||||
followMlModelHandler: () => Promise<void>;
|
||||
unFollowMlModelHandler: () => Promise<void>;
|
||||
descriptionUpdateHandler: (updatedMlModel: Mlmodel) => Promise<void>;
|
||||
|
@ -126,6 +126,14 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditorPreviewer</p>);
|
||||
});
|
||||
|
||||
jest.mock('components/TableTags/TableTags.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>TableTags</p>);
|
||||
});
|
||||
|
||||
jest.mock('components/TableDescription/TableDescription.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>TableDescription</p>);
|
||||
});
|
||||
|
||||
jest.mock('../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor', () => ({
|
||||
ModalWithMarkdownEditor: jest
|
||||
.fn()
|
||||
@ -152,6 +160,16 @@ const mockProp = {
|
||||
mlFeatures: mockData['mlFeatures'] as Mlmodel['mlFeatures'],
|
||||
handleFeaturesUpdate,
|
||||
permissions: DEFAULT_ENTITY_PERMISSION,
|
||||
onThreadLinkSelect: jest.fn(),
|
||||
entityFieldThreads: [
|
||||
{
|
||||
entityLink:
|
||||
'<#E::mlmodel::mlflow_svc.eta_predictions::mlFeatures::sales::description>',
|
||||
count: 1,
|
||||
entityField: 'mlFeatures::sales::description',
|
||||
},
|
||||
],
|
||||
entityFqn: 'mlflow_svc.eta_predictions',
|
||||
};
|
||||
|
||||
describe('Test MlModel feature list', () => {
|
||||
|
@ -11,9 +11,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Card, Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { Card, Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import TableDescription from 'components/TableDescription/TableDescription.component';
|
||||
import TableTags from 'components/TableTags/TableTags.component';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { TagSource } from 'generated/type/schema';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { EntityTags } from 'Models';
|
||||
@ -22,7 +23,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import { MlFeature } from '../../generated/entity/data/mlmodel';
|
||||
import { LabelType, State } from '../../generated/type/tagLabel';
|
||||
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||
import { MlModelFeaturesListProp } from './MlModel.interface';
|
||||
import SourceList from './SourceList.component';
|
||||
@ -32,6 +32,9 @@ const MlModelFeaturesList = ({
|
||||
handleFeaturesUpdate,
|
||||
permissions,
|
||||
isDeleted,
|
||||
entityFqn,
|
||||
entityFieldThreads,
|
||||
onThreadLinkSelect,
|
||||
}: MlModelFeaturesListProp) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFeature, setSelectedFeature] = useState<MlFeature>(
|
||||
@ -153,6 +156,9 @@ const MlModelFeaturesList = ({
|
||||
<Col flex="auto">
|
||||
<TableTags<MlFeature>
|
||||
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}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -175,6 +182,9 @@ const MlModelFeaturesList = ({
|
||||
<Col flex="auto">
|
||||
<TableTags<MlFeature>
|
||||
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}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -195,31 +206,25 @@ const MlModelFeaturesList = ({
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Space align="start">
|
||||
{feature.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
markdown={feature.description}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
{(permissions.EditAll ||
|
||||
permissions.EditDescription) && (
|
||||
<Button
|
||||
className="m-l-xxs no-border p-0 text-primary h-auto"
|
||||
icon={<EditIcon width={16} />}
|
||||
type="text"
|
||||
onClick={() => {
|
||||
setSelectedFeature(feature);
|
||||
setEditDescription(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: feature.fullyQualifiedName ?? '',
|
||||
description: feature.description,
|
||||
}}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.MLMODEL}
|
||||
hasEditPermission={
|
||||
permissions.EditAll || permissions.EditDescription
|
||||
}
|
||||
index={index}
|
||||
isReadOnly={isDeleted}
|
||||
onClick={() => {
|
||||
setSelectedFeature(feature);
|
||||
setEditDescription(true);
|
||||
}}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
@ -11,9 +11,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Card, Col, Radio, Row, Space, Tabs, Tooltip, Typography } from 'antd';
|
||||
import { Card, Col, Radio, Row, Space, Tabs, Typography } from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import ActivityFeedProvider, {
|
||||
useActivityFeedProvider,
|
||||
@ -28,6 +27,7 @@ import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAss
|
||||
import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component';
|
||||
import ExecutionsTab from 'components/Execution/Execution.component';
|
||||
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
|
||||
import TableDescription from 'components/TableDescription/TableDescription.component';
|
||||
import TableTags from 'components/TableTags/TableTags.component';
|
||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||
@ -36,7 +36,7 @@ import { EntityField } from 'constants/Feeds.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { TagSource } from 'generated/type/schema';
|
||||
import { isEmpty, isUndefined, map, noop } from 'lodash';
|
||||
import { isEmpty, isUndefined, map } from 'lodash';
|
||||
import { EntityTags, TagOption } from 'Models';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -71,16 +71,16 @@ import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
||||
import { PipeLineDetailsProp } from './PipelineDetails.interface';
|
||||
|
||||
const PipelineDetails = ({
|
||||
pipelineDetails,
|
||||
descriptionUpdateHandler,
|
||||
followers,
|
||||
pipelineDetails,
|
||||
fetchPipeline,
|
||||
descriptionUpdateHandler,
|
||||
followPipelineHandler,
|
||||
unFollowPipelineHandler,
|
||||
settingsUpdateHandler,
|
||||
@ -101,6 +101,7 @@ const PipelineDetails = ({
|
||||
entityName,
|
||||
tier,
|
||||
tags,
|
||||
entityFqn,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
deleted: pipelineDetails.deleted,
|
||||
@ -112,6 +113,7 @@ const PipelineDetails = ({
|
||||
tier: getTierTags(pipelineDetails.tags ?? []),
|
||||
tags: getTagsWithoutTier(pipelineDetails.tags ?? []),
|
||||
entityName: getEntityName(pipelineDetails),
|
||||
entityFqn: pipelineDetails.fullyQualifiedName ?? '',
|
||||
};
|
||||
}, [pipelineDetails]);
|
||||
|
||||
@ -164,7 +166,6 @@ const PipelineDetails = ({
|
||||
EntityType.PIPELINE,
|
||||
pipelineFQN,
|
||||
setEntityFieldThreadCount,
|
||||
noop,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
@ -389,43 +390,26 @@ const PipelineDetails = ({
|
||||
dataIndex: 'description',
|
||||
width: 350,
|
||||
title: t('label.description'),
|
||||
render: (text, record, index) => (
|
||||
<Space
|
||||
className="w-full tw-group cursor-pointer"
|
||||
data-testid="description">
|
||||
<div>
|
||||
{text ? (
|
||||
<RichTextEditorPreviewer markdown={text} />
|
||||
) : (
|
||||
<span className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{!deleted && (
|
||||
<Tooltip
|
||||
title={
|
||||
pipelinePermissions.EditDescription ||
|
||||
pipelinePermissions.EditAll
|
||||
? t('label.edit-entity', { entity: t('label.description') })
|
||||
: t('message.no-permission-for-action')
|
||||
}>
|
||||
<button
|
||||
className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"
|
||||
disabled={
|
||||
!(
|
||||
pipelinePermissions.EditDescription ||
|
||||
pipelinePermissions.EditAll
|
||||
)
|
||||
}
|
||||
onClick={() => setEditTask({ task: record, index })}>
|
||||
<EditIcon width={16} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
render: (_, record, index) => (
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
description: record.description,
|
||||
}}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.TASKS,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
</Space>
|
||||
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) => (
|
||||
<TableTags<Task>
|
||||
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) => (
|
||||
<TableTags<Task>
|
||||
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}
|
||||
/>
|
||||
</ActivityFeedProvider>
|
||||
),
|
||||
|
@ -21,6 +21,7 @@ export interface PipeLineDetailsProp {
|
||||
pipelineDetails: Pipeline;
|
||||
followers: Array<EntityReference>;
|
||||
paging: Paging;
|
||||
fetchPipeline: () => void;
|
||||
followPipelineHandler: (fetchCount: () => void) => Promise<void>;
|
||||
unFollowPipelineHandler: (fetchCount: () => void) => Promise<void>;
|
||||
settingsUpdateHandler: (updatedPipeline: Pipeline) => Promise<void>;
|
||||
|
@ -98,6 +98,7 @@ const PipelineDetailsProps = {
|
||||
pipelineTags: [],
|
||||
slashedPipelineName: [],
|
||||
taskUpdateHandler: mockTaskUpdateHandler,
|
||||
fetchPipeline: jest.fn(),
|
||||
setActiveTabHandler: jest.fn(),
|
||||
followPipelineHandler: jest.fn(),
|
||||
unFollowPipelineHandler: jest.fn(),
|
||||
|
@ -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<Props> = ({
|
||||
@ -27,11 +27,9 @@ const SchemaTab: FunctionComponent<Props> = ({
|
||||
hasTagEditAccess,
|
||||
entityFieldThreads,
|
||||
onThreadLinkSelect,
|
||||
onEntityFieldSelect,
|
||||
isReadOnly = false,
|
||||
entityFqn,
|
||||
tableConstraints,
|
||||
entityFieldTasks,
|
||||
}: Props) => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
@ -51,9 +49,8 @@ const SchemaTab: FunctionComponent<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EntityTableV1
|
||||
<SchemaTable
|
||||
columnName={columnName}
|
||||
entityFieldTasks={entityFieldTasks}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
hasDescriptionEditAccess={hasDescriptionEditAccess}
|
||||
@ -63,7 +60,6 @@ const SchemaTab: FunctionComponent<Props> = ({
|
||||
searchText={lowerCase(searchText)}
|
||||
tableColumns={columns}
|
||||
tableConstraints={tableConstraints}
|
||||
onEntityFieldSelect={onEntityFieldSelect}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
|
@ -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<void>;
|
||||
};
|
||||
|
@ -69,8 +69,8 @@ jest.mock('../SampleDataTable/SampleDataTable.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>SampleDataTable</p>);
|
||||
});
|
||||
|
||||
jest.mock('../EntityTable/EntityTable.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>EntityTableV1</p>);
|
||||
jest.mock('../SchemaTable/SchemaTable.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>SchemaTable</p>);
|
||||
});
|
||||
|
||||
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();
|
||||
|
@ -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<Column[]>([]);
|
||||
@ -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 (
|
||||
<Button
|
||||
className="p-0 w-7 h-7 flex-none flex-center link-text focus:tw-outline-none hover-cell-icon m-r-xss"
|
||||
data-testid="request-description"
|
||||
type="text"
|
||||
onClick={() =>
|
||||
hasDescription
|
||||
? onUpdateDescriptionHandler(cell)
|
||||
: onRequestDescriptionHandler(cell)
|
||||
}>
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
content={
|
||||
hasDescription
|
||||
? t('message.request-update-description')
|
||||
: t('message.request-description')
|
||||
}
|
||||
overlayClassName="ant-popover-request-description"
|
||||
trigger="hover"
|
||||
zIndex={9999}>
|
||||
<IconRequest
|
||||
height={14}
|
||||
name={t('message.request-description')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
</Popover>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDataTypeDisplay: TableCellRendered<Column, 'dataTypeDisplay'> = (
|
||||
dataTypeDisplay,
|
||||
record
|
||||
@ -355,92 +249,35 @@ const EntityTable = ({
|
||||
};
|
||||
|
||||
const renderDescription: TableCellRendered<Column, 'description'> = (
|
||||
description,
|
||||
_,
|
||||
record,
|
||||
index
|
||||
) => {
|
||||
return (
|
||||
<div className="hover-icon-group">
|
||||
<div className="d-inline-block">
|
||||
<Space
|
||||
data-testid="description"
|
||||
direction={isEmpty(description) ? 'horizontal' : 'vertical'}
|
||||
id={`column-description-${index}`}
|
||||
size={4}>
|
||||
<div>
|
||||
{description ? (
|
||||
<RichTextEditorPreviewer markdown={description} />
|
||||
) : (
|
||||
<span className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="d-flex tw--mt-1.5">
|
||||
{!isReadOnly ? (
|
||||
<Fragment>
|
||||
{hasDescriptionEditAccess && (
|
||||
<>
|
||||
<Button
|
||||
className="p-0 tw-self-start flex-center w-7 h-7 d-flex-none hover-cell-icon"
|
||||
type="text"
|
||||
onClick={() => handleUpdate(record, index)}>
|
||||
<IconEdit
|
||||
height={14}
|
||||
name={t('label.edit')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{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
|
||||
)}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
<>
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
description: record.description,
|
||||
}}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.TABLE}
|
||||
hasEditPermission={hasDescriptionEditAccess}
|
||||
index={index}
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => handleUpdate(record, index)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
{getFrequentlyJoinedColumns(
|
||||
record?.name,
|
||||
joins,
|
||||
t('label.frequently-joined-column-plural')
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getColumnFieldFQN = (record: Column) =>
|
||||
`${EntityField.COLUMNS}${ENTITY_LINK_SEPARATOR}${getColumnName(
|
||||
record
|
||||
)}${ENTITY_LINK_SEPARATOR}${EntityField.TAGS}`;
|
||||
|
||||
const columns: ColumnsType<Column> = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -487,8 +324,7 @@ const EntityTable = ({
|
||||
<TableTags<Column>
|
||||
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 = ({
|
||||
<TableTags<Column>
|
||||
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;
|
@ -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<ColumnJoins>;
|
||||
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<void>;
|
||||
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
|
||||
onEntityFieldSelect?: (value: string) => void;
|
||||
onThreadLinkSelect: (value: string, threadType?: ThreadType) => void;
|
||||
}
|
||||
|
||||
export type TableCellRendered<T, K extends keyof T> = (
|
@ -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<struct<name:character varying(32),street_address:character varying(128),city:character varying(32),postcode:character varying(8)>>',
|
||||
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(<p>EditorModal</p>),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'components/common/error-with-placeholder/FilterTablePlaceHolder',
|
||||
() => {
|
||||
return jest.fn().mockReturnValue(<p>FilterTablePlaceHolder</p>);
|
||||
}
|
||||
);
|
||||
|
||||
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(<p>FilterTablePlaceHolder</p>);
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('components/TableTags/TableTags.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>TableTags</p>);
|
||||
});
|
||||
|
||||
jest.mock('components/TableDescription/TableDescription.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>TableDescription</p>);
|
||||
});
|
||||
|
||||
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(<EntityTableV1 {...mockEntityTableProp} />, {
|
||||
@ -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(<EntityTableV1 {...mockEntityTableProp} />, {
|
||||
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(<EntityTableV1 {...mockEntityTableProp} tableColumns={[]} />, {
|
||||
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(<EntityTableV1 {...mockEntityTableProp} />, {
|
||||
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();
|
||||
});
|
||||
});
|
@ -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 (
|
||||
<Space
|
||||
className="hover-icon-group"
|
||||
data-testid="description"
|
||||
direction="vertical"
|
||||
id={`field-description-${index}`}>
|
||||
{columnData.description ? (
|
||||
<RichTextEditorPreviewer markdown={columnData.description} />
|
||||
) : (
|
||||
<span className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
{!isReadOnly ? (
|
||||
<Space align="baseline" size="middle">
|
||||
{hasEditPermission && (
|
||||
<EditIcon
|
||||
className="cursor-pointer hover-cell-icon"
|
||||
data-testid="edit-button"
|
||||
height={14}
|
||||
name={t('label.edit')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EntityTaskDescription
|
||||
data={columnData}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={entityType}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Space>
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableDescription;
|
@ -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;
|
||||
}
|
@ -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 = <T extends TableUnion>({
|
||||
@ -35,64 +26,11 @@ const TableTags = <T extends TableUnion>({
|
||||
isReadOnly,
|
||||
hasTagEditAccess,
|
||||
entityFieldThreads,
|
||||
getColumnFieldFQN,
|
||||
showInlineEditTagButton,
|
||||
getColumnName,
|
||||
onUpdateTagsHandler,
|
||||
onRequestTagsHandler,
|
||||
onThreadLinkSelect,
|
||||
handleTagSelection,
|
||||
entityType,
|
||||
}: TableTagsComponentProps<T>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const hasTagOperationAccess = useMemo(
|
||||
() =>
|
||||
getColumnFieldFQN &&
|
||||
getColumnName &&
|
||||
onUpdateTagsHandler &&
|
||||
onRequestTagsHandler,
|
||||
[
|
||||
getColumnFieldFQN,
|
||||
getColumnName,
|
||||
onUpdateTagsHandler,
|
||||
onRequestTagsHandler,
|
||||
]
|
||||
);
|
||||
|
||||
const getRequestTagsElement = useMemo(() => {
|
||||
const hasTags = !isEmpty(record.tags || []);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
overlayClassName="ant-popover-request-description"
|
||||
title={
|
||||
hasTags
|
||||
? t('label.update-request-tag-plural')
|
||||
: t('label.request-tag-plural')
|
||||
}>
|
||||
<Button
|
||||
className="p-0 w-7 h-7 flex-center m-r-xss link-text hover-cell-icon"
|
||||
data-testid="request-tags"
|
||||
icon={
|
||||
<IconRequest
|
||||
height={14}
|
||||
name={t('label.request-tag-plural')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() =>
|
||||
hasTags
|
||||
? onUpdateTagsHandler?.(record)
|
||||
: onRequestTagsHandler?.(record)
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}, [record, onUpdateTagsHandler, onRequestTagsHandler]);
|
||||
|
||||
return (
|
||||
<div className="hover-icon-group" data-testid={`${type}-tags-${index}`}>
|
||||
<div
|
||||
@ -110,26 +48,17 @@ const TableTags = <T extends TableUnion>({
|
||||
}}>
|
||||
<>
|
||||
{!isReadOnly && (
|
||||
<div className="d-flex items-center">
|
||||
{hasTagOperationAccess && (
|
||||
<>
|
||||
{/* Request and Update tags */}
|
||||
{type === TagSource.Classification && getRequestTagsElement}
|
||||
|
||||
{/* List Conversation */}
|
||||
{getFieldThreadElement(
|
||||
getColumnName?.(record) ?? '',
|
||||
EntityField.TAGS,
|
||||
entityFieldThreads as EntityFieldThreads[],
|
||||
onThreadLinkSelect,
|
||||
EntityType.TABLE,
|
||||
entityFqn,
|
||||
getColumnFieldFQN,
|
||||
Boolean(record?.name?.length)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<EntityTaskTags
|
||||
data={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
tags: record.tags ?? [],
|
||||
}}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={entityType}
|
||||
tagSource={type}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</TagsContainerV2>
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { MlFeature } from 'generated/entity/data/mlmodel';
|
||||
import { Task } from 'generated/entity/data/pipeline';
|
||||
import { Field } from 'generated/entity/data/topic';
|
||||
@ -23,23 +24,20 @@ import { EntityFieldThreads } from '../../interface/feed.interface';
|
||||
|
||||
export interface TableTagsComponentProps<T> {
|
||||
tags: TagLabel[];
|
||||
onUpdateTagsHandler?: (cell: T) => void;
|
||||
isReadOnly?: boolean;
|
||||
entityFqn?: string;
|
||||
entityFqn: string;
|
||||
record: T;
|
||||
index: number;
|
||||
hasTagEditAccess: boolean;
|
||||
entityFieldThreads: EntityFieldThreads[];
|
||||
type: TagSource;
|
||||
showInlineEditTagButton?: boolean;
|
||||
entityType: EntityType;
|
||||
handleTagSelection: (
|
||||
selectedTags: EntityTags[],
|
||||
editColumnTag: T
|
||||
) => Promise<void>;
|
||||
onRequestTagsHandler?: (cell: T) => void;
|
||||
getColumnName?: (cell: T) => string;
|
||||
getColumnFieldFQN?: string;
|
||||
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
|
||||
entityFieldThreads?: EntityFieldThreads[];
|
||||
type: TagSource;
|
||||
showInlineEditTagButton?: boolean;
|
||||
onThreadLinkSelect: (value: string, threadType?: ThreadType) => void;
|
||||
}
|
||||
|
||||
export interface TagsCollection {
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { Constraint, DataType } from 'generated/entity/data/table';
|
||||
import { LabelType, State, TagSource } from 'generated/type/schema';
|
||||
import React from 'react';
|
||||
@ -26,6 +27,10 @@ jest.mock('utils/FeedElementUtils', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('pages/TasksPage/EntityTaskTags/EntityTaskTags.component', () => {
|
||||
return jest.fn().mockImplementation(() => <div>EntityTaskTags</div>);
|
||||
});
|
||||
|
||||
const glossaryTags = [
|
||||
{
|
||||
tagFQN: 'glossary.term1',
|
||||
@ -92,6 +97,7 @@ const mockProp = {
|
||||
entityFqn: 'sample_data.ecommerce_db.shopify.raw_customer',
|
||||
handleTagSelection: jest.fn(),
|
||||
type: TagSource.Classification,
|
||||
entityType: EntityType.TABLE,
|
||||
};
|
||||
|
||||
describe('Test EntityTableTags Component', () => {
|
||||
@ -148,10 +154,11 @@ describe('Test EntityTableTags Component', () => {
|
||||
expect(tagPersonal).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not render update and request tags buttons', async () => {
|
||||
it('Should not render entity task component if entity is deleted', async () => {
|
||||
render(
|
||||
<TableTags
|
||||
{...mockProp}
|
||||
isReadOnly
|
||||
record={{
|
||||
...mockProp.record,
|
||||
tags: [...classificationTags, ...glossaryTags],
|
||||
@ -164,10 +171,10 @@ describe('Test EntityTableTags Component', () => {
|
||||
);
|
||||
|
||||
const tagContainer = await screen.findByTestId('Classification-tags-0');
|
||||
const requestTags = screen.queryByTestId('field-thread-element');
|
||||
const entityTaskTags = screen.queryByText('EntityTaskTags');
|
||||
|
||||
expect(tagContainer).toBeInTheDocument();
|
||||
expect(requestTags).not.toBeInTheDocument();
|
||||
expect(entityTaskTags).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render update and request tags buttons', async () => {
|
||||
@ -187,9 +194,9 @@ describe('Test EntityTableTags Component', () => {
|
||||
);
|
||||
|
||||
const tagContainer = await screen.findByTestId('Classification-tags-0');
|
||||
const requestTags = await screen.findAllByTestId('field-thread-element');
|
||||
const entityTaskTags = screen.queryByText('EntityTaskTags');
|
||||
|
||||
expect(tagContainer).toBeInTheDocument();
|
||||
expect(requestTags).toHaveLength(1);
|
||||
expect(entityTaskTags).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -18,7 +18,7 @@ import { ReactElement } from 'react';
|
||||
|
||||
export type TagsContainerV2Props = {
|
||||
permission: boolean;
|
||||
isVersionView?: boolean;
|
||||
showTaskHandler?: boolean;
|
||||
selectedTags: EntityTags[];
|
||||
entityType?: string;
|
||||
entityThreadLink?: string;
|
||||
|
@ -11,12 +11,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Col, Form, Row, Space, Tooltip, Typography } from 'antd';
|
||||
import { Col, Form, Row, Space, Tooltip, Typography } from 'antd';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { TableTagsProps } from 'components/TableTags/TableTags.interface';
|
||||
import { DE_ACTIVE_COLOR } from 'constants/constants';
|
||||
import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { SearchIndex } from 'enums/search.enum';
|
||||
import { Paging } from 'generated/type/paging';
|
||||
import { TagSource } from 'generated/type/tagLabel';
|
||||
@ -30,11 +29,7 @@ import { formatSearchGlossaryTermResponse } from 'utils/APIUtils';
|
||||
import { getEntityFeedLink } from 'utils/EntityUtils';
|
||||
import { getFilterTags } from 'utils/TableTags/TableTags.utils';
|
||||
import { fetchTagsElasticSearch, getTagPlaceholder } from 'utils/TagsUtils';
|
||||
import {
|
||||
getRequestTagsPath,
|
||||
getUpdateTagsPath,
|
||||
TASK_ENTITIES,
|
||||
} from 'utils/TasksUtils';
|
||||
import { getRequestTagsPath, getUpdateTagsPath } from 'utils/TasksUtils';
|
||||
import { ReactComponent as IconComments } from '../../../assets/svg/comment.svg';
|
||||
import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg';
|
||||
import TagSelectForm from '../TagsSelectForm/TagsSelectForm.component';
|
||||
@ -44,7 +39,7 @@ import { TagsContainerV2Props } from './TagsContainerV2.interface';
|
||||
|
||||
const TagsContainerV2 = ({
|
||||
permission,
|
||||
isVersionView,
|
||||
showTaskHandler = true,
|
||||
selectedTags,
|
||||
entityType,
|
||||
entityThreadLink,
|
||||
@ -197,71 +192,62 @@ const TagsContainerV2 = ({
|
||||
handleSave,
|
||||
]);
|
||||
|
||||
const handleRequestTags = () => {
|
||||
history.push(getRequestTagsPath(entityType as string, entityFqn as string));
|
||||
};
|
||||
const handleUpdateTags = () => {
|
||||
history.push(getUpdateTagsPath(entityType as string, entityFqn as string));
|
||||
const handleTagsTask = (hasTags: boolean) => {
|
||||
history.push(
|
||||
(hasTags ? getUpdateTagsPath : getRequestTagsPath)(
|
||||
entityType as string,
|
||||
entityFqn as string
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const requestTagElement = useMemo(() => {
|
||||
const hasTags = !isEmpty(tags?.[tagType]);
|
||||
|
||||
return TASK_ENTITIES.includes(entityType as EntityType) ? (
|
||||
return (
|
||||
<Col>
|
||||
<Button
|
||||
className="p-0 flex-center"
|
||||
data-testid="request-entity-tags"
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
|
||||
<Tooltip
|
||||
placement="left"
|
||||
title={
|
||||
hasTags
|
||||
? t('label.update-request-tag-plural')
|
||||
: t('label.request-tag-plural')
|
||||
}>
|
||||
<IconRequest
|
||||
className="anticon"
|
||||
height={14}
|
||||
name="request-tags"
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<Tooltip
|
||||
title={
|
||||
hasTags
|
||||
? t('label.update-request-tag-plural')
|
||||
: t('label.request-tag-plural')
|
||||
}>
|
||||
<IconRequest
|
||||
className="cursor-pointer"
|
||||
data-testid="request-entity-tags"
|
||||
height={14}
|
||||
name="request-tags"
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
onClick={() => handleTagsTask(hasTags)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
) : null;
|
||||
}, [tags?.[tagType], handleUpdateTags, handleRequestTags]);
|
||||
);
|
||||
}, [tags?.[tagType], handleTagsTask]);
|
||||
|
||||
const conversationThreadElement = useMemo(
|
||||
() => (
|
||||
<Col>
|
||||
<Button
|
||||
className="p-0 flex-center"
|
||||
data-testid="tag-thread"
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={() =>
|
||||
onThreadLinkSelect?.(
|
||||
entityThreadLink ??
|
||||
getEntityFeedLink(entityType, entityFqn, 'tags')
|
||||
)
|
||||
}>
|
||||
<Tooltip
|
||||
placement="left"
|
||||
title={t('label.list-entity', {
|
||||
entity: t('label.conversation'),
|
||||
})}>
|
||||
<IconComments
|
||||
height={14}
|
||||
name="comments"
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
<Tooltip
|
||||
title={t('label.list-entity', {
|
||||
entity: t('label.conversation'),
|
||||
})}>
|
||||
<IconComments
|
||||
className="cursor-pointer"
|
||||
data-testid="tag-thread"
|
||||
height={14}
|
||||
name="comments"
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
onClick={() =>
|
||||
onThreadLinkSelect?.(
|
||||
entityThreadLink ??
|
||||
getEntityFeedLink(entityType, entityFqn, 'tags')
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
),
|
||||
[
|
||||
@ -281,22 +267,23 @@ const TagsContainerV2 = ({
|
||||
{isGlossaryType ? t('label.glossary-term') : t('label.tag-plural')}
|
||||
</Typography.Text>
|
||||
{permission && (
|
||||
<Row gutter={8}>
|
||||
<Row gutter={12}>
|
||||
{!isEmpty(tags?.[tagType]) && !isEditTags && (
|
||||
<Button
|
||||
className="cursor-pointer flex-center"
|
||||
data-testid="edit-button"
|
||||
icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />}
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
<Col>
|
||||
<EditIcon
|
||||
className="cursor-pointer"
|
||||
color={DE_ACTIVE_COLOR}
|
||||
data-testid="edit-button"
|
||||
width="14px"
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
{permission && !isVersionView && (
|
||||
<Row gutter={8}>
|
||||
{showTaskHandler && (
|
||||
<>
|
||||
{tagType === TagSource.Classification && requestTagElement}
|
||||
{onThreadLinkSelect && conversationThreadElement}
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
@ -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]) ? (
|
||||
<Button
|
||||
className="p-0 w-7 h-7 flex-center text-primary hover-cell-icon"
|
||||
<EditIcon
|
||||
className="hover-cell-icon cursor-pointer"
|
||||
data-testid="edit-button"
|
||||
icon={
|
||||
<EditIcon
|
||||
height={14}
|
||||
name={t('label.edit')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="text"
|
||||
height={14}
|
||||
name={t('label.edit')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
onClick={handleAddClick}
|
||||
/>
|
||||
) : null,
|
||||
@ -348,7 +329,7 @@ const TagsContainerV2 = ({
|
||||
{header}
|
||||
|
||||
{!isEditTags && (
|
||||
<Space wrap align="center" data-testid="entity-tags" size={4}>
|
||||
<Space wrap data-testid="entity-tags" size={4}>
|
||||
{addTagButton}
|
||||
{renderTags}
|
||||
{showInlineEditButton && editTagButton}
|
||||
@ -356,10 +337,10 @@ const TagsContainerV2 = ({
|
||||
)}
|
||||
{isEditTags && tagsSelectContainer}
|
||||
|
||||
<div className="m-t-xss d-flex items-center">
|
||||
<Space align="baseline" className="m-t-xs w-full" size="middle">
|
||||
{showBottomEditButton && !showInlineEditButton && editTagButton}
|
||||
{children}
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -74,6 +74,7 @@ const TagsInput: React.FC<Props> = ({
|
||||
<TagsContainerV2
|
||||
permission={editable}
|
||||
selectedTags={getSelectedTags()}
|
||||
showTaskHandler={false}
|
||||
tagType={TagSource.Classification}
|
||||
onSelectionChange={handleTagSelection}
|
||||
/>
|
||||
|
@ -58,6 +58,7 @@ import TopicSchemaFields from './TopicSchema/TopicSchema';
|
||||
|
||||
const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
topicDetails,
|
||||
fetchTopic,
|
||||
followTopicHandler,
|
||||
unFollowTopicHandler,
|
||||
versionHandler,
|
||||
@ -76,9 +77,6 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
|
||||
const [threadType, setThreadType] = useState<ThreadType>(
|
||||
ThreadType.Conversation
|
||||
@ -253,7 +251,6 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
EntityType.TOPIC,
|
||||
topicFQN,
|
||||
setEntityFieldThreadCount,
|
||||
setEntityFieldTaskCount,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
@ -294,12 +291,8 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
<TopicSchemaFields
|
||||
entityFieldTasks={getEntityFieldThreadCounts(
|
||||
EntityField.COLUMNS,
|
||||
entityFieldTaskCount
|
||||
)}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.COLUMNS,
|
||||
EntityField.MESSAGE_SCHEMA,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={topicDetails.fullyQualifiedName ?? ''}
|
||||
@ -369,6 +362,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
entityType={EntityType.TOPIC}
|
||||
fqn={topicDetails?.fullyQualifiedName ?? ''}
|
||||
onFeedUpdate={getEntityFeedCount}
|
||||
onUpdateEntityDetails={fetchTopic}
|
||||
/>
|
||||
</ActivityFeedProvider>
|
||||
),
|
||||
@ -443,7 +437,6 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
activeTab,
|
||||
feedCount,
|
||||
topicDetails,
|
||||
entityFieldTaskCount,
|
||||
entityFieldThreadCount,
|
||||
topicPermissions,
|
||||
isEdit,
|
||||
|
@ -19,6 +19,7 @@ import { SchemaType } from '../../generated/type/schema';
|
||||
export interface TopicDetailsProps {
|
||||
topicDetails: Topic;
|
||||
topicPermissions: OperationPermission;
|
||||
fetchTopic: () => void;
|
||||
createThread: (data: CreateThread) => void;
|
||||
followTopicHandler: () => Promise<void>;
|
||||
unFollowTopicHandler: () => Promise<void>;
|
||||
|
@ -51,6 +51,7 @@ const mockUserTeam = [
|
||||
|
||||
const topicDetailsProps: TopicDetailsProps = {
|
||||
topicDetails: TOPIC_DETAILS,
|
||||
fetchTopic: jest.fn(),
|
||||
followTopicHandler: jest.fn(),
|
||||
unFollowTopicHandler: jest.fn(),
|
||||
onTopicUpdate: jest.fn(),
|
||||
|
@ -32,10 +32,9 @@ export interface TopicSchemaFieldsProps
|
||||
entityFqn: string;
|
||||
defaultExpandAllRows?: boolean;
|
||||
showSchemaDisplayTypeSwitch?: boolean;
|
||||
entityFieldThreads: EntityFieldThreads[];
|
||||
onUpdate?: (updatedMessageSchema: Topic['messageSchema']) => Promise<void>;
|
||||
entityFieldThreads?: EntityFieldThreads[];
|
||||
entityFieldTasks?: EntityFieldThreads[];
|
||||
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
|
||||
onThreadLinkSelect: (value: string, threadType?: ThreadType) => void;
|
||||
}
|
||||
|
||||
export enum SchemaViewType {
|
||||
|
@ -36,6 +36,15 @@ const mockProps: TopicSchemaFieldsProps = {
|
||||
onUpdate: mockOnUpdate,
|
||||
hasTagEditAccess: true,
|
||||
entityFqn: 'topic.fqn',
|
||||
entityFieldThreads: [
|
||||
{
|
||||
entityLink:
|
||||
'#E::topic::sample_kafka.address_book::messageSchema::schemaFields::AddressBook::description>',
|
||||
count: 1,
|
||||
entityField: 'messageSchema::schemaFields::AddressBook::description',
|
||||
},
|
||||
],
|
||||
onThreadLinkSelect: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('utils/TagsUtils', () => ({
|
||||
|
@ -24,45 +24,31 @@ import {
|
||||
} from 'antd';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import { Key } from 'antd/lib/table/interface';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { ReactComponent as DownUpArrowIcon } from 'assets/svg/ic-down-up-arrow.svg';
|
||||
import { ReactComponent as UpDownArrowIcon } from 'assets/svg/ic-up-down-arrow.svg';
|
||||
import classNames from 'classnames';
|
||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import SchemaEditor from 'components/schema-editor/SchemaEditor';
|
||||
import TableDescription from 'components/TableDescription/TableDescription.component';
|
||||
import TableTags from 'components/TableTags/TableTags.component';
|
||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||
import { DE_ACTIVE_COLOR } from 'constants/constants';
|
||||
import { EntityField } from 'constants/Feeds.constants';
|
||||
import { TABLE_SCROLL_VALUE } from 'constants/Table.constants';
|
||||
import { CSMode } from 'enums/codemirror.enum';
|
||||
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';
|
||||
import { cloneDeep, isEmpty, isUndefined, map } from 'lodash';
|
||||
import { EntityTags, TagOption } from 'Models';
|
||||
import React, { FC, Fragment, useMemo, useState } from 'react';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getPartialNameFromTopicFQN } from 'utils/CommonUtils';
|
||||
import { ENTITY_LINK_SEPARATOR, getEntityName } from 'utils/EntityUtils';
|
||||
import { getFieldThreadElement } from 'utils/FeedElementUtils';
|
||||
import {
|
||||
getRequestDescriptionPath,
|
||||
getUpdateDescriptionPath,
|
||||
} from 'utils/TasksUtils';
|
||||
import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { DataTypeTopic, Field } from '../../../generated/entity/data/topic';
|
||||
import { getTableExpandableConfig } from '../../../utils/TableUtils';
|
||||
import {
|
||||
updateFieldDescription,
|
||||
updateFieldTags,
|
||||
} from '../../../utils/TopicSchema.utils';
|
||||
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||
import {
|
||||
CellRendered,
|
||||
SchemaViewType,
|
||||
TopicSchemaFieldsProps,
|
||||
} from './TopicSchema.interface';
|
||||
@ -79,9 +65,7 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
entityFqn,
|
||||
entityFieldThreads,
|
||||
onThreadLinkSelect,
|
||||
entityFieldTasks,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const [editFieldDescription, setEditFieldDescription] = useState<Field>();
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
@ -105,16 +89,6 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
return getAllRowKeys(messageSchema?.schemaFields ?? []);
|
||||
}, [messageSchema?.schemaFields]);
|
||||
|
||||
const getColumnName = (cell: Field) => {
|
||||
const fqn = cell?.fullyQualifiedName || '';
|
||||
const columnName = getPartialNameFromTopicFQN(fqn);
|
||||
// wrap it in quotes if dot is present
|
||||
|
||||
return columnName.includes(FQN_SEPARATOR_CHAR)
|
||||
? `"${columnName}"`
|
||||
: columnName;
|
||||
};
|
||||
|
||||
const handleFieldTagsChange = async (
|
||||
selectedTags: EntityTags[],
|
||||
editColumnTag: Field
|
||||
@ -162,141 +136,6 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
setExpandedRowKeys(keys as string[]);
|
||||
};
|
||||
|
||||
const onUpdateDescriptionHandler = (cell: Field) => {
|
||||
const field = EntityField.COLUMNS;
|
||||
const value = getColumnName(cell);
|
||||
history.push(
|
||||
getUpdateDescriptionPath(
|
||||
EntityType.TOPIC,
|
||||
entityFqn as string,
|
||||
field,
|
||||
value
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onRequestDescriptionHandler = (cell: Field) => {
|
||||
const field = EntityField.COLUMNS;
|
||||
const value = getColumnName(cell);
|
||||
history.push(
|
||||
getRequestDescriptionPath(
|
||||
EntityType.TOPIC,
|
||||
entityFqn as string,
|
||||
field,
|
||||
value
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const getRequestDescriptionElement = (cell: Field) => {
|
||||
const hasDescription = Boolean(cell?.description ?? '');
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="p-0 w-7 h-7 flex-none flex-center link-text focus:tw-outline-none hover-cell-icon m-r-xss"
|
||||
data-testid="request-description"
|
||||
type="text"
|
||||
onClick={() =>
|
||||
hasDescription
|
||||
? onUpdateDescriptionHandler(cell)
|
||||
: onRequestDescriptionHandler(cell)
|
||||
}>
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
content={
|
||||
hasDescription
|
||||
? t('message.request-update-description')
|
||||
: t('message.request-description')
|
||||
}
|
||||
overlayClassName="ant-popover-request-description"
|
||||
trigger="hover"
|
||||
zIndex={9999}>
|
||||
<IconRequest
|
||||
height={14}
|
||||
name={t('message.request-description')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
</Popover>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFieldDescription: CellRendered<Field, 'description'> = (
|
||||
description,
|
||||
record,
|
||||
index
|
||||
) => {
|
||||
return (
|
||||
<Space
|
||||
className="custom-group w-full"
|
||||
data-testid="description"
|
||||
direction={isEmpty(description) ? 'horizontal' : 'vertical'}
|
||||
id={`field-description-${index}`}
|
||||
size={4}>
|
||||
<div>
|
||||
{description ? (
|
||||
<RichTextEditorPreviewer markdown={description} />
|
||||
) : (
|
||||
<span className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="d-flex tw--mt-1.5">
|
||||
{!isReadOnly ? (
|
||||
<Fragment>
|
||||
{hasDescriptionEditAccess && (
|
||||
<>
|
||||
<Button
|
||||
className="p-0 tw-self-start flex-center w-7 h-7 d-flex-none hover-cell-icon"
|
||||
data-testid="edit-button"
|
||||
type="text"
|
||||
onClick={() => setEditFieldDescription(record)}>
|
||||
<EditIcon
|
||||
height={14}
|
||||
name={t('label.edit')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{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
|
||||
)}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<Field> = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -339,7 +178,22 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
width: 350,
|
||||
render: renderFieldDescription,
|
||||
render: (_, record, index) => (
|
||||
<TableDescription
|
||||
columnData={{
|
||||
fqn: record.fullyQualifiedName ?? '',
|
||||
description: record.description,
|
||||
}}
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.TOPIC}
|
||||
hasEditPermission={hasDescriptionEditAccess}
|
||||
index={index}
|
||||
isReadOnly={isReadOnly}
|
||||
onClick={() => setEditFieldDescription(record)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('label.tag-plural'),
|
||||
@ -349,6 +203,9 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
width: 300,
|
||||
render: (tags: TagLabel[], record: Field, index: number) => (
|
||||
<TableTags<Field>
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.TOPIC}
|
||||
handleTagSelection={handleFieldTagsChange}
|
||||
hasTagEditAccess={hasTagEditAccess}
|
||||
index={index}
|
||||
@ -356,6 +213,7 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
record={record}
|
||||
tags={tags}
|
||||
type={TagSource.Classification}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -367,6 +225,9 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
width: 300,
|
||||
render: (tags: TagLabel[], record: Field, index: number) => (
|
||||
<TableTags<Field>
|
||||
entityFieldThreads={entityFieldThreads}
|
||||
entityFqn={entityFqn}
|
||||
entityType={EntityType.TOPIC}
|
||||
handleTagSelection={handleFieldTagsChange}
|
||||
hasTagEditAccess={hasTagEditAccess}
|
||||
index={index}
|
||||
@ -374,6 +235,7 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
record={record}
|
||||
tags={tags}
|
||||
type={TagSource.Glossary}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -453,26 +315,24 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
bordered
|
||||
className={className}
|
||||
columns={columns}
|
||||
data-testid="topic-schema-fields-table"
|
||||
dataSource={messageSchema?.schemaFields}
|
||||
expandable={{
|
||||
...getTableExpandableConfig<Field>(),
|
||||
rowExpandable: (record) => !isEmpty(record.children),
|
||||
onExpandedRowsChange: handleExpandedRowsChange,
|
||||
defaultExpandAllRows,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
scroll={TABLE_SCROLL_VALUE}
|
||||
size="small"
|
||||
/>
|
||||
</>
|
||||
<Table
|
||||
bordered
|
||||
className={className}
|
||||
columns={columns}
|
||||
data-testid="topic-schema-fields-table"
|
||||
dataSource={messageSchema?.schemaFields}
|
||||
expandable={{
|
||||
...getTableExpandableConfig<Field>(),
|
||||
rowExpandable: (record) => !isEmpty(record.children),
|
||||
onExpandedRowsChange: handleExpandedRowsChange,
|
||||
defaultExpandAllRows,
|
||||
expandedRowKeys,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
scroll={TABLE_SCROLL_VALUE}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</>
|
||||
|
@ -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<TopicVersionProp> = ({
|
||||
<TopicSchemaFields
|
||||
defaultExpandAllRows
|
||||
isReadOnly
|
||||
entityFieldThreads={[]}
|
||||
entityFqn={currentVersionData?.fullyQualifiedName ?? ''}
|
||||
hasDescriptionEditAccess={false}
|
||||
hasTagEditAccess={false}
|
||||
messageSchema={messageSchemaDiff}
|
||||
showSchemaDisplayTypeSwitch={false}
|
||||
onThreadLinkSelect={noop}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -143,7 +146,6 @@ const TopicVersion: FC<TopicVersionProp> = ({
|
||||
<Space className="w-full" direction="vertical" size="large">
|
||||
{Object.keys(TagSource).map((tagType) => (
|
||||
<TagsContainerV2
|
||||
isVersionView
|
||||
entityFqn={currentVersionData.fullyQualifiedName}
|
||||
entityType={EntityType.TOPIC}
|
||||
key={tagType}
|
||||
|
@ -71,6 +71,9 @@ export enum EntityField {
|
||||
EXTENSION = 'extension',
|
||||
DISPLAYNAME = 'displayName',
|
||||
NAME = 'name',
|
||||
MESSAGE_SCHEMA = 'messageSchema',
|
||||
CHARTS = 'charts',
|
||||
DATA_MODEL = 'dataModel',
|
||||
}
|
||||
|
||||
export const ANNOUNCEMENT_BG = '#FFFDF8';
|
||||
|
@ -109,6 +109,7 @@ export enum FqnPart {
|
||||
Table,
|
||||
Column,
|
||||
NestedColumn,
|
||||
Topic,
|
||||
}
|
||||
|
||||
export enum EntityInfo {
|
||||
|
@ -25,7 +25,15 @@ export interface EntityFieldThreadCount {
|
||||
entityLink: string;
|
||||
}
|
||||
|
||||
export type EntityThreadField = 'description' | 'columns' | 'tags' | 'tasks';
|
||||
export type EntityThreadField =
|
||||
| 'description'
|
||||
| 'columns'
|
||||
| 'tags'
|
||||
| 'tasks'
|
||||
| 'charts'
|
||||
| 'dataModel'
|
||||
| 'mlFeatures'
|
||||
| 'messageSchema';
|
||||
export interface EntityFieldThreads {
|
||||
entityLink: string;
|
||||
count: number;
|
||||
|
@ -124,9 +124,6 @@ const ContainerPage = () => {
|
||||
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
|
||||
const [threadLink, setThreadLink] = useState<string>('');
|
||||
const [threadType, setThreadType] = useState<ThreadType>(
|
||||
@ -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 = () => {
|
||||
|
||||
<ContainerDataModel
|
||||
dataModel={containerData?.dataModel}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.DATA_MODEL,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={entityFqn}
|
||||
hasDescriptionEditAccess={hasEditDescriptionPermission}
|
||||
hasTagEditAccess={hasEditTagsPermission}
|
||||
isReadOnly={Boolean(deleted)}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
onUpdate={handleUpdateDataModel}
|
||||
/>
|
||||
</div>
|
||||
@ -748,7 +752,6 @@ const ContainerPage = () => {
|
||||
entityFieldThreadCount,
|
||||
tags,
|
||||
entityLineage,
|
||||
entityFieldTaskCount,
|
||||
feedCount,
|
||||
containerChildrenData,
|
||||
handleAddLineage,
|
||||
|
@ -312,6 +312,7 @@ const DashboardDetailsPage = () => {
|
||||
charts={charts}
|
||||
createThread={createThread}
|
||||
dashboardDetails={dashboardDetails}
|
||||
fetchDashboard={() => fetchDashboardDetail(dashboardFQN)}
|
||||
followDashboardHandler={followDashboard}
|
||||
unFollowDashboardHandler={unFollowDashboard}
|
||||
versionHandler={versionHandler}
|
||||
|
@ -321,6 +321,7 @@ const DataModelsPage = () => {
|
||||
createThread={createThread}
|
||||
dataModelData={dataModelData}
|
||||
dataModelPermissions={dataModelPermissions}
|
||||
fetchDataModel={() => fetchDataModelDetails(dashboardDataModelFQN)}
|
||||
handleColumnUpdateDataModel={handleColumnUpdateDataModel}
|
||||
handleFollowDataModel={handleFollowDataModel}
|
||||
handleUpdateDescription={handleUpdateDescription}
|
||||
|
@ -291,6 +291,7 @@ const MlModelPage = () => {
|
||||
<MlModelDetailComponent
|
||||
createThread={createThread}
|
||||
descriptionUpdateHandler={descriptionUpdateHandler}
|
||||
fetchMlModel={() => fetchMlModelDetails(mlModelFqn)}
|
||||
followMlModelHandler={followMlModel}
|
||||
mlModelDetail={mlModelDetail}
|
||||
settingsUpdateHandler={settingsUpdateHandler}
|
||||
|
@ -259,6 +259,7 @@ const PipelineDetailsPage = () => {
|
||||
return (
|
||||
<PipelineDetails
|
||||
descriptionUpdateHandler={descriptionUpdateHandler}
|
||||
fetchPipeline={() => fetchPipelineDetail(pipelineFQN)}
|
||||
followPipelineHandler={followPipeline}
|
||||
followers={followers}
|
||||
paging={paging}
|
||||
|
@ -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<string>('');
|
||||
const [threadType, setThreadType] = useState<ThreadType>(
|
||||
@ -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
|
||||
|
@ -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 (
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
title={
|
||||
hasDescription
|
||||
? t('message.request-update-description')
|
||||
: t('message.request-description')
|
||||
}>
|
||||
<IconRequest
|
||||
className="cursor-pointer hover-cell-icon"
|
||||
data-testid="request-description"
|
||||
height={14}
|
||||
name={t('message.request-description')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
onClick={() => handleDescriptionTask(hasDescription)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Space size="middle">
|
||||
{requestDescriptionElement}
|
||||
{getFieldThreadElement(
|
||||
columnName,
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldThreads,
|
||||
onThreadLinkSelect,
|
||||
entityType,
|
||||
entityFqn,
|
||||
`${entityField}${ENTITY_LINK_SEPARATOR}${columnName}${ENTITY_LINK_SEPARATOR}${EntityField.DESCRIPTION}`
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityTaskDescription;
|
@ -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;
|
||||
}
|
@ -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 (
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
overlayClassName="ant-popover-request-description"
|
||||
title={
|
||||
hasTags
|
||||
? t('label.update-request-tag-plural')
|
||||
: t('label.request-tag-plural')
|
||||
}>
|
||||
<IconRequest
|
||||
className="hover-cell-icon cursor-pointer"
|
||||
data-testid="request-tags"
|
||||
height={14}
|
||||
name={t('label.request-tag-plural')}
|
||||
style={{ color: DE_ACTIVE_COLOR }}
|
||||
width={14}
|
||||
onClick={() => handleTagTask(hasTags)}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Space size="middle">
|
||||
{/* 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}`
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityTaskTags;
|
@ -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;
|
||||
}
|
@ -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)) {
|
||||
|
@ -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<EntityData>({} as EntityData);
|
||||
const [chartData, setChartData] = useState([] as Chart[]);
|
||||
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
const [assignees, setAssignees] = useState<Option[]>([]);
|
||||
const [currentTags, setCurrentTags] = useState<TagLabel[]>([]);
|
||||
@ -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());
|
||||
|
@ -237,6 +237,7 @@ const TopicDetailsPage: FunctionComponent = () => {
|
||||
return (
|
||||
<TopicDetails
|
||||
createThread={createThread}
|
||||
fetchTopic={() => fetchTopicDetail(topicFQN)}
|
||||
followTopicHandler={followTopic}
|
||||
topicDetails={topicDetails}
|
||||
topicPermissions={topicPermissions}
|
||||
|
@ -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 {
|
||||
|
@ -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<EntityFieldThreadCount[]>
|
||||
) => void,
|
||||
taskCallback: (value: React.SetStateAction<EntityFieldThreadCount[]>) => void,
|
||||
entityCallback: (value: React.SetStateAction<number>) => 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);
|
||||
};
|
||||
|
@ -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}`;
|
||||
|
@ -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) ? (
|
||||
<Button
|
||||
className="link-text tw-self-start w-8 h-7 m-r-xss d-flex tw-items-center hover-cell-icon p-0"
|
||||
data-testid="field-thread"
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onThreadLinkSelect?.(
|
||||
threadValue.entityLink,
|
||||
isTaskType ? ThreadType.Task : ThreadType.Conversation
|
||||
);
|
||||
}}>
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
overlayClassName="ant-popover-request-description"
|
||||
title={t('label.list-entity', {
|
||||
entity: isTaskType ? t('label.task') : t('label.conversation'),
|
||||
})}>
|
||||
<Space align="center" className="w-full h-full" size={4}>
|
||||
{isTaskType ? (
|
||||
<IconTaskColor {...iconsProps} />
|
||||
) : (
|
||||
<IconComments {...iconsProps} />
|
||||
)}
|
||||
</Space>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
) : (
|
||||
<Fragment>
|
||||
{entityType && entityFqn && entityField && flag && !isTaskType ? (
|
||||
<Button
|
||||
className="link-text tw-self-start w-7 h-7 m-r-xss flex-none hover-cell-icon p-0"
|
||||
data-testid="start-field-thread"
|
||||
type="text"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onThreadLinkSelect?.(
|
||||
getEntityFeedLink(entityType, entityFqn, entityField)
|
||||
);
|
||||
}}>
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
overlayClassName="ant-popover-request-description"
|
||||
title={t('label.start-entity', {
|
||||
entity: t('label.conversation'),
|
||||
})}>
|
||||
<IconComments {...iconsProps} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
) : null}
|
||||
</Fragment>
|
||||
return (
|
||||
<Tooltip
|
||||
destroyTooltipOnHide
|
||||
overlayClassName="ant-popover-request-description"
|
||||
title={t('label.list-entity', {
|
||||
entity: t('label.conversation'),
|
||||
})}>
|
||||
<IconComments
|
||||
{...iconsProps}
|
||||
className="hover-cell-icon cursor-pointer"
|
||||
data-testid="field-thread"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isEmpty(threadValue)
|
||||
? onThreadLinkSelect(
|
||||
getEntityFeedLink(entityType, entityFqn, entityField)
|
||||
)
|
||||
: onThreadLinkSelect(
|
||||
threadValue.entityLink,
|
||||
ThreadType.Conversation
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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<EntityData>) => void
|
||||
setEntityData: (value: React.SetStateAction<EntityData>) => void,
|
||||
setChartData?: (value: React.SetStateAction<Chart[]>) => 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 };
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user