ui: display source of lineage (#15391)

* ui: display lineage source

* added locale files

* rename dbt source name

* test: added unit test case
This commit is contained in:
Harsh Vador 2024-03-02 22:20:30 +05:30 committed by GitHub
parent 7424b2f430
commit ec475bce03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 198 additions and 3 deletions

View File

@ -21,8 +21,10 @@ import { Node } from 'reactflow';
import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg';
import DescriptionV1 from '../../../components/common/EntityDescription/DescriptionV1';
import { DE_ACTIVE_COLOR } from '../../../constants/constants';
import { LINEAGE_SOURCE } from '../../../constants/Lineage.constants';
import { CSMode } from '../../../enums/codemirror.enum';
import { EntityType } from '../../../enums/entity.enum';
import { Source } from '../../../generated/type/entityLineage';
import { getNameFromFQN } from '../../../utils/CommonUtils';
import { getLineageDetailsObject } from '../../../utils/EntityLineageUtils';
import entityUtilClassBase from '../../../utils/EntityUtilClassBase';
@ -266,6 +268,15 @@ const EdgeInfoDrawer = ({
</Typography.Paragraph>
)}
</Col>
<Col>
<Divider />
<Typography.Paragraph className="right-panel-label m-b-sm">
{`${t('label.lineage-source')}`}
</Typography.Paragraph>
<Typography.Text className="m-b-0">
{LINEAGE_SOURCE[edgeEntity.source as keyof typeof Source]}
</Typography.Text>
</Col>
</Row>
)}
</Drawer>

View File

@ -0,0 +1,163 @@
/*
* Copyright 2024 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 { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { Edge } from 'reactflow';
import { MOCK_NODES_AND_EDGES } from '../../../mocks/Lineage.mock';
import EdgeInfoDrawer from './EdgeInfoDrawer.component';
jest.mock('../../../components/common/EntityDescription/DescriptionV1', () =>
jest.fn().mockImplementation(({ onDescriptionUpdate }) => (
<div data-testid="description-v1">
DescriptionV1
<button
data-testid="update-description-button"
onClick={() => onDescriptionUpdate('updatedHTML')}>
Update Description
</button>
</div>
))
);
jest.mock('../../../utils/CommonUtils', () => ({
getNameFromFQN: jest.fn().mockReturnValue('getNameFromFQN'),
}));
jest.mock('../../../utils/EntityUtils', () => ({
getEntityName: jest.fn().mockReturnValue('username'),
}));
jest.mock('../../common/Loader/Loader', () =>
jest.fn().mockImplementation(() => <div>Loader</div>)
);
jest.mock('../../Database/SchemaEditor/SchemaEditor', () => {
return jest.fn().mockImplementation(() => <div>SchemaEditor.component</div>);
});
jest.mock('../../Modals/ModalWithQueryEditor/ModalWithQueryEditor', () => {
return jest.fn().mockImplementation(() => <div>ModalWithQueryEditor</div>);
});
jest.mock('antd', () => ({
...jest.requireActual('antd'),
Drawer: jest.fn().mockImplementation(({ children, title }) => (
<div data-testid="drawer-component">
Drawer
<div data-testid="title">{title}</div>
<div>{children}</div>
</div>
)),
}));
const mockOnEdgeDetailsUpdate = jest.fn();
const mockEdgeInfoDrawer = {
edge: {
id: 'edge-5c97531f-d164-4707-842e-af52e0c43e26-5d816d56-40a2-493f-ae9d-012f1cd337dd',
source: '5c97531f-d164-4707-842e-af52e0c43e26',
target: '5d816d56-40a2-493f-ae9d-012f1cd337dd',
type: 'buttonedge',
animated: false,
style: {
strokeWidth: '2px',
},
markerEnd: {
type: 'arrowclosed',
},
data: {
edge: {
toEntity: {
fqn: 'RedshiftProd.dev.demo_dbt_jaffle.customers',
id: '5d816d56-40a2-493f-ae9d-012f1cd337dd',
type: 'table',
},
pipeline: null,
fromEntity: {
fqn: 'RedshiftProd.dev.demo_dbt_jaffle.stg_orders',
id: '5c97531f-d164-4707-842e-af52e0c43e26',
type: 'table',
},
sqlQuery: null,
description: null,
source: 'DbtLineage',
doc_id:
'5c97531f-d164-4707-842e-af52e0c43e26-5d816d56-40a2-493f-ae9d-012f1cd337dd',
},
isColumnLineage: false,
isPipelineRootNode: false,
},
selected: true,
} as Edge,
nodes: MOCK_NODES_AND_EDGES.nodes,
visible: true,
hasEditAccess: true,
onEdgeDetailsUpdate: mockOnEdgeDetailsUpdate,
onClose: jest.fn(),
};
describe('EdgeInfoDrawer Component', () => {
it('should render the component', async () => {
render(<EdgeInfoDrawer {...mockEdgeInfoDrawer} />);
expect(await screen.findByTestId('title')).toHaveTextContent(
'label.edge-information'
);
expect(await screen.findByTestId('description-v1')).toBeInTheDocument();
expect(
await screen.findByText('label.sql-uppercase-query')
).toBeInTheDocument();
expect(await screen.findByTestId('edit-sql')).toBeInTheDocument();
});
it('should render no query if no query is present', async () => {
render(<EdgeInfoDrawer {...mockEdgeInfoDrawer} />);
expect(
await screen.findByText('server.no-query-available')
).toBeInTheDocument();
});
it('should render source of lineage', async () => {
render(<EdgeInfoDrawer {...mockEdgeInfoDrawer} />);
expect(await screen.findByText('label.lineage-source')).toBeInTheDocument();
expect(await screen.findByText('dbt Lineage')).toBeInTheDocument();
});
it('should call onEdgeDetailsUpdate on update description', async () => {
render(<EdgeInfoDrawer {...mockEdgeInfoDrawer} />);
const updateDescriptionButton = await screen.findByTestId(
'update-description-button'
);
await act(async () => {
fireEvent.click(updateDescriptionButton);
});
expect(mockOnEdgeDetailsUpdate).toHaveBeenCalledWith(
expect.objectContaining({
edge: expect.objectContaining({
lineageDetails: expect.objectContaining({
description: 'updatedHTML',
}),
}),
})
);
});
it('should not render edit SQL button if has no edit access', () => {
render(<EdgeInfoDrawer {...mockEdgeInfoDrawer} hasEditAccess={false} />);
expect(screen.queryByTestId('edit-sql')).not.toBeInTheDocument();
});
});

View File

@ -14,6 +14,7 @@
import { t } from 'i18next';
import { ElementLoadingState } from '../components/Entity/EntityLineage/EntityLineage.interface';
import { SearchIndex } from '../enums/search.enum';
import { Source } from '../generated/type/entityLineage';
export const FOREIGN_OBJECT_SIZE = 40;
export const ZOOM_VALUE = 0.75;
@ -73,3 +74,13 @@ export const LINEAGE_DEFAULT_QUICK_FILTERS = [
'owner.displayName.keyword',
'tags.tagFQN',
];
export const LINEAGE_SOURCE: { [key in Source]: string } = {
[Source.DashboardLineage]: 'Dashboard Lineage',
[Source.DbtLineage]: 'dbt Lineage',
[Source.Manual]: 'Manual',
[Source.PipelineLineage]: 'Pipeline Lineage',
[Source.QueryLineage]: 'Query Lineage',
[Source.SparkLineage]: 'Spark Lineage',
[Source.ViewLineage]: 'View Lineage',
};

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "Abstammungsinjektion",
"lineage-lowercase": "Abstammung",
"lineage-node-lowercase": "Abstammungsknoten",
"lineage-source": "Source of Lineage",
"list": "Liste",
"list-entity": "{{entity}}-Liste",
"live": "Live",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "Lineage Ingestion",
"lineage-lowercase": "lineage",
"lineage-node-lowercase": "lineage node",
"lineage-source": "Source of Lineage",
"list": "List",
"list-entity": "List {{entity}}",
"live": "Live",

View File

@ -613,9 +613,10 @@
"lineage": "Linaje",
"lineage-config": "Configuración del linaje",
"lineage-data-lowercase": "lineage data",
"lineage-ingestion": "Ingesta de linaje",
"lineage-lowercase": "linaje",
"lineage-node-lowercase": "nodo de linaje",
"lineage-ingestion": "Ingesta de lineaje",
"lineage-lowercase": "lineaje",
"lineage-node-lowercase": "lineage node",
"lineage-source": "Source of Lineage",
"list": "Lista",
"list-entity": "Lista de {{entity}}",
"live": "Vivo",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "Ingestion de la Traçabilité",
"lineage-lowercase": "traçabilité",
"lineage-node-lowercase": "Nœud de Traçabilité",
"lineage-source": "Source of Lineage",
"list": "Liste",
"list-entity": "Liste de {{entity}}",
"live": "En Direct",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "כניסת שורשים",
"lineage-lowercase": "שורשים",
"lineage-node-lowercase": "צומת שורש",
"lineage-source": "Source of Lineage",
"list": "רשימה",
"list-entity": "רשימת {{entity}}",
"live": "שידור חי",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "リネージのインジェスチョン",
"lineage-lowercase": "リネージ",
"lineage-node-lowercase": "lineage node",
"lineage-source": "Source of Lineage",
"list": "リスト",
"list-entity": "{{entity}}のリスト",
"live": "Live",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "Herkomstingestie",
"lineage-lowercase": "herkomst",
"lineage-node-lowercase": "herkomstknooppunt",
"lineage-source": "Source of Lineage",
"list": "Lijst",
"list-entity": "Lijst {{entity}}",
"live": "Live",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "Ingestão de Linhagem",
"lineage-lowercase": "linhagem",
"lineage-node-lowercase": "nó de linhagem",
"lineage-source": "Source of Lineage",
"list": "Lista",
"list-entity": "Listar {{entity}}",
"live": "Ao Vivo",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "Получение проихождения",
"lineage-lowercase": "происходение",
"lineage-node-lowercase": "узел происхождения",
"lineage-source": "Source of Lineage",
"list": "Список",
"list-entity": "Список {{entity}}",
"live": "Процесс",

View File

@ -616,6 +616,7 @@
"lineage-ingestion": "血缘关系提取",
"lineage-lowercase": "血缘",
"lineage-node-lowercase": "血缘关系节点",
"lineage-source": "Source of Lineage",
"list": "列表",
"list-entity": "列出{{entity}}",
"live": "实时",