DataWorks提供了豐富的OpenAPI,您可以根據需要使用DataWorks的OpenAPI等開放能力實現各種業務情境。本文以中繼資料表管理為例,為您介紹如何串聯中繼資料的OpenAPI來達成查詢列表、查詢表詳情等操作。
前置概念
在進行本實踐之前,建議您先參考以下連結,瞭解DataWorks的OpenAPI的基本能力和概念:
一、查詢表列表
本實踐介紹如何使用OpenAPI查詢MaxCompute專案/模式下的表列表,並支援分頁查詢。主要流程如下:
後端:使用MetaServiceProxy查詢表詳情
在MetaServiceProxy中編寫一個ListTables方法,處理前端參數並發送請求到OpenAPI ListTables中,以擷取中繼資料表的列表資訊。
支援查詢的參數:父層級中繼資料實體ID、名稱(模糊比對)、注釋(模糊比對)、類型以及排序參數和分頁的參數。
說明參考中繼資料實體相關概念說明,此處的父層級中繼資料實體ID可能存在兩種格式。
父層級中繼資料實體ID為MaxCompute專案ID,格式為:
maxcompute-project:${主帳號ID}::{projectName}父層級中繼資料實體ID為MaxCompute模式ID,格式為:
maxcompute-schema:${主帳號ID}::{projectName}:{schemaName}
查詢結果中包含內容:資料表總數、分頁資訊、每個資料表的ID、父層級中繼資料實體ID、表名稱、注釋、資料庫名稱、模式名稱、類型、分區欄位列表、建立時間與修改時間。
擷取主帳號ID:無論是RAM子帳號還是主帳號都可在頁面右上方的帳號處查看主帳號ID。
登入主帳號或RAM子帳號到DataWorks控制台。
滑鼠懸浮右上方的頭像按鈕上即可查看主帳號ID。
主帳號:帳號ID即為需要擷取的主帳號ID。
RAM帳號:可直接查看到主帳號ID。
範例程式碼:
/** * @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(); // 父層級實體ID,中繼資料相關實體概念說明。 listTablesRequest.setParentMetaEntityId(listTablesDto.getParentMetaEntityId()); // 表名稱,支援模糊比對 listTablesRequest.setName(listTablesDto.getName()); // 表注釋,支援模糊比對 listTablesRequest.setComment(listTablesDto.getComment()); // 表類型,預設返回所有類型 listTablesRequest.setTableTypes(listTablesDto.getTableTypes()); // 排序方式,CreateTime (預設) / ModifyTime / Name / TableType listTablesRequest.setSortBy(listTablesDto.getSortBy()); // 排序方向,支援 Asc (預設) / Desc listTablesRequest.setOrder(listTablesDto.getOrder()); // 分頁頁碼,預設為1 listTablesRequest.setPageNumber(listTablesDto.getPageNumber()); // 分頁大小,預設為10,最大100 listTablesRequest.setPageSize(listTablesDto.getPageSize()); ListTablesResponse response = client.listTables(listTablesRequest); // 擷取符合要求的表總數 System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Table table : response.getBody().getPagingInfo().getTables()) { // 表ID System.out.println(table.getId()); // 表的父層級實體ID System.out.println(table.getParentMetaEntityId()); // 表名稱 System.out.println(table.getName()); // 表注釋 System.out.println(table.getComment()); // 表所屬資料庫/maxcompute專案名稱 System.out.println(table.getId().split(":")[3]); // 表所屬schema名稱 System.out.println(table.getId().split(":")[4]); // 表類型 System.out.println(table.getTableType()); // 表建立時間 System.out.println(table.getCreateTime()); // 表修改時間 System.out.println(table.getModifyTime()); // 表的分區欄位列表 System.out.println(table.getPartitionKeys()); } return response.getBody().getPagingInfo(); } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }
在MetaRestController中添加一個
listTables方法作為入口供前端訪問。/** * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台 * * @author dataworks demo */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * 分頁查詢表資料 * * @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); } }
前端:展示表中繼資料資訊
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<Props> = () => {
const field = Field.useField();
const [datasource, setDatasource] = React.useState<TableEntity[]>([]);
const [loading, setLoading] = React.useState<boolean>(false);
const [total, setTotal] = React.useState<number>(0);
const [current, setCurrent] = React.useState<number>(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: "資料表詳情",
content: <DetailContent item={record} />,
shouldUpdatePosition: true,
footerActions: ["ok"],
});
}, []);
const onViewLineage = React.useCallback((record: TableEntity) => {
Dialog.show({
title: "表血緣關係",
content: <LineageContent item={record} />,
shouldUpdatePosition: true,
footerActions: ["ok"],
});
}, []);
React.useEffect(() => {
field.setValue("dataSourceType", "odps");
}, []);
return (
<div className={cn(classes.appWrapper)}>
<Card free hasBorder={false}>
<Header title="中繼資料表管理情境Demo(MaxCompute)" />
<Content style={{ marginTop: 24 }}>
<Form field={field} colon fullWidth>
<Row gutter="1">
<Col>
<Item
label="MaxCompute專案/模式ID"
name="parentMetaEntityId"
required
help="ID"
>
<Input />
</Item>
</Col>
</Row>
<Row gutter="4">
<Col>
<Item label="名稱" name="name">
<Input placeholder="輸入表名稱,模糊比對" />
</Item>
</Col>
<Col>
<Item label="注釋" name="comment">
<Input placeholder="輸入表注釋,模糊比對" />
</Item>
</Col>
<Col>
<Item label="類型" name="tableTypes">
<Select mode="multiple" style={{ width: "100%" }}>
<Option value="TABLE">表</Option>
<Option value="VIEW">視圖</Option>
<Option value="MATERIALIZED_VIEW">物化視圖</Option>
</Select>
</Item>
</Col>
<Col>
<Item label="排序方式" name="sortBy">
<Select defaultValue="CreateTime" style={{ width: "100%" }}>
<Option value="CreateTime">建立時間</Option>
<Option value="ModifyTime">修改時間</Option>
<Option value="Name">名稱</Option>
<Option value="TableType">類型</Option>
</Select>
</Item>
</Col>
<Col>
<Item label="排序方向" name="order">
<Select defaultValue="Asc" style={{ width: "100%" }}>
<Option value="Asc">升序</Option>
<Option value="Desc">降序</Option>
</Select>
</Item>
</Col>
</Row>
<div
className={cn(
classes.searchPanelButtonWrapper,
classes.buttonGroup
)}
>
<Button type="primary" onClick={() => onSubmit()}>
查詢
</Button>
</div>
</Form>
<div>
<Table
dataSource={datasource}
loading={loading}
className={cn(classes.tableWrapper)}
emptyContent={
<div className={cn(classes.noDataWrapper)}>暫無資料</div>
}
>
<Column
title="專案名稱"
dataIndex="id"
cell={(value) => {
const parts = value.split(":");
return parts.length > 1 ? parts[parts.length - 3] : "";
}}
/>
<Column
title="schema"
dataIndex="id"
cell={(value) => {
const parts = value.split(":");
return parts.length > 1 ? parts[parts.length - 2] : "";
}}
/>
<Column title="表名稱" dataIndex="name" />
<Column title="表注釋" dataIndex="comment" />
<Column title="表類型" dataIndex="tableType" />
<Column
title="是否分區表"
dataIndex="partitionKeys"
cell={(value) => {
return value != null && value.length > 0 ? "是" : "否";
}}
/>
<Column
title="建立時間"
dataIndex="createTime"
cell={(value) => {
return moment(value).format("YYYY-MM-DD HH:mm:ss");
}}
/>
<Column
title="修改時間"
dataIndex="modifyTime"
cell={(value) => {
return moment(value).format("YYYY-MM-DD HH:mm:ss");
}}
/>
<Column
title="操作"
width={150}
cell={(value, index, record) => (
<div className={cn(classes.buttonGroup)}>
<Button
type="primary"
onClick={() => onViewDetail(record)}
text
>
查看詳情
</Button>
<Button
type="primary"
onClick={() => onViewLineage(record)}
text
>
查看錶血緣
</Button>
</div>
)}
/>
</Table>
<Pagination
current={current}
total={total}
onChange={onSubmit}
showJump={false}
className={cn(classes.tablePaginationWrapper)}
/>
</div>
</Content>
</Card>
</div>
);
};
export default App;完成上述代碼開發後,您可在通用操作:本地部署運行工程代碼。您可以輸入MaxCompute專案空間ID及其他參數進行查詢,獲得專案空間下的所有表的列表,支援分頁查詢。
二、尋找表詳情
以下實踐將結合中繼資料中GetTable、ListColumns與ListPartitions三個OpenAPI來實現查詢表詳情,通過UpdateColumnBusinessMetadataAPI,更新欄位的業務說明,實踐操作流程如下。
後端:使用MetaServiceProxy查詢表詳情
在MetaServiceProxy中建立
getTable、listColumns、listPartitions方法來調用OpenAPI服務擷取表的基礎資訊、表的欄位資訊與表的分區資訊,建立updateColumnBusinessMetadata方法來更新欄位的業務說明/** * @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(); // 資料表ID getTableRequest.setId(getTableDto.getId()); // 是否返回業務中繼資料 getTableRequest.setIncludeBusinessMetadata(getTableDto.getIncludeBusinessMetadata()); GetTableResponse response = client.getTable(getTableRequest); // 對應的資料表 Table table = response.getBody().getTable(); // 表ID System.out.println(table.getId()); // 表的父層級實體ID System.out.println(table.getParentMetaEntityId()); // 表名稱 System.out.println(table.getName()); // 表注釋 System.out.println(table.getComment()); // 表所屬資料庫 / maxcompute專案名稱 System.out.println(table.getId().split(":")[3]); // 表所屬schema名稱 System.out.println(table.getId().split(":")[4]); // 表類型 System.out.println(table.getTableType()); // 表建立時間 System.out.println(table.getCreateTime()); // 表修改時間 System.out.println(table.getModifyTime()); // 是否分區表 System.out.println(table.getPartitionKeys() != null && !table.getPartitionKeys().isEmpty()); // 表的技術中繼資料 TableTechnicalMetadata technicalMetadata = table.getTechnicalMetadata(); // 表的負責人(名稱) System.out.println(technicalMetadata.getOwner()); // 是否壓縮表 System.out.println(technicalMetadata.getCompressed()); // 輸入格式(HMS, DLF等類型支援) System.out.println(technicalMetadata.getInputFormat()); // 輸出格式(HMS, DLF等類型支援) System.out.println(technicalMetadata.getOutputFormat()); // 表序列化使用的類(HMS, DLF等類型支援) System.out.println(technicalMetadata.getSerializationLibrary()); // 表格儲存體位置(HMS, DLF等類型支援) System.out.println(technicalMetadata.getLocation()); // 其他參數 Map<String, String> parameters = technicalMetadata.getParameters(); // 上次DDL表更時間(毫秒級時間戳記),僅MaxCompute類型支援 System.out.println(parameters.get("lastDDLTime")); // 生命週期(單位:天),僅MaxCompute類型支援 System.out.println(parameters.get("lifecycle")); // 儲存量(單位:位元組),非即時,僅MaxCompute類型支援 System.out.println(parameters.get("dataSize")); // 表的業務中繼資料 TableBusinessMetadata businessMetadata = table.getBusinessMetadata(); if (businessMetadata != null) { // 使用說明 System.out.println(businessMetadata.getReadme()); // 自訂標籤資訊 List<TableBusinessMetadataTags> tags = businessMetadata.getTags(); if (tags != null && !tags.isEmpty()) { for (TableBusinessMetadataTags tag : tags) { System.out.println(tag.getKey() + ":" + tag.getValue()); } } // 上遊產出任務 List<TableBusinessMetadataUpstreamTasks> upstreamTasks = businessMetadata.getUpstreamTasks(); if (upstreamTasks != null && !upstreamTasks.isEmpty()) { for (TableBusinessMetadataUpstreamTasks upstreamTask : upstreamTasks) { // 任務ID 與任務名稱,可通過 GetTask API 擷取任務詳情資訊 System.out.println(upstreamTask.getId() + ":" + upstreamTask.getName()); } } // 所屬類目資訊 List<List<TableBusinessMetadataCategories>> categories = businessMetadata.getCategories(); if (categories != null && !categories.isEmpty()) { // 多級類目的遍曆 for (List<TableBusinessMetadataCategories> category : categories) { // 輸出單個多級類目 System.out.println( category.stream() .map(TableBusinessMetadataCategories::getName) .collect(Collectors.joining("->")) ); } } // 擴充資訊,僅MaxCompute類型支援 TableBusinessMetadataExtension extension = businessMetadata.getExtension(); if (extension != null) { // 所屬專案空間ID System.out.println(extension.getProjectId()); // 所屬環境類型,Prod 生產 / Dev 開發 System.out.println(extension.getEnvType()); // 收藏次數 System.out.println(extension.getFavorCount()); // 讀取次數 System.out.println(extension.getReadCount()); // 瀏覽次數 System.out.println(extension.getViewCount()); } } return table; } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } /** * DataWorks OpenAPI : ListColumns * * @param listColumnsDto */ public ListColumnsResponseBodyPagingInfo listColumns(ListColumnsDto listColumnsDto) { try { Client client = dataWorksOpenApiClient.createClient(); ListColumnsRequest listColumnsRequest = new ListColumnsRequest(); // 資料表ID,可通過 ListTables 介面擷取 listColumnsRequest.setTableId(listColumnsDto.getTableId()); // 欄位名稱,支援模糊比對 listColumnsRequest.setName(listColumnsDto.getName()); // 欄位注釋,支援分詞匹配 listColumnsRequest.setComment(listColumnsDto.getComment()); // 排序方式,支援 Name / Position (預設) listColumnsRequest.setSortBy(listColumnsDto.getSortBy()); // 排序方向,支援 Asc (預設) / Desc listColumnsRequest.setOrder(listColumnsDto.getOrder()); // 分頁頁碼,預設為1 listColumnsRequest.setPageNumber(listColumnsDto.getPageNumber()); // 分頁大小,預設為10,最大100 listColumnsRequest.setPageSize(listColumnsDto.getPageSize()); ListColumnsResponse response = client.listColumns(listColumnsRequest); // 擷取符合要求的欄位總數 System.out.println(response.getBody().getRequestId()); System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Column column : response.getBody().getPagingInfo().getColumns()) { // 欄位ID System.out.println(column.getId()); // 欄位名稱 System.out.println(column.getName()); // 欄位注釋 System.out.println(column.getComment()); // 欄位類型 System.out.println(column.getType()); // 欄位位置 System.out.println(column.getPosition()); // 是否分區欄位 System.out.println(column.getPartitionKey()); // 是否主鍵,僅MaxCompute類型支援 System.out.println(column.getPrimaryKey()); // 是否外鍵,僅MaxCompute類型支援 System.out.println(column.getForeignKey()); // 欄位業務說明,目前MaxCompute, HMS(EMR叢集), DLF類型支援 if (column.getBusinessMetadata() != null) { System.out.println(column.getBusinessMetadata().getDescription()); } } return response.getBody().getPagingInfo(); } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } /** * DataWorks OpenAPI : UpdateColumnBusinessMetadata * * @param updateColumnBusinessMetadataDto */ public boolean updateColumnBusinessMetadata(UpdateColumnBusinessMetadataDto updateColumnBusinessMetadataDto) { try { // 當前支援MaxCompute, DLF, HMS(EMR叢集)類型 Client client = dataWorksOpenApiClient.createClient(); UpdateColumnBusinessMetadataRequest updateColumnBusinessMetadataRequest = new UpdateColumnBusinessMetadataRequest(); // 欄位ID updateColumnBusinessMetadataRequest.setId(updateColumnBusinessMetadataDto.getId()); // 欄位業務說明 updateColumnBusinessMetadataRequest.setDescription(updateColumnBusinessMetadataDto.getDescription()); UpdateColumnBusinessMetadataResponse response = client.updateColumnBusinessMetadata(updateColumnBusinessMetadataRequest); System.out.println(response.getBody().getRequestId()); // 是否更新成功 return response.getBody().getSuccess(); } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return false; } /** * DataWorks OpenAPI : ListPartitions * * @param listPartitionsDto */ public ListPartitionsResponseBodyPagingInfo listPartitions(ListPartitionsDto listPartitionsDto) { try { // 當前支援 MaxCompute類型和HMS(EMR叢集)類型 Client client = dataWorksOpenApiClient.createClient(); ListPartitionsRequest listPartitionsRequest = new ListPartitionsRequest(); // 資料表ID,可通過 ListTables 介面擷取 listPartitionsRequest.setTableId(listPartitionsDto.getTableId()); // 分區名稱,支援模糊比對 listPartitionsRequest.setName(listPartitionsDto.getName()); // 排序方式,支援 Name(HMS方式預設,Maxcompute類型支援) / CreateTime (MaxCompute類型預設) / ModifyTime (MaxCompute類型支援)/ RecordCount(MaxCompute類型支援)/ DataSize(MaxCompute類型支援) listPartitionsRequest.setSortBy(listPartitionsDto.getSortBy()); // 排序方向,支援 Asc (預設) / Desc listPartitionsRequest.setOrder(listPartitionsDto.getOrder()); // 分頁頁碼,預設為1 listPartitionsRequest.setPageNumber(listPartitionsDto.getPageNumber()); // 分頁大小,預設為10,最大100 listPartitionsRequest.setPageSize(listPartitionsDto.getPageSize()); ListPartitionsResponse response = client.listPartitions(listPartitionsRequest); // 擷取符合要求的分區總數 System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Partition partition : response.getBody().getPagingInfo().getPartitionList()) { // 所屬資料表ID System.out.println(partition.getTableId()); // 分區名稱 System.out.println(partition.getName()); // 建立時間(毫秒級時間戳記) System.out.println(partition.getCreateTime()); // 修改時間(毫秒級時間戳記) System.out.println(partition.getModifyTime()); // 分區記錄數,僅MaxCompute類型支援 System.out.println(partition.getRecordCount()); // 分區儲存量(單位:位元組),僅MaxCompute類型支援 System.out.println(partition.getDataSize()); } return response.getBody().getPagingInfo(); } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }在MetaRestController中提供四個方法分別為
getTable、listColumns、listPartitions與updateColumnBusinessMetadata供前端進行調用。/** * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台 * * @author dataworks demo */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * 擷取表詳情 * * @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); } /** * 分頁查詢欄位資料 * * @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); } /** * 更新欄位業務說明 * * @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); } /** * 分頁查詢分區資料 * * @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); } }
前端:展示表基本資料、表欄位資訊以及表分區的資訊。
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> = (props) => {
let columnsPageSize = 10;
let partitionsPageSize = 5;
const listColumnsField = Field.useField();
const listPartitionsField = Field.useField();
const labelAlign = "top";
const [detail, setDetail] = React.useState<Partial<TableEntity>>({});
const [columns, setColumns] = React.useState<Partial<TableColumn[]>>([]);
const [partitions, setPartitions] = React.useState<Partial<TablePartition[]>>(
[]
);
const [columnsTotal, setColumnsTotal] = React.useState<number>(0);
const [partitionTotal, setPartitionTotal] = React.useState<number>(0);
const [editingKey, setEditingKey] = React.useState<string>("");
const [editingDescription, setEditingDescription] =
React.useState<string>("");
const [updateColumnDialogVisible, setUpdateColumnDialogVisible] =
React.useState<boolean>(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("請求更新失敗");
}
} catch (e) {
console.error("更新失敗", 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 (
<div className={cn(classes.detailContentWrapper)}>
<Dialog
v2
title="更新欄位業務中繼資料"
visible={updateColumnDialogVisible}
onOk={onUpdateColumnDialogOk}
onClose={onUpdateColumnDialogClose}
>
<Row>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="欄位業務說明"
>
<Input
value={editingDescription}
onChange={(e) => handleColumnDesrciptionChange(e.toString())}
></Input>
</Item>
</Col>
</Row>
</Dialog>
<Form labelTextAlign="left">
<Row>
<Col>
<Item {...formItemLayout} label="表ID">
<span className={cn(classes.formContentWrapper)}>
{detail.id}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="MaxCompute專案">
<span className={cn(classes.formContentWrapper)}>
{detail?.id?.split(":").slice(-3, -2)[0]}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="schema名稱">
<span className={cn(classes.formContentWrapper)}>
{detail?.id?.split(":").slice(-2, -1)[0]}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="表名稱">
<span className={cn(classes.formContentWrapper)}>
{detail.name}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="注釋">
<span className={cn(classes.formContentWrapper)}>
{detail.comment}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="表類型">
<span className={cn(classes.formContentWrapper)}>
{detail.tableType}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="是否壓縮表">
<span className={cn(classes.formContentWrapper)}>
{detail.technicalMetadata?.compressed ? "是" : "否"}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="所屬工作空間ID">
<span className={cn(classes.formContentWrapper)}>
{detail.businessMetadata?.extension?.projectId}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="環境">
<span className={cn(classes.formContentWrapper)}>
{detail.businessMetadata?.extension?.envType === "Dev"
? "開發"
: "生產"}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="表生命週期">
<span className={cn(classes.formContentWrapper)}>
{detail.technicalMetadata?.parameters["lifecycle"]
? `${detail.technicalMetadata?.parameters["lifecycle"]} 天`
: "永久"}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="儲存空間">
<span
className={cn(classes.formContentWrapper)}
>{`${detail.technicalMetadata?.parameters["dataSize"]} Bytes`}</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="DDL上次變更時間">
<span className={cn(classes.formContentWrapper)}>
{moment(
Number(detail.technicalMetadata?.parameters["lastDDLTime"])
).format("YYYY-MM-DD HH:mm:ss")}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="是否為分區表">
<span className={cn(classes.formContentWrapper)}>
{detail.partitionKeys != null && detail.partitionKeys.length > 0
? "是"
: "否"}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="標籤">
<div className={cn(classes.formContentWrapper)}>
{detail.businessMetadata?.tags?.map((tag, index) => (
<span key={index} className={cn(classes.tag)}>
<Tag type="primary" size="small">
{tag.key + (tag.value ? ":" + tag.value : "")}
</Tag>
</span>
))}
</div>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="類目">
<span className={cn(classes.formContentWrapper)}>
{detail.businessMetadata?.categories?.map((category, index) => (
<span key={index} className={cn(classes.tag)}>
<Tag type="normal" color="gray" size="small">
{category.map((obj) => obj.name).join("->")}
</Tag>
</span>
))}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="上遊產出任務">
<span className={cn(classes.formContentWrapper)}>
{detail.businessMetadata?.upstreamTasks?.map((task, index) => (
<span key={index} className={cn(classes.tag)}>
<Tag type="primary" size="small">
{task.name + " (" + task.id + ")"}
</Tag>
</span>
))}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="瀏覽次數">
<span className={cn(classes.formContentWrapper)}>
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.viewCount}
</Tag>
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="讀取次數">
<span className={cn(classes.formContentWrapper)}>
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.readCount}
</Tag>
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="收藏次數">
<span className={cn(classes.formContentWrapper)}>
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.favorCount}
</Tag>
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="建立時間">
<span className={cn(classes.formContentWrapper)}>
{moment(detail.createTime).format("YYYY-MM-DD HH:mm:ss")}
</span>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="修改時間">
<span className={cn(classes.formContentWrapper)}>
{moment(detail.modifyTime).format("YYYY-MM-DD HH:mm:ss")}
</span>
</Item>
</Col>
</Row>
<br />
<Item label="欄位詳情" colon>
<Form
field={listColumnsField}
colon
fullWidth
style={{ marginTop: 10, marginBottom: 10 }}
>
<Row gutter="4">
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="名稱"
name="name"
>
<Input placeholder="輸入欄位名稱,模糊比對" />
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="注釋"
name="comment"
>
<Input placeholder="輸入欄位注釋,模糊比對" />
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="排序方式"
name="sortBy"
>
<Select defaultValue="Position" style={{ width: "100%" }}>
<Option value="Position">位置</Option>
<Option value="Name">名稱</Option>
</Select>
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="排序方向"
name="order"
>
<Select defaultValue="Asc" style={{ width: "100%" }}>
<Option value="Asc">升序</Option>
<Option value="Desc">降序</Option>
</Select>
</Item>
</Col>
<Col>
<div
className={cn(
classes.searchPanelButtonWrapper,
classes.buttonGroup
)}
style={{ marginTop: "20px", textAlign: "right" }}
>
<Button type="primary" onClick={() => listColumns()}>
查詢
</Button>
</div>
</Col>
</Row>
</Form>
<Table dataSource={columns}>
<Column title="名稱" dataIndex="name" />
<Column title="類型" dataIndex="type" />
<Column title="注釋" dataIndex="comment" />
<Column
title="業務說明"
dataIndex="businessMetadata"
cell={(value, index, record) => (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span>{value?.description}</span>
<Button
onClick={() => handleEditColumnDescription(record)}
size="small"
type="primary"
text
style={{ marginLeft: 8 }}
>
編輯
</Button>
</div>
)}
/>
<Column
title="是否為主鍵"
dataIndex="primaryKey"
cell={(value) => (value ? "是" : "")}
/>
<Column
title="是否為外鍵"
dataIndex="foreignKey"
cell={(value) => (value ? "是" : "")}
/>
<Column
title="是否為分區欄位"
dataIndex="partitionKey"
cell={(value) => (value ? "是" : "")}
/>
<Column title="位置" dataIndex="position" />
</Table>
<Pagination
total={columnsTotal}
pageSize={columnsPageSize}
onChange={listColumns}
showJump={false}
className={cn(classes.tablePaginationWrapper)}
/>
</Item>
<br />
<Item label="分區詳情" style={{ marginBottom: 32 }} colon>
<Form
field={listPartitionsField}
colon
fullWidth
style={{ marginTop: 10, marginBottom: 10 }}
>
<Row gutter="4">
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="名稱"
name="name"
>
<Input placeholder="輸入分區名稱,模糊比對" />
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="排序方式"
name="sortBy"
>
<Select defaultValue="CreateTime" style={{ width: "100%" }}>
<Option value="CreateTime">建立時間</Option>
<Option value="ModifyTime">修改時間</Option>
<Option value="Name">分區名稱</Option>
<Option value="RecordCount">分區記錄數</Option>
<Option value="DataSize">分區大小</Option>
</Select>
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="排序方向"
name="order"
>
<Select defaultValue="Asc" style={{ width: "100%" }}>
<Option value="Asc">升序</Option>
<Option value="Desc">降序</Option>
</Select>
</Item>
</Col>
<Col>
<div
className={cn(
classes.searchPanelButtonWrapper,
classes.buttonGroup
)}
style={{ marginTop: "20px", textAlign: "right" }}
>
<Button type="primary" onClick={() => listPartitions()}>
查詢
</Button>
</div>
</Col>
</Row>
</Form>
<Table dataSource={partitions} emptyContent={<span>非分區表</span>}>
<Column title="名稱" dataIndex="name" />
<Column
title="建立時間"
dataIndex="createTime"
cell={(value) => moment(value).format("YYYY-MM-DD HH:mm:ss")}
/>
<Column
title="修改時間"
dataIndex="modifyTime"
cell={(value) => moment(value).format("YYYY-MM-DD HH:mm:ss")}
/>
<Column
title="分區大小"
dataIndex="dataSize"
cell={(value) => `${value} bytes`}
/>
<Column title="分區記錄數" dataIndex="recordCount" />
</Table>
<Pagination
total={partitionTotal}
pageSize={partitionsPageSize}
onChange={listPartitions}
showJump={false}
className={cn(classes.tablePaginationWrapper)}
/>
</Item>
</Form>
</div>
);
};
export default DetailContent;
完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行。
三、尋找表血緣
當一張表口徑發生變更時,您可以通過DataWorks OpenAPI進行上下遊任務的血緣分析,尋找表對應的節點。
使用ListLineages來查詢資料表的上下遊血緣。
後端:使用MetaServiceProxy查詢表血緣
在MetaServiceProxy中建立listTableLineages方法來調用OpenAPI服務擷取表的上下遊血緣實體和關係資訊。
/** * @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(); // 血緣查詢方向,是否為上遊 boolean needUpstream = "up".equalsIgnoreCase(listTableLineagesDto.getDirection()); // 資料表ID,可通過 ListTables 介面擷取 // 分區名稱,支援模糊比對 if (needUpstream) { listLineagesRequest.setDstEntityId(listTableLineagesDto.getTableId()); listLineagesRequest.setSrcEntityName(listTableLineagesDto.getName()); } else { listLineagesRequest.setSrcEntityId(listTableLineagesDto.getTableId()); listLineagesRequest.setDstEntityName(listTableLineagesDto.getName()); } // 排序方式,支援 Name listLineagesRequest.setSortBy(listTableLineagesDto.getSortBy()); // 排序方向,支援 Asc (預設) / Desc listLineagesRequest.setOrder(listTableLineagesDto.getOrder()); // 分頁頁碼,預設為1 listLineagesRequest.setPageNumber(listTableLineagesDto.getPageNumber()); // 分頁大小,預設為10,最大100 listLineagesRequest.setPageSize(listTableLineagesDto.getPageSize()); // 是否需要返回具體的血緣關係資訊 listLineagesRequest.setNeedAttachRelationship(true); ListLineagesResponse response = client.listLineages(listLineagesRequest); // 擷取符合要求的血緣實體總數 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; // 擷取對應血緣實體 if (needUpstream) { entity = lineage.getSrcEntity(); } else { entity = lineage.getDstEntity(); } // 所屬實體ID System.out.println(entity.getId()); // 所屬實體名稱 System.out.println(entity.getName()); // 所屬實體的屬性資訊 System.out.println(entity.getAttributes()); // 實體間血緣關係列表 List<LineageRelationship> relationships = lineage.getRelationships(); if (relationships != null) { relationships.forEach(relationship -> { // 血緣關係ID System.out.println(relationship.getId()); // 建立時間 System.out.println(relationship.getCreateTime()); // 血緣關係對應的任務 LineageTask task = relationship.getTask(); if (task != null) { // 任務ID,對於DataWorks任務,可以通過GetTask擷取任務詳情資訊 System.out.println(task.getId()); // 任務類型 System.out.println(task.getType()); // 任務屬性資訊 Map<String, String> attributes = task.getAttributes(); if (attributes != null) { // 對於DataWorks任務,可以從中擷取任務和執行個體的屬性資訊 // 專案空間ID System.out.println(attributes.get("projectId")); // 任務ID String taskId = attributes.containsKey("taskId") ? attributes.get("taskId") : attributes.get("nodeId"); System.out.println(taskId); // 執行個體ID String taskInstanceId = attributes.containsKey("taskInstanceId") ? attributes.get("taskInstanceId") : attributes.get("jobId"); System.out.println(taskInstanceId); } } }); } } return response.getBody().getPagingInfo(); } } catch (TeaException error) { // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此處僅做列印展示,請謹慎對待異常處理,在工程專案中切勿直接忽略異常。 // 錯誤 message System.out.println(error.getMessage()); // 診斷地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }在MetaRestController中提供listTableLineages方法供前端進行調用。
/** * 示範如何通過DataWorks OpenAPI構建自訂中繼資料平台 * * @author dataworks demo */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * 查詢表的血緣關係 * * @param listTableLineagesDto * @return */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listTableLineages") public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) { System.out.println("listTableLineagesDto = " + listTableLineagesDto); return metaService.listTableLineages(listTableLineagesDto); } }
前端:展示表血緣關係
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> = (props) => {
const ref = React.useRef<Graphin>();
const [data, setData] = React.useState<GraphinData>({ 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 (
<div className={cn(classes.lineageContentWrapper)}>
<Graphin
data={data}
ref={ref as React.LegacyRef<Graphin>}
layout={{
type: 'dagre',
rankdir: 'LR',
align: 'DL',
nodesep: 10,
ranksep: 40,
}}
>
<ActivateRelations />
<DragNode/>
<ZoomCanvas enableOptimize />
</Graphin>
</div>
);
};
export default LineageContent;
完成上述代碼開發後,您可在本地部署並運行工程代碼。部署並啟動並執行操作請參見通用操作:本地部署運行。
四、尋找表對應的任務
當一張表口徑發生變更時,您可以通過DataWorks OpenAPI、OpenData、訊息訂閱的方式進行下遊任務的血緣分析,尋找表對應的節點。具體操作如下。
訊息目前支援表變更、任務變更等。企業版使用者可以對接表變更的訊息,當接收到表變更的時候,您可以查看錶的血緣關係。
血緣任務
通過ListLineages,查看錶的血緣關係,從血緣關係的Task 中擷取DataWorks 任務ID (以及任務執行個體ID)。
根據擷取的任務ID,使用GetTask擷取任務詳情。
產出任務
通用操作:本地部署運行
準備依賴環境。
您需準備好以下依賴環境:java8及以上、maven構建工具、node環境、pnpm工具。您可以執行以下命令來確定是否具備上述環境:
npm -v // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤 java -version // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤 pnpm -v // 如果已安裝成功,執行命令將展示版本號碼,否則會報沒有命令錯誤下載工程代碼並執行以下命令。
工程代碼下載連結:meta-api-demo.zip。
pnpm i在範例工程中的backend/src/main/resources路徑下找到application.properties檔案,修改檔案中的核心參數。
api.access-key-id與api.access-key-secret需修改為您阿里雲帳號的AccessKey ID 和 AccessKey Secret。
說明您可以參考存取金鑰管理(AccessKey)擷取AccessKey等阿里雲帳號的相關資訊。
api.region-id、api.endpoint需修改為待調用OpenAPI的地區資訊。
其他參數可根據實際情況修改,修改後的填寫樣本如下。

在工程根目錄下執行以下命令,運行樣本實踐代碼。
npm run dev