feat(ui): pipeline view support for node or edge (#23893)

* feat(ui): pipeline view support for node or edge

* support edge / node view from UI

* update condition

* fix conditions

* update doc and translation

* add tests

* revert lineage spec changes

* Update generated TypeScript types

* Downstream Iteration Fix

* Fix OpenSearch Failures

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: mohitdeuex <mohit.y@deuexsolutions.com>
Co-authored-by: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com>
This commit is contained in:
Chirag Madlani 2025-10-29 17:44:38 +05:30 committed by GitHub
parent 288f5d0ce7
commit c2896d896e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 395 additions and 191 deletions

View File

@ -321,7 +321,6 @@ public class OSLineageGraphBuilder {
getDownstreamLineage(
lineageRequest
.withDirection(LineageDirection.DOWNSTREAM)
.withDownstreamDepth(lineageRequest.getDownstreamDepth() + 1)
.withDirectionValue(
getLineageDirection(
lineageRequest.getDirection(), lineageRequest.getIsConnectedVia())));
@ -376,7 +375,6 @@ public class OSLineageGraphBuilder {
getDownstreamLineage(
lineageRequest
.withDirection(LineageDirection.DOWNSTREAM)
.withDownstreamDepth(lineageRequest.getDownstreamDepth() + 1)
.withDirectionValue(
getLineageDirection(
lineageRequest.getDirection(), lineageRequest.getIsConnectedVia())));

View File

@ -10,12 +10,15 @@
"javaType": "org.openmetadata.schema.api.lineage.LineageLayer",
"description": "Lineage Layers",
"type": "string",
"enum": [
"EntityLineage",
"ColumnLevelLineage",
"DataObservability"
],
"enum": ["EntityLineage", "ColumnLevelLineage", "DataObservability"],
"default": "EntityLineage"
},
"pipelineViewMode": {
"javaType": "org.openmetadata.schema.api.lineage.PipelineViewMode",
"description": "Determines the view mode for pipelines in lineage.",
"type": "string",
"enum": ["Edge", "Node"],
"default": "Node"
}
},
"properties": {
@ -36,8 +39,17 @@
"lineageLayer": {
"description": "Lineage Layer.",
"$ref": "#/definitions/lineageLayer"
},
"pipelineViewMode": {
"description": "Pipeline View Mode for Lineage.",
"$ref": "#/definitions/pipelineViewMode"
}
},
"required": ["upstreamDepth", "downstreamDepth", "lineageLayer"],
"required": [
"upstreamDepth",
"downstreamDepth",
"lineageLayer",
"pipelineViewMode"
],
"additionalProperties": false
}
}

View File

@ -10,22 +10,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import test, { expect } from '@playwright/test';
import { expect } from '@playwright/test';
import { get } from 'lodash';
import { GlobalSettingOptions } from '../../constant/settings';
import { SidebarItem } from '../../constant/sidebar';
import { ContainerClass } from '../../support/entity/ContainerClass';
import { DashboardClass } from '../../support/entity/DashboardClass';
import { MetricClass } from '../../support/entity/MetricClass';
import { MlModelClass } from '../../support/entity/MlModelClass';
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 {
createNewPage,
getApiContext,
redirectToHomePage,
toastNotification,
} from '../../utils/common';
import { performAdminLogin } from '../../utils/admin';
import { redirectToHomePage, toastNotification } from '../../utils/common';
import { waitForAllLoadersToDisappear } from '../../utils/entity';
import {
addPipelineBetweenNodes,
fillLineageConfigForm,
@ -36,191 +35,274 @@ import {
verifyNodePresent,
visitLineageTab,
} from '../../utils/lineage';
import { settingClick } from '../../utils/sidebar';
import { settingClick, sidebarClick } from '../../utils/sidebar';
import { test } from '../fixtures/pages';
test.describe('Lineage Settings Tests', () => {
test.use({ storageState: 'playwright/.auth/admin.json' });
const table = new TableClass();
const topic = new TopicClass();
const dashboard = new DashboardClass();
const mlModel = new MlModelClass();
const searchIndex = new SearchIndexClass();
const container = new ContainerClass();
const metric = new MetricClass();
const pipeline = new PipelineClass();
test.fixme('Verify global lineage config', async ({ browser }) => {
test.beforeAll('setup lineage settings', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await Promise.all([
table.create(apiContext),
topic.create(apiContext),
dashboard.create(apiContext),
mlModel.create(apiContext),
searchIndex.create(apiContext),
container.create(apiContext),
metric.create(apiContext),
pipeline.create(apiContext),
]);
await afterAction();
});
test.afterAll('cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await Promise.all([
table.delete(apiContext),
topic.delete(apiContext),
dashboard.delete(apiContext),
mlModel.delete(apiContext),
searchIndex.delete(apiContext),
container.delete(apiContext),
metric.delete(apiContext),
pipeline.delete(apiContext),
]);
await afterAction();
});
test.beforeEach(async ({ page }) => {
await redirectToHomePage(page);
});
test('Verify global lineage config', async ({ page }) => {
test.slow(true);
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();
const searchIndex = new SearchIndexClass();
const container = new ContainerClass();
const metric = new MetricClass();
await addPipelineBetweenNodes(page, table, topic);
await addPipelineBetweenNodes(page, topic, dashboard);
await addPipelineBetweenNodes(page, dashboard, mlModel);
await addPipelineBetweenNodes(page, mlModel, searchIndex);
await addPipelineBetweenNodes(page, searchIndex, container);
await addPipelineBetweenNodes(page, container, metric);
await addPipelineBetweenNodes(page, metric, pipeline);
try {
await Promise.all([
table.create(apiContext),
topic.create(apiContext),
dashboard.create(apiContext),
mlModel.create(apiContext),
searchIndex.create(apiContext),
container.create(apiContext),
metric.create(apiContext),
]);
await test.step(
'Lineage config should throw error if upstream depth is less than 0',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await addPipelineBetweenNodes(page, table, topic);
await addPipelineBetweenNodes(page, topic, dashboard);
await addPipelineBetweenNodes(page, dashboard, mlModel);
await addPipelineBetweenNodes(page, mlModel, searchIndex);
await addPipelineBetweenNodes(page, searchIndex, container);
await addPipelineBetweenNodes(page, container, metric);
await page.getByTestId('field-upstream').fill('-1');
await page.getByTestId('field-downstream').fill('-1');
await page.getByTestId('save-button').click();
await test.step(
'Lineage config should throw error if upstream depth is less than 0',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await expect(
page.getByText('Upstream Depth size cannot be less than 0')
).toBeVisible();
await expect(
page.getByText('Downstream Depth size cannot be less than 0')
).toBeVisible();
await page.getByTestId('field-upstream').fill('-1');
await page.getByTestId('field-downstream').fill('-1');
await page.getByTestId('save-button').click();
await page.getByTestId('field-upstream').fill('0');
await page.getByTestId('field-downstream').fill('0');
await expect(
page.getByText('Upstream Depth size cannot be less than 0')
).toBeVisible();
await expect(
page.getByText('Downstream Depth size cannot be less than 0')
).toBeVisible();
const saveRes = page.waitForResponse('/api/v1/system/settings');
await page.getByTestId('save-button').click();
await saveRes;
await page.getByTestId('field-upstream').fill('0');
await page.getByTestId('field-downstream').fill('0');
await toastNotification(page, /Lineage Config updated successfully/);
}
);
const saveRes = page.waitForResponse('/api/v1/system/settings');
await page.getByTestId('save-button').click();
await saveRes;
await test.step(
'Update global lineage config and verify lineage for column layer',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 1,
downstreamDepth: 1,
layer: 'Column Level Lineage',
});
await toastNotification(page, /Lineage Config updated successfully/);
}
);
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 test.step(
'Update global lineage config and verify lineage for column layer',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 1,
downstreamDepth: 1,
layer: 'Column Level Lineage',
});
await expect(mlModelNode).not.toBeVisible();
await topic.visitEntityPage(page);
await visitLineageTab(page);
await verifyNodePresent(page, table);
await verifyNodePresent(page, dashboard);
const mlModelFqn = get(
mlModel,
await verifyColumnLayerActive(page);
}
);
await test.step(
'Update global lineage config and verify lineage for entity layer',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 1,
downstreamDepth: 1,
layer: 'Entity Lineage',
});
await dashboard.visitEntityPage(page);
await visitLineageTab(page);
await verifyNodePresent(page, dashboard);
await verifyNodePresent(page, mlModel);
await verifyNodePresent(page, topic);
const tableNode = page.locator(
`[data-testid="lineage-node-${get(
table,
'entityResponseData.fullyQualifiedName'
);
const mlModelNode = page.locator(
`[data-testid="lineage-node-${mlModelFqn}"]`
);
)}"]`
);
await expect(mlModelNode).not.toBeVisible();
await verifyColumnLayerActive(page);
}
);
await test.step(
'Update global lineage config and verify lineage for entity layer',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 1,
downstreamDepth: 1,
layer: 'Entity Lineage',
});
await dashboard.visitEntityPage(page);
await visitLineageTab(page);
await verifyNodePresent(page, dashboard);
await verifyNodePresent(page, mlModel);
await verifyNodePresent(page, topic);
const tableNode = page.locator(
`[data-testid="lineage-node-${get(
table,
'entityResponseData.fullyQualifiedName'
)}"]`
);
const searchIndexNode = page.locator(
`[data-testid="lineage-node-${get(
searchIndex,
'entityResponseData.fullyQualifiedName'
)}"]`
);
await expect(tableNode).not.toBeVisible();
await expect(searchIndexNode).not.toBeVisible();
}
);
await test.step(
'Verify Upstream and Downstream expand collapse buttons',
async () => {
await redirectToHomePage(page);
await dashboard.visitEntityPage(page);
await visitLineageTab(page);
const closeIcon = page.getByTestId('entity-panel-close-icon');
if (await closeIcon.isVisible()) {
await closeIcon.click();
}
await performZoomOut(page);
await verifyNodePresent(page, topic);
await verifyNodePresent(page, mlModel);
await performExpand(page, mlModel, false, searchIndex);
await performExpand(page, searchIndex, false, container);
await performExpand(page, container, false, metric);
await performExpand(page, topic, true, table);
// perform collapse
await performCollapse(page, mlModel, false, [
const searchIndexNode = page.locator(
`[data-testid="lineage-node-${get(
searchIndex,
container,
metric,
]);
await performCollapse(page, dashboard, true, [table, topic]);
'entityResponseData.fullyQualifiedName'
)}"]`
);
await expect(tableNode).not.toBeVisible();
await expect(searchIndexNode).not.toBeVisible();
}
);
await test.step(
'Verify Upstream and Downstream expand collapse buttons',
async () => {
await redirectToHomePage(page);
await dashboard.visitEntityPage(page);
await visitLineageTab(page);
const closeIcon = page.getByTestId('entity-panel-close-icon');
if (await closeIcon.isVisible()) {
await closeIcon.click();
}
);
await performZoomOut(page);
await verifyNodePresent(page, topic);
await verifyNodePresent(page, mlModel);
await performExpand(page, mlModel, false, searchIndex);
await performExpand(page, searchIndex, false, container);
await performExpand(page, container, false, metric);
await performExpand(page, topic, true, table);
await test.step(
'Reset global lineage config and verify lineage',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 2,
downstreamDepth: 2,
layer: 'Entity Lineage',
});
// perform collapse
await performCollapse(page, mlModel, false, [
searchIndex,
container,
metric,
]);
await performCollapse(page, dashboard, true, [table, topic]);
}
);
await dashboard.visitEntityPage(page);
await visitLineageTab(page);
await test.step(
'Reset global lineage config and verify lineage',
async () => {
await settingClick(page, GlobalSettingOptions.LINEAGE_CONFIG);
await fillLineageConfigForm(page, {
upstreamDepth: 2,
downstreamDepth: 2,
layer: 'Entity Lineage',
});
await verifyNodePresent(page, table);
await verifyNodePresent(page, dashboard);
await verifyNodePresent(page, mlModel);
await verifyNodePresent(page, searchIndex);
await verifyNodePresent(page, topic);
}
);
} finally {
await Promise.all([
table.delete(apiContext),
topic.delete(apiContext),
dashboard.delete(apiContext),
mlModel.delete(apiContext),
searchIndex.delete(apiContext),
]);
await dashboard.visitEntityPage(page);
await visitLineageTab(page);
await afterAction();
}
await verifyNodePresent(page, table);
await verifyNodePresent(page, dashboard);
await verifyNodePresent(page, mlModel);
await verifyNodePresent(page, searchIndex);
await verifyNodePresent(page, topic);
}
);
});
test('Verify lineage settings for PipelineViewMode as Edge', async ({
page,
dataStewardPage,
}) => {
test.slow();
// Update setting to show pipeline as Edge
await redirectToHomePage(page);
await sidebarClick(page, SidebarItem.SETTINGS);
await page.getByTestId('preferences').click();
await page.getByTestId('preferences.lineageConfig').click();
await page.getByTestId('field-pipeline-view-mode').click();
await page.getByTitle('Edge').filter({ visible: true }).click();
const lineageSettingUpdate = page.waitForResponse(
'/api/v1/system/settings'
);
await page.getByTestId('save-button').click();
await lineageSettingUpdate;
await redirectToHomePage(dataStewardPage);
await addPipelineBetweenNodes(
dataStewardPage,
table,
topic,
pipeline,
true
);
await pipeline.visitEntityPage(dataStewardPage);
await dataStewardPage.getByRole('tab', { name: 'Lineage' }).click();
await dataStewardPage.waitForLoadState('networkidle');
await waitForAllLoadersToDisappear(dataStewardPage);
await dataStewardPage.getByTestId('fit-screen').click();
// Pipeline should be shown as Edge and not as Node
await expect(
dataStewardPage.getByTestId(
`pipeline-label-${table.entityResponseData.fullyQualifiedName}-${topic.entityResponseData.fullyQualifiedName}`
)
).toBeVisible();
// update pipeline view mode to Node
await page.getByTestId('field-pipeline-view-mode').click();
await page.getByTitle('Node').filter({ visible: true }).click();
const settingsUpdate = page.waitForResponse('/api/v1/system/settings');
await page.getByTestId('save-button').click();
await settingsUpdate;
await dataStewardPage.reload();
await dataStewardPage.waitForLoadState('networkidle');
await waitForAllLoadersToDisappear(dataStewardPage);
await dataStewardPage.getByTestId('fit-screen').click();
// Pipeline should be shown as Node and not as Edge
await expect(
dataStewardPage.getByTestId(
`pipeline-label-${table.entityResponseData.fullyQualifiedName}-${topic.entityResponseData.fullyQualifiedName}`
)
).not.toBeVisible();
await expect(
dataStewardPage.getByTestId(
`lineage-node-${pipeline.entityResponseData.fullyQualifiedName}`
)
).toBeVisible();
});
});

View File

@ -25,3 +25,13 @@ Select a lineage layer to view specific data relationships:
- **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.
$$
$$section
### Pipeline View Mode $(id="pipelineViewMode")
Choose how pipelines are displayed in the lineage view:
- **Node**: Displays pipelines as separate nodes in the graph, providing a detailed view of data flow through each pipeline (Default)
- **Edge**: Represents pipelines as direct connections between nodes, offering a simplified view of data relationships
$$

View File

@ -15,6 +15,7 @@ import { LoadingState } from 'Models';
import { ReactNode } from 'react';
import { Edge as FlowEdge, Node } from 'reactflow';
import { LineageDirection } from '../../../generated/api/lineage/lineageDirection';
import { LineageSettings } from '../../../generated/configuration/lineageSettings';
import { EntityReference } from '../../../generated/entity/type';
export interface Edge {
@ -59,9 +60,7 @@ export interface CustomEdgeData {
export type ElementLoadingState = Exclude<LoadingState, 'waiting'>;
export type CustomElement = { node: Node[]; edge: FlowEdge[] };
export interface LineageConfig {
upstreamDepth: number;
downstreamDepth: number;
export interface LineageConfig extends Omit<LineageSettings, 'lineageLayer'> {
nodesPerLayer: number;
}

View File

@ -83,7 +83,10 @@ import { mockDatasetData } from '../../constants/mockTourData.constants';
import { EntityLineageNodeType, EntityType } from '../../enums/entity.enum';
import { AddLineage } from '../../generated/api/lineage/addLineage';
import { LineageDirection } from '../../generated/api/lineage/lineageDirection';
import { LineageSettings } from '../../generated/configuration/lineageSettings';
import {
LineageSettings,
PipelineViewMode,
} from '../../generated/configuration/lineageSettings';
import { Table } from '../../generated/entity/data/table';
import { LineageLayer } from '../../generated/settings/settings';
import {
@ -211,6 +214,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
upstreamDepth: 3,
downstreamDepth: 3,
nodesPerLayer: 50,
pipelineViewMode: PipelineViewMode.Node,
});
const [selectedQuickFilters, setSelectedQuickFilters] = useState<
ExploreQuickFilterField[]
@ -406,7 +410,12 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
setLineageData(res);
const { nodes, edges, entity } = parseLineageData(res, '', entityFqn);
const { nodes, edges, entity } = parseLineageData(
res,
'',
entityFqn,
config?.pipelineViewMode
);
const updatedEntityLineage = {
nodes,
edges,
@ -450,7 +459,12 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
});
setLineageData(res);
const { nodes, edges, entity } = parseLineageData(res, fqn, entityFqn);
const { nodes, edges, entity } = parseLineageData(
res,
fqn,
entityFqn,
config?.pipelineViewMode
);
const updatedEntityLineage = {
nodes,
edges,
@ -498,7 +512,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
const exportLineageData = useCallback(async () => {
return exportLineageAsync(
entityFqn,
entityType,
entityType ?? '',
lineageConfig,
queryFilter
);
@ -549,6 +563,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
upstreamDepth: direction === LineageDirection.Upstream ? 1 : 0,
downstreamDepth: direction === LineageDirection.Downstream ? 1 : 0,
nodesPerLayer: lineageConfig.nodesPerLayer,
pipelineViewMode: lineageConfig.pipelineViewMode,
}, // load only one level of child nodes
queryFilter,
direction,
@ -1593,6 +1608,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
setLineageConfig({
upstreamDepth: defaultLineageConfig.upstreamDepth,
downstreamDepth: defaultLineageConfig.downstreamDepth,
pipelineViewMode: defaultLineageConfig.pipelineViewMode,
nodesPerLayer: 50,
});

View File

@ -22,6 +22,10 @@ export interface LineageSettings {
* Lineage Layer.
*/
lineageLayer: LineageLayer;
/**
* Pipeline View Mode for Lineage.
*/
pipelineViewMode: PipelineViewMode;
/**
* Upstream Depth for Lineage.
*/
@ -38,3 +42,13 @@ export enum LineageLayer {
DataObservability = "DataObservability",
EntityLineage = "EntityLineage",
}
/**
* Pipeline View Mode for Lineage.
*
* Determines the view mode for pipelines in lineage.
*/
export enum PipelineViewMode {
Edge = "Edge",
Node = "Node",
}

View File

@ -439,6 +439,10 @@ export interface PipelineServiceClientConfiguration {
* Lineage Layer.
*/
lineageLayer?: LineageLayer;
/**
* Pipeline View Mode for Lineage.
*/
pipelineViewMode?: PipelineViewMode;
/**
* Upstream Depth for Lineage.
*/
@ -2061,6 +2065,16 @@ export interface TitleSection {
[property: string]: any;
}
/**
* Pipeline View Mode for Lineage.
*
* Determines the view mode for pipelines in lineage.
*/
export enum PipelineViewMode {
Edge = "Edge",
Node = "Node",
}
/**
* This schema defines the language options available for search index mappings.
*/

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Kein Prüfer",
"no-tags-added": "Keine Tags hinzugefügt",
"no-tasks-yet": "Noch keine Aufgaben",
"node": "Knoten",
"node-depth": "Knotentiefe",
"nodes-per-layer": "Knoten pro Ebene",
"non-partitioned": "Nicht partitioniert",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Pipeline Service Typ",
"pipeline-state": "Pipeline-Status",
"pipeline-type": "Pipeline Typ",
"pipeline-view-mode": "Pipeline-Ansichtsmodus",
"platform": "Plattform",
"platform-service-client-unavailable": "Plattform-Service-Client nicht verfügbar",
"platform-type-lineage": "{{platformType}} Abstammung",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "No reviewer",
"no-tags-added": "No Tags added",
"no-tasks-yet": "No Tasks Yet",
"node": "Node",
"node-depth": "Node Depth",
"nodes-per-layer": "Nodes Per Layer",
"non-partitioned": "Non-partitioned",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Pipeline Service Type",
"pipeline-state": "Pipeline State",
"pipeline-type": "Pipeline Type",
"pipeline-view-mode": "Pipeline View Mode",
"platform": "Platform",
"platform-service-client-unavailable": "Platform Service Client Unavailable",
"platform-type-lineage": "{{platformType}} Lineage",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Sin revisor",
"no-tags-added": "No se añadieron etiquetas",
"no-tasks-yet": "Aún no hay tareas",
"node": "Nodo",
"node-depth": "Profundidad de nodo",
"nodes-per-layer": "Nodos por capa",
"non-partitioned": "Sin partición",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Pipeline Service Type",
"pipeline-state": "Estado de la pipeline",
"pipeline-type": "Pipeline Type",
"pipeline-view-mode": "Modo de vista de pipeline",
"platform": "Plataforma",
"platform-service-client-unavailable": "Cliente de Servicio de Plataforma no Disponible",
"platform-type-lineage": "{{platformType}} Linaje",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Aucun Réviseur",
"no-tags-added": "Aucun Tag ajouté",
"no-tasks-yet": "Pas encore de tâches",
"node": "Nœud",
"node-depth": "Profondeur du nœud",
"nodes-per-layer": "Nœuds par Couche",
"non-partitioned": "Aucune Partition",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Type du Service de la Pipeline",
"pipeline-state": "État de la Pipeline",
"pipeline-type": "Type de la Pipeline",
"pipeline-view-mode": "Mode d'affichage du pipeline",
"platform": "Plateforme",
"platform-service-client-unavailable": "Client de Service de Plateforme Indisponible",
"platform-type-lineage": "{{platformType}} Lignage",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Sen revisor",
"no-tags-added": "Non se engadiron etiquetas",
"no-tasks-yet": "Aínda non hai tarefas",
"node": "Nodo",
"node-depth": "Profundidade do nodo",
"nodes-per-layer": "Nós por capa",
"non-partitioned": "Non particionado",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Tipo de servizo do pipeline",
"pipeline-state": "Estado do pipeline",
"pipeline-type": "Tipo de pipeline",
"pipeline-view-mode": "Modo de vista do pipeline",
"platform": "Plataforma",
"platform-service-client-unavailable": "Cliente de Servizo de Plataforma Non Dispoñible",
"platform-type-lineage": "{{platformType}} Liñaxe",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "אין בודק",
"no-tags-added": "לא הוספו תגיות",
"no-tasks-yet": "אין משימות עדיין",
"node": "צומת",
"node-depth": "עומק צומת",
"nodes-per-layer": "צמתים לשכבה",
"non-partitioned": "לא מחולק לחלקים",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "סוג שירות צינור",
"pipeline-state": "מצב תהליך הטעינה/עיבוד",
"pipeline-type": "סוג תהליך הטעינה/עיבוד",
"pipeline-view-mode": "מצב תצוגת תהליך הטעינה",
"platform": "Platform",
"platform-service-client-unavailable": "לקוח שירות פלטפורמה לא זמין",
"platform-type-lineage": "{{platformType}} שורשים",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "レビュワーがいません",
"no-tags-added": "タグは追加されていません",
"no-tasks-yet": "タスクはまだありません",
"node": "ノード",
"node-depth": "ノードの深さ",
"nodes-per-layer": "レイヤーごとのノード数",
"non-partitioned": "非パーティション化",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "パイプラインサービスタイプ",
"pipeline-state": "パイプライン状態",
"pipeline-type": "パイプラインタイプ",
"pipeline-view-mode": "パイプラインビューモード",
"platform": "プラットフォーム",
"platform-service-client-unavailable": "プラットフォームサービスクライアントが利用できません",
"platform-type-lineage": "{{platformType}}リネージ",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "검토자 없음",
"no-tags-added": "추가된 태그 없음",
"no-tasks-yet": "작업 없음",
"node": "노드",
"node-depth": "노드 깊이",
"nodes-per-layer": "계층당 노드",
"non-partitioned": "비분할",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "파이프라인 서비스 유형",
"pipeline-state": "파이프라인 상태",
"pipeline-type": "파이프라인 유형",
"pipeline-view-mode": "파이프라인 보기 모드",
"platform": "플랫폼",
"platform-service-client-unavailable": "플랫폼 서비스 클라이언트를 사용할 수 없음",
"platform-type-lineage": "{{platformType}} 계보",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "कोणताही पुनरावलोकक नाही",
"no-tags-added": "कोणतेही टॅग जोडलेले नाहीत",
"no-tasks-yet": "कार्ये आहेत",
"node": "नोड",
"node-depth": "नोड खोली",
"nodes-per-layer": "प्रत्येक स्तरातील नोड्स",
"non-partitioned": "विभाजन नसलेले",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "पाइपलाइन सेवा प्रकार",
"pipeline-state": "पाइपलाइन स्थिती",
"pipeline-type": "पाइपलाइन प्रकार",
"pipeline-view-mode": "पाइपलाइन दृश्य मोड",
"platform": "प्लॅटफॉर्म",
"platform-service-client-unavailable": "प्लॅटफॉर्म सेवा क्लायंट उपलब्ध नाही",
"platform-type-lineage": "{{platformType}} वंशावळ",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Geen reviewer",
"no-tags-added": "Geen tags toegevoegd",
"no-tasks-yet": "Nog geen taken",
"node": "Knooppunt",
"node-depth": "Node-diepte",
"nodes-per-layer": "Knooppunten per laag",
"non-partitioned": "Niet-gepartitioneerd",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Pipeline service type",
"pipeline-state": "Pipelinestatus",
"pipeline-type": "Pipeline type",
"pipeline-view-mode": "Pipeline weergavemodus",
"platform": "Platform",
"platform-service-client-unavailable": "Platform Service Client Niet Beschikbaar",
"platform-type-lineage": "{{platformType}} Herkomst",

View File

@ -1129,7 +1129,8 @@
"no-reviewer": "بدون بازبین",
"no-tags-added": "بدون برچسب افزوده شده",
"no-tasks-yet": "هنوز وظیفه‌ای ندارید",
"node-depth": "Node Depth",
"node": "گره",
"node-depth": "عمق گره",
"nodes-per-layer": "گره‌ها در هر لایه",
"non-partitioned": "بدون تقسیم‌بندی",
"none": "هیچ‌کدام",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "نوع سرویس خط لوله",
"pipeline-state": "وضعیت خط لوله",
"pipeline-type": "نوع خط لوله",
"pipeline-view-mode": "حالت نمایش خط لوله",
"platform": "پلتفرم",
"platform-service-client-unavailable": "پلیٹ فارم سروس کلائنٹ دستیاب نہیں",
"platform-type-lineage": "{{platformType}} شجره داده",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Sem revisor",
"no-tags-added": "Nenhuma Tag adicionada",
"no-tasks-yet": "Ainda não há tarefas",
"node": "Nó",
"node-depth": "Profundidade do nó",
"nodes-per-layer": "Nós Por Camada",
"non-partitioned": "Não-particionado",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Pipeline Service Type",
"pipeline-state": "Estado do Pipeline",
"pipeline-type": "Tipo de Pipeline",
"pipeline-view-mode": "Modo de visualização do pipeline",
"platform": "Plataforma",
"platform-service-client-unavailable": "Cliente do Serviço de Plataforma Indisponível",
"platform-type-lineage": "{{platformType}} Linhagem",

View File

@ -1129,7 +1129,8 @@
"no-reviewer": "Sem revisor",
"no-tags-added": "Nenhuma Etiqueta adicionada",
"no-tasks-yet": "Ainda não há tarefas",
"node-depth": "Node Depth",
"node": "Nó",
"node-depth": "Profundidade do Nó",
"nodes-per-layer": "Nós Por Camada",
"non-partitioned": "Não-particionado",
"none": "Nenhum",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Pipeline Service Type",
"pipeline-state": "Estado do Pipeline",
"pipeline-type": "Tipo de Pipeline",
"pipeline-view-mode": "Modo de Visualização do Pipeline",
"platform": "Plataforma",
"platform-service-client-unavailable": "Cliente do Serviço de Plataforma Indisponível",
"platform-type-lineage": "{{platformType}} Linhagem",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "Нет согласующего",
"no-tags-added": "Теги не добавлены",
"no-tasks-yet": "Пока нет задач",
"node": "Узел",
"node-depth": "Глубина узла",
"nodes-per-layer": "Количество объектов",
"non-partitioned": "Неразделенный",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "Тип сервиса пайплайна",
"pipeline-state": "Состояние",
"pipeline-type": "Тип пайплайна",
"pipeline-view-mode": "Режим просмотра конвейера",
"platform": "Платформа",
"platform-service-client-unavailable": "Клиент сервиса платформы недоступен",
"platform-type-lineage": "Происхождение {{platformType}}",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "ไม่มีผู้ตรวจสอบ",
"no-tags-added": "ไม่มีแท็กที่เพิ่ม",
"no-tasks-yet": "ไม่มีงานที่คุณต้องทำ",
"node": "โหนด",
"node-depth": "ความลึกของโหนด",
"nodes-per-layer": "โหนดต่อชั้น",
"non-partitioned": "ไม่มีการแบ่งส่วน",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "ประเภทบริการท่อ",
"pipeline-state": "สถานะท่อ",
"pipeline-type": "ประเภทท่อ",
"pipeline-view-mode": "โหมดมุมมองท่อ",
"platform": "แพลตฟอร์ม",
"platform-service-client-unavailable": "ไคลเอนต์ของบริการแพลตฟอร์มไม่พร้อมใช้งาน",
"platform-type-lineage": "{{platformType}} ลำดับชั้น",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "İnceleyici yok",
"no-tags-added": "Etiket eklenmedi",
"no-tasks-yet": "Henüz görev yok",
"node": "Düğüm",
"node-depth": "Düğüm derinliği",
"nodes-per-layer": "Katman Başına Düğüm Sayısı",
"non-partitioned": "Bölümlenmemiş",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "İş Akışı Servisi Türü",
"pipeline-state": "İş Akışı Durumu",
"pipeline-type": "İş Akışı Türü",
"pipeline-view-mode": "İş Akışı Görünüm Modu",
"platform": "Platform",
"platform-service-client-unavailable": "Platform Servis İstemcisi Kullanılamıyor",
"platform-type-lineage": "{{platformType}} Veri Soyu",

View File

@ -1129,7 +1129,8 @@
"no-reviewer": "无评审人",
"no-tags-added": "没有标签",
"no-tasks-yet": "暂无任务",
"node-depth": "Node Depth",
"node": "节点",
"node-depth": "节点深度",
"nodes-per-layer": "每层节点数",
"non-partitioned": "未分区",
"none": "无",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "流水线服务类型",
"pipeline-state": "工作流状态",
"pipeline-type": "工作流类型",
"pipeline-view-mode": "管道视图模式",
"platform": "平台",
"platform-service-client-unavailable": "平台服务客户端不可用",
"platform-type-lineage": "{{platformType}} 血缘关系",

View File

@ -1129,6 +1129,7 @@
"no-reviewer": "無審核者",
"no-tags-added": "未新增標籤",
"no-tasks-yet": "尚無任務",
"node": "節點",
"node-depth": "節點深度",
"nodes-per-layer": "每層節點數",
"non-partitioned": "非分割",
@ -1255,6 +1256,7 @@
"pipeline-service-type": "管線服務類型",
"pipeline-state": "管線狀態",
"pipeline-type": "管線類型",
"pipeline-view-mode": "管線檢視模式",
"platform": "平台",
"platform-service-client-unavailable": "平台服務客戶端無法使用",
"platform-type-lineage": "{{platformType}} 血緣",

View File

@ -26,6 +26,7 @@ import { OPEN_METADATA } from '../../constants/service-guide.constant';
import {
LineageLayer,
LineageSettings,
PipelineViewMode,
} from '../../generated/configuration/lineageSettings';
import { Settings, SettingType } from '../../generated/settings/settings';
import { withPageLayout } from '../../hoc/withPageLayout';
@ -82,6 +83,7 @@ const LineageConfigPage = () => {
upstreamDepth: Number(values.upstreamDepth),
downstreamDepth: Number(values.downstreamDepth),
lineageLayer: values.lineageLayer,
pipelineViewMode: values.pipelineViewMode,
},
};
@ -203,6 +205,21 @@ const LineageConfigPage = () => {
</Select.Option>
</Select>
</Form.Item>
<Form.Item
className="m-t-sm"
id="root/pipelineViewMode"
label={t('label.pipeline-view-mode')}
name="pipelineViewMode">
<Select data-testid="field-pipeline-view-mode">
<Select.Option value={PipelineViewMode.Edge}>
{t('label.edge')}
</Select.Option>
<Select.Option value={PipelineViewMode.Node}>
{t('label.node')}
</Select.Option>
</Select>
</Form.Item>
</Form>
<Row className="m-b-xl" justify="end">
<Col className="d-flex justify-end gap-2" span={24}>

View File

@ -86,6 +86,7 @@ import {
} from '../enums/entity.enum';
import { AddLineage, EntitiesEdge } from '../generated/api/lineage/addLineage';
import { LineageDirection } from '../generated/api/lineage/lineageDirection';
import { PipelineViewMode } from '../generated/configuration/lineageSettings';
import { APIEndpoint } from '../generated/entity/data/apiEndpoint';
import { Container } from '../generated/entity/data/container';
import { Dashboard } from '../generated/entity/data/dashboard';
@ -1507,6 +1508,7 @@ const processEdges = (
return [...acc, edge];
}
// Process pipeline edge to create two edges
const pipelineEdges = processPipelineEdge(
edge,
pipelineNode as unknown as Pipeline
@ -1558,7 +1560,8 @@ const processPagination = (
export const parseLineageData = (
data: LineageData,
entityFqn: string, // This contains fqn of node or entity that is being viewed in lineage page
rootFqn: string // This contains the fqn of the entity that is being viewed in lineage page
rootFqn: string, // This contains the fqn of the entity that is being viewed in lineage page,
pipelineViewMode: PipelineViewMode = PipelineViewMode.Node
): {
nodes: LineageEntityReference[];
edges: EdgeDetails[];
@ -1576,7 +1579,10 @@ export const parseLineageData = (
...Object.values(downstreamEdges),
...Object.values(upstreamEdges),
];
const processedEdges = processEdges(allEdges, nodesArray);
const processedEdges =
pipelineViewMode === PipelineViewMode.Node
? processEdges(allEdges, nodesArray)
: allEdges;
// Handle pagination
const { newNodes, newEdges } = processPagination(