App settings - lineage config (#18522)

* add lineage settings page

* update locales

* minor ui changes

* add playwright for lineage config

(cherry picked from commit 3dcbeb469f655ee3372bedf0c94fab7f247e4882)
This commit is contained in:
karanh37 2024-11-07 09:40:01 +05:30
parent ea9b4fe484
commit 3d62581b29
40 changed files with 706 additions and 78 deletions

View File

@ -72,6 +72,9 @@ export enum GlobalSettingOptions {
API_ENDPOINTS = 'apiEndpoints',
DATA_PRODUCTS = 'dataProducts',
DASHBOARD_DATA_MODEL = 'dashboardDataModels',
METRICS = 'metrics',
SEARCH_RBAC = 'search-rbac',
LINEAGE_CONFIG = 'lineageConfig',
}
export const SETTINGS_OPTIONS_PATH = {
@ -188,6 +191,15 @@ export const SETTINGS_OPTIONS_PATH = {
GlobalSettingsMenuCategory.CUSTOM_PROPERTIES,
`${GlobalSettingsMenuCategory.CUSTOM_PROPERTIES}.${GlobalSettingOptions.GLOSSARY_TERM}`,
],
[GlobalSettingOptions.SEARCH_RBAC]: [
GlobalSettingsMenuCategory.PREFERENCES,
`${GlobalSettingsMenuCategory.PREFERENCES}.${GlobalSettingOptions.SEARCH_RBAC}`,
],
[GlobalSettingOptions.LINEAGE_CONFIG]: [
GlobalSettingsMenuCategory.PREFERENCES,
`${GlobalSettingsMenuCategory.PREFERENCES}.${GlobalSettingOptions.LINEAGE_CONFIG}`,
],
};
export const SETTING_CUSTOM_PROPERTIES_PATH = {

View File

@ -12,6 +12,7 @@
*/
import test from '@playwright/test';
import { get } from 'lodash';
import { GlobalSettingOptions } from '../../constant/settings';
import { ApiEndpointClass } from '../../support/entity/ApiEndpointClass';
import { ContainerClass } from '../../support/entity/ContainerClass';
import { DashboardClass } from '../../support/entity/DashboardClass';
@ -33,13 +34,16 @@ import {
connectEdgeBetweenNodes,
deleteEdge,
deleteNode,
fillLineageConfigForm,
performZoomOut,
removeColumnLineage,
setupEntitiesForLineage,
verifyColumnLayerActive,
verifyColumnLayerInactive,
verifyNodePresent,
visitLineageTab,
} from '../../utils/lineage';
import { settingClick } from '../../utils/sidebar';
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
@ -276,3 +280,148 @@ test('Verify column lineage between table and api endpoint', async ({
await afterAction();
});
test('Verify function data in edge drawer', async ({ browser }) => {
const { page } = await createNewPage(browser);
const { apiContext, afterAction } = await getApiContext(page);
const table1 = new TableClass();
const table2 = new TableClass();
try {
await table1.create(apiContext);
await table2.create(apiContext);
const sourceTableFqn = get(table1, 'entityResponseData.fullyQualifiedName');
const sourceColName = `${sourceTableFqn}.${get(
table1,
'entityResponseData.columns[0].name'
)}`;
const targetTableFqn = get(table2, 'entityResponseData.fullyQualifiedName');
const targetColName = `${targetTableFqn}.${get(
table2,
'entityResponseData.columns[0].name'
)}`;
await addPipelineBetweenNodes(page, table1, table2);
await activateColumnLayer(page);
await addColumnLineage(page, sourceColName, targetColName);
const lineageReq = page.waitForResponse('/api/v1/lineage/getLineage?*');
await page.reload();
const lineageRes = await lineageReq;
const jsonRes = await lineageRes.json();
const edge = jsonRes.edges[0];
const columnData = edge.columns[0];
const newEdge = {
edge: {
fromEntity: {
id: edge.fromEntity.id,
type: edge.fromEntity.type,
},
toEntity: {
id: edge.toEntity.id,
type: edge.toEntity.type,
},
lineageDetails: {
columnsLineage: [
{
fromColumns: [columnData.fromColumns[0]],
function: 'count',
toColumn: columnData.toColumn,
},
],
description: 'test',
},
},
};
await apiContext.put(`/api/v1/lineage`, {
data: newEdge,
});
const lineageReq1 = page.waitForResponse('/api/v1/lineage/getLineage?*');
await page.reload();
await lineageReq1;
await activateColumnLayer(page);
await page
.locator(
`[data-testid="column-edge-${btoa(sourceColName)}-${btoa(
targetColName
)}"]`
)
.dispatchEvent('click');
await page.locator('.edge-info-drawer').isVisible();
await expect(await page.locator('[data-testid="Function"]')).toContainText(
'count'
);
} finally {
await table1.delete(apiContext);
await table2.delete(apiContext);
await afterAction();
}
});
test('Verify global lineage config', async ({ browser }) => {
const { page } = await createNewPage(browser);
const { apiContext, afterAction } = await getApiContext(page);
const table = new TableClass();
const topic = new TopicClass();
const dashboard = new DashboardClass();
const mlModel = new MlModelClass();
try {
await table.create(apiContext);
await topic.create(apiContext);
await dashboard.create(apiContext);
await mlModel.create(apiContext);
await addPipelineBetweenNodes(page, table, topic);
await addPipelineBetweenNodes(page, topic, dashboard);
await addPipelineBetweenNodes(page, dashboard, mlModel);
await topic.visitEntityPage(page);
await visitLineageTab(page);
await verifyNodePresent(page, table);
await verifyNodePresent(page, dashboard);
await verifyNodePresent(page, mlModel);
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 1,
downstreamDepth: 1,
layer: 'Column Level Lineage',
});
await topic.visitEntityPage(page);
await visitLineageTab(page);
await verifyNodePresent(page, table);
await verifyNodePresent(page, dashboard);
const mlModelFqn = get(mlModel, 'entityResponseData.fullyQualifiedName');
const mlModelNode = page.locator(
`[data-testid="lineage-node-${mlModelFqn}"]`
);
await expect(mlModelNode).not.toBeVisible();
await verifyColumnLayerActive(page);
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 2,
downstreamDepth: 2,
layer: 'Entity Lineage',
});
} finally {
await table.delete(apiContext);
await topic.delete(apiContext);
await dashboard.delete(apiContext);
await mlModel.delete(apiContext);
await afterAction();
}
});

View File

@ -21,7 +21,11 @@ import { PipelineClass } from '../support/entity/PipelineClass';
import { SearchIndexClass } from '../support/entity/SearchIndexClass';
import { TableClass } from '../support/entity/TableClass';
import { TopicClass } from '../support/entity/TopicClass';
import { getApiContext, getEntityTypeSearchIndexMapping } from './common';
import {
getApiContext,
getEntityTypeSearchIndexMapping,
toastNotification,
} from './common';
export const verifyColumnLayerInactive = async (page: Page) => {
await page.click('[data-testid="lineage-layer-btn"]'); // Open Layer popover
@ -395,3 +399,29 @@ export const visitLineageTab = async (page: Page) => {
await page.click('[data-testid="lineage"]');
await lineageRes;
};
export const fillLineageConfigForm = async (
page: Page,
config: { upstreamDepth: number; downstreamDepth: number; layer: string }
) => {
await page
.getByTestId('field-upstream')
.fill(config.upstreamDepth.toString());
await page
.getByTestId('field-downstream')
.fill(config.downstreamDepth.toString());
await page.getByTestId('field-lineage-layer').click();
await page.locator(`.ant-select-item[title="${config.layer}"]`).click();
const saveRes = page.waitForResponse('/api/v1/system/settings');
await page.getByTestId('save-button').click();
await saveRes;
await toastNotification(page, /Lineage Config updated successfully/);
};
export const verifyColumnLayerActive = async (page: Page) => {
await page.click('[data-testid="lineage-layer-btn"]'); // Open Layer popover
await page.waitForSelector('[data-testid="lineage-layer-column-btn"].active');
await page.click('[data-testid="lineage-layer-btn"]'); // Close Layer popover
};

View File

@ -0,0 +1,27 @@
# Lineage Configuration
Configure lineage view settings.
$$section
### Upstream Depth $(id="upstreamDepth")
Display up to 5 nodes of upstream lineage to identify the source (parent levels).
$$
$$section
### Downstream Depth $(id="downstreamDepth")
Display up to 5 nodes of downstream lineage to identify the target (child levels).
$$
$$section
### Lineage Layer $(id="lineageLayer")
Select a lineage layer to view specific data relationships:
- **Entity Lineage**: Displays relationships at the entity level, focusing on higher-level data connections.
- **Column Level Lineage**: Shows lineage at the column level, detailing relationships between individual columns.
- **Data Observability**: Highlights data quality and monitoring insights within the lineage.
$$

View File

@ -0,0 +1 @@
<svg id="Layer_1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g><g fill="#006db7"><path d="m50 54.608c-1.15 0-2.083-.933-2.083-2.084v-18.862c0-1.151.933-2.084 2.083-2.084s2.083.933 2.083 2.084v18.862c0 1.152-.933 2.084-2.083 2.084z"/><path d="m50 54.608h-33.229c-1.15 0-2.083-.933-2.083-2.084 0-1.15.933-2.083 2.083-2.083h33.229c1.15 0 2.083.933 2.083 2.083 0 1.152-.933 2.084-2.083 2.084z"/><path d="m16.771 68.419c-1.15 0-2.083-.933-2.083-2.083v-13.812c0-1.15.933-2.083 2.083-2.083s2.083.933 2.083 2.083v13.812c.001 1.15-.932 2.083-2.083 2.083z"/><path d="m83.228 54.608h-33.228c-1.15 0-2.083-.933-2.083-2.084 0-1.15.933-2.083 2.083-2.083h33.228c1.15 0 2.083.933 2.083 2.083 0 1.152-.933 2.084-2.083 2.084z"/><path d="m83.228 68.419c-1.15 0-2.083-.933-2.083-2.083v-13.812c0-1.15.933-2.083 2.083-2.083s2.083.933 2.083 2.083v13.812c0 1.15-.933 2.083-2.083 2.083z"/></g><path d="m33.229 4.865h33.544v9.493h-33.544z" fill="#09afaa"/><path d="m33.229 14.358h33.544v19.304h-33.544z" fill="#0fd8ff"/><path d="m57.279 26.095h-14.557c-1.15 0-2.083-.934-2.083-2.084s.933-2.083 2.083-2.083h14.558c1.15 0 2.084.933 2.084 2.083s-.934 2.084-2.085 2.084z" fill="#61a8aa"/><path d="m0 66.336h33.544v9.496h-33.544z" fill="#09afaa"/><path d="m0 75.832h33.544v19.303h-33.544z" fill="#0fd8ff"/><path d="m24.051 87.565h-14.558c-1.151 0-2.083-.934-2.083-2.084s.933-2.083 2.083-2.083h14.558c1.15 0 2.083.933 2.083 2.083s-.933 2.084-2.083 2.084z" fill="#61a8aa"/><path d="m66.457 66.336h33.543v9.496h-33.543z" fill="#09afaa"/><path d="m66.457 75.832h33.543v19.303h-33.543z" fill="#0fd8ff"/><path d="m90.507 87.565h-14.557c-1.15 0-2.083-.934-2.083-2.084s.933-2.083 2.083-2.083h14.557c1.15 0 2.083.933 2.083 2.083s-.933 2.084-2.083 2.084z" fill="#61a8aa"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -16,8 +16,11 @@ import classNames from 'classnames';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useLimitStore } from '../../context/LimitsProvider/useLimitsStore';
import { LineageSettings } from '../../generated/configuration/lineageSettings';
import { SettingType } from '../../generated/settings/settings';
import { useApplicationStore } from '../../hooks/useApplicationStore';
import { getLimitConfig } from '../../rest/limitsAPI';
import { getSettingsByType } from '../../rest/settingConfigAPI';
import applicationRoutesClass from '../../utils/ApplicationRoutesClassBase';
import Appbar from '../AppBar/Appbar';
import { LimitBanner } from '../common/LimitBanner/LimitBanner';
@ -29,18 +32,22 @@ import './app-container.less';
const AppContainer = () => {
const { i18n } = useTranslation();
const { Header, Sider, Content } = Layout;
const { currentUser } = useApplicationStore();
const { currentUser, setAppPreferences } = useApplicationStore();
const { applications } = useApplicationsProvider();
const AuthenticatedRouter = applicationRoutesClass.getRouteElements();
const ApplicationExtras = applicationsClassBase.getApplicationExtension();
const isDirectionRTL = useMemo(() => i18n.dir() === 'rtl', [i18n]);
const { setConfig, bannerDetails } = useLimitStore();
const fetchLimitConfig = useCallback(async () => {
const fetchAppConfigurations = useCallback(async () => {
try {
const response = await getLimitConfig();
const [response, lineageConfig] = await Promise.all([
getLimitConfig(),
getSettingsByType(SettingType.LineageSettings),
]);
setConfig(response);
setAppPreferences({ lineageConfig: lineageConfig as LineageSettings });
} catch (error) {
// silent fail
}
@ -53,7 +60,7 @@ const AppContainer = () => {
useEffect(() => {
if (currentUser?.id) {
fetchLimitConfig();
fetchAppConfigurations();
}
}, [currentUser?.id]);

View File

@ -35,6 +35,7 @@ import EditEmailConfigPage from '../../pages/EditEmailConfigPage/EditEmailConfig
import EmailConfigSettingsPage from '../../pages/EmailConfigSettingsPage/EmailConfigSettingsPage.component';
import GlobalSettingCategoryPage from '../../pages/GlobalSettingPage/GlobalSettingCategory/GlobalSettingCategoryPage';
import GlobalSettingPage from '../../pages/GlobalSettingPage/GlobalSettingPage';
import LineageConfigPage from '../../pages/LineageConfigPage/LineageConfigPage';
import NotificationListPage from '../../pages/NotificationListPage/NotificationListPage';
import OmHealthPage from '../../pages/OmHealth/OmHealthPage';
import { PersonaDetailsPage } from '../../pages/Persona/PersonaDetailsPage/PersonaDetailsPage';
@ -245,6 +246,15 @@ const SettingsRouter = () => {
* Do not change the order of these route
*/}
<AdminProtectedRoute
exact
component={LineageConfigPage}
path={getSettingPath(
GlobalSettingsMenuCategory.PREFERENCES,
GlobalSettingOptions.LINEAGE_CONFIG
)}
/>
<AdminProtectedRoute
exact
component={PoliciesListPage}

View File

@ -32,8 +32,8 @@ import { ReactComponent as ExportIcon } from '../../../assets/svg/ic-export.svg'
import { NO_PERMISSION_FOR_ACTION } from '../../../constants/HelperTextUtil';
import { LINEAGE_DEFAULT_QUICK_FILTERS } from '../../../constants/Lineage.constants';
import { useLineageProvider } from '../../../context/LineageProvider/LineageProvider';
import { LineageLayerView } from '../../../context/LineageProvider/LineageProvider.interface';
import { SearchIndex } from '../../../enums/search.enum';
import { LineageLayer } from '../../../generated/settings/settings';
import { useApplicationStore } from '../../../hooks/useApplicationStore';
import { getAssetsPageQuickFilters } from '../../../utils/AdvancedSearchUtils';
import { getLoadingStatusValue } from '../../../utils/EntityLineageUtils';
@ -75,7 +75,7 @@ const CustomControls: FC<ControlProps> = ({
>([]);
const [filters, setFilters] = useState<ExploreQuickFilterField[]>([]);
const isColumnLayerActive = useMemo(() => {
return activeLayer.includes(LineageLayerView.COLUMN);
return activeLayer.includes(LineageLayer.ColumnLevelLineage);
}, [activeLayer]);
const handleMenuClick = ({ key }: { key: string }) => {

View File

@ -12,8 +12,8 @@
*/
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { LineageLayerView } from '../../../context/LineageProvider/LineageProvider.interface';
import { LOADING_STATE } from '../../../enums/common.enum';
import { LineageLayer } from '../../../generated/settings/settings';
import { MOCK_LINEAGE_DATA } from '../../../mocks/Lineage.mock';
import CustomControlsComponent from './CustomControls.component';
@ -51,7 +51,7 @@ jest.mock('../../../context/LineageProvider/LineageProvider', () => ({
useLineageProvider: jest.fn().mockImplementation(() => ({
onLineageEditClick: mockOnEditLineageClick,
onExportClick: mockOnExportClick,
activeLayer: [LineageLayerView.COLUMN],
activeLayer: [LineageLayer.ColumnLevelLineage],
})),
}));

View File

@ -15,8 +15,8 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { EdgeProps, Position } from 'reactflow';
import { LineageLayerView } from '../../../context/LineageProvider/LineageProvider.interface';
import { EntityType } from '../../../enums/entity.enum';
import { LineageLayer } from '../../../generated/settings/settings';
import { CustomEdge } from './CustomEdge.component';
jest.mock('../../../constants/Lineage.constants', () => ({
@ -71,7 +71,7 @@ jest.mock('../../../context/LineageProvider/LineageProvider', () => ({
tracedNodes: [],
tracedColumns: [],
pipelineStatus: {},
activeLayer: [LineageLayerView.COLUMN],
activeLayer: [LineageLayer.ColumnLevelLineage],
fetchPipelineStatus: jest.fn(),
})),
}));

View File

@ -21,9 +21,9 @@ import { ReactComponent as IconTimesCircle } from '../../../assets/svg/ic-times-
import { ReactComponent as PipelineIcon } from '../../../assets/svg/pipeline-grey.svg';
import { FOREIGN_OBJECT_SIZE } from '../../../constants/Lineage.constants';
import { useLineageProvider } from '../../../context/LineageProvider/LineageProvider';
import { LineageLayerView } from '../../../context/LineageProvider/LineageProvider.interface';
import { EntityType } from '../../../enums/entity.enum';
import { StatusType } from '../../../generated/entity/data/pipeline';
import { LineageLayer } from '../../../generated/settings/settings';
import { useApplicationStore } from '../../../hooks/useApplicationStore';
import { getColumnSourceTargetHandles } from '../../../utils/EntityLineageUtils';
import { getEntityName } from '../../../utils/EntityUtils';
@ -207,7 +207,7 @@ export const CustomEdge = ({
const currentPipelineStatus = useMemo(() => {
const isPipelineActiveNow = activeLayer.includes(
LineageLayerView.DATA_OBSERVARABILITY
LineageLayer.DataObservability
);
const pipelineData = pipeline?.pipelineStatus;
if (pipelineData && isPipelineActiveNow) {

View File

@ -13,8 +13,8 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { ReactFlowProvider } from 'reactflow';
import { LineageLayerView } from '../../../context/LineageProvider/LineageProvider.interface';
import { ModelType } from '../../../generated/entity/data/table';
import { LineageLayer } from '../../../generated/settings/settings';
import CustomNodeV1Component from './CustomNodeV1.component';
const mockNodeDataProps = {
@ -87,7 +87,7 @@ jest.mock('../../../context/LineageProvider/LineageProvider', () => ({
downstreamEdges: [],
},
columnsHavingLineage: [],
activeLayer: [LineageLayerView.COLUMN],
activeLayer: [LineageLayer.ColumnLevelLineage],
fetchPipelineStatus: jest.fn(),
onColumnClick: onMockColumnClick,
})),

View File

@ -11,13 +11,19 @@
* limitations under the License.
*/
import { Form, Input, Modal } from 'antd';
import React, { useState } from 'react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
LineageConfig,
LineageConfigModalProps,
} from './EntityLineage.interface';
type LineageConfigFormFields = {
upstreamDepth: number;
downstreamDepth: number;
nodesPerLayer: number;
};
const LineageConfigModal: React.FC<LineageConfigModalProps> = ({
visible,
config,
@ -26,21 +32,12 @@ const LineageConfigModal: React.FC<LineageConfigModalProps> = ({
}) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const [upstreamDepth, setUpstreamDepth] = useState<number>(
config?.upstreamDepth || 1
);
const [downstreamDepth, setDownstreamDepth] = useState<number>(
config?.downstreamDepth || 1
);
const [nodesPerLayer, setNodesPerLayer] = useState<number>(
config?.nodesPerLayer || 1
);
const handleSave = () => {
const handleSave = (values: LineageConfigFormFields) => {
const updatedConfig: LineageConfig = {
upstreamDepth,
downstreamDepth,
nodesPerLayer,
upstreamDepth: Number(values.upstreamDepth),
downstreamDepth: Number(values.downstreamDepth),
nodesPerLayer: Number(values.nodesPerLayer),
};
onSave(updatedConfig);
};
@ -67,12 +64,7 @@ const LineageConfigModal: React.FC<LineageConfigModalProps> = ({
},
]}
tooltip={t('message.upstream-depth-tooltip')}>
<Input
data-testid="field-upstream"
type="number"
value={upstreamDepth}
onChange={(e) => setUpstreamDepth(Number(e.target.value))}
/>
<Input data-testid="field-upstream" type="number" />
</Form.Item>
<Form.Item
@ -85,12 +77,7 @@ const LineageConfigModal: React.FC<LineageConfigModalProps> = ({
},
]}
tooltip={t('message.downstream-depth-tooltip')}>
<Input
data-testid="field-downstream"
type="number"
value={downstreamDepth}
onChange={(e) => setDownstreamDepth(Number(e.target.value))}
/>
<Input data-testid="field-downstream" type="number" />
</Form.Item>
<Form.Item
@ -108,7 +95,6 @@ const LineageConfigModal: React.FC<LineageConfigModalProps> = ({
data-testid="field-nodes-per-layer"
min={5}
type="number"
onChange={(e) => setNodesPerLayer(Number(e.target.value))}
/>
</Form.Item>
</Form>

View File

@ -14,7 +14,7 @@ import { act, queryByText, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { ReactFlowProvider } from 'reactflow';
import { LineageLayerView } from '../../../../context/LineageProvider/LineageProvider.interface';
import { LineageLayer } from '../../../../generated/settings/settings';
import LineageLayers from './LineageLayers';
const onMockColumnClick = jest.fn();
@ -113,13 +113,13 @@ describe('LineageLayers component', () => {
userEvent.click(columnButton as HTMLElement);
expect(onMockUpdateLayerView).toHaveBeenCalledWith([
LineageLayerView.COLUMN,
LineageLayer.ColumnLevelLineage,
]);
userEvent.click(dataObservabilityBtn as HTMLElement);
expect(onMockUpdateLayerView).toHaveBeenCalledWith([
LineageLayerView.DATA_OBSERVARABILITY,
LineageLayer.DataObservability,
]);
});
});

View File

@ -18,15 +18,15 @@ import React from 'react';
import { ReactComponent as DataQualityIcon } from '../../../../assets/svg/ic-data-contract.svg';
import { ReactComponent as Layers } from '../../../../assets/svg/ic-layers.svg';
import { useLineageProvider } from '../../../../context/LineageProvider/LineageProvider';
import { LineageLayerView } from '../../../../context/LineageProvider/LineageProvider.interface';
import { EntityType } from '../../../../enums/entity.enum';
import { LineageLayer } from '../../../../generated/settings/settings';
import searchClassBase from '../../../../utils/SearchClassBase';
import './lineage-layers.less';
const LineageLayers = () => {
const { activeLayer, onUpdateLayerView, isEditMode } = useLineageProvider();
const onButtonClick = (value: LineageLayerView) => {
const onButtonClick = (value: LineageLayer) => {
const index = activeLayer.indexOf(value);
if (index === -1) {
onUpdateLayerView([...activeLayer, value]);
@ -41,10 +41,10 @@ const LineageLayers = () => {
<ButtonGroup>
<Button
className={classNames('lineage-layer-button h-15', {
active: activeLayer.includes(LineageLayerView.COLUMN),
active: activeLayer.includes(LineageLayer.ColumnLevelLineage),
})}
data-testid="lineage-layer-column-btn"
onClick={() => onButtonClick(LineageLayerView.COLUMN)}>
onClick={() => onButtonClick(LineageLayer.ColumnLevelLineage)}>
<div className="lineage-layer-btn">
<div className="layer-icon">
{searchClassBase.getEntityIcon(EntityType.TABLE)}
@ -56,14 +56,10 @@ const LineageLayers = () => {
</Button>
<Button
className={classNames('lineage-layer-button h-15', {
active: activeLayer.includes(
LineageLayerView.DATA_OBSERVARABILITY
),
active: activeLayer.includes(LineageLayer.DataObservability),
})}
data-testid="lineage-layer-observability-btn"
onClick={() =>
onButtonClick(LineageLayerView.DATA_OBSERVARABILITY)
}>
onClick={() => onButtonClick(LineageLayer.DataObservability)}>
<div className="lineage-layer-btn">
<div className="layer-icon">
<DataQualityIcon />

View File

@ -12,8 +12,8 @@
*/
import { act, fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { LineageLayerView } from '../../../../context/LineageProvider/LineageProvider.interface';
import { EntityType } from '../../../../enums/entity.enum';
import { LineageLayer } from '../../../../generated/settings/settings';
import LineageSearchSelect from './LineageSearchSelect';
const mockedNodes = [
@ -38,7 +38,7 @@ const mockColumnClick = jest.fn();
jest.mock('../../../../context/LineageProvider/LineageProvider', () => ({
useLineageProvider: jest.fn().mockImplementation(() => ({
activeLayer: [LineageLayerView.COLUMN],
activeLayer: [LineageLayer.ColumnLevelLineage],
nodes: mockedNodes,
onNodeClick: mockNodeClick,
onColumnClick: mockColumnClick,

View File

@ -21,9 +21,9 @@ import {
LINEAGE_COLUMN_NODE_SUPPORTED,
} from '../../../../constants/Lineage.constants';
import { useLineageProvider } from '../../../../context/LineageProvider/LineageProvider';
import { LineageLayerView } from '../../../../context/LineageProvider/LineageProvider.interface';
import { EntityType } from '../../../../enums/entity.enum';
import { Column, Table } from '../../../../generated/entity/data/table';
import { LineageLayer } from '../../../../generated/settings/settings';
import { getEntityChildrenAndLabel } from '../../../../utils/EntityLineageUtils';
import { getEntityName } from '../../../../utils/EntityUtils';
import searchClassBase from '../../../../utils/SearchClassBase';
@ -50,9 +50,9 @@ const NodeChildren = ({ node, isConnectable }: NodeChildrenProps) => {
const { showColumns, showDataObservability } = useMemo(() => {
return {
showColumns: activeLayer.includes(LineageLayerView.COLUMN),
showColumns: activeLayer.includes(LineageLayer.ColumnLevelLineage),
showDataObservability: activeLayer.includes(
LineageLayerView.DATA_OBSERVARABILITY
LineageLayer.DataObservability
),
};
}, [activeLayer]);

View File

@ -1,3 +1,15 @@
/*
* 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 React, { FC } from 'react';
import './banner.less';

View File

@ -1,3 +1,15 @@
/*
* 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 (reference) url('../../../styles/variables.less');
.message-banner-wrapper {

View File

@ -75,6 +75,8 @@ export enum GlobalSettingOptions {
API_COLLECTIONS = 'apiCollections',
API_ENDPOINTS = 'apiEndpoints',
DATA_PRODUCT = 'dataProducts',
METRICS = 'metrics',
LINEAGE_CONFIG = 'lineageConfig',
}
export const GLOBAL_SETTING_PERMISSION_RESOURCES = [

View File

@ -198,6 +198,10 @@ export const PAGE_HEADERS = {
header: i18n.t('label.login'),
subHeader: i18n.t('message.page-sub-header-for-login-configuration'),
},
LINEAGE_CONFIG: {
header: i18n.t('label.lineage-config'),
subHeader: i18n.t('message.page-sub-header-for-lineage-config-setting'),
},
OM_HEALTH: {
header: i18n.t('label.health-check'),
subHeader: i18n.t('message.page-sub-header-for-om-health-configuration'),

View File

@ -32,6 +32,7 @@ import {
import { SourceType } from '../../components/SearchedData/SearchedData.interface';
import { EntityType } from '../../enums/entity.enum';
import { EntityReference } from '../../generated/entity/type';
import { LineageLayer } from '../../generated/settings/settings';
export interface LineageProviderProps {
children: ReactNode;
@ -44,11 +45,6 @@ export type UpstreamDownstreamData = {
upstreamNodes: EntityReference[];
};
export enum LineageLayerView {
COLUMN = 'COLUMN',
DATA_OBSERVARABILITY = 'DATA_OBSERVARABILITY',
}
export interface LineageContextType {
reactFlowInstance?: ReactFlowInstance;
nodes: Node[];
@ -67,7 +63,7 @@ export interface LineageContextType {
selectedNode: SourceType;
upstreamDownstreamData: UpstreamDownstreamData;
selectedColumn: string;
activeLayer: LineageLayerView[];
activeLayer: LineageLayer[];
expandAllColumns: boolean;
toggleColumnView: () => void;
onInitReactFlow: (reactFlowInstance: ReactFlowInstance) => void;
@ -99,5 +95,5 @@ export interface LineageContextType {
onAddPipelineClick: () => void;
onConnect: (connection: Edge | Connection) => void;
updateEntityType: (entityType: EntityType) => void;
onUpdateLayerView: (layers: LineageLayerView[]) => void;
onUpdateLayerView: (layers: LineageLayer[]) => void;
}

View File

@ -23,6 +23,20 @@ const mockLocation = {
pathname: '/lineage',
};
const mockData = {
lineageConfig: {
upstreamDepth: 1,
downstreamDepth: 1,
lineageLayer: 'EntityLineage',
},
};
jest.mock('../../hooks/useApplicationStore', () => ({
useApplicationStore: jest.fn().mockImplementation(() => ({
appPreferences: mockData,
})),
}));
const DummyChildrenComponent = () => {
const {
loadChildNodesHandler,
@ -56,6 +70,7 @@ const DummyChildrenComponent = () => {
},
},
};
const handleButtonClick = () => {
// Trigger the loadChildNodesHandler method when the button is clicked
loadChildNodesHandler(nodeData, EdgeTypeEnum.DOWN_STREAM);
@ -85,6 +100,10 @@ const DummyChildrenComponent = () => {
);
};
jest.mock('../../hooks/useCustomLocation/useCustomLocation', () => {
return jest.fn().mockImplementation(() => ({ ...mockLocation }));
});
jest.mock('react-router-dom', () => ({
useHistory: jest.fn().mockReturnValue({ push: jest.fn(), listen: jest.fn() }),
useLocation: jest.fn().mockImplementation(() => mockLocation),
@ -141,7 +160,7 @@ describe('LineageProvider', () => {
);
});
const loadButton = await screen.getByTestId('load-nodes');
const loadButton = screen.getByTestId('load-nodes');
fireEvent.click(loadButton);
expect(getLineageDataByFQN).toHaveBeenCalled();
@ -156,7 +175,7 @@ describe('LineageProvider', () => {
);
});
const loadButton = await screen.getByTestId('editLineage');
const loadButton = screen.getByTestId('editLineage');
fireEvent.click(loadButton);
const edgeDrawer = screen.getByText('Entity Lineage Sidebar');
@ -173,7 +192,7 @@ describe('LineageProvider', () => {
);
});
const edgeClick = await screen.getByTestId('edge-click');
const edgeClick = screen.getByTestId('edge-click');
fireEvent.click(edgeClick);
const edgeDrawer = screen.getByText('Edge Info Drawer');

View File

@ -68,11 +68,14 @@ import {
EntityType,
} from '../../enums/entity.enum';
import { AddLineage } from '../../generated/api/lineage/addLineage';
import { LineageSettings } from '../../generated/configuration/lineageSettings';
import { LineageLayer } from '../../generated/settings/settings';
import {
ColumnLineage,
EntityReference,
LineageDetails,
} from '../../generated/type/entityLineage';
import { useApplicationStore } from '../../hooks/useApplicationStore';
import { useFqn } from '../../hooks/useFqn';
import { getLineageDataByFQN, updateLineageEdge } from '../../rest/lineageAPI';
import {
@ -105,7 +108,6 @@ import { showErrorToast } from '../../utils/ToastUtils';
import { useTourProvider } from '../TourProvider/TourProvider';
import {
LineageContextType,
LineageLayerView,
LineageProviderProps,
UpstreamDownstreamData,
} from './LineageProvider.interface';
@ -114,9 +116,11 @@ export const LineageContext = createContext({} as LineageContextType);
const LineageProvider = ({ children }: LineageProviderProps) => {
const { t } = useTranslation();
const { fqn: decodedFqn } = useFqn();
const { isTourOpen, isTourPage } = useTourProvider();
const { appPreferences } = useApplicationStore();
const defaultLineageConfig = appPreferences?.lineageConfig as LineageSettings;
const isLineageSettingsLoaded = !isUndefined(defaultLineageConfig);
const [reactFlowInstance, setReactFlowInstance] =
useState<ReactFlowInstance>();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
@ -124,7 +128,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
const [selectedNode, setSelectedNode] = useState<SourceType>(
{} as SourceType
);
const [activeLayer, setActiveLayer] = useState<LineageLayerView[]>([]);
const [activeLayer, setActiveLayer] = useState<LineageLayer[]>([]);
const [activeNode, setActiveNode] = useState<Node>();
const [expandAllColumns, setExpandAllColumns] = useState(false);
const [selectedColumn, setSelectedColumn] = useState<string>('');
@ -375,7 +379,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
[nodes, edges]
);
const onUpdateLayerView = useCallback((layers: LineageLayerView[]) => {
const onUpdateLayerView = useCallback((layers: LineageLayer[]) => {
setActiveLayer(layers);
}, []);
@ -1036,7 +1040,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
const repositionLayout = useCallback(
(activateNode = false) => {
const isColView = activeLayer.includes(LineageLayerView.COLUMN);
const isColView = activeLayer.includes(LineageLayer.ColumnLevelLineage);
const { node, edge } = getLayoutedElements(
{
node: nodes,
@ -1092,7 +1096,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
allNodes,
lineageData.edges ?? [],
decodedFqn,
activeLayer.includes(LineageLayerView.COLUMN)
activeLayer.includes(LineageLayer.ColumnLevelLineage)
);
const { edges: updatedEdges, columnsHavingLineage } = createEdges(
allNodes,
@ -1119,10 +1123,32 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
);
useEffect(() => {
if (decodedFqn && entityType) {
if (defaultLineageConfig) {
setLineageConfig({
upstreamDepth: defaultLineageConfig.upstreamDepth,
downstreamDepth: defaultLineageConfig.downstreamDepth,
nodesPerLayer: 50,
});
setActiveLayer(
defaultLineageConfig.lineageLayer === LineageLayer.EntityLineage
? []
: [defaultLineageConfig.lineageLayer]
);
}
}, [defaultLineageConfig]);
useEffect(() => {
if (decodedFqn && entityType && isLineageSettingsLoaded) {
fetchLineageData(decodedFqn, entityType, lineageConfig);
}
}, [lineageConfig, decodedFqn, queryFilter, entityType]);
}, [
lineageConfig,
decodedFqn,
queryFilter,
entityType,
isLineageSettingsLoaded,
]);
useEffect(() => {
if (!loading) {

View File

@ -52,6 +52,7 @@ export const useApplicationStore = create<ApplicationStore>()(
searchCriteria: '',
inlineAlertDetails: undefined,
applications: [],
appPreferences: {},
setInlineAlertDetails: (inlineAlertDetails) => {
set({ inlineAlertDetails });
@ -150,6 +151,16 @@ export const useApplicationStore = create<ApplicationStore>()(
setRefreshToken: (refreshToken) => {
set({ refreshTokenKey: refreshToken });
},
setAppPreferences: (
preferences: Partial<ApplicationStore['appPreferences']>
) => {
set((state) => ({
appPreferences: {
...state.appPreferences,
...preferences,
},
}));
},
getOidcToken: () => {
return get()?.oidcIdToken;
},

View File

@ -23,6 +23,7 @@ import {
} from '../components/Explore/ExplorePage.interface';
import { AuthenticationConfiguration } from '../generated/configuration/authenticationConfiguration';
import { AuthorizerConfiguration } from '../generated/configuration/authorizerConfiguration';
import { LineageSettings } from '../generated/configuration/lineageSettings';
import { LoginConfiguration } from '../generated/configuration/loginConfiguration';
import { LogoConfiguration } from '../generated/configuration/logoConfiguration';
import { UIThemePreference } from '../generated/configuration/uiThemePreference';
@ -39,6 +40,10 @@ export interface HelperFunctions {
trySilentSignIn: (forceLogout?: boolean) => Promise<void>;
}
export interface AppPreferences {
lineageConfig?: LineageSettings;
}
export interface ApplicationStore
extends IAuthContext,
LogoConfiguration,
@ -56,9 +61,11 @@ export interface ApplicationStore
theme: UIThemePreference['customTheme'];
inlineAlertDetails?: InlineAlertProps;
applications: string[];
appPreferences: AppPreferences;
setInlineAlertDetails: (alertDetails?: InlineAlertProps) => void;
setSelectedPersona: (persona: EntityReference) => void;
setApplicationConfig: (config: UIThemePreference) => void;
setAppPreferences: (preferences: AppPreferences) => void;
setCurrentUser: (user: User) => void;
setAuthConfig: (authConfig: AuthenticationConfigurationWithScope) => void;
setAuthorizerConfig: (authorizerConfig: AuthorizerConfiguration) => void;

View File

@ -181,6 +181,7 @@
"color": "Color",
"column": "Spalte",
"column-entity": "{{entity}} Spalten",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "spalte",
"column-lowercase-plural": "spalten",
"column-plural": "Spalten",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Entitäts-ID-Übereinstimmung",
"entity-index": "{{entity}}-Index",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "{{entity}}-Name",
"entity-plural": "Entitäten",
@ -643,6 +645,7 @@
"last-run-result": "Ergebnis der letzten Ausführung",
"last-updated": "Zuletzt aktualisiert",
"latest": "Neueste",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "Learn more & Support",
@ -655,6 +658,7 @@
"lineage-config": "Abstammungskonfiguration",
"lineage-data-lowercase": "Abstammungsdaten",
"lineage-ingestion": "Abstammungsinjektion",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "Abstammung",
"lineage-node-lowercase": "Abstammungsknoten",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Vertrauen in deine Daten aufbauen mit Qualitätsprüfungen und zuverlässige Datenerzeugnisse erstellen.",
"page-sub-header-for-databases": "Ingestion von Metadaten aus den beliebtesten Datenbankdiensten.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Login configuration such as failed attempts or expiry timer.",
"page-sub-header-for-messagings": "Ingestion von Metadaten aus den am häufigsten verwendeten Messaging-Diensten.",
"page-sub-header-for-metadata": "Ingestion von Metadaten aus Metadatendiensten direkt über die Benutzeroberfläche.",

View File

@ -181,6 +181,7 @@
"color": "Color",
"column": "Column",
"column-entity": "Column {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "column",
"column-lowercase-plural": "columns",
"column-plural": "Columns",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Match By Id",
"entity-index": "{{entity}} index",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "{{entity}} Name",
"entity-plural": "Entities",
@ -643,6 +645,7 @@
"last-run-result": "Last Run Result",
"last-updated": "Last Updated",
"latest": "Latest",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "Learn more & Support",
@ -655,6 +658,7 @@
"lineage-config": "Lineage Config",
"lineage-data-lowercase": "lineage data",
"lineage-ingestion": "Lineage Ingestion",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "lineage",
"lineage-node-lowercase": "lineage node",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Build trust in your data with quality tests and create reliable data products.",
"page-sub-header-for-databases": "Ingest metadata from the most popular database services.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Define the handling of failed login attempts and JWT token expiry.",
"page-sub-header-for-messagings": "Ingest metadata from the most used messaging services.",
"page-sub-header-for-metadata": "Ingest metadata from metadata services right from the UI.",

View File

@ -181,6 +181,7 @@
"color": "Color",
"column": "Columna",
"column-entity": "Columna {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "columna",
"column-lowercase-plural": "columnas",
"column-plural": "Columnas",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Coincidir por Id",
"entity-index": "Índice de {{entity}}",
"entity-lineage": "Entity Lineage",
"entity-list": "Lista de {{entity}}",
"entity-name": "Nombre de {{entity}}",
"entity-plural": "Entidades",
@ -643,6 +645,7 @@
"last-run-result": "Último resultado de la ejecución",
"last-updated": "Última actualización",
"latest": "Último",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Más información",
"learn-more-and-support": "Más información y soporte",
@ -655,6 +658,7 @@
"lineage-config": "Configuración del linaje",
"lineage-data-lowercase": "lineage data",
"lineage-ingestion": "Ingesta de lineaje",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "lineaje",
"lineage-node-lowercase": "lineage node",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Construye confianza en tus datos con pruebas de calidad y crea productos de datos fiables.",
"page-sub-header-for-databases": "Ingresa metadatos desde los servicios de base de datos más populares.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Configuración de inicio de sesión como intentos fallidos o temporizador de vencimiento.",
"page-sub-header-for-messagings": "Ingresa metadatos desde los servicios de mensajería más utilizados.",
"page-sub-header-for-metadata": "Ingresa metadatos desde servicios de metadatos directamente desde la interfaz de usuario.",

View File

@ -181,6 +181,7 @@
"color": "Color",
"column": "Colonne",
"column-entity": "{{entity}} Colonnes",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "colonne",
"column-lowercase-plural": "colonnes",
"column-plural": "Colonnes",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Correspondance par ID de l'Entité",
"entity-index": "Index de {{entity}}",
"entity-lineage": "Entity Lineage",
"entity-list": "Liste de {{entity}}",
"entity-name": "Nom de {{entity}}",
"entity-plural": "Entités",
@ -643,6 +645,7 @@
"last-run-result": "Résultat de la Dernière Exécution",
"last-updated": "Dernière Mise à Jour",
"latest": "Dernier·ère",
"layer": "Layer",
"layer-plural": "Couches",
"learn-more": "Learn More",
"learn-more-and-support": "Learn more & Support",
@ -655,6 +658,7 @@
"lineage-config": "Config de Lignage",
"lineage-data-lowercase": "lignage des données",
"lineage-ingestion": "Ingestion de lignage",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "lignage",
"lineage-node-lowercase": "Nœud de Lignage",
"lineage-source": "Source du Lignage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingestion de metadonnées à partir des services de tests directement depuis l'interface utilisateur.",
"page-sub-header-for-data-quality": "Gagnez en confiance dans vos données grâce à des tests de qualité et créez des produits de données fiables.",
"page-sub-header-for-databases": "Ingestion de métadonnées à partir des services de base de données les plus populaires.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Configuration du login comme les tentatives infructueuses ou l'expiration du délai.",
"page-sub-header-for-messagings": "Ingestion de métadonnées à partir des services de messagerie les plus utilisés.",
"page-sub-header-for-metadata": "Ingestion de métadonnées à partir des services de métadonnées directement depuis l'interface utilisateur.",

View File

@ -181,6 +181,7 @@
"color": "צבע",
"column": "עמודה",
"column-entity": "עמודה {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "עמודה",
"column-lowercase-plural": "עמודות",
"column-plural": "עמודות",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "התאם לפי זיהוי",
"entity-index": "אינדקס {{entity}}",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "שם {{entity}}",
"entity-plural": "ישויות",
@ -643,6 +645,7 @@
"last-run-result": "תוצאת הרצה אחרונה",
"last-updated": "עודכן לאחרונה",
"latest": "אחרון",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "למידע נוסף ותמיכה",
@ -655,6 +658,7 @@
"lineage-config": "תצורת שורשים",
"lineage-data-lowercase": "נתוני שורשים",
"lineage-ingestion": "כניסת שורשים",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "שורשים",
"lineage-node-lowercase": "צומת שורש",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "בנה אמון בנתונים שלך עם בדיקות איכות וצור מוצרי נתונים אמינים.",
"page-sub-header-for-databases": "שלב מטה-דאטה מבסיס הנתונים הארגוניים. רוב בסיסי הנתונים הפופולריים נתמכים.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Login configuration such as failed attempts or expiry timer.",
"page-sub-header-for-messagings": "שלב מטה-דאטה משירותי הזרמת המידע המובילים כגון קפקא ואחרים",
"page-sub-header-for-metadata": "שלב מטה-דאטה משירותי מטה-דאטה אחרים הקיימים בארגון כגון Atlas ואחרים.",

View File

@ -181,6 +181,7 @@
"color": "Color",
"column": "カラム",
"column-entity": "カラム {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "column",
"column-lowercase-plural": "columns",
"column-plural": "カラム",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "IDによるマッチング",
"entity-index": "{{entity}} インデックス",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "{{entity}} 名",
"entity-plural": "エンティティ",
@ -643,6 +645,7 @@
"last-run-result": "Last Run Result",
"last-updated": "最終更新日",
"latest": "最新",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "Learn more & Support",
@ -655,6 +658,7 @@
"lineage-config": "Lineage Config",
"lineage-data-lowercase": "lineage data",
"lineage-ingestion": "リネージのインジェスチョン",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "リネージ",
"lineage-node-lowercase": "lineage node",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Build trust in your data with quality tests and create reliable data products.",
"page-sub-header-for-databases": "Ingest metadata from the most popular database services.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Login configuration such as failed attempts or expiry timer.",
"page-sub-header-for-messagings": "Ingest metadata from the most used messaging services.",
"page-sub-header-for-metadata": "Ingest metadata from metadata services right from the UI.",

View File

@ -181,6 +181,7 @@
"color": "Kleur",
"column": "Kolom",
"column-entity": "Kolom {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "kolom",
"column-lowercase-plural": "kolommen",
"column-plural": "Kolommen",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Overeenkomen op ID",
"entity-index": "{{entity}}-index",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "{{entity}}-naam",
"entity-plural": "Entiteiten",
@ -643,6 +645,7 @@
"last-run-result": "Laatste uitvoerresultaat",
"last-updated": "Laatst bijgewerkt",
"latest": "Laatste",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "Meer leren & Ondersteuning",
@ -655,6 +658,7 @@
"lineage-config": "Herkomstconfiguratie",
"lineage-data-lowercase": "Herkomstdata",
"lineage-ingestion": "Herkomstingestie",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "herkomst",
"lineage-node-lowercase": "herkomstknooppunt",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Bouw vertrouwen op in je data met kwaliteitstests en maak betrouwbare dataproducten.",
"page-sub-header-for-databases": "Ingest metadata van de meestgebruikte databaseservices.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Loginconfiguratie zoals mislukte pogingen of verlooptimer.",
"page-sub-header-for-messagings": "Ingest metadata van de meestgebruikte berichtenservices.",
"page-sub-header-for-metadata": "Ingest metadata van metadataservices direct vanuit de UI.",

View File

@ -181,6 +181,7 @@
"color": "رنگ",
"column": "ستون",
"column-entity": "ستون {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "ستون",
"column-lowercase-plural": "ستون‌ها",
"column-plural": "ستون‌ها",
@ -433,6 +434,7 @@
"entity-id": "شناسه {{entity}}",
"entity-id-match": "مطابقت با شناسه",
"entity-index": "ایندکس {{entity}}",
"entity-lineage": "Entity Lineage",
"entity-list": "لیست {{entity}}",
"entity-name": "نام {{entity}}",
"entity-plural": "نهادها",
@ -647,6 +649,7 @@
"last-run-result": "نتیجه آخرین اجرا",
"last-updated": "آخرین به‌روزرسانی",
"latest": "آخرین",
"layer": "Layer",
"layer-plural": "لایه‌ها",
"learn-more": "بیشتر بدانید",
"learn-more-and-support": "بیشتر بدانید و پشتیبانی کنید",
@ -659,6 +662,7 @@
"lineage-config": "تنظیمات شجره داده",
"lineage-data-lowercase": "داده‌های شجره داده",
"lineage-ingestion": "دریافت شجره داده",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "شجره داده",
"lineage-node-lowercase": "گره شجره داده",
"lineage-source": "منبع شجره داده",
@ -1709,6 +1713,7 @@
"page-sub-header-for-data-observability": "متادیتا را از سرویس‌های مجموعه آزمایشی مستقیماً از رابط کاربری ingest کنید.",
"page-sub-header-for-data-quality": "با تست‌های کیفیت اعتماد به داده‌های خود را بسازید و محصولات داده قابل اعتماد ایجاد کنید.",
"page-sub-header-for-databases": "متادیتا را از محبوب‌ترین سرویس‌های پایگاه داده ingest کنید.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "تعریف نحوه برخورد با تلاش‌های ناموفق ورود و انقضای توکن JWT.",
"page-sub-header-for-messagings": "ورود متادیتا از پرکاربردترین سرویس‌های پیام‌رسانی.",
"page-sub-header-for-metadata": "ورود متادیتا از سرویس‌های متادیتا مستقیماً از رابط کاربری.",

View File

@ -181,6 +181,7 @@
"color": "Cor",
"column": "Coluna",
"column-entity": "Coluna {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "coluna",
"column-lowercase-plural": "colunas",
"column-plural": "Colunas",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Correspondência por ID de Entidade",
"entity-index": "índice de {{entity}}",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "Nome de {{entity}}",
"entity-plural": "Entidades",
@ -643,6 +645,7 @@
"last-run-result": "Resultado da Última Execução",
"last-updated": "Última Atualização",
"latest": "Mais Recente",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "Saiba mais e Suporte",
@ -655,6 +658,7 @@
"lineage-config": "Configuração de Linhagem",
"lineage-data-lowercase": "dados de linhagem",
"lineage-ingestion": "Ingestão de Linhagem",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "linhagem",
"lineage-node-lowercase": "nó de linhagem",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Construa confiança em seus dados com testes de qualidade e crie produtos de dados confiáveis.",
"page-sub-header-for-databases": "Ingestão de metadados dos serviços de banco de dados mais populares.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Login configuration such as failed attempts or expiry timer.",
"page-sub-header-for-messagings": "Ingestão de metadados dos serviços de mensagens mais utilizados.",
"page-sub-header-for-metadata": "Ingestão de metadados de serviços de metadados diretamente da interface do usuário.",

View File

@ -181,6 +181,7 @@
"color": "Color",
"column": "Столбец",
"column-entity": "Столбец {{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "столбец",
"column-lowercase-plural": "столбцы",
"column-plural": "Столбцы",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} Id",
"entity-id-match": "Совпадение по Id",
"entity-index": "{{entity}} индекс",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}} List",
"entity-name": "{{entity}} Имя",
"entity-plural": "Сущности",
@ -643,6 +645,7 @@
"last-run-result": "Результат последнего запуска",
"last-updated": "Последнее обновление",
"latest": "Последний",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
"learn-more-and-support": "Learn more & Support",
@ -655,6 +658,7 @@
"lineage-config": "Конфигурация происхождения",
"lineage-data-lowercase": "данные о происхождении",
"lineage-ingestion": "Получение проихождения",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "происходение",
"lineage-node-lowercase": "узел происхождения",
"lineage-source": "Source of Lineage",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "Ingest metadata from test suite services right from the UI.",
"page-sub-header-for-data-quality": "Укрепляйте доверие к своим данным с помощью тестов качества и создавайте надежные информационные продукты.",
"page-sub-header-for-databases": "Получение метаданных из самых популярных сервисов баз данных.",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Login configuration such as failed attempts or expiry timer.",
"page-sub-header-for-messagings": "Получение метаданных из наиболее часто используемых служб обмена сообщениями.",
"page-sub-header-for-metadata": "Получайте метаданные из служб метаданных прямо из пользовательского интерфейса.",

View File

@ -181,6 +181,7 @@
"color": "颜色",
"column": "列",
"column-entity": "列{{entity}}",
"column-level-lineage": "Column Level Lineage",
"column-lowercase": "列",
"column-lowercase-plural": "列",
"column-plural": "列",
@ -432,6 +433,7 @@
"entity-id": "{{entity}} ID",
"entity-id-match": "根据ID匹配",
"entity-index": "{{entity}}索引",
"entity-lineage": "Entity Lineage",
"entity-list": "{{entity}}列表",
"entity-name": "{{entity}}名称",
"entity-plural": "实体",
@ -643,6 +645,7 @@
"last-run-result": "最近运行结果",
"last-updated": "最近更新",
"latest": "最新",
"layer": "Layer",
"layer-plural": "面板",
"learn-more": "Learn More",
"learn-more-and-support": "Learn more & Support",
@ -655,6 +658,7 @@
"lineage-config": "血缘关系配置",
"lineage-data-lowercase": "血缘关系数据",
"lineage-ingestion": "血缘关系提取",
"lineage-layer": "Lineage Layer",
"lineage-lowercase": "血缘",
"lineage-node-lowercase": "血缘关系节点",
"lineage-source": "血缘关系来源",
@ -1701,6 +1705,7 @@
"page-sub-header-for-data-observability": "通过 UI 界面, 从质控测试集服务中提取元数据",
"page-sub-header-for-data-quality": "通过引入数据质控测试提升数据的可信任度, 构建稳健的衍生数据产品",
"page-sub-header-for-databases": "从最流行的数据库类型服务中提取元数据",
"page-sub-header-for-lineage-config-setting": "Configure the lineage view settings.",
"page-sub-header-for-login-configuration": "Login configuration such as failed attempts or expiry timer.",
"page-sub-header-for-messagings": "从最常用的消息队列类型服务中提取元数据",
"page-sub-header-for-metadata": "通过 UI 界面, 从元数据类型服务中提取元数据",

View File

@ -0,0 +1,244 @@
/*
* 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 { Button, Col, Form, Input, Row, Select, Typography } from 'antd';
import { AxiosError } from 'axios';
import React, {
FocusEvent,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import Loader from '../../components/common/Loader/Loader';
import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels';
import ServiceDocPanel from '../../components/common/ServiceDocPanel/ServiceDocPanel';
import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { TitleBreadcrumbProps } from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.interface';
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
import { OPEN_METADATA } from '../../constants/service-guide.constant';
import {
LineageLayer,
LineageSettings,
} from '../../generated/configuration/lineageSettings';
import { Settings, SettingType } from '../../generated/settings/settings';
import { useApplicationStore } from '../../hooks/useApplicationStore';
import {
getSettingsByType,
updateSettingsConfig,
} from '../../rest/settingConfigAPI';
import { getSettingPageEntityBreadCrumb } from '../../utils/GlobalSettingsUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
const LineageConfigPage = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(true);
const [activeField, setActiveField] = useState<string>('');
const [lineageConfig, setLineageConfig] = useState<LineageSettings>();
const [isUpdating, setIsUpdating] = useState(false);
const [form] = Form.useForm();
const history = useHistory();
const { setAppPreferences } = useApplicationStore();
const breadcrumbs: TitleBreadcrumbProps['titleLinks'] = useMemo(
() =>
getSettingPageEntityBreadCrumb(
GlobalSettingsMenuCategory.PREFERENCES,
t('label.lineage')
),
[]
);
const fetchSearchConfig = async () => {
try {
setIsLoading(true);
const config = await getSettingsByType(SettingType.LineageSettings);
setLineageConfig(config as LineageSettings);
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
setIsLoading(false);
}
};
const handleFieldFocus = useCallback((event: FocusEvent<HTMLFormElement>) => {
setActiveField(event.target.id);
}, []);
const handleSave = async (values: LineageSettings) => {
try {
setIsUpdating(true);
const configData = {
config_type: SettingType.LineageSettings,
config_value: {
upstreamDepth: Number(values.upstreamDepth),
downstreamDepth: Number(values.downstreamDepth),
lineageLayer: values.lineageLayer,
},
};
const { data } = await updateSettingsConfig(configData as Settings);
showSuccessToast(
t('server.update-entity-success', {
entity: t('label.lineage-config'),
})
);
const lineageConfig = data.config_value as LineageSettings;
setLineageConfig(lineageConfig);
// Update lineage config in store
setAppPreferences({ lineageConfig });
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
setIsUpdating(false);
}
};
useEffect(() => {
fetchSearchConfig();
}, []);
if (isLoading) {
return <Loader />;
}
return (
<ResizablePanels
className="content-height-with-resizable-panel"
firstPanel={{
className: 'content-resizable-panel-container',
children: (
<div
className="max-width-md w-9/10 service-form-container"
data-testid="add-metric-container">
<Row gutter={[16, 16]}>
<Col span={24}>
<TitleBreadcrumb titleLinks={breadcrumbs} />
</Col>
<Col span={24}>
<Typography.Title
className="m-b-0"
data-testid="heading"
level={5}>
{t('label.lineage')}
</Typography.Title>
</Col>
<Col span={24}>
<Form
form={form}
id="lineage-config"
initialValues={lineageConfig}
layout="vertical"
onFinish={handleSave}
onFocus={handleFieldFocus}>
<Form.Item
id="root/upstreamDepth"
label={t('label.upstream-depth')}
name="upstreamDepth"
rules={[
{
required: true,
message: t('message.upstream-depth-message'),
},
]}>
<Input
data-testid="field-upstream"
max={5}
min={1}
type="number"
/>
</Form.Item>
<Form.Item
className="m-t-sm"
id="root/downstreamDepth"
label={t('label.downstream-depth')}
name="downstreamDepth"
rules={[
{
required: true,
message: t('message.downstream-depth-message'),
},
]}>
<Input
data-testid="field-downstream"
max={5}
min={1}
type="number"
/>
</Form.Item>
<Form.Item
className="m-t-sm"
id="root/lineageLayer"
label={t('label.lineage-layer')}
name="lineageLayer">
<Select data-testid="field-lineage-layer">
<Select.Option value={LineageLayer.EntityLineage}>
{t('label.entity-lineage')}
</Select.Option>
<Select.Option value={LineageLayer.ColumnLevelLineage}>
{t('label.column-level-lineage')}
</Select.Option>
<Select.Option value={LineageLayer.DataObservability}>
{t('label.data-observability')}
</Select.Option>
</Select>
</Form.Item>
</Form>
<Row className="m-b-xl" justify="end">
<Col className="d-flex justify-end gap-2" span={24}>
<Button
data-testid="cancel-button"
onClick={() => history.goBack()}>
{t('label.cancel')}
</Button>
<Button
data-testid="save-button"
form="lineage-config"
htmlType="submit"
loading={isUpdating}
type="primary">
{t('label.save')}
</Button>
</Col>
</Row>
</Col>
</Row>
</div>
),
minWidth: 700,
flex: 0.7,
}}
pageTitle={t('label.lineage-config')}
secondPanel={{
className: 'service-doc-panel content-resizable-panel-container',
minWidth: 400,
flex: 0.3,
children: (
<ServiceDocPanel
activeField={activeField}
serviceName="LineageConfiguration"
serviceType={OPEN_METADATA}
/>
),
}}
/>
);
};
export default LineageConfigPage;

View File

@ -14,7 +14,9 @@
import { AxiosResponse } from 'axios';
import axiosClient from '.';
import { APPLICATION_JSON_CONTENT_TYPE_HEADER } from '../constants/constants';
import { LineageSettings } from '../generated/configuration/lineageSettings';
import { LoginConfiguration } from '../generated/configuration/loginConfiguration';
import { SearchSettings } from '../generated/configuration/searchSettings';
import { UIThemePreference } from '../generated/configuration/uiThemePreference';
import { Settings, SettingType } from '../generated/settings/settings';
@ -59,3 +61,13 @@ export const testEmailConnection = async (data: { email: string }) => {
return response;
};
export const getSettingsByType = async (
settingType: SettingType
): Promise<SearchSettings | LineageSettings> => {
const response = await axiosClient.get<Settings>(
`/system/settings/${settingType}`
);
return response.data.config_value as SearchSettings | LineageSettings;
};

View File

@ -28,6 +28,7 @@ import { ReactComponent as IconAPI } from '../assets/svg/ic-api-service.svg';
import { ReactComponent as DashboardDataModelIcon } from '../assets/svg/ic-dashboard-data-model-colored.svg';
import { ReactComponent as DataProductIcon } from '../assets/svg/ic-data-product-colored.svg';
import { ReactComponent as SchemaIcon } from '../assets/svg/ic-database-schema-colored.svg';
import { ReactComponent as LineageIcon } from '../assets/svg/ic-lineage-config.svg';
import { ReactComponent as LoginIcon } from '../assets/svg/login-colored.svg';
import { ReactComponent as OpenMetadataIcon } from '../assets/svg/logo-monogram.svg';
import { ReactComponent as MessagingIcon } from '../assets/svg/messaging-colored.svg';
@ -354,6 +355,15 @@ class GlobalSettingsClassBase {
key: `${GlobalSettingsMenuCategory.PREFERENCES}.${GlobalSettingOptions.PROFILER_CONFIGURATION}`,
icon: ProfilerConfigIcon,
},
{
label: t('label.lineage'),
description: t(
'message.page-sub-header-for-lineage-config-setting'
),
isProtected: Boolean(isAdminUser),
key: `${GlobalSettingsMenuCategory.PREFERENCES}.${GlobalSettingOptions.LINEAGE_CONFIG}`,
icon: LineageIcon,
},
],
},
{