fix(ui): revamp lineage edge layout (#13612)

* revamp lineage edge layout

* fix unit test
This commit is contained in:
Ashish Gupta 2023-10-17 21:30:37 +05:30 committed by GitHub
parent 210a8f9d5c
commit 858d75f9c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 230 additions and 129 deletions

View File

@ -1,98 +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 { Button, Modal, Select } from 'antd';
import { t } from 'i18next';
import { isUndefined } from 'lodash';
import React from 'react';
import { EntityReference } from '../../../generated/entity/type';
import { getEntityName } from '../../../utils/EntityUtils';
interface AddPipeLineModalType {
showAddEdgeModal: boolean;
edgeSearchValue: string;
selectedEdgeId: string | undefined;
edgeOptions: EntityReference[];
onModalCancel: () => void;
onSave: () => void;
onClear: () => void;
onRemoveEdgeClick: (evt: React.MouseEvent<HTMLButtonElement>) => void;
onSearch: (value: string) => void;
onSelect: (value: string) => void;
}
const AddPipeLineModal = ({
showAddEdgeModal,
edgeOptions,
edgeSearchValue,
selectedEdgeId,
onRemoveEdgeClick,
onModalCancel,
onSave,
onClear,
onSearch,
onSelect,
}: AddPipeLineModalType) => {
return (
<Modal
destroyOnClose
data-testid="add-edge-modal"
footer={[
<Button
data-testid="remove-edge-button"
key="remove-edge-btn"
type="text"
onClick={onRemoveEdgeClick}>
{t('label.remove-entity', {
entity: t('label.edge-lowercase'),
})}
</Button>,
<Button
data-testid="save-button"
key="save-btn"
type="primary"
onClick={onSave}>
{t('label.save')}
</Button>,
]}
maskClosable={false}
open={showAddEdgeModal}
title={t(`label.${isUndefined(selectedEdgeId) ? 'add' : 'edit'}-entity`, {
entity: t('label.edge'),
})}
onCancel={onModalCancel}>
<Select
allowClear
showSearch
className="w-full"
data-testid="field-select"
defaultActiveFirstOption={false}
filterOption={false}
notFoundContent={false}
options={edgeOptions.map((option) => ({
label: getEntityName(option),
value: option.id,
}))}
placeholder={t('message.search-for-edge')}
searchValue={edgeSearchValue}
showArrow={false}
value={selectedEdgeId}
onClear={onClear}
onSearch={onSearch}
onSelect={onSelect}
/>
</Modal>
);
};
export default AddPipeLineModal;

View File

@ -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
@ -26,11 +26,11 @@ const mockProps = {
name: 'Pipeline 1',
id: 'test-pipeline-1',
type: 'pipeline',
fullyQualifiedName: 'sample_airflow/presto_etl',
},
],
onModalCancel: jest.fn(),
onSave: jest.fn(),
onClear: jest.fn(),
onRemoveEdgeClick: jest.fn(),
onSearch: jest.fn(),
onSelect: jest.fn(),
@ -41,7 +41,7 @@ describe('Test CustomEdge Component', () => {
render(<AddPipeLineModal {...mockProps} />);
const edgeModal = await screen.findByTestId('add-edge-modal');
const fieldSelect = await screen.findByTestId('field-select');
const fieldSelect = await screen.findByTestId('field-input');
const removeEdge = await screen.findByTestId('remove-edge-button');
const saveButton = await screen.findByTestId('save-button');

View File

@ -0,0 +1,137 @@
/*
* 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 { Button, Input, Modal } from 'antd';
import classNames from 'classnames';
import { t } from 'i18next';
import { isEmpty, isUndefined } from 'lodash';
import React, { useMemo } from 'react';
import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../../../enums/common.enum';
import { EntityReference } from '../../../../generated/entity/type';
import { getEntityName } from '../../../../utils/EntityUtils';
import Fqn from '../../../../utils/Fqn';
import { getEntityIcon } from '../../../../utils/TableUtils';
import ErrorPlaceHolder from '../../../common/error-with-placeholder/ErrorPlaceHolder';
import '../../../FeedEditor/FeedEditor.css';
import './add-pipeline-modal.less';
interface AddPipeLineModalType {
showAddEdgeModal: boolean;
edgeSearchValue: string;
selectedEdgeId: string | undefined;
edgeOptions: EntityReference[];
onModalCancel: () => void;
onSave: () => void;
onRemoveEdgeClick: (evt: React.MouseEvent<HTMLButtonElement>) => void;
onSearch: (value: string) => void;
onSelect: (value: string) => void;
}
const AddPipeLineModal = ({
showAddEdgeModal,
edgeOptions,
edgeSearchValue,
selectedEdgeId,
onRemoveEdgeClick,
onModalCancel,
onSave,
onSearch,
onSelect,
}: AddPipeLineModalType) => {
const errorPlaceholderEdge = useMemo(() => {
if (isEmpty(edgeOptions)) {
if (edgeSearchValue) {
return (
<ErrorPlaceHolder
className="mt-0-important"
size={SIZE.MEDIUM}
type={ERROR_PLACEHOLDER_TYPE.FILTER}
/>
);
}
return <ErrorPlaceHolder />;
}
return;
}, [edgeOptions, edgeSearchValue]);
return (
<Modal
destroyOnClose
data-testid="add-edge-modal"
footer={[
<Button
data-testid="remove-edge-button"
key="remove-edge-btn"
type="text"
onClick={onRemoveEdgeClick}>
{t('label.remove-entity', {
entity: t('label.edge-lowercase'),
})}
</Button>,
<Button
data-testid="save-button"
key="save-btn"
type="primary"
onClick={onSave}>
{t('label.save')}
</Button>,
]}
maskClosable={false}
open={showAddEdgeModal}
title={t(`label.${isUndefined(selectedEdgeId) ? 'add' : 'edit'}-entity`, {
entity: t('label.edge'),
})}
onCancel={onModalCancel}>
<Input
data-testid="field-input"
placeholder={t('message.search-for-edge')}
value={edgeSearchValue}
onChange={(e) => onSearch(e.target.value)}
/>
<div className="edge-option-container">
{edgeOptions.map((item) => {
const icon = getEntityIcon(item.type);
const breadcrumb = Fqn.split(item.fullyQualifiedName).join('/');
return (
<div
className={classNames('edge-option-item gap-2', {
active: selectedEdgeId === item.id,
})}
key={item.id}
onClick={() => onSelect(item.id)}>
<div className="flex-center mention-icon-image">{icon}</div>
<div>
<div className="d-flex flex-wrap">
<span className="truncate breadcrumb">{breadcrumb}</span>
</div>
<div className="d-flex flex-col">
<span className="font-medium truncate">
{getEntityName(item)}
</span>
</div>
</div>
</div>
);
})}
{errorPlaceholderEdge}
</div>
</Modal>
);
};
export default AddPipeLineModal;

View File

@ -0,0 +1,47 @@
/*
* 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 (reference) url('../../../../styles/variables.less');
.edge-option-container {
margin-top: 6px;
width: 100%;
height: 310px;
overflow: scroll;
.edge-option-item {
display: flex;
align-items: center;
margin: 8px 0;
padding: 8px;
border-radius: 4px;
cursor: pointer;
&.active {
background: @primary-color-hover;
&:hover {
background: @primary-color-hover;
}
}
&:hover {
background: @body-dark-bg-color;
}
.breadcrumb {
color: @text-grey-muted;
font-size: 10px;
}
}
}

View File

@ -118,7 +118,7 @@ import Loader from '../../Loader/Loader';
import { useTourProvider } from '../../TourProvider/TourProvider';
import EdgeInfoDrawer from '../EntityInfoDrawer/EdgeInfoDrawer.component';
import EntityInfoDrawer from '../EntityInfoDrawer/EntityInfoDrawer.component';
import AddPipeLineModal from './AddPipeLineModal';
import AddPipeLineModal from './AppPipelineModel/AddPipeLineModal';
import CustomControlsComponent from './CustomControls.component';
import './entity-lineage.style.less';
import {
@ -398,6 +398,13 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
}
};
const handleModalCancel = () => {
setSelectedEdgeId(undefined);
setShowAddEdgeModal(false);
setSelectedEdge({} as SelectedEdge);
setEdgeOptions([]);
};
/**
*
* @param data selected edge
@ -440,6 +447,7 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
resetSelectedData();
setConfirmDelete(false);
handleModalCancel();
}
};
@ -999,18 +1007,6 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
setSelectedEdgeId(value);
};
const handleModalCancel = () => {
setSelectedEdgeId(undefined);
setShowAddEdgeModal(false);
setSelectedEdge({} as SelectedEdge);
setEdgeOptions([]);
};
const onEdgeSelectionClear = () => {
setSelectedEdgeId(undefined);
setEdgeSearchValue('');
};
const handleModalSave = () => {
if (selectedEdge.data && updatedLineageData) {
setStatus('waiting');
@ -1031,7 +1027,8 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
selectedEdgeValue,
selectedEdgeId,
selectedEdge.data,
(edgeDetails?.type as EntityType) ?? EntityType.PIPELINE
(edgeDetails?.type as EntityType) ?? EntityType.PIPELINE,
edgeDetails
);
addLineageHandler(newEdge)
@ -1417,14 +1414,29 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
SearchIndex.PIPELINE,
SearchIndex.STORED_PROCEDURE,
]);
setEdgeOptions(
data.data.hits.hits.map((hit) =>
getEntityReferenceFromEntity(
hit._source,
hit._source.entityType as EntityType
)
const selectedPipeline = selectedEdge.data?.pipeline;
const edgeOptions = data.data.hits.hits.map((hit) =>
getEntityReferenceFromEntity(
hit._source,
hit._source.entityType as EntityType
)
);
const optionContainItem = edgeOptions.find(
(item) => item.id === selectedEdgeId
);
setEdgeOptions([
...(selectedPipeline &&
isEmpty(optionContainItem) &&
isEmpty(edgeSearchValue) &&
selectedPipeline.id === selectedEdgeId
? [selectedPipeline]
: []),
...edgeOptions,
]);
} catch (error) {
showErrorToast(
error as AxiosError,
@ -1630,10 +1642,10 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
}, [selectedEdge, confirmDelete]);
useEffect(() => {
if (edgeSearchValue) {
if (showAddEdgeModal) {
getSearchResults(edgeSearchValue);
}
}, [edgeSearchValue]);
}, [edgeSearchValue, showAddEdgeModal]);
useEffect(() => {
edgesRef.current = edges;
@ -1775,7 +1787,6 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
edgeSearchValue={edgeSearchValue}
selectedEdgeId={selectedEdgeId}
showAddEdgeModal={showAddEdgeModal}
onClear={onEdgeSelectionClear}
onModalCancel={handleModalCancel}
onRemoveEdgeClick={handleRemoveEdgeClick}
onSave={handleModalSave}

View File

@ -410,10 +410,12 @@ const TagsContainerV2 = ({
{header}
{tagBody}
<Space align="baseline" className="m-t-xs w-full" size="middle">
{showBottomEditButton && !showInlineEditButton && editTagButton}
{children}
</Space>
{(children || showBottomEditButton) && (
<Space align="baseline" className="m-t-xs w-full" size="middle">
{showBottomEditButton && !showInlineEditButton && editTagButton}
{children}
</Space>
)}
</div>
);
};

View File

@ -824,7 +824,8 @@ export const getNewLineageConnectionDetails = (
selectedEdgeValue: EntityLineageEdge | undefined,
selectedPipelineId: string | undefined,
customEdgeData: CustomEdgeData,
type: EntityType
type: EntityType,
edgeDetails?: EntityReference
) => {
const { source, sourceType, target, targetType } = customEdgeData;
const updatedLineageDetails: LineageDetails = {
@ -836,6 +837,7 @@ export const getNewLineageConnectionDetails = (
: {
id: selectedPipelineId,
type,
fullyQualifiedName: edgeDetails?.fullyQualifiedName ?? '',
},
};