mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-05 03:54:23 +00:00
feat(ui): lineage - allow pipelines to be added for table to topic and vice versa (#12088)
* fix: show columns button should only be visible for too many columns * fix: allow pipeline edge for topic * fix: lineage feedbacks * fix: lineage feedbacks * fix: review comments
This commit is contained in:
parent
d01eddc175
commit
35ed33af5f
@ -1,11 +0,0 @@
|
||||
<svg viewBox="0 0 25 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_19596_44593)">
|
||||
<path d="M23.3002 4.75H6.50074C6.18249 4.75 5.87728 4.86853 5.65224 5.07951C5.42721 5.29048 5.30078 5.57663 5.30078 5.875C5.30078 6.17337 5.42721 6.45952 5.65224 6.67049C5.87728 6.88147 6.18249 7 6.50074 7H23.3002C23.6185 7 23.9237 6.88147 24.1487 6.67049C24.3737 6.45952 24.5002 6.17337 24.5002 5.875C24.5002 5.57663 24.3737 5.29048 24.1487 5.07951C23.9237 4.86853 23.6185 4.75 23.3002 4.75Z" fill="currentColor"/>
|
||||
<path d="M23.3 9.25H6.66857C5.89427 9.22356 5.14413 8.99035 4.5064 8.5778C3.86867 8.16525 3.36987 7.59053 3.06869 6.92125C2.89223 6.41389 2.84662 5.87454 2.93559 5.34745C3.02456 4.82036 3.24558 4.32057 3.58051 3.88908C3.91544 3.4576 4.35473 3.10672 4.86233 2.86526C5.36993 2.62379 5.93137 2.49861 6.50058 2.5H23.3C23.6183 2.5 23.9235 2.38147 24.1485 2.1705C24.3736 1.95952 24.5 1.67337 24.5 1.375C24.5 1.07663 24.3736 0.790483 24.1485 0.579505C23.9235 0.368526 23.6183 0.25 23.3 0.25H6.50058C5.70453 0.249905 4.91642 0.398327 4.18216 0.686621C3.4479 0.974915 2.78217 1.39732 2.22377 1.92922C1.66536 2.46112 1.22544 3.09188 0.929626 3.78476C0.633815 4.47764 0.488031 5.21878 0.500768 5.965V30.625C0.500768 30.9234 0.627192 31.2095 0.852229 31.4205C1.07727 31.6315 1.38248 31.75 1.70073 31.75H23.3C23.6183 31.75 23.9235 31.6315 24.1485 31.4205C24.3736 31.2095 24.5 30.9234 24.5 30.625V10.375C24.5 10.0766 24.3736 9.79048 24.1485 9.57951C23.9235 9.36853 23.6183 9.25 23.3 9.25ZM7.70054 11.5H10.1005V16.675L9.75247 16.3375C9.64092 16.2321 9.5082 16.1484 9.36198 16.0912C9.21575 16.0341 9.05891 16.0047 8.9005 16.0047C8.74209 16.0047 8.58525 16.0341 8.43902 16.0912C8.29279 16.1484 8.16008 16.2321 8.04853 16.3375L7.70054 16.675V11.5ZM22.1001 29.5H2.90069V10.2513C3.62005 10.7462 4.43513 11.1053 5.30061 11.3087V19.375C5.3018 19.5971 5.37308 19.8139 5.50547 19.9981C5.63787 20.1823 5.82545 20.3256 6.04459 20.41C6.26312 20.4962 6.50396 20.5197 6.73674 20.4776C6.96952 20.4356 7.18381 20.3299 7.35255 20.1737L8.9005 18.7112L10.4484 20.1737C10.5606 20.278 10.6935 20.3605 10.8397 20.4165C10.9859 20.4725 11.1425 20.5009 11.3004 20.5C11.4578 20.5038 11.614 20.473 11.7564 20.41C11.9755 20.3256 12.1631 20.1823 12.2955 19.9981C12.4279 19.8139 12.4992 19.5971 12.5004 19.375V11.5H22.1001V29.5Z" fill="currentColor"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_19596_44593">
|
||||
<rect width="25" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@ -187,6 +187,7 @@ const CustomControls: FC<ControlProps> = ({
|
||||
<Space className="justify-end w-full" size={16}>
|
||||
<Button
|
||||
ghost
|
||||
className="expand-btn"
|
||||
data-testid="expand-column"
|
||||
type="primary"
|
||||
onClick={onExpandColumnClick}>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import { Button } from 'antd';
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, useCallback } from 'react';
|
||||
import { EdgeProps, getBezierPath } from 'reactflow';
|
||||
import { ReactComponent as FunctionIcon } from '../../assets/svg/ic-function.svg';
|
||||
import { ReactComponent as PipelineIcon } from '../../assets/svg/pipeline-grey.svg';
|
||||
@ -87,12 +87,23 @@ export const CustomEdge = ({
|
||||
targetPosition,
|
||||
});
|
||||
|
||||
const isTableToTableEdge = () => {
|
||||
const { sourceType, targetType } = data;
|
||||
|
||||
return sourceType === EntityType.TABLE && targetType === EntityType.TABLE;
|
||||
const isPipelineEdgeAllowed = (
|
||||
sourceType: EntityType,
|
||||
targetType: EntityType
|
||||
) => {
|
||||
return (
|
||||
[EntityType.TABLE, EntityType.TOPIC].indexOf(sourceType) > -1 &&
|
||||
[EntityType.TABLE, EntityType.TOPIC].indexOf(targetType) > -1
|
||||
);
|
||||
};
|
||||
|
||||
const isColumnLineageAllowed =
|
||||
!data.isColumnLineage &&
|
||||
isPipelineEdgeAllowed(data.sourceType, data.targetType);
|
||||
const hasLabel = data.label;
|
||||
const isSelectedEditMode = selected && data.isEditMode;
|
||||
const isSelected = selected;
|
||||
|
||||
const getInvisiblePath = (path: string) => {
|
||||
return (
|
||||
<path
|
||||
@ -106,6 +117,61 @@ export const CustomEdge = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getLineageEdgeIcon = useCallback(
|
||||
(icon: React.ReactNode, dataTestId: string) => {
|
||||
return (
|
||||
<LineageEdgeIcon offset={3} x={edgeCenterX} y={edgeCenterY}>
|
||||
<Button
|
||||
className="d-flex justify-center items-center custom-edge-pipeline-button"
|
||||
data-testid={dataTestId}
|
||||
icon={icon}
|
||||
type="primary"
|
||||
onClick={(event) =>
|
||||
data.isEditMode &&
|
||||
data.addPipelineClick?.(event, rest as CustomEdgeData)
|
||||
}
|
||||
/>
|
||||
</LineageEdgeIcon>
|
||||
);
|
||||
},
|
||||
[edgeCenterX, edgeCenterY, rest, data]
|
||||
);
|
||||
|
||||
const getEditLineageIcon = useCallback(
|
||||
(
|
||||
dataTestId: string,
|
||||
rotate: boolean,
|
||||
onClick:
|
||||
| ((
|
||||
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
data: CustomEdgeData
|
||||
) => void)
|
||||
| undefined
|
||||
) => {
|
||||
return (
|
||||
<LineageEdgeIcon offset={offset} x={edgeCenterX} y={edgeCenterY}>
|
||||
<Button
|
||||
className="tw-cursor-pointer d-flex tw-z-9999"
|
||||
data-testid={dataTestId}
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt="times-circle"
|
||||
icon="icon-times-circle"
|
||||
width="16px"
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
transform: rotate ? 'rotate(45deg)' : 'none',
|
||||
}}
|
||||
type="link"
|
||||
onClick={(event) => onClick?.(event, rest as CustomEdgeData)}
|
||||
/>
|
||||
</LineageEdgeIcon>
|
||||
);
|
||||
},
|
||||
[offset, edgeCenterX, edgeCenterY, rest, data]
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<path
|
||||
@ -119,80 +185,20 @@ export const CustomEdge = ({
|
||||
{getInvisiblePath(invisibleEdgePath)}
|
||||
{getInvisiblePath(invisibleEdgePath1)}
|
||||
|
||||
{!data.isColumnLineage && isTableToTableEdge() ? (
|
||||
data.label ? (
|
||||
<LineageEdgeIcon offset={3} x={edgeCenterX} y={edgeCenterY}>
|
||||
<Button
|
||||
className="d-flex justify-center items-center custom-edge-pipeline-button"
|
||||
data-testid="pipeline-label"
|
||||
icon={<PipelineIcon />}
|
||||
type="primary"
|
||||
onClick={(event) =>
|
||||
data.isEditMode &&
|
||||
addPipelineClick?.(event, rest as CustomEdgeData)
|
||||
}
|
||||
/>
|
||||
</LineageEdgeIcon>
|
||||
) : (
|
||||
selected &&
|
||||
data.isEditMode && (
|
||||
<LineageEdgeIcon offset={offset} x={edgeCenterX} y={edgeCenterY}>
|
||||
<Button
|
||||
className="tw-cursor-pointer d-flex tw-z-9999"
|
||||
data-testid="add-pipeline"
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt="times-circle"
|
||||
icon="icon-times-circle"
|
||||
width="16px"
|
||||
/>
|
||||
}
|
||||
style={{
|
||||
transform: 'rotate(45deg)',
|
||||
}}
|
||||
type="link"
|
||||
onClick={(event) =>
|
||||
addPipelineClick?.(event, rest as CustomEdgeData)
|
||||
}
|
||||
/>
|
||||
</LineageEdgeIcon>
|
||||
)
|
||||
)
|
||||
) : data.isEditMode ? (
|
||||
selected && (
|
||||
<LineageEdgeIcon offset={offset} x={edgeCenterX} y={edgeCenterY}>
|
||||
<Button
|
||||
className="tw-cursor-pointer d-flex tw-z-9999"
|
||||
data-testid="delete-button"
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt="times-circle"
|
||||
icon="icon-times-circle"
|
||||
width="16px"
|
||||
/>
|
||||
}
|
||||
type="link"
|
||||
onClick={(event) => onEdgeClick?.(event, rest as CustomEdgeData)}
|
||||
/>
|
||||
</LineageEdgeIcon>
|
||||
)
|
||||
) : (
|
||||
{isColumnLineageAllowed &&
|
||||
hasLabel &&
|
||||
getLineageEdgeIcon(<PipelineIcon />, 'pipeline-label')}
|
||||
{isColumnLineageAllowed &&
|
||||
isSelectedEditMode &&
|
||||
getEditLineageIcon('add-pipeline', true, addPipelineClick)}
|
||||
{!isColumnLineageAllowed &&
|
||||
isSelectedEditMode &&
|
||||
isSelected &&
|
||||
getEditLineageIcon('delete-button', false, onEdgeClick)}
|
||||
{!isColumnLineageAllowed &&
|
||||
data.columnFunctionValue &&
|
||||
data.isExpanded && (
|
||||
<LineageEdgeIcon offset={3} x={edgeCenterX} y={edgeCenterY}>
|
||||
<Button
|
||||
className="d-flex justify-center items-center custom-edge-pipeline-button"
|
||||
data-tesid="function-icon"
|
||||
icon={<FunctionIcon />}
|
||||
type="primary"
|
||||
onClick={(event) =>
|
||||
data.isEditMode &&
|
||||
addPipelineClick?.(event, rest as CustomEdgeData)
|
||||
}
|
||||
/>
|
||||
</LineageEdgeIcon>
|
||||
)
|
||||
)}
|
||||
data.isExpanded &&
|
||||
getLineageEdgeIcon(<FunctionIcon />, 'function-icon')}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
findAllByTestId,
|
||||
findByTestId,
|
||||
queryAllByTestId,
|
||||
queryByTestId,
|
||||
render,
|
||||
} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { EntityLineageNodeType } from '../../enums/entity.enum';
|
||||
import CustomNode from './CustomNode.component';
|
||||
|
||||
const mockTableColumns = [
|
||||
{
|
||||
name: 'product_id',
|
||||
displayName: 'product_id',
|
||||
dataType: 'NUMERIC',
|
||||
dataTypeDisplay: 'numeric',
|
||||
description:
|
||||
'Unique identifier for the product. This column is the primary key for this table.',
|
||||
fullyQualifiedName: 'bigquery_gcp.shopify.dim_product.product_id',
|
||||
constraint: 'PRIMARY_KEY',
|
||||
ordinalPosition: 1,
|
||||
},
|
||||
{
|
||||
name: 'shop_id',
|
||||
displayName: 'shop_id',
|
||||
dataType: 'NUMERIC',
|
||||
dataTypeDisplay: 'numeric',
|
||||
description:
|
||||
'ID of the store. This column is a foreign key reference to the shop_id column in the dim_shop table.',
|
||||
fullyQualifiedName: 'bigquery_gcp.shopify.dim_product.shop_id',
|
||||
ordinalPosition: 2,
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
displayName: 'title',
|
||||
dataType: 'VARCHAR',
|
||||
dataLength: 100,
|
||||
dataTypeDisplay: 'varchar',
|
||||
description: 'Name of the product.',
|
||||
fullyQualifiedName: 'bigquery_gcp.shopify.dim_product.title',
|
||||
ordinalPosition: 3,
|
||||
},
|
||||
{
|
||||
name: 'vendor',
|
||||
displayName: 'vendor',
|
||||
dataType: 'VARCHAR',
|
||||
dataLength: 100,
|
||||
dataTypeDisplay: 'varchar',
|
||||
description:
|
||||
'Name of the manufacturer, wholesaler, or other vendor of the product.',
|
||||
fullyQualifiedName: 'bigquery_gcp.shopify.dim_product.vendor',
|
||||
ordinalPosition: 4,
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
displayName: 'created_at',
|
||||
dataType: 'TIMESTAMP',
|
||||
dataTypeDisplay: 'timestamp',
|
||||
description:
|
||||
'Date (ISO 8601) and time (UTC) when the product was added to the store. The format is YYYY-MM-DD HH:mm:ss (for example, 2016-02-05 17:04:01).',
|
||||
fullyQualifiedName: 'bigquery_gcp.shopify.dim_product.created_at',
|
||||
ordinalPosition: 5,
|
||||
},
|
||||
{
|
||||
name: 'deleted_at',
|
||||
displayName: 'deleted_at',
|
||||
dataType: 'TIMESTAMP',
|
||||
dataTypeDisplay: 'timestamp',
|
||||
description:
|
||||
'Date (ISO 8601) and time (UTC) when the product was deleted. The format is YYYY-MM-DD HH:mm:ss (for example, 2016-02-05 17:04:01).',
|
||||
fullyQualifiedName: 'bigquery_gcp.shopify.dim_product.deleted_at',
|
||||
ordinalPosition: 6,
|
||||
},
|
||||
];
|
||||
|
||||
const mockCustomNodeProp = {
|
||||
id: 'node-1',
|
||||
type: EntityLineageNodeType.DEFAULT,
|
||||
selected: false,
|
||||
isConnectable: false,
|
||||
data: {
|
||||
label: <p>label</p>,
|
||||
columns: mockTableColumns,
|
||||
isNewNode: undefined,
|
||||
isExpanded: true,
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../../utils/TableUtils', () => ({
|
||||
getConstraintIcon: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('reactflow', () => ({
|
||||
Handle: jest.fn().mockReturnValue(<span>Handle</span>),
|
||||
Position: {
|
||||
Left: 'left',
|
||||
Top: 'top',
|
||||
Right: 'right',
|
||||
Bottom: 'bottom',
|
||||
},
|
||||
useUpdateNodeInternals: jest.fn().mockImplementation(() => jest.fn()),
|
||||
}));
|
||||
|
||||
describe('Test CustomNode Component', () => {
|
||||
it('Check if CustomNode has all child elements', async () => {
|
||||
const { container } = render(
|
||||
<CustomNode
|
||||
dragging={false}
|
||||
xPos={0}
|
||||
yPos={0}
|
||||
zIndex={0}
|
||||
{...mockCustomNodeProp}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const nodeLabel = await findByTestId(container, 'node-label');
|
||||
const tableColumns = await findAllByTestId(container, 'column');
|
||||
|
||||
expect(nodeLabel).toBeInTheDocument();
|
||||
expect(tableColumns).toHaveLength(mockTableColumns.length);
|
||||
});
|
||||
|
||||
it('Check if CustomNode has data columns as undefined', async () => {
|
||||
const { container } = render(
|
||||
<CustomNode
|
||||
dragging={false}
|
||||
xPos={0}
|
||||
yPos={0}
|
||||
zIndex={0}
|
||||
{...mockCustomNodeProp}
|
||||
data={{ ...mockCustomNodeProp.data, columns: undefined }}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const nodeLabel = await findByTestId(container, 'node-label');
|
||||
const labelSeparator = queryByTestId(container, 'label-separator');
|
||||
const tableColumns = queryAllByTestId(container, 'column');
|
||||
|
||||
expect(nodeLabel).toBeInTheDocument();
|
||||
expect(labelSeparator).not.toBeInTheDocument();
|
||||
expect(tableColumns).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { CSSProperties, Fragment, useEffect } from 'react';
|
||||
import {
|
||||
Handle,
|
||||
HandleProps,
|
||||
NodeProps,
|
||||
Position,
|
||||
useUpdateNodeInternals,
|
||||
} from 'reactflow';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { EntityLineageNodeType } from '../../enums/entity.enum';
|
||||
import { getNodeRemoveButton } from '../../utils/EntityLineageUtils';
|
||||
import { getConstraintIcon } from '../../utils/TableUtils';
|
||||
import { ModifiedColumn } from './EntityLineage.interface';
|
||||
|
||||
const handleStyles: CSSProperties = {
|
||||
width: '20%',
|
||||
height: '100%',
|
||||
borderRadius: '3px',
|
||||
position: 'absolute',
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
};
|
||||
|
||||
const leftHandleStyle: CSSProperties = {
|
||||
...handleStyles,
|
||||
borderLeft: '5px solid #d9ceee',
|
||||
left: -1,
|
||||
};
|
||||
|
||||
const rightHandleStyle: CSSProperties = {
|
||||
...handleStyles,
|
||||
borderRight: '5px solid #d9ceee',
|
||||
right: -1,
|
||||
};
|
||||
|
||||
const getHandle = (
|
||||
nodeType: string,
|
||||
isConnectable: HandleProps['isConnectable'],
|
||||
id?: string
|
||||
) => {
|
||||
if (nodeType === EntityLineageNodeType.OUTPUT) {
|
||||
return (
|
||||
<Handle
|
||||
id={id}
|
||||
isConnectable={isConnectable}
|
||||
position={Position.Left}
|
||||
style={leftHandleStyle}
|
||||
type="target"
|
||||
/>
|
||||
);
|
||||
} else if (nodeType === EntityLineageNodeType.INPUT) {
|
||||
return (
|
||||
<Handle
|
||||
id={id}
|
||||
isConnectable={isConnectable}
|
||||
position={Position.Right}
|
||||
style={rightHandleStyle}
|
||||
type="source"
|
||||
/>
|
||||
);
|
||||
} else if (nodeType === EntityLineageNodeType.NOT_CONNECTED) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Fragment>
|
||||
<Handle
|
||||
id={id}
|
||||
isConnectable={isConnectable}
|
||||
position={Position.Left}
|
||||
style={leftHandleStyle}
|
||||
type="target"
|
||||
/>
|
||||
<Handle
|
||||
id={id}
|
||||
isConnectable={isConnectable}
|
||||
position={Position.Right}
|
||||
style={rightHandleStyle}
|
||||
type="source"
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const CustomNode = (props: NodeProps) => {
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { data, type, isConnectable, selected, id } = props;
|
||||
/* eslint-disable-next-line */
|
||||
const {
|
||||
label,
|
||||
columns,
|
||||
removeNodeHandler,
|
||||
handleColumnClick,
|
||||
isEditMode,
|
||||
isExpanded,
|
||||
isTraced,
|
||||
selectedColumns = [],
|
||||
} = data;
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(id);
|
||||
}, [isEditMode, isExpanded]);
|
||||
|
||||
return (
|
||||
<div className="nowheel custom-node">
|
||||
{/* Node label could be simple text or reactNode */}
|
||||
<div
|
||||
className={classNames(
|
||||
'custom-node-header',
|
||||
selected || data.selected
|
||||
? 'custom-node-header-active'
|
||||
: 'custom-node-header-normal',
|
||||
{ 'custom-node-header-tracing': isTraced }
|
||||
)}
|
||||
data-testid="node-label">
|
||||
{getHandle(type, isConnectable)}
|
||||
{label}{' '}
|
||||
{selected && isEditMode
|
||||
? getNodeRemoveButton(() => {
|
||||
removeNodeHandler?.(props);
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div
|
||||
className={classNames(
|
||||
'custom-node-column-lineage',
|
||||
selected || isTraced
|
||||
? 'custom-node-column-lineage-active'
|
||||
: 'custom-node-column-lineage-normal',
|
||||
{
|
||||
'p-y-sm': !isEmpty(columns),
|
||||
}
|
||||
)}>
|
||||
<section className="p-x-sm" id="table-columns">
|
||||
<div className="custom-node-column-lineage-body">
|
||||
{(Object.values(columns || {}) as ModifiedColumn[])?.map(
|
||||
(column, index) => {
|
||||
const isColumnTraced = selectedColumns.includes(
|
||||
column.fullyQualifiedName
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'custom-node-column-container',
|
||||
isColumnTraced
|
||||
? 'custom-node-header-tracing'
|
||||
: 'custom-node-column-lineage-normal bg-white'
|
||||
)}
|
||||
data-testid="column"
|
||||
key={index}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleColumnClick(column.fullyQualifiedName);
|
||||
}}>
|
||||
{getHandle(
|
||||
column.type,
|
||||
isConnectable,
|
||||
column.fullyQualifiedName
|
||||
)}
|
||||
{getConstraintIcon(column.constraint, 'tw-')}
|
||||
<p className="m-0">{getEntityName(column)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomNode;
|
||||
@ -103,8 +103,10 @@ const CustomNodeV1 = (props: NodeProps) => {
|
||||
updateNodeInternals(id);
|
||||
if (!isExpanded) {
|
||||
setShowAllColumns(false);
|
||||
} else if (Object.values(columns).length < 5) {
|
||||
setShowAllColumns(true);
|
||||
}
|
||||
}, [isEditMode, isExpanded]);
|
||||
}, [isEditMode, isExpanded, columns]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(columns)) {
|
||||
|
||||
@ -38,7 +38,6 @@ import { useHistory, useParams } from 'react-router-dom';
|
||||
import ReactFlow, {
|
||||
addEdge,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
Connection,
|
||||
Edge,
|
||||
getConnectedEdges,
|
||||
@ -1628,7 +1627,7 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
|
||||
onPaneClick={onPaneClick}>
|
||||
{updatedLineageData && (
|
||||
<CustomControlsComponent
|
||||
className="absolute top-1 right-1 bottom-full"
|
||||
className="absolute top-1 right-1 bottom-full p-md"
|
||||
deleted={deleted}
|
||||
fitViewParams={{
|
||||
minZoom: MIN_ZOOM_VALUE,
|
||||
@ -1654,9 +1653,7 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
|
||||
onOptionSelect={handleOptionSelect}
|
||||
/>
|
||||
)}
|
||||
{isEditMode && (
|
||||
<Background gap={12} size={1} variant={BackgroundVariant.Lines} />
|
||||
)}
|
||||
<Background gap={12} size={1} />
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
|
||||
@ -23,6 +23,13 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.ant-btn.ant-btn-background-ghost.expand-btn {
|
||||
background-color: white;
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.lineage-node-content {
|
||||
margin: 1px;
|
||||
}
|
||||
@ -89,6 +96,9 @@
|
||||
background: @primary-color-hover;
|
||||
border-top: 1px solid @border-color;
|
||||
}
|
||||
.custom-node-header-tracing {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.react-flow__handle.connectable {
|
||||
@ -113,6 +123,23 @@
|
||||
top: 38px; // Need to show handles on top half
|
||||
}
|
||||
|
||||
.react-flow .lineage-column-node-handle {
|
||||
width: 20px;
|
||||
border-radius: 0;
|
||||
border-color: @border-color;
|
||||
background: @white;
|
||||
top: 0;
|
||||
height: 27px;
|
||||
transform: none;
|
||||
border: none;
|
||||
&.react-flow__handle-left {
|
||||
left: 0;
|
||||
}
|
||||
&.react-flow__handle-right {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-node-column-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
}
|
||||
|
||||
.custom-node-header-tracing {
|
||||
border-color: @primary-color;
|
||||
border: 1px solid @primary-color;
|
||||
.react-flow__handle {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import Icon, { DownOutlined } from '@ant-design/icons';
|
||||
import { Button, Col, Dropdown, Row, Space, Tooltip, Typography } from 'antd';
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { ReactComponent as IconFolder } from 'assets/svg/folder.svg';
|
||||
import { ReactComponent as GlossaryIcon } from 'assets/svg/glossary.svg';
|
||||
import { ReactComponent as ExportIcon } from 'assets/svg/ic-export.svg';
|
||||
import { ReactComponent as IconFlatDoc } from 'assets/svg/ic-flat-doc.svg';
|
||||
import { ReactComponent as ImportIcon } from 'assets/svg/ic-import.svg';
|
||||
@ -322,7 +322,7 @@ const GlossaryHeader = ({
|
||||
entityType={EntityType.GLOSSARY_TERM}
|
||||
icon={
|
||||
isGlossary ? (
|
||||
<IconFolder
|
||||
<GlossaryIcon
|
||||
color={DE_ACTIVE_COLOR}
|
||||
height={36}
|
||||
name="folder"
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { Tag, Tooltip, Typography } from 'antd';
|
||||
import { ReactComponent as IconTag } from 'assets/svg/classification.svg';
|
||||
import classNames from 'classnames';
|
||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||
import { ROUTES } from 'constants/constants';
|
||||
@ -21,7 +22,6 @@ import { useHistory } from 'react-router-dom';
|
||||
import { getTagDisplay, getTagTooltip } from 'utils/TagsUtils';
|
||||
import { ReactComponent as IconPage } from '../../../assets/svg/ic-flat-doc.svg';
|
||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||
import { ReactComponent as IconTag } from '../../../assets/svg/tag-grey.svg';
|
||||
|
||||
import { TAG_START_WITH } from 'constants/Tag.constants';
|
||||
import { TagProps } from './tags.interface';
|
||||
|
||||
@ -19,9 +19,9 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getTagDisplay, getTagTooltip } from 'utils/TagsUtils';
|
||||
|
||||
import { ReactComponent as IconTag } from 'assets/svg/classification.svg';
|
||||
import { reduceColorOpacity } from 'utils/CommonUtils';
|
||||
import { ReactComponent as IconPage } from '../../../assets/svg/ic-flat-doc.svg';
|
||||
import { ReactComponent as IconTag } from '../../../assets/svg/tag-grey.svg';
|
||||
import { TagsV1Props } from './TagsV1.interface';
|
||||
import './tagsV1.less';
|
||||
|
||||
|
||||
@ -11,12 +11,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Col, Divider, Row, Typography } from 'antd';
|
||||
import { ReactComponent as TagIcon } from 'assets/svg/classification.svg';
|
||||
import { EntityUnion } from 'components/Explore/explore.interface';
|
||||
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
|
||||
import { TagLabel } from 'generated/type/tagLabel';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as TagIcon } from '../../../assets/svg/tag-grey.svg';
|
||||
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
|
||||
|
||||
const SummaryTagsDescription = ({
|
||||
|
||||
@ -55,7 +55,7 @@ export const PAGE_HEADERS = {
|
||||
subHeader: i18n.t('message.page-sub-header-for-databases'),
|
||||
},
|
||||
MESSAGING_SERVICES: {
|
||||
header: i18n.t('label.messaging-plural'),
|
||||
header: i18n.t('label.messaging'),
|
||||
subHeader: i18n.t('message.page-sub-header-for-messagings'),
|
||||
},
|
||||
DASHBOARD_SERVICES: {
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
import { Button, Col, Menu, MenuProps, Row, Typography } from 'antd';
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import { ReactComponent as IconFolder } from 'assets/svg/folder.svg';
|
||||
import { ReactComponent as GlossaryIcon } from 'assets/svg/glossary.svg';
|
||||
import { ReactComponent as PlusIcon } from 'assets/svg/plus-primary.svg';
|
||||
import LeftPanelCard from 'components/common/LeftPanelCard/LeftPanelCard';
|
||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||
@ -56,7 +56,7 @@ const GlossaryLeftPanel = ({ glossaries }: GlossaryLeftPanelProps) => {
|
||||
{
|
||||
key: glossary.name,
|
||||
label: getEntityName(glossary),
|
||||
icon: <IconFolder height={16} width={16} />,
|
||||
icon: <GlossaryIcon height={16} width={16} />,
|
||||
},
|
||||
];
|
||||
}, [] as ItemType[]);
|
||||
|
||||
@ -23,10 +23,10 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ReactComponent as IconTag } from 'assets/svg/classification.svg';
|
||||
import { ReactComponent as LockIcon } from 'assets/svg/closed-lock.svg';
|
||||
import { ReactComponent as IconDisableTag } from 'assets/svg/disable-tag.svg';
|
||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { ReactComponent as IconTag } from 'assets/svg/tag-grey.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import AppBadge from 'components/common/Badge/Badge.component';
|
||||
import Description from 'components/common/description/Description';
|
||||
|
||||
@ -404,6 +404,9 @@ a[href].link-text-grey,
|
||||
// lineage
|
||||
.lineage-card {
|
||||
height: calc(100vh - 240px);
|
||||
> .ant-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple label in a field with required
|
||||
|
||||
@ -842,7 +842,6 @@ body .list-option.rdw-option-active {
|
||||
display: none;
|
||||
border-right: 1px solid #d9ceee;
|
||||
transition: transform 0.3s ease-out;
|
||||
margin-left: -16px;
|
||||
}
|
||||
|
||||
.entity-lineage.sidebar.open {
|
||||
|
||||
@ -691,11 +691,6 @@ body .profiler-graph .recharts-active-dot circle {
|
||||
.react-flow__node {
|
||||
min-width: max-content;
|
||||
}
|
||||
.react-flow__background {
|
||||
background-image: url('../assets/img/graph-bg.png');
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
.leaf-node .react-flow__handle {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import IconFlatFolder from 'assets/svg/folder.svg';
|
||||
import IconFlatDoc from 'assets/svg/ic-flat-doc.svg';
|
||||
import { omit } from 'lodash';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
@ -1165,10 +1164,6 @@ const SVGIcons: FunctionComponent<Props> = ({ icon, ...props }: Props) => {
|
||||
case Icons.CONTAINER:
|
||||
IconComponent = IconContainer;
|
||||
|
||||
break;
|
||||
case Icons.FLAT_FOLDER:
|
||||
IconComponent = IconFlatFolder;
|
||||
|
||||
break;
|
||||
case Icons.FLAT_DOC:
|
||||
IconComponent = IconFlatDoc;
|
||||
|
||||
@ -14,9 +14,9 @@
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import { ExpandableConfig } from 'antd/lib/table/interface';
|
||||
import { ReactComponent as IconFlatFolder } from 'assets/svg/folder.svg';
|
||||
import { ReactComponent as ClassificationIcon } from 'assets/svg/classification.svg';
|
||||
import { ReactComponent as GlossaryIcon } from 'assets/svg/glossary.svg';
|
||||
import { ReactComponent as ContainerIcon } from 'assets/svg/ic-storage.svg';
|
||||
import { ReactComponent as IconTag } from 'assets/svg/tag-grey.svg';
|
||||
import classNames from 'classnames';
|
||||
import { SourceType } from 'components/searched-data/SearchedData.interface';
|
||||
import { t } from 'i18next';
|
||||
@ -275,11 +275,11 @@ export const getEntityLink = (
|
||||
|
||||
export const getServiceIcon = (source: SourceType) => {
|
||||
if (source.entityType === EntityType.GLOSSARY_TERM) {
|
||||
return (
|
||||
<IconFlatFolder className="h-7" style={{ color: DE_ACTIVE_COLOR }} />
|
||||
);
|
||||
return <GlossaryIcon className="h-7" style={{ color: DE_ACTIVE_COLOR }} />;
|
||||
} else if (source.entityType === EntityType.TAG) {
|
||||
return <IconTag className="h-7" style={{ color: DE_ACTIVE_COLOR }} />;
|
||||
return (
|
||||
<ClassificationIcon className="h-7" style={{ color: DE_ACTIVE_COLOR }} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<img
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user