DataWorks provides various API operations. You can use the OpenAPI module and other open capabilities of DataWorks to implement various business scenarios based on your requirements. This topic uses metadata table management as an example to describe how to connect metadata API operations to perform operations such as querying lists, querying table details.
Prerequisites
Before you perform operations that are described in this topic, we recommend that you review the following topics to learn more about the basic capabilities and concepts of the OpenAPI module in DataWorks:
I. Query table lists
This practice describes how to use OpenAPI to query the table list under a MaxCompute project or schema, with support for paged queries. The main procedure is as follows:
Backend: Use MetaServiceProxy to query table details
Write a ListTables method in MetaServiceProxy to process frontend parameters and send requests to the ListTables API operation to obtain the list information of metadata tables.
Supported query parameters: Parent-level metadata entity ID, name (fuzzy matching), comment (fuzzy matching), type, sorting parameters, and paging parameters.
NoteRefer to Metadata entity-related concepts. The parent-level metadata entity ID may exist in two formats.
Parent-level metadata entity ID as MaxCompute project ID, format:
maxcompute-project:${Alibaba Cloud account ID}::{projectName}
Parent-level metadata entity ID as MaxCompute schema ID, format:
maxcompute-schema:${Alibaba Cloud account ID}::{projectName}:{schemaName}
Query results include: Total number of data tables, paging information, and for each data table: ID, parent-level metadata entity ID, table name, comment, database name, schema name, type, partition field list, creation time, and modification time.
Obtain the Alibaba Cloud account ID: Whether you are using a RAM user or an Alibaba Cloud account, you can view the Alibaba Cloud account ID in the upper-right corner of the page.
Log on to the DataWorks console using your Alibaba Cloud account or RAM user.
Hover your mouse over the profile picture in the upper-right corner to view the Alibaba Cloud account ID.
Alibaba Cloud account: The Account ID is the Alibaba Cloud account ID that you need to obtain.
RAM user: You can directly view the Alibaba Cloud Account ID.
Sample code:
/** * @author dataworks demo */ @Service public class MetaServiceProxy { @Autowired private DataWorksOpenApiClient dataWorksOpenApiClient; /** * DataWorks OpenAPI : ListTables * * @param listTablesDto */ public ListTablesResponseBodyPagingInfo listTables(ListTablesDto listTablesDto) { try { Client client = dataWorksOpenApiClient.createClient(); ListTablesRequest listTablesRequest = new ListTablesRequest(); // Parent entity ID, metadata entity concept description. listTablesRequest.setParentMetaEntityId(listTablesDto.getParentMetaEntityId()); // Table name, supports fuzzy matching listTablesRequest.setName(listTablesDto.getName()); // Table comment, supports fuzzy matching listTablesRequest.setComment(listTablesDto.getComment()); // Table type, returns all types by default listTablesRequest.setTableTypes(listTablesDto.getTableTypes()); // Sort by, CreateTime (default) / ModifyTime / Name / TableType listTablesRequest.setSortBy(listTablesDto.getSortBy()); // Sort direction, supports Asc (default) / Desc listTablesRequest.setOrder(listTablesDto.getOrder()); // Page number, default is 1 listTablesRequest.setPageNumber(listTablesDto.getPageNumber()); // Page size, default is 10, maximum 100 listTablesRequest.setPageSize(listTablesDto.getPageSize()); ListTablesResponse response = client.listTables(listTablesRequest); // Get the total number of tables that meet the requirements System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Table table : response.getBody().getPagingInfo().getTables()) { // Table ID System.out.println(table.getId()); // Parent entity ID of the table System.out.println(table.getParentMetaEntityId()); // Table name System.out.println(table.getName()); // Table comment System.out.println(table.getComment()); // Database/MaxCompute project name that the table belongs to System.out.println(table.getId().split(":")[3]); // Schema name that the table belongs to System.out.println(table.getId().split(":")[4]); // Table type System.out.println(table.getTableType()); // Table creation time System.out.println(table.getCreateTime()); // Table modification time System.out.println(table.getModifyTime()); // Table partition field list System.out.println(table.getPartitionKeys()); } return response.getBody().getPagingInfo(); } catch (TeaException error) { // Handle exceptions with caution based on your actual business scenario and do not ignore exceptions in your project. The error messages displayed in this example are for reference only. // The error message System.out.println(error.getMessage()); // Display the URL for troubleshooting. System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // Handle exceptions with caution based on your actual business scenario and do not ignore exceptions in your project. The error messages displayed in this example are for reference only. // The error message System.out.println(error.getMessage()); // Display the URL for troubleshooting. System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }
Add a
listTables
method in MetaRestController as an entry point for frontend access./** * Demonstrate how to build a custom metadata platform by using the DataWorks OpenAPI module * * @author dataworks demo */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * Query data in the metatables by page. * * @param listTablesDto * @return {@link ListTablesResponseBodyPagingInfo} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listTables") public ListTablesResponseBodyPagingInfo listTables(ListTablesDto listTablesDto) { System.out.println("listTablesDto = " + listTablesDto); return metaService.listTables(listTablesDto); } }
Frontend: Display table metadata information
import React from "react";
import cn from "classnames";
import {
Table,
Form,
Field,
Input,
Button,
Pagination,
Dialog,
Card,
Grid,
Select,
} from "@alifd/next";
import type { TableListInput, TableEntity } from "../services/meta";
import * as services from "../services";
import DetailContent from "./detailContent";
import LineageContent from "./lineageContent";
import classes from "../styles/app.module.css";
import moment from "moment";
export interface Props {}
const { Column } = Table;
const { Item } = Form;
const { Header, Content } = Card;
const { Row, Col } = Grid;
const { Option } = Select;
const App: React.FunctionComponent = () => {
const field = Field.useField();
const [datasource, setDatasource] = React.useState([]);
const [loading, setLoading] = React.useState(false);
const [total, setTotal] = React.useState(0);
const [current, setCurrent] = React.useState(1);
const onSubmit = React.useCallback(
(pageNumber: number = 1) => {
field.validate(async (errors, values) => {
if (errors) {
return;
}
setLoading(true);
try {
const response = await services.meta.getTableList({
pageNumber,
...values,
} as TableListInput);
setDatasource(response.tables);
setCurrent(pageNumber);
setTotal(response.totalCount);
} catch (e) {
throw e;
} finally {
setLoading(false);
}
});
},
[field]
);
const onViewDetail = React.useCallback((record: TableEntity) => {
Dialog.show({
title: "Data Table Details",
content: ,
shouldUpdatePosition: true,
footerActions: ["ok"],
});
}, []);
const onViewLineage = React.useCallback((record: TableEntity) => {
Dialog.show({
title: "Table Lineage",
content: ,
shouldUpdatePosition: true,
footerActions: ["ok"],
});
}, []);
React.useEffect(() => {
field.setValue("dataSourceType", "odps");
}, []);
return (
Table
View
Materialized View
Creation Time
Modification Time
Name
Type
Ascending
Descending
onSubmit()}>
Query
No Data
}
>
{
const parts = value.split(":");
return parts.length > 1 ? parts[parts.length - 3] : "";
}}
/>
{
const parts = value.split(":");
return parts.length > 1 ? parts[parts.length - 2] : "";
}}
/>
{
return value != null && value.length > 0 ? "Yes" : "No";
}}
/>
{
return moment(value).format("YYYY-MM-DD HH:mm:ss");
}}
/>
{
return moment(value).format("YYYY-MM-DD HH:mm:ss");
}}
/>
(
onViewDetail(record)}
text
>
View Details
onViewLineage(record)}
text
>
View Table Lineage
)}
/>
After completing the code development above, you can deploy and run the project code locally. You can enter the MaxCompute project ID and other parameters to query and obtain the list of all tables in the project, with support for paged query.
2. Lookup table details
The following practice will combine three OpenAPI methods: GetTable, ListColumns, and ListPartitions to query table details, and use the UpdateColumnBusinessMetadata API to update the business description of fields. The practice workflow is as follows.
Backend: Use MetaServiceProxy to query table details
Create
getTable
,listColumns
, andlistPartitions
methods in MetaServiceProxy to call the OpenAPI service to obtain the basic information, field information, and partition information of a table. Create theupdateColumnBusinessMetadata
method to update the business description of fields/** * @author dataworks demo */ @Service public class MetaServiceProxy { @Autowired private DataWorksOpenApiClient dataWorksOpenApiClient; /** * DataWorks OpenAPI : getTableDto * * @param getTableDto */ public Table getTable(GetTableDto getTableDto) { try { Client client = dataWorksOpenApiClient.createClient(); GetTableRequest getTableRequest = new GetTableRequest(); // Table ID getTableRequest.setId(getTableDto.getId()); // Whether to return business metadata getTableRequest.setIncludeBusinessMetadata(getTableDto.getIncludeBusinessMetadata()); GetTableResponse response = client.getTable(getTableRequest); // Corresponding data table Table table = response.getBody().getTable(); // Table ID System.out.println(table.getId()); // Parent entity ID of the table System.out.println(table.getParentMetaEntityId()); // Table name System.out.println(table.getName()); // Table comment System.out.println(table.getComment()); // Database / MaxCompute project name that the table belongs to System.out.println(table.getId().split(":")[3]); // Schema name that the table belongs to System.out.println(table.getId().split(":")[4]); // Table type System.out.println(table.getTableType()); // Table creation time System.out.println(table.getCreateTime()); // Table modification time System.out.println(table.getModifyTime()); // Whether it is a partitioned table System.out.println(table.getPartitionKeys() != null && !table.getPartitionKeys().isEmpty()); // Technical metadata of the table TableTechnicalMetadata technicalMetadata = table.getTechnicalMetadata(); // Table owner (name) System.out.println(technicalMetadata.getOwner()); // Whether it is a compressed table System.out.println(technicalMetadata.getCompressed()); // Input format (supported by HMS, DLF, etc.) System.out.println(technicalMetadata.getInputFormat()); // Output format (supported by HMS, DLF, etc.) System.out.println(technicalMetadata.getOutputFormat()); // Class used for table serialization (supported by HMS, DLF, etc.) System.out.println(technicalMetadata.getSerializationLibrary()); // Table storage location (supported by HMS, DLF, etc.) System.out.println(technicalMetadata.getLocation()); // Other parameters Map parameters = technicalMetadata.getParameters(); // Last DDL update time (millisecond timestamp), only supported by MaxCompute type System.out.println(parameters.get("lastDDLTime")); // Lifecycle (in days), only supported by MaxCompute type System.out.println(parameters.get("lifecycle")); // Storage volume (in bytes), not real-time, only supported by MaxCompute type System.out.println(parameters.get("dataSize")); // Business metadata of the table TableBusinessMetadata businessMetadata = table.getBusinessMetadata(); if (businessMetadata != null) { // Usage instructions System.out.println(businessMetadata.getReadme()); // Custom tag information List tags = businessMetadata.getTags(); if (tags != null && !tags.isEmpty()) { for (TableBusinessMetadataTags tag : tags) { System.out.println(tag.getKey() + ":" + tag.getValue()); } } // Upstream production tasks List upstreamTasks = businessMetadata.getUpstreamTasks(); if (upstreamTasks != null && !upstreamTasks.isEmpty()) { for (TableBusinessMetadataUpstreamTasks upstreamTask : upstreamTasks) { // Task ID and task name, you can get task details through GetTask API System.out.println(upstreamTask.getId() + ":" + upstreamTask.getName()); } } // Category information List> categories = businessMetadata.getCategories(); if (categories != null && !categories.isEmpty()) { // Traverse multi-level categories for (List
In MetaRestController, provide four methods:
getTable
,listColumns
,listPartitions
, andupdateColumnBusinessMetadata
for frontend calls./** * Demonstrate how to build a custom metadata platform by using the OpenAPI module. * * @author dataworks demo */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * Get table details * * @param getTableDto * @return {@link Table} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/getTable") public Table getTable(GetTableDto getTableDto) { System.out.println("getTableDto = " + getTableDto); return metaService.getTable(getTableDto); } /** * Paged query for field data * * @param listColumnsDto * @return {@link ListColumnsResponseBodyPagingInfo} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listColumns") public ListColumnsResponseBodyPagingInfo listColumns(ListColumnsDto listColumnsDto) { System.out.println("listColumnsDto = " + listColumnsDto); return metaService.listColumns(listColumnsDto); } /** * Update field business description * * @param updateColumnBusinessMetadataDto * @return {@link Boolean} */ @CrossOrigin(origins = "http://localhost:8080") @PostMapping("/updateColumnBusinessMetadata") public Boolean updateColumnBusinessMetadata(@RequestBody UpdateColumnBusinessMetadataDto updateColumnBusinessMetadataDto) { System.out.println("updateColumnBusinessMetadataDto = " + updateColumnBusinessMetadataDto); return metaService.updateColumnBusinessMetadata(updateColumnBusinessMetadataDto); } /** * Paged query for partition data * * @param listPartitionsDto * @return {@link ListPartitionsResponseBodyPagingInfo} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listPartitions") public ListPartitionsResponseBodyPagingInfo listPartitions(ListPartitionsDto listPartitionsDto) { System.out.println("listPartitionsDto = " + listPartitionsDto); return metaService.listPartitions(listPartitionsDto); } }
Frontend: Display basic table information, field information, and partition information.
import React from "react";
import moment from "moment";
import {
Form,
Grid,
Table,
Pagination,
Tag,
Field,
Input,
Button,
Select,
Dialog,
} from "@alifd/next";
import cn from "classnames";
import * as services from "../services";
import {
type ListColumnsInput,
type TableColumn,
type TableEntity,
type TablePartition,
} from "../services/meta";
import classes from "../styles/detailContent.module.css";
export interface Props {
item: TableEntity;
}
const formItemLayout = {
labelCol: {
fixedSpan: 6,
},
wrapperCol: {
span: 18,
},
labelTextAlign: "left" as const,
colon: true,
className: cn(classes.formItemWrapper),
};
const { Row, Col } = Grid;
const { Item } = Form;
const { Column } = Table;
const { Option } = Select;
const DetailContent: React.FunctionComponent = (props) => {
let columnsPageSize = 10;
let partitionsPageSize = 5;
const listColumnsField = Field.useField();
const listPartitionsField = Field.useField();
const labelAlign = "top";
const [detail, setDetail] = React.useState>({});
const [columns, setColumns] = React.useState>([]);
const [partitions, setPartitions] = React.useState>(
[]
);
const [columnsTotal, setColumnsTotal] = React.useState(0);
const [partitionTotal, setPartitionTotal] = React.useState(0);
const [editingKey, setEditingKey] = React.useState("");
const [editingDescription, setEditingDescription] =
React.useState("");
const [updateColumnDialogVisible, setUpdateColumnDialogVisible] =
React.useState(false);
const onUpdateColumnDialogOpen = () => {
setUpdateColumnDialogVisible(true);
};
const onUpdateColumnDialogOk = () => {
handleColumnDescriptionSave();
onUpdateColumnDialogClose();
};
const onUpdateColumnDialogClose = () => {
setUpdateColumnDialogVisible(false);
};
const handleEditColumnDescription = async (record: TableColumn) => {
setEditingKey(record.id);
setEditingDescription(record.businessMetadata?.description);
onUpdateColumnDialogOpen();
};
const handleColumnDesrciptionChange = async (e: string) => {
setEditingDescription(e);
};
const handleColumnDescriptionSave = async () => {
try {
const res = await services.meta.updateColumnDescription({
id: editingKey,
description: editingDescription,
});
if (!res) {
console.log("Request update failed");
}
} catch (e) {
console.error("Update failed", e);
} finally {
listColumns();
setEditingKey("");
}
};
const getTableDetail = React.useCallback(async () => {
const response = await services.meta.getTableDetail({
id: props.item.id,
includeBusinessMetadata: true,
});
setDetail(response);
}, [props.item.id]);
const listColumns = React.useCallback(
async (pageNumber: number = 1) => {
listColumnsField.setValue("tableId", props.item.id);
listColumnsField.setValue("pageSize", columnsPageSize);
listColumnsField.validate(async (errors, values) => {
if (errors) {
return;
}
try {
const response = await services.meta.getMetaTableColumns({
pageNumber,
...values,
} as ListColumnsInput);
setColumns(response.columns);
setColumnsTotal(response.totalCount);
} catch (e) {
throw e;
} finally {
}
});
},
[listColumnsField]
);
const listPartitions = React.useCallback(
async (pageNumber: number = 1) => {
listPartitionsField.setValue("tableId", props.item.id);
listPartitionsField.setValue("pageSize", partitionsPageSize);
listPartitionsField.validate(async (errors, values) => {
if (errors) {
return;
}
try {
const response = await services.meta.getTablePartition({
pageNumber,
...values,
} as ListColumnsInput);
setPartitions(response.partitionList);
setPartitionTotal(response.totalCount);
} catch (e) {
throw e;
} finally {
}
});
},
[listPartitionsField]
);
React.useEffect(() => {
if (props.item.id) {
getTableDetail();
listColumns();
listPartitions();
}
}, [props.item.id]);
return (
handleColumnDesrciptionChange(e.toString())}
>
{detail.id}
{detail?.id?.split(":").slice(-3, -2)[0]}
{detail?.id?.split(":").slice(-2, -1)[0]}
{detail.name}
{detail.comment}
{detail.tableType}
{detail.technicalMetadata?.compressed ? "Yes" : "No"}
{detail.businessMetadata?.extension?.projectId}
{detail.businessMetadata?.extension?.envType === "Dev"
? "Development"
: "Production"}
{detail.technicalMetadata?.parameters["lifecycle"]
? `${detail.technicalMetadata?.parameters["lifecycle"]} days`
: "Permanent"}
{`${detail.technicalMetadata?.parameters["dataSize"]} Bytes`}
{moment(
Number(detail.technicalMetadata?.parameters["lastDDLTime"])
).format("YYYY-MM-DD HH:mm:ss")}
{detail.partitionKeys != null && detail.partitionKeys.length > 0
? "Yes"
: "No"}
{detail.businessMetadata?.tags?.map((tag, index) => (
{tag.key + (tag.value ? ":" + tag.value : "")}
))}
{detail.businessMetadata?.categories?.map((category, index) => (
{category.map((obj) => obj.name).join("->")}
))}
{detail.businessMetadata?.upstreamTasks?.map((task, index) => (
{task.name + " (" + task.id + ")"}
))}
{detail.businessMetadata?.extension?.viewCount}
{detail.businessMetadata?.extension?.readCount}
{detail.businessMetadata?.extension?.favorCount}
{moment(detail.createTime).format("YYYY-MM-DD HH:mm:ss")}
{moment(detail.modifyTime).format("YYYY-MM-DD HH:mm:ss")}
Position
Name
Ascending
Descending
listColumns()}>
Query
(
{value?.description}
handleEditColumnDescription(record)}
size="small"
type="primary"
text
style={{ marginLeft: 8 }}
>
Edit
)}
/>
(value ? "Yes" : "")}
/>
(value ? "Yes" : "")}
/>
(value ? "Yes" : "")}
/>
Creation Time
Modification Time
Partition Name
Partition Record Count
Partition Size
Ascending
Descending
listPartitions()}>
Query
Not a partitioned table}>
moment(value).format("YYYY-MM-DD HH:mm:ss")}
/>
moment(value).format("YYYY-MM-DD HH:mm:ss")}
/>
`${value} bytes`}
/>
);
};
export default DetailContent;
After you write the preceding code, you can deploy and run the project code offline. For deployment and running operations, see Common operation: Local deployment and running.
3. Query table lineage
When the definition of a table changes, you can use DataWorks OpenAPI to analyze the lineage of upstream and downstream tasks to find the nodes associated with the table.
Use ListLineages to query the upstream and downstream lineage of a metatable.
Backend: Use MetaServiceProxy to query table lineage
Create the listTableLineages method in MetaServiceProxy to call the OpenAPI service to obtain the upstream and downstream lineage entities and relationship information of a table.
/** * @author dataworks demo */ @Service public class MetaServiceProxy { @Autowired private DataWorksOpenApiClient dataWorksOpenApiClient; /** * DataWorks OpenAPI : ListLineages * * @param listTableLineagesDto */ public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) { try { Client client = dataWorksOpenApiClient.createClient(); ListLineagesRequest listLineagesRequest = new ListLineagesRequest(); // Lineage query direction, whether it is upstream boolean needUpstream = "up".equalsIgnoreCase(listTableLineagesDto.getDirection()); // Table ID, which can be obtained through the ListTables interface // Partition name, supports fuzzy matching if (needUpstream) { listLineagesRequest.setDstEntityId(listTableLineagesDto.getTableId()); listLineagesRequest.setSrcEntityName(listTableLineagesDto.getName()); } else { listLineagesRequest.setSrcEntityId(listTableLineagesDto.getTableId()); listLineagesRequest.setDstEntityName(listTableLineagesDto.getName()); } // Sort method, supports Name listLineagesRequest.setSortBy(listTableLineagesDto.getSortBy()); // Sort direction, supports Asc (default) / Desc listLineagesRequest.setOrder(listTableLineagesDto.getOrder()); // Page number, default is 1 listLineagesRequest.setPageNumber(listTableLineagesDto.getPageNumber()); // Page size, default is 10, maximum is 100 listLineagesRequest.setPageSize(listTableLineagesDto.getPageSize()); // Whether to return specific lineage relationship information listLineagesRequest.setNeedAttachRelationship(true); ListLineagesResponse response = client.listLineages(listLineagesRequest); // Get the total number of lineage entities that meet the requirements System.out.println(response.getBody().getPagingInfo().getTotalCount()); if (response.getBody().getPagingInfo().getLineages() == null) { response.getBody().getPagingInfo().setLineages(Collections.emptyList()); } if (response.getBody().getPagingInfo().getLineages() != null) { for (ListLineagesResponseBodyPagingInfoLineages lineage : response.getBody().getPagingInfo().getLineages()) { LineageEntity entity; // Get the corresponding lineage entity if (needUpstream) { entity = lineage.getSrcEntity(); } else { entity = lineage.getDstEntity(); } // Entity ID System.out.println(entity.getId()); // Entity name System.out.println(entity.getName()); // Entity property information System.out.println(entity.getAttributes()); // List of lineage relationships between entities List relationships = lineage.getRelationships(); if (relationships != null) { relationships.forEach(relationship -> { // Lineage relationship ID System.out.println(relationship.getId()); // Creation time System.out.println(relationship.getCreateTime()); // Task corresponding to the lineage relationship LineageTask task = relationship.getTask(); if (task != null) { // Task ID, for DataWorks tasks, you can get task details through GetTask System.out.println(task.getId()); // Task type System.out.println(task.getType()); // Task property information Map attributes = task.getAttributes(); if (attributes != null) { // For DataWorks tasks, you can get task and instance property information // Project ID System.out.println(attributes.get("projectId")); // Task ID String taskId = attributes.containsKey("taskId") ? attributes.get("taskId") : attributes.get("nodeId"); System.out.println(taskId); // Instance ID String taskInstanceId = attributes.containsKey("taskInstanceId") ? attributes.get("taskInstanceId") : attributes.get("jobId"); System.out.println(taskInstanceId); } } }); } } return response.getBody().getPagingInfo(); } } catch (TeaException error) { // Handle exceptions with caution in actual business scenarios and do not ignore the exceptions in your project. In this example, exceptions are provided only for reference. // The error message System.out.println(error.getMessage()); // Display the troubleshooting information System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // Handle exceptions with caution in actual business scenarios and do not ignore the exceptions in your project. In this example, exceptions are provided only for reference. // The error message System.out.println(error.getMessage()); // Display the troubleshooting information System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }
In MetaRestController, provide the listTableLineages method for frontend calls.
/** * Demonstrate how to build a custom metadata platform by using the OpenAPI module * * @author dataworks demo */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * Query the data lineage of the metatable * * @param listTableLineagesDto * @return */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listTableLineages") public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) { System.out.println("listTableLineagesDto = " + listTableLineagesDto); return metaService.listTableLineages(listTableLineagesDto); } }
Frontend: Display table lineage
import React from 'react';
import Graphin, { Behaviors } from '@antv/graphin';
import type { IUserNode, IUserEdge, GraphinData, GraphEvent } from '@antv/graphin';
import cn from 'classnames';
import * as services from '../services';
import type { TableEntity, ListTableLineageOutput, LineageEntity } from '../services/meta';
import classes from '../styles/lineageContent.module.css';
import '@antv/graphin/dist/index.css';
export interface Props {
item: TableEntity;
}
function transNode(entity: LineageEntity | TableEntity, direction?: string): IUserNode {
return {
id: entity.id,
label: entity.name,
data: {
...entity,
direction,
},
style: {
label: {
value: entity.name,
}
},
}
}
function transEdge(source: LineageEntity | TableEntity, target: LineageEntity | TableEntity): IUserEdge {
return {
source: source.id,
target: target.id,
style: {
keyshape: {
lineDash: [8, 4],
lineWidth: 2,
},
animate: {
type: 'line-dash',
repeat: true,
},
}
};
}
function parse(
source: LineageEntity | TableEntity,
data: ListTableLineageOutput,
direction: string,
): [IUserNode[], IUserEdge[]] {
const nodes: IUserNode[] = [];
const edges: IUserEdge[] = [];
data.lineages.forEach((lineageRelationship) => {
if (direction === 'down') {
nodes.push(transNode(lineageRelationship.dstEntity, direction));
edges.push(transEdge(source, lineageRelationship.dstEntity));
} else {
nodes.push(transNode(lineageRelationship.srcEntity, direction));
edges.push(transEdge(lineageRelationship.srcEntity, source));
}
});
return [nodes, edges];
}
function mergeNodes(prev: IUserNode[], next: IUserNode[]) {
const result: IUserNode[] = prev.slice();
next.forEach((item) => {
const hasValue = prev.findIndex(i => i.id === item.id) >= 0;
!hasValue && result.push(item);
});
return result;
}
function mergeEdges(source: IUserEdge[], target: IUserEdge[]) {
const result: IUserEdge[] = source.slice();
target.forEach((item) => {
const hasValue = source.findIndex(i => i.target === item.target && i.source === item.source) >= 0;
!hasValue && result.push(item);
});
return result;
}
const { ActivateRelations, DragNode, ZoomCanvas } = Behaviors;
const LineageContent: React.FunctionComponent = (props) => {
const ref = React.useRef();
const [data, setData] = React.useState({ nodes: [], edges: [] });
const getTableLineage = async (
collection: GraphinData,
target: TableEntity | LineageEntity,
direction: string,
) => {
if (!direction) {
return collection;
}
const response = await services.meta.getTableLineage({
direction,
tableId: target.id,
});
const [nodes, edges] = parse(target, response, direction);
collection.nodes = mergeNodes(collection.nodes!, nodes);
collection.edges = mergeEdges(collection.edges!, edges);
return collection;
};
const init = async () => {
let nextData = Object.assign({}, data, { nodes: [transNode(props.item)] });
nextData = await getTableLineage(nextData, props.item, 'up');
nextData = await getTableLineage(nextData, props.item, 'down');
setData(nextData);
ref.current!.graph.fitCenter();
};
React.useEffect(() => {
ref.current?.graph && init();
}, [
ref.current?.graph,
]);
React.useEffect(() => {
const graph = ref.current?.graph;
const event = async (event: GraphEvent) => {
const source = event.item?.get('model').data;
let nextData = Object.assign({}, data);
nextData = await getTableLineage(nextData, source, source.direction);
setData(nextData);
};
graph?.on('node:click', event);
return () => {
graph?.off('node:click', event);
};
}, [
data,
ref.current?.graph,
]);
return (
}
layout={{
type: 'dagre',
rankdir: 'LR',
align: 'DL',
nodesep: 10,
ranksep: 40,
}}
>
);
};
export default LineageContent;
After you complete the code development, you can deploy and run the project code locally. For information about how to deploy and run the code, see Common operation: Deploy and run locally.
4. Find tasks corresponding to a lookup table
When the specification of a table changes, you can perform kinship analysis of downstream tasks through DataWorks OpenAPI, OpenData, or message subscription to find the nodes corresponding to the table. The specific operations are as follows.
Messages currently support table changes, task changes, and more. Enterprise Edition users can connect to table change messages. When you receive a table change message, you can view the kinship relationships of the table.
Kinship tasks
Use ListLineages to view the kinship relationships of the table and obtain the DataWorks task ID (and task instance ID) from the Task in the kinship relationships.
Based on the obtained task ID, use GetTask to obtain the task details.
Output tasks
Based on the entity ID, use the GetTable API to obtain the business metadata of the table, which includes the DataWorks task ID in the upstream output tasks.
Based on the specified task ID, use GetTask to obtain the task details.
Common operations: Local deployment and running
Prepare the dependencies.
You need to prepare the following dependencies: Java 8 or later, Maven build tool, Node environment, and pnpm tool. You can run the following commands to check whether you have these environments:
npm -v // If the installation is successful, the version is displayed in the command output. If the installation fails, an error that indicates no command is available is reported. java -version // If the installation is successful, the version is displayed in the command output. If the installation fails, an error that indicates no command is available is reported. pnpm -v // If the installation is successful, the version is displayed in the command output. If the installation fails, an error that indicates no command is available is reported.
Download the project code and run the following command.
Project code download link: meta-api-demo.zip.
pnpm i
Find the application.properties file in the backend/src/main/resources path of the example project, and modify the core parameters in the file.
Set the api.access-key-id and api.access-key-secret parameters to the AccessKey ID and AccessKey Secret of your Alibaba Cloud account.
NoteYou can refer to AccessKey management (AccessKey) to obtain AccessKey and other related information of your Alibaba Cloud account.
Configure the api.region-id and api.endpoint parameters based on the information about the region of the API operations that you want to call.
Other parameters can be modified according to your actual situation. The following is an example of the filled parameters.
Run the following command in the root directory of the project to execute the example code.
npm run dev