From 3d62581b29eccba36c6c1388ccc949657bc9738e Mon Sep 17 00:00:00 2001 From: karanh37 Date: Thu, 7 Nov 2024 09:40:01 +0530 Subject: [PATCH] App settings - lineage config (#18522) * add lineage settings page * update locales * minor ui changes * add playwright for lineage config (cherry picked from commit 3dcbeb469f655ee3372bedf0c94fab7f247e4882) --- .../ui/playwright/constant/settings.ts | 12 + .../ui/playwright/e2e/Pages/Lineage.spec.ts | 149 +++++++++++ .../resources/ui/playwright/utils/lineage.ts | 32 ++- .../OpenMetadata/LineageConfiguration.md | 27 ++ .../ui/src/assets/svg/ic-lineage-config.svg | 1 + .../components/AppContainer/AppContainer.tsx | 15 +- .../components/AppRouter/SettingsRouter.tsx | 10 + .../CustomControls.component.tsx | 4 +- .../EntityLineage/CustomControls.test.tsx | 4 +- .../CustomEdge.component.test.tsx | 4 +- .../EntityLineage/CustomEdge.component.tsx | 4 +- .../EntityLineage/CustomNodeV1.test.tsx | 4 +- .../EntityLineage/LineageConfigModal.tsx | 40 +-- .../LineageLayers/LineageLayers.test.tsx | 6 +- .../LineageLayers/LineageLayers.tsx | 16 +- .../LineageSearchSelect.test.tsx | 4 +- .../NodeChildren/NodeChildren.component.tsx | 6 +- .../src/components/common/Banner/Banner.tsx | 12 + .../src/components/common/Banner/banner.less | 12 + .../src/constants/GlobalSettings.constants.ts | 2 + .../ui/src/constants/PageHeaders.constant.ts | 4 + .../LineageProvider.interface.tsx | 10 +- .../LineageProvider/LineageProvider.test.tsx | 25 +- .../LineageProvider/LineageProvider.tsx | 42 ++- .../ui/src/hooks/useApplicationStore.ts | 11 + .../ui/src/interface/store.interface.ts | 7 + .../ui/src/locale/languages/de-de.json | 5 + .../ui/src/locale/languages/en-us.json | 5 + .../ui/src/locale/languages/es-es.json | 5 + .../ui/src/locale/languages/fr-fr.json | 5 + .../ui/src/locale/languages/he-he.json | 5 + .../ui/src/locale/languages/ja-jp.json | 5 + .../ui/src/locale/languages/nl-nl.json | 5 + .../ui/src/locale/languages/pr-pr.json | 5 + .../ui/src/locale/languages/pt-br.json | 5 + .../ui/src/locale/languages/ru-ru.json | 5 + .../ui/src/locale/languages/zh-cn.json | 5 + .../LineageConfigPage/LineageConfigPage.tsx | 244 ++++++++++++++++++ .../resources/ui/src/rest/settingConfigAPI.ts | 12 + .../ui/src/utils/GlobalSettingsClassBase.ts | 10 + 40 files changed, 706 insertions(+), 78 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/public/locales/en-US/OpenMetadata/LineageConfiguration.md create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-lineage-config.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/LineageConfigPage/LineageConfigPage.tsx diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts index e075043c4fa..ea2c9972413 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts @@ -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 = { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage.spec.ts index 1b3577281f0..77e655769a7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage.spec.ts @@ -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(); + } +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts index 13523fe2f53..4824cab434e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts @@ -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 +}; diff --git a/openmetadata-ui/src/main/resources/ui/public/locales/en-US/OpenMetadata/LineageConfiguration.md b/openmetadata-ui/src/main/resources/ui/public/locales/en-US/OpenMetadata/LineageConfiguration.md new file mode 100644 index 00000000000..882ddd2dc94 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/public/locales/en-US/OpenMetadata/LineageConfiguration.md @@ -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. +$$ diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-lineage-config.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-lineage-config.svg new file mode 100644 index 00000000000..dbe1c90e6fc --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/ic-lineage-config.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx index 58d344f6af9..7de73ea1777 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx @@ -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]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.tsx index c41c19f6e9f..1ac606a6873 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AppRouter/SettingsRouter.tsx @@ -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 */} + + = ({ >([]); const [filters, setFilters] = useState([]); const isColumnLayerActive = useMemo(() => { - return activeLayer.includes(LineageLayerView.COLUMN); + return activeLayer.includes(LineageLayer.ColumnLevelLineage); }, [activeLayer]); const handleMenuClick = ({ key }: { key: string }) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomControls.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomControls.test.tsx index dddc1e27657..97f5070a011 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomControls.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomControls.test.tsx @@ -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], })), })); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.test.tsx index aa3d78a61c6..4ab96f3e1f7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.test.tsx @@ -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(), })), })); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.tsx index 4c4a3e8e3f9..a0d3e7a3691 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomEdge.component.tsx @@ -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) { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomNodeV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomNodeV1.test.tsx index 064f5fcf49f..3d77436ab8f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomNodeV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/CustomNodeV1.test.tsx @@ -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, })), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageConfigModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageConfigModal.tsx index 83ec104af80..61711687de3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageConfigModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageConfigModal.tsx @@ -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 = ({ visible, config, @@ -26,21 +32,12 @@ const LineageConfigModal: React.FC = ({ }) => { const { t } = useTranslation(); const [form] = Form.useForm(); - const [upstreamDepth, setUpstreamDepth] = useState( - config?.upstreamDepth || 1 - ); - const [downstreamDepth, setDownstreamDepth] = useState( - config?.downstreamDepth || 1 - ); - const [nodesPerLayer, setNodesPerLayer] = useState( - 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 = ({ }, ]} tooltip={t('message.upstream-depth-tooltip')}> - setUpstreamDepth(Number(e.target.value))} - /> + = ({ }, ]} tooltip={t('message.downstream-depth-tooltip')}> - setDownstreamDepth(Number(e.target.value))} - /> + = ({ data-testid="field-nodes-per-layer" min={5} type="number" - onChange={(e) => setNodesPerLayer(Number(e.target.value))} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.test.tsx index 30f1f8ed954..3a33f471a60 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.test.tsx @@ -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, ]); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.tsx index 644cd903e39..0e014e94558 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageLayers/LineageLayers.tsx @@ -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 = () => { + + + + + + + ), + 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: ( + + ), + }} + /> + ); +}; + +export default LineageConfigPage; diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/settingConfigAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/settingConfigAPI.ts index f5495b3bf82..729980bd475 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/settingConfigAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/settingConfigAPI.ts @@ -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 => { + const response = await axiosClient.get( + `/system/settings/${settingType}` + ); + + return response.data.config_value as SearchSettings | LineageSettings; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsClassBase.ts index dbe8ec747a9..b943405a924 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsClassBase.ts @@ -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, + }, ], }, {