mirror of
https://github.com/tencentmusic/supersonic.git
synced 2026-04-19 21:14:25 +08:00
first commit
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import type { HookHub, ICmdHooks as IHooks, NsGraph } from '@antv/xflow';
|
||||
import { Deferred, ManaSyringe } from '@antv/xflow';
|
||||
import { Modal, ConfigProvider } from 'antd';
|
||||
import type { IArgsBase, ICommandHandler } from '@antv/xflow';
|
||||
import { ICommandContextProvider } from '@antv/xflow';
|
||||
import { DATASOURCE_NODE_RENDER_ID } from '../constant';
|
||||
import { CustomCommands } from './constants';
|
||||
|
||||
import 'antd/es/modal/style/index.css';
|
||||
|
||||
export namespace NsConfirmModalCmd {
|
||||
/** Command: 用于注册named factory */
|
||||
// eslint-disable-next-line
|
||||
export const command = CustomCommands.SHOW_CONFIRM_MODAL;
|
||||
/** hook name */
|
||||
// eslint-disable-next-line
|
||||
export const hookKey = 'confirmModal';
|
||||
/** hook 参数类型 */
|
||||
export interface IArgs extends IArgsBase {
|
||||
nodeConfig: NsGraph.INodeConfig;
|
||||
confirmModalCallBack: IConfirmModalService;
|
||||
}
|
||||
export interface IConfirmModalService {
|
||||
(): Promise<any>;
|
||||
}
|
||||
/** hook handler 返回类型 */
|
||||
export type IResult = any;
|
||||
/** hooks 类型 */
|
||||
export interface ICmdHooks extends IHooks {
|
||||
confirmModal: HookHub<IArgs, IResult>;
|
||||
}
|
||||
}
|
||||
|
||||
const deleteDataSourceConfirmNode = (name: string) => {
|
||||
return (
|
||||
<>
|
||||
数据源<span style={{ color: '#296DF3', fontWeight: 'bold' }}>{name}</span>
|
||||
将被删除,是否确认?
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
type ICommand = ICommandHandler<NsConfirmModalCmd.IArgs, NsConfirmModalCmd.IResult, NsConfirmModalCmd.ICmdHooks>;
|
||||
|
||||
@ManaSyringe.injectable()
|
||||
/** 部署画布数据 */
|
||||
export class ConfirmModalCommand implements ICommand {
|
||||
/** api */
|
||||
@ManaSyringe.inject(ICommandContextProvider) contextProvider!: ICommand['contextProvider'];
|
||||
|
||||
/** 执行Cmd */
|
||||
execute = async () => {
|
||||
const ctx = this.contextProvider();
|
||||
const { args } = ctx.getArgs();
|
||||
const hooks = ctx.getHooks();
|
||||
await hooks.confirmModal.call(args, async (confirmArgs: NsConfirmModalCmd.IArgs) => {
|
||||
const { nodeConfig, confirmModalCallBack } = confirmArgs;
|
||||
const { renderKey, label } = nodeConfig;
|
||||
if (!nodeConfig.modalProps?.modalContent) {
|
||||
let modalContent = <></>;
|
||||
if (renderKey === DATASOURCE_NODE_RENDER_ID) {
|
||||
modalContent = deleteDataSourceConfirmNode(label!);
|
||||
}
|
||||
nodeConfig.modalProps = {
|
||||
...(nodeConfig.modalProps || {}),
|
||||
modalContent,
|
||||
};
|
||||
}
|
||||
const getAppContext: IGetAppCtx = () => {
|
||||
return {
|
||||
confirmModalCallBack,
|
||||
};
|
||||
};
|
||||
|
||||
const x6Graph = await ctx.getX6Graph();
|
||||
const cell = x6Graph.getCellById(nodeConfig.id);
|
||||
|
||||
if (!cell || !cell.isNode()) {
|
||||
throw new Error(`${nodeConfig.id} is not a valid node`);
|
||||
}
|
||||
/** 通过modal 获取 new name */
|
||||
await showModal(nodeConfig, getAppContext);
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
// ctx.setResult(result);
|
||||
return this;
|
||||
};
|
||||
|
||||
/** undo cmd */
|
||||
undo = async () => {
|
||||
if (this.isUndoable()) {
|
||||
const ctx = this.contextProvider();
|
||||
ctx.undo();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/** redo cmd */
|
||||
redo = async () => {
|
||||
if (!this.isUndoable()) {
|
||||
await this.execute();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
isUndoable(): boolean {
|
||||
const ctx = this.contextProvider();
|
||||
return ctx.isUndoable();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IGetAppCtx {
|
||||
(): {
|
||||
confirmModalCallBack: NsConfirmModalCmd.IConfirmModalService;
|
||||
};
|
||||
}
|
||||
|
||||
export type IModalInstance = ReturnType<typeof Modal.confirm>;
|
||||
|
||||
function showModal(node: NsGraph.INodeConfig, getAppContext: IGetAppCtx) {
|
||||
/** showModal 返回一个Promise */
|
||||
const defer = new Deferred<string | void>();
|
||||
const modalTitle = node.modalProps?.title;
|
||||
const modalContent = node.modalProps?.modalContent;
|
||||
/** modal确认保存逻辑 */
|
||||
class ModalCache {
|
||||
static modal: IModalInstance;
|
||||
}
|
||||
|
||||
/** modal确认保存逻辑 */
|
||||
const onOk = async () => {
|
||||
const { modal } = ModalCache;
|
||||
const appContext = getAppContext();
|
||||
const { confirmModalCallBack } = appContext;
|
||||
try {
|
||||
modal.update({ okButtonProps: { loading: true } });
|
||||
|
||||
/** 执行 confirm回调*/
|
||||
if (confirmModalCallBack) {
|
||||
await confirmModalCallBack();
|
||||
}
|
||||
/** 更新成功后,关闭modal */
|
||||
onHide();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
/** 如果resolve空字符串则不更新 */
|
||||
modal.update({ okButtonProps: { loading: false } });
|
||||
}
|
||||
};
|
||||
|
||||
/** modal销毁逻辑 */
|
||||
const onHide = () => {
|
||||
modal.destroy();
|
||||
ModalCache.modal = null as any;
|
||||
container.destroy();
|
||||
};
|
||||
|
||||
/** modal内容 */
|
||||
const ModalContent = () => {
|
||||
return (
|
||||
<div>
|
||||
<ConfigProvider>{modalContent}</ConfigProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/** 创建modal dom容器 */
|
||||
const container = createContainer();
|
||||
/** 创建modal */
|
||||
const modal = Modal.confirm({
|
||||
title: modalTitle,
|
||||
content: <ModalContent />,
|
||||
getContainer: () => {
|
||||
return container.element;
|
||||
},
|
||||
okButtonProps: {
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
onOk();
|
||||
},
|
||||
},
|
||||
onCancel: () => {
|
||||
onHide();
|
||||
},
|
||||
afterClose: () => {
|
||||
onHide();
|
||||
},
|
||||
});
|
||||
|
||||
/** 缓存modal实例 */
|
||||
ModalCache.modal = modal;
|
||||
|
||||
/** showModal 返回一个Promise,用于await */
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
const createContainer = () => {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('xflow-modal-container');
|
||||
window.document.body.append(div);
|
||||
return {
|
||||
element: div,
|
||||
destroy: () => {
|
||||
window.document.body.removeChild(div);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import type {
|
||||
NsGraphCmd,
|
||||
ICmdHooks as IHooks,
|
||||
NsGraph,
|
||||
IArgsBase,
|
||||
ICommandHandler,
|
||||
HookHub,
|
||||
} from '@antv/xflow';
|
||||
import { XFlowGraphCommands, ManaSyringe } from '@antv/xflow';
|
||||
import { ICommandContextProvider } from '@antv/xflow';
|
||||
import { CustomCommands } from './constants';
|
||||
|
||||
// prettier-ignore
|
||||
type ICommand = ICommandHandler<NsDeployDagCmd.IArgs, NsDeployDagCmd.IResult, NsDeployDagCmd.ICmdHooks>;
|
||||
|
||||
export namespace NsDeployDagCmd {
|
||||
/** Command: 用于注册named factory */
|
||||
// eslint-disable-next-line
|
||||
export const command = CustomCommands.DEPLOY_SERVICE;
|
||||
/** hook name */
|
||||
// eslint-disable-next-line
|
||||
export const hookKey = 'deployDag';
|
||||
/** hook 参数类型 */
|
||||
export interface IArgs extends IArgsBase {
|
||||
deployDagService: IDeployDagService;
|
||||
}
|
||||
export interface IDeployDagService {
|
||||
(meta: NsGraph.IGraphMeta, data: NsGraph.IGraphData): Promise<{ success: boolean }>;
|
||||
}
|
||||
/** hook handler 返回类型 */
|
||||
export interface IResult {
|
||||
success: boolean;
|
||||
}
|
||||
/** hooks 类型 */
|
||||
export interface ICmdHooks extends IHooks {
|
||||
deployDag: HookHub<IArgs, IResult>;
|
||||
}
|
||||
}
|
||||
|
||||
@ManaSyringe.injectable()
|
||||
/** 部署画布数据 */
|
||||
export class DeployDagCommand implements ICommand {
|
||||
/** api */
|
||||
@ManaSyringe.inject(ICommandContextProvider) contextProvider!: ICommand['contextProvider'];
|
||||
|
||||
/** 执行Cmd */
|
||||
execute = async () => {
|
||||
const ctx = this.contextProvider();
|
||||
const { args } = ctx.getArgs();
|
||||
const hooks = ctx.getHooks();
|
||||
const result = await hooks.deployDag.call(args, async (handlerArgs) => {
|
||||
const { commandService, deployDagService } = handlerArgs;
|
||||
/** 执行Command */
|
||||
await commandService!.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
|
||||
XFlowGraphCommands.SAVE_GRAPH_DATA.id,
|
||||
{
|
||||
saveGraphDataService: async (meta, graph) => {
|
||||
await deployDagService(meta, graph);
|
||||
},
|
||||
},
|
||||
);
|
||||
return { success: true };
|
||||
});
|
||||
ctx.setResult(result);
|
||||
return this;
|
||||
};
|
||||
|
||||
/** undo cmd */
|
||||
undo = async () => {
|
||||
if (this.isUndoable()) {
|
||||
const ctx = this.contextProvider();
|
||||
ctx.undo();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/** redo cmd */
|
||||
redo = async () => {
|
||||
if (!this.isUndoable()) {
|
||||
await this.execute();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
isUndoable(): boolean {
|
||||
const ctx = this.contextProvider();
|
||||
return ctx.isUndoable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
import type { HookHub, ICmdHooks as IHooks, NsGraph, IModelService } from '@antv/xflow';
|
||||
import { Deferred, ManaSyringe } from '@antv/xflow';
|
||||
import type { FormInstance } from 'antd';
|
||||
import { Modal, Form, Input, ConfigProvider } from 'antd';
|
||||
|
||||
import type { IArgsBase, ICommandHandler, IGraphCommandService } from '@antv/xflow';
|
||||
import { ICommandContextProvider } from '@antv/xflow';
|
||||
|
||||
import { CustomCommands } from './constants';
|
||||
|
||||
import 'antd/es/modal/style/index.css';
|
||||
|
||||
// prettier-ignore
|
||||
type ICommand = ICommandHandler<NsRenameNodeCmd.IArgs, NsRenameNodeCmd.IResult, NsRenameNodeCmd.ICmdHooks>;
|
||||
|
||||
export namespace NsRenameNodeCmd {
|
||||
/** Command: 用于注册named factory */
|
||||
// eslint-disable-next-line
|
||||
export const command = CustomCommands.SHOW_RENAME_MODAL;
|
||||
/** hook name */
|
||||
// eslint-disable-next-line
|
||||
export const hookKey = 'renameNode';
|
||||
/** hook 参数类型 */
|
||||
export interface IArgs extends IArgsBase {
|
||||
nodeConfig: NsGraph.INodeConfig;
|
||||
updateNodeNameService: IUpdateNodeNameService;
|
||||
}
|
||||
export interface IUpdateNodeNameService {
|
||||
(newName: string, nodeConfig: NsGraph.INodeConfig, meta: NsGraph.IGraphMeta): Promise<{
|
||||
err: string | null;
|
||||
nodeName: string;
|
||||
}>;
|
||||
}
|
||||
/** hook handler 返回类型 */
|
||||
export interface IResult {
|
||||
err: string | null;
|
||||
preNodeName?: string;
|
||||
currentNodeName?: string;
|
||||
}
|
||||
/** hooks 类型 */
|
||||
export interface ICmdHooks extends IHooks {
|
||||
renameNode: HookHub<IArgs, IResult>;
|
||||
}
|
||||
}
|
||||
|
||||
@ManaSyringe.injectable()
|
||||
/** 部署画布数据 */
|
||||
// prettier-ignore
|
||||
export class RenameNodeCommand implements ICommand {
|
||||
/** api */
|
||||
@ManaSyringe.inject(ICommandContextProvider) contextProvider!: ICommand['contextProvider'];
|
||||
|
||||
/** 执行Cmd */
|
||||
execute = async () => {
|
||||
const ctx = this.contextProvider();
|
||||
// const app = useXFlowApp();
|
||||
const { args } = ctx.getArgs();
|
||||
const hooks = ctx.getHooks();
|
||||
const result = await hooks.renameNode.call(args, async (args) => {
|
||||
const { nodeConfig, graphMeta, commandService, modelService, updateNodeNameService } = args;
|
||||
const preNodeName = nodeConfig.label;
|
||||
|
||||
const getAppContext: IGetAppCtx = () => {
|
||||
return {
|
||||
graphMeta,
|
||||
commandService,
|
||||
modelService,
|
||||
updateNodeNameService,
|
||||
};
|
||||
};
|
||||
|
||||
const x6Graph = await ctx.getX6Graph();
|
||||
const cell = x6Graph.getCellById(nodeConfig.id);
|
||||
const nodes = x6Graph.getNodes();
|
||||
const edges = x6Graph.getEdges();
|
||||
nodes.forEach((node) => {
|
||||
if (node !== cell) {
|
||||
x6Graph.removeCell(node);
|
||||
}
|
||||
});
|
||||
edges.forEach((edge) => {
|
||||
x6Graph.removeEdge(edge);
|
||||
});
|
||||
if (!cell || !cell.isNode()) {
|
||||
throw new Error(`${nodeConfig.id} is not a valid node`);
|
||||
}
|
||||
/** 通过modal 获取 new name */
|
||||
const newName = await showModal(nodeConfig, getAppContext);
|
||||
/** 更新 node name */
|
||||
if (newName) {
|
||||
const cellData = cell.getData<NsGraph.INodeConfig>();
|
||||
|
||||
cell.setData({ ...cellData, label: newName } as NsGraph.INodeConfig);
|
||||
return { err: null, preNodeName, currentNodeName: newName };
|
||||
}
|
||||
return { err: null, preNodeName, currentNodeName: '' };
|
||||
});
|
||||
|
||||
ctx.setResult(result);
|
||||
return this;
|
||||
};
|
||||
|
||||
/** undo cmd */
|
||||
undo = async () => {
|
||||
if (this.isUndoable()) {
|
||||
const ctx = this.contextProvider();
|
||||
ctx.undo();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/** redo cmd */
|
||||
redo = async () => {
|
||||
if (!this.isUndoable()) {
|
||||
await this.execute();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
isUndoable(): boolean {
|
||||
const ctx = this.contextProvider();
|
||||
return ctx.isUndoable();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IGetAppCtx {
|
||||
(): {
|
||||
graphMeta: NsGraph.IGraphMeta;
|
||||
commandService: IGraphCommandService;
|
||||
modelService: IModelService;
|
||||
updateNodeNameService: NsRenameNodeCmd.IUpdateNodeNameService;
|
||||
};
|
||||
}
|
||||
|
||||
export type IModalInstance = ReturnType<typeof Modal.confirm>;
|
||||
export interface IFormProps {
|
||||
newNodeName: string;
|
||||
}
|
||||
|
||||
const layout = {
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
function showModal(node: NsGraph.INodeConfig, getAppContext: IGetAppCtx) {
|
||||
/** showModal 返回一个Promise */
|
||||
const defer = new Deferred<string | void>();
|
||||
|
||||
/** modal确认保存逻辑 */
|
||||
class ModalCache {
|
||||
static modal: IModalInstance;
|
||||
static form: FormInstance<IFormProps>;
|
||||
}
|
||||
|
||||
/** modal确认保存逻辑 */
|
||||
const onOk = async () => {
|
||||
const { form, modal } = ModalCache;
|
||||
const appContext = getAppContext();
|
||||
const { updateNodeNameService, graphMeta } = appContext;
|
||||
try {
|
||||
modal.update({ okButtonProps: { loading: true } });
|
||||
await form.validateFields();
|
||||
const values = await form.getFieldsValue();
|
||||
const newName: string = values.newNodeName;
|
||||
/** 执行 backend service */
|
||||
if (updateNodeNameService) {
|
||||
const { err, nodeName } = await updateNodeNameService(newName, node, graphMeta);
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
defer.resolve(nodeName);
|
||||
}
|
||||
/** 更新成功后,关闭modal */
|
||||
onHide();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
/** 如果resolve空字符串则不更新 */
|
||||
modal.update({ okButtonProps: { loading: false } });
|
||||
}
|
||||
};
|
||||
|
||||
/** modal销毁逻辑 */
|
||||
const onHide = () => {
|
||||
modal.destroy();
|
||||
ModalCache.form = null as any;
|
||||
ModalCache.modal = null as any;
|
||||
container.destroy();
|
||||
};
|
||||
|
||||
/** modal内容 */
|
||||
const ModalContent = () => {
|
||||
const [form] = Form.useForm<IFormProps>();
|
||||
/** 缓存form实例 */
|
||||
ModalCache.form = form;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ConfigProvider>
|
||||
<Form form={form} {...layout} initialValues={{ newNodeName: node.label }}>
|
||||
<Form.Item
|
||||
name="newNodeName"
|
||||
label="节点名"
|
||||
rules={[
|
||||
{ required: true, message: '请输入新节点名' },
|
||||
{ min: 3, message: '节点名不能少于3个字符' },
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</ConfigProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/** 创建modal dom容器 */
|
||||
const container = createContainer();
|
||||
/** 创建modal */
|
||||
const modal = Modal.confirm({
|
||||
title: '重命名',
|
||||
content: <ModalContent />,
|
||||
getContainer: () => {
|
||||
return container.element;
|
||||
},
|
||||
okButtonProps: {
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
onOk();
|
||||
},
|
||||
},
|
||||
onCancel: () => {
|
||||
onHide();
|
||||
},
|
||||
afterClose: () => {
|
||||
onHide();
|
||||
},
|
||||
});
|
||||
|
||||
/** 缓存modal实例 */
|
||||
ModalCache.modal = modal;
|
||||
|
||||
/** showModal 返回一个Promise,用于await */
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
const createContainer = () => {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('xflow-modal-container');
|
||||
window.document.body.append(div);
|
||||
return {
|
||||
element: div,
|
||||
destroy: () => {
|
||||
window.document.body.removeChild(div);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import type {
|
||||
ICmdHooks as IHooks,
|
||||
NsGraph,
|
||||
IArgsBase,
|
||||
ICommandHandler,
|
||||
HookHub,
|
||||
} from '@antv/xflow';
|
||||
import { ManaSyringe } from '@antv/xflow';
|
||||
import { ICommandContextProvider } from '@antv/xflow';
|
||||
import { CustomCommands } from './constants';
|
||||
import { getDatasourceRelaList } from '../../service';
|
||||
|
||||
// prettier-ignore
|
||||
type ICommand = ICommandHandler<NsDataSourceRelationCmd.IArgs, NsDataSourceRelationCmd.IResult, NsDataSourceRelationCmd.ICmdHooks>;
|
||||
|
||||
export namespace NsDataSourceRelationCmd {
|
||||
/** Command: 用于注册named factory */
|
||||
// eslint-disable-next-line
|
||||
export const command = CustomCommands.DATASOURCE_RELATION;
|
||||
/** hook name */
|
||||
// eslint-disable-next-line
|
||||
export const hookKey = 'dataSourceRelation';
|
||||
/** hook 参数类型 */
|
||||
export interface IArgs extends IArgsBase {
|
||||
dataSourceRelationService: IDataSourceRelationService;
|
||||
}
|
||||
export interface IDataSourceRelationService {
|
||||
(meta: NsGraph.IGraphMeta, data: NsGraph.IGraphData): Promise<{ success: boolean }>;
|
||||
}
|
||||
/** hook handler 返回类型 */
|
||||
export type IResult = any[] | undefined;
|
||||
/** hooks 类型 */
|
||||
export interface ICmdHooks extends IHooks {
|
||||
dataSourceRelation: HookHub<IArgs, IResult>;
|
||||
}
|
||||
}
|
||||
|
||||
@ManaSyringe.injectable()
|
||||
/** 部署画布数据 */
|
||||
export class DataSourceRelationCommand implements ICommand {
|
||||
/** api */
|
||||
@ManaSyringe.inject(ICommandContextProvider) contextProvider!: ICommand['contextProvider'];
|
||||
|
||||
/** 执行Cmd */
|
||||
execute = async () => {
|
||||
const ctx = this.contextProvider();
|
||||
const { args } = ctx.getArgs();
|
||||
const hooks = ctx.getHooks();
|
||||
const graphMeta = await ctx.getGraphMeta();
|
||||
|
||||
const domainId = graphMeta?.meta?.domainManger?.selectDomainId;
|
||||
if (!domainId) {
|
||||
return this;
|
||||
}
|
||||
const result = await hooks.dataSourceRelation.call(args, async () => {
|
||||
const { code, data } = await getDatasourceRelaList(domainId);
|
||||
if (code === 200) {
|
||||
return data;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
ctx.setResult(result);
|
||||
ctx.setGlobal('dataSourceRelationList', result);
|
||||
return this;
|
||||
};
|
||||
|
||||
/** undo cmd */
|
||||
undo = async () => {
|
||||
if (this.isUndoable()) {
|
||||
const ctx = this.contextProvider();
|
||||
ctx.undo();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/** redo cmd */
|
||||
redo = async () => {
|
||||
if (!this.isUndoable()) {
|
||||
await this.execute();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
isUndoable(): boolean {
|
||||
const ctx = this.contextProvider();
|
||||
return ctx.isUndoable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { IGraphCommand } from '@antv/xflow';
|
||||
|
||||
/** 节点命令 */
|
||||
export namespace CustomCommands {
|
||||
const category = '节点操作';
|
||||
/** 异步请求demo */
|
||||
export const TEST_ASYNC_CMD: IGraphCommand = {
|
||||
id: 'xflow:async-cmd',
|
||||
label: '异步请求',
|
||||
category,
|
||||
};
|
||||
/** 重命名节点弹窗 */
|
||||
export const SHOW_RENAME_MODAL: IGraphCommand = {
|
||||
id: 'xflow:rename-node-modal',
|
||||
label: '打开重命名弹窗',
|
||||
category,
|
||||
};
|
||||
/** 二次确认弹窗 */
|
||||
export const SHOW_CONFIRM_MODAL: IGraphCommand = {
|
||||
id: 'xflow:confirm-modal',
|
||||
label: '打开二次确认弹窗',
|
||||
category,
|
||||
};
|
||||
/** 部署服务 */
|
||||
export const DEPLOY_SERVICE: IGraphCommand = {
|
||||
id: 'xflow:deploy-service',
|
||||
label: '部署服务',
|
||||
category,
|
||||
};
|
||||
|
||||
export const DATASOURCE_RELATION: IGraphCommand = {
|
||||
id: 'xflow:datasource-relation',
|
||||
label: '获取数据源关系数据',
|
||||
category,
|
||||
};
|
||||
|
||||
/** 查看维度 */
|
||||
export const VIEW_DIMENSION: IGraphCommand = {
|
||||
id: 'xflow:view-dimension',
|
||||
label: '查看维度',
|
||||
category,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { DeployDagCommand, NsDeployDagCmd } from './CmdDeploy';
|
||||
import { RenameNodeCommand, NsRenameNodeCmd } from './CmdRenameNodeModal';
|
||||
import { ConfirmModalCommand, NsConfirmModalCmd } from './CmdConfirmModal';
|
||||
import {
|
||||
DataSourceRelationCommand,
|
||||
NsDataSourceRelationCmd,
|
||||
} from './CmdUpdateDataSourceRelationList';
|
||||
import type { ICommandContributionConfig } from '@antv/xflow';
|
||||
/** 注册成为可以执行的命令 */
|
||||
|
||||
export const COMMAND_CONTRIBUTIONS: ICommandContributionConfig[] = [
|
||||
{
|
||||
...NsDeployDagCmd,
|
||||
CommandHandler: DeployDagCommand,
|
||||
},
|
||||
{
|
||||
...NsRenameNodeCmd,
|
||||
CommandHandler: RenameNodeCommand,
|
||||
},
|
||||
{
|
||||
...NsConfirmModalCmd,
|
||||
CommandHandler: ConfirmModalCommand,
|
||||
},
|
||||
{
|
||||
...NsDataSourceRelationCmd,
|
||||
CommandHandler: DataSourceRelationCommand,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,266 @@
|
||||
import type { NsGraphCmd } from '@antv/xflow';
|
||||
import { createCmdConfig, DisposableCollection, XFlowGraphCommands } from '@antv/xflow';
|
||||
import type { IApplication } from '@antv/xflow';
|
||||
import type { IGraphPipelineCommand, IGraphCommandService, NsGraph } from '@antv/xflow';
|
||||
import { GraphApi } from './service';
|
||||
import { addDataSourceInfoAsDimensionParents } from './utils';
|
||||
import { COMMAND_CONTRIBUTIONS } from './CmdExtensions';
|
||||
import { CustomCommands } from './CmdExtensions/constants';
|
||||
|
||||
export const useCmdConfig = createCmdConfig((config) => {
|
||||
// 注册全局Command扩展
|
||||
config.setCommandContributions(() => COMMAND_CONTRIBUTIONS);
|
||||
// 设置hook
|
||||
config.setRegisterHookFn((hooks) => {
|
||||
const list = [
|
||||
hooks.graphMeta.registerHook({
|
||||
name: 'get graph meta from backend',
|
||||
handler: async (args) => {
|
||||
args.graphMetaService = GraphApi.queryGraphMeta;
|
||||
},
|
||||
}),
|
||||
hooks.saveGraphData.registerHook({
|
||||
name: 'save graph data',
|
||||
handler: async (args) => {
|
||||
if (!args.saveGraphDataService) {
|
||||
args.saveGraphDataService = GraphApi.saveGraphData;
|
||||
}
|
||||
},
|
||||
}),
|
||||
hooks.addNode.registerHook({
|
||||
name: 'get node config from backend api',
|
||||
handler: async (args) => {
|
||||
args.createNodeService = GraphApi.addNode;
|
||||
},
|
||||
}),
|
||||
hooks.delNode.registerHook({
|
||||
name: 'get edge config from backend api',
|
||||
handler: async (args) => {
|
||||
args.deleteNodeService = GraphApi.delNode;
|
||||
},
|
||||
}),
|
||||
hooks.addEdge.registerHook({
|
||||
name: '获取起始和结束节点的业务数据,并写入在边上',
|
||||
handler: async (handlerArgs, handler: any) => {
|
||||
const { commandService } = handlerArgs;
|
||||
const main = async (args: any) => {
|
||||
const res = await handler(args);
|
||||
if (res && res.edgeCell) {
|
||||
const sourceNode = res.edgeCell.getSourceNode();
|
||||
const targetNode = res.edgeCell.getTargetNode();
|
||||
const sourceNodeData = sourceNode?.getData() || {};
|
||||
const targetNodeData = targetNode?.getData() || {};
|
||||
res.edgeCell.setData({
|
||||
sourceNodeData,
|
||||
targetNodeData,
|
||||
source: sourceNodeData.id,
|
||||
target: targetNodeData.id,
|
||||
});
|
||||
// 对边进行tooltips设置
|
||||
res.edgeCell.addTools([
|
||||
{
|
||||
name: 'tooltip',
|
||||
args: {
|
||||
tooltip: '左键点击进行关系编辑,右键点击进行删除操作',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
if (commandService) {
|
||||
const initGraphCmdsState: any = commandService.getGlobal('initGraphCmdsSuccess');
|
||||
if (initGraphCmdsState) {
|
||||
// 保存图数据
|
||||
commandService!.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
|
||||
XFlowGraphCommands.SAVE_GRAPH_DATA.id,
|
||||
{
|
||||
saveGraphDataService: (meta, graphData) =>
|
||||
GraphApi.saveGraphData!(meta, graphData),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
return main;
|
||||
},
|
||||
}),
|
||||
hooks.delEdge.registerHook({
|
||||
name: '边删除,并向后台请求删除数据源间关联关系',
|
||||
handler: async (args) => {
|
||||
args.deleteEdgeService = GraphApi.delEdge;
|
||||
},
|
||||
}),
|
||||
];
|
||||
const toDispose = new DisposableCollection();
|
||||
toDispose.pushAll(list);
|
||||
return toDispose;
|
||||
});
|
||||
});
|
||||
|
||||
/** 查询图的节点和边的数据 */
|
||||
export const initGraphCmds = async (app: IApplication) => {
|
||||
const { commandService } = app;
|
||||
await app.executeCommandPipeline([
|
||||
/** 1. 从服务端获取数据 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.LOAD_DATA.id,
|
||||
getCommandOption: async () => {
|
||||
commandService.setGlobal('initGraphCmdsSuccess', false);
|
||||
return {
|
||||
args: {
|
||||
loadDataService: GraphApi.loadDataSourceData,
|
||||
},
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphLoadData.IArgs>,
|
||||
/** 2. 执行布局算法 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.GRAPH_LAYOUT.id,
|
||||
getCommandOption: async (ctx) => {
|
||||
const { graphData } = ctx.getResult();
|
||||
return {
|
||||
args: {
|
||||
layoutType: 'dagre',
|
||||
layoutOptions: {
|
||||
type: 'dagre',
|
||||
/** 布局方向 */
|
||||
rankdir: 'LR',
|
||||
/** 节点间距 */
|
||||
nodesep: 30,
|
||||
/** 层间距 */
|
||||
ranksep: 80,
|
||||
begin: [0, 0],
|
||||
},
|
||||
graphData,
|
||||
},
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphLayout.IArgs>,
|
||||
/** 3. 画布内容渲染 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.GRAPH_RENDER.id,
|
||||
getCommandOption: async (ctx) => {
|
||||
const { graphData } = ctx.getResult();
|
||||
const { edges, nodes } = graphData;
|
||||
const filterClassNodeEdges = edges.filter((item: NsGraph.IEdgeConfig) => {
|
||||
return !item.source.includes('classNodeId-');
|
||||
});
|
||||
const filterClassNodeNodes = nodes.filter((item: NsGraph.INodeConfig) => {
|
||||
return !item.id.includes('classNodeId-');
|
||||
});
|
||||
return {
|
||||
args: {
|
||||
graphData: {
|
||||
edges: filterClassNodeEdges,
|
||||
nodes: filterClassNodeNodes,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphRender.IArgs>,
|
||||
/** 4. 缩放画布 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.GRAPH_ZOOM.id,
|
||||
getCommandOption: async () => {
|
||||
commandService.setGlobal('initGraphCmdsSuccess', true);
|
||||
return {
|
||||
args: { factor: 'fit', zoomOptions: { maxScale: 0.9 } },
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphZoom.IArgs>,
|
||||
// commandService.executeCommand(CustomCommands.DATASOURCE_RELATION.id, {});
|
||||
{
|
||||
commandId: CustomCommands.DATASOURCE_RELATION.id,
|
||||
getCommandOption: async () => {
|
||||
return {
|
||||
args: {},
|
||||
};
|
||||
},
|
||||
},
|
||||
]);
|
||||
// const nodes = await app.getAllNodes();
|
||||
// const classNodes = nodes.filter((item) => {
|
||||
// return item.id.includes('classNodeId');
|
||||
// });
|
||||
// if (classNodes?.[0]) {
|
||||
// const targetClassId = classNodes[0].id;
|
||||
// await app.commandService.executeCommand<NsNodeCmd.DelNode.IArgs>(
|
||||
// XFlowNodeCommands.DEL_NODE.id,
|
||||
// {
|
||||
// nodeConfig: { id: targetClassId, type: 'class' },
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
};
|
||||
|
||||
/** 查询当前数据源下的维度节点和边的数据 */
|
||||
export const initDimensionGraphCmds = async (args: {
|
||||
commandService: IGraphCommandService;
|
||||
target: NsGraph.INodeConfig;
|
||||
}) => {
|
||||
const { commandService, target } = args;
|
||||
await commandService.executeCommandPipeline([
|
||||
{
|
||||
commandId: XFlowGraphCommands.LOAD_DATA.id,
|
||||
getCommandOption: async () => {
|
||||
return {
|
||||
args: {
|
||||
loadDataService: GraphApi.loadDimensionData,
|
||||
},
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphLoadData.IArgs>,
|
||||
/** 2. 执行布局算法 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.GRAPH_LAYOUT.id,
|
||||
getCommandOption: async (ctx) => {
|
||||
const { graphData } = ctx.getResult();
|
||||
const targetData = {
|
||||
...target.data,
|
||||
};
|
||||
delete targetData.x;
|
||||
delete targetData.y;
|
||||
const addGraphData = addDataSourceInfoAsDimensionParents(graphData, targetData);
|
||||
ctx.setResult(addGraphData);
|
||||
return {
|
||||
args: {
|
||||
layoutType: 'dagre',
|
||||
layoutOptions: {
|
||||
type: 'dagre',
|
||||
/** 布局方向 */
|
||||
rankdir: 'LR',
|
||||
/** 节点间距 */
|
||||
nodesep: 30,
|
||||
/** 层间距 */
|
||||
ranksep: 80,
|
||||
begin: [0, 0],
|
||||
},
|
||||
graphData: addGraphData,
|
||||
},
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphLayout.IArgs>,
|
||||
/** 3. 画布内容渲染 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.GRAPH_RENDER.id,
|
||||
getCommandOption: async (ctx) => {
|
||||
const { graphData } = ctx.getResult();
|
||||
return {
|
||||
args: {
|
||||
graphData,
|
||||
},
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphRender.IArgs>,
|
||||
/** 4. 缩放画布 */
|
||||
{
|
||||
commandId: XFlowGraphCommands.GRAPH_ZOOM.id,
|
||||
getCommandOption: async () => {
|
||||
return {
|
||||
args: { factor: 'fit', zoomOptions: { maxScale: 0.9 } },
|
||||
};
|
||||
},
|
||||
} as IGraphPipelineCommand<NsGraphCmd.GraphZoom.IArgs>,
|
||||
]);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { uuidv4 } from '@antv/xflow';
|
||||
import { XFlowNodeCommands } from '@antv/xflow';
|
||||
import { DATASOURCE_NODE_RENDER_ID } from './constant';
|
||||
import type { NsNodeCmd } from '@antv/xflow';
|
||||
import type { NsNodeCollapsePanel } from '@antv/xflow';
|
||||
import { Card } from 'antd';
|
||||
|
||||
export const onNodeDrop: NsNodeCollapsePanel.IOnNodeDrop = async (node, commands, modelService) => {
|
||||
const args: NsNodeCmd.AddNode.IArgs = {
|
||||
nodeConfig: { ...node, id: uuidv4() },
|
||||
};
|
||||
commands.executeCommand(XFlowNodeCommands.ADD_NODE.id, args);
|
||||
};
|
||||
|
||||
const NodeDescription = (props: { name: string }) => {
|
||||
return (
|
||||
<Card size="small" style={{ width: '200px' }} bordered={false}>
|
||||
将数据源组件拖入画布,对数据源进行设置及关联
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const nodeDataService: NsNodeCollapsePanel.INodeDataService = async (meta, modelService) => {
|
||||
return [
|
||||
{
|
||||
id: '数据源',
|
||||
header: '数据源',
|
||||
children: [
|
||||
{
|
||||
id: '2',
|
||||
label: '新增数据源',
|
||||
parentId: '1',
|
||||
renderKey: DATASOURCE_NODE_RENDER_ID,
|
||||
// renderComponent: (props) => (
|
||||
// <div className="react-dnd-node react-custom-node-1"> {props.data.label} </div>
|
||||
// ),
|
||||
popoverContent: <NodeDescription name="数据源" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const searchService: NsNodeCollapsePanel.ISearchService = async (
|
||||
nodes: NsNodeCollapsePanel.IPanelNode[] = [],
|
||||
keyword: string,
|
||||
) => {
|
||||
const list = nodes.filter((node) => node.label.includes(keyword));
|
||||
return list;
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { IProps } from './index';
|
||||
import { NsGraph } from '@antv/xflow';
|
||||
import type { Graph } from '@antv/x6';
|
||||
import { createHookConfig, DisposableCollection } from '@antv/xflow';
|
||||
import { DATASOURCE_NODE_RENDER_ID, GROUP_NODE_RENDER_ID } from './constant';
|
||||
import { AlgoNode } from './ReactNodes/algoNode';
|
||||
import { GroupNode } from './ReactNodes/group';
|
||||
|
||||
export const useGraphHookConfig = createHookConfig<IProps>((config) => {
|
||||
// 获取 Props
|
||||
// const props = proxy.getValue();
|
||||
config.setRegisterHook((hooks) => {
|
||||
const disposableList = [
|
||||
// 注册增加 react Node Render
|
||||
hooks.reactNodeRender.registerHook({
|
||||
name: 'add react node',
|
||||
handler: async (renderMap) => {
|
||||
renderMap.set(DATASOURCE_NODE_RENDER_ID, AlgoNode);
|
||||
renderMap.set(GROUP_NODE_RENDER_ID, GroupNode);
|
||||
},
|
||||
}),
|
||||
// 注册修改graphOptions配置的钩子
|
||||
hooks.graphOptions.registerHook({
|
||||
name: 'custom-x6-options',
|
||||
after: 'dag-extension-x6-options',
|
||||
handler: async (options) => {
|
||||
const graphOptions: Graph.Options = {
|
||||
connecting: {
|
||||
allowLoop: false,
|
||||
// 是否触发交互事件
|
||||
validateMagnet() {
|
||||
// return magnet.getAttribute('port-group') !== NsGraph.AnchorGroup.TOP
|
||||
return true;
|
||||
},
|
||||
// 显示可用的链接桩
|
||||
validateConnection(args: any) {
|
||||
const { sourceView, targetView, sourceMagnet, targetMagnet } = args;
|
||||
|
||||
// 不允许连接到自己
|
||||
if (sourceView === targetView) {
|
||||
return false;
|
||||
}
|
||||
// 没有起点的返回false
|
||||
if (!sourceMagnet) {
|
||||
return false;
|
||||
}
|
||||
if (!targetMagnet) {
|
||||
return false;
|
||||
}
|
||||
// 只能从上游节点的输出链接桩创建连接
|
||||
if (sourceMagnet?.getAttribute('port-group') === NsGraph.AnchorGroup.LEFT) {
|
||||
return false;
|
||||
}
|
||||
// 只能连接到下游节点的输入桩
|
||||
if (targetMagnet?.getAttribute('port-group') === NsGraph.AnchorGroup.RIGHT) {
|
||||
return false;
|
||||
}
|
||||
const node = targetView!.cell as any;
|
||||
|
||||
// 判断目标链接桩是否可连接
|
||||
const portId = targetMagnet.getAttribute('port')!;
|
||||
const port = node.getPort(portId);
|
||||
return !!port;
|
||||
},
|
||||
},
|
||||
};
|
||||
options.connecting = { ...options.connecting, ...graphOptions.connecting };
|
||||
},
|
||||
}),
|
||||
// hooks.afterGraphInit.registerHook({
|
||||
// name: '注册toolTips工具',
|
||||
// handler: async (args) => {},
|
||||
// }),
|
||||
];
|
||||
const toDispose = new DisposableCollection();
|
||||
toDispose.pushAll(disposableList);
|
||||
return toDispose;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,176 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import type { NsNodeCmd, NsEdgeCmd, IMenuOptions, NsGraph, NsGraphCmd } from '@antv/xflow';
|
||||
import type { NsRenameNodeCmd } from './CmdExtensions/CmdRenameNodeModal';
|
||||
import { createCtxMenuConfig, MenuItemType } from '@antv/xflow';
|
||||
import { IconStore, XFlowNodeCommands, XFlowEdgeCommands, XFlowGraphCommands } from '@antv/xflow';
|
||||
import { initDimensionGraphCmds } from './ConfigCmd';
|
||||
import type { NsConfirmModalCmd } from './CmdExtensions/CmdConfirmModal';
|
||||
import { NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE } from './ConfigModelService';
|
||||
import { DeleteOutlined, EditOutlined, StopOutlined } from '@ant-design/icons';
|
||||
import { CustomCommands } from './CmdExtensions/constants';
|
||||
import { GraphApi } from './service';
|
||||
|
||||
/** menuitem 配置 */
|
||||
export namespace NsMenuItemConfig {
|
||||
/** 注册菜单依赖的icon */
|
||||
IconStore.set('DeleteOutlined', DeleteOutlined);
|
||||
IconStore.set('EditOutlined', EditOutlined);
|
||||
IconStore.set('StopOutlined', StopOutlined);
|
||||
|
||||
export const DELETE_EDGE: IMenuOptions = {
|
||||
id: XFlowEdgeCommands.DEL_EDGE.id,
|
||||
label: '删除边',
|
||||
iconName: 'DeleteOutlined',
|
||||
onClick: async (args) => {
|
||||
const { target, commandService, modelService } = args;
|
||||
await commandService.executeCommand<NsEdgeCmd.DelEdge.IArgs>(XFlowEdgeCommands.DEL_EDGE.id, {
|
||||
edgeConfig: target.data as NsGraph.IEdgeConfig,
|
||||
});
|
||||
// 保存数据源关联关系
|
||||
await commandService.executeCommand(CustomCommands.DATASOURCE_RELATION.id, {});
|
||||
// 保存图数据
|
||||
commandService.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
|
||||
XFlowGraphCommands.SAVE_GRAPH_DATA.id,
|
||||
{ saveGraphDataService: (meta, graphData) => GraphApi.saveGraphData!(meta, graphData) },
|
||||
);
|
||||
// 关闭设置关联关系弹窗
|
||||
const modalModel = await modelService!.awaitModel(
|
||||
NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE.ID,
|
||||
);
|
||||
modalModel.setValue({ open: false });
|
||||
},
|
||||
};
|
||||
|
||||
export const DELETE_NODE: IMenuOptions = {
|
||||
id: XFlowNodeCommands.DEL_NODE.id,
|
||||
label: '删除节点',
|
||||
iconName: 'DeleteOutlined',
|
||||
onClick: async ({ target, commandService }) => {
|
||||
commandService.executeCommand<NsNodeCmd.DelNode.IArgs>(XFlowNodeCommands.DEL_NODE.id, {
|
||||
nodeConfig: { id: target?.data?.id || '', targetData: target.data },
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const EMPTY_MENU: IMenuOptions = {
|
||||
id: 'EMPTY_MENU_ITEM',
|
||||
label: '暂无可用',
|
||||
isEnabled: false,
|
||||
iconName: 'DeleteOutlined',
|
||||
};
|
||||
|
||||
export const RENAME_NODE: IMenuOptions = {
|
||||
id: CustomCommands.SHOW_RENAME_MODAL.id,
|
||||
label: '重命名',
|
||||
isVisible: true,
|
||||
iconName: 'EditOutlined',
|
||||
onClick: async ({ target, commandService }) => {
|
||||
const nodeConfig = target.data as NsGraph.INodeConfig;
|
||||
commandService.executeCommand<NsRenameNodeCmd.IArgs>(CustomCommands.SHOW_RENAME_MODAL.id, {
|
||||
nodeConfig,
|
||||
updateNodeNameService: GraphApi.renameNode,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const DELETE_DATASOURCE_NODE: IMenuOptions = {
|
||||
id: CustomCommands.SHOW_RENAME_MODAL.id,
|
||||
label: '删除数据源',
|
||||
isVisible: true,
|
||||
iconName: 'EditOutlined',
|
||||
onClick: async ({ target, commandService }) => {
|
||||
const nodeConfig = {
|
||||
...target.data,
|
||||
modalProps: {
|
||||
title: '确认删除?',
|
||||
},
|
||||
} as NsGraph.INodeConfig;
|
||||
await commandService.executeCommand<NsConfirmModalCmd.IArgs>(
|
||||
CustomCommands.SHOW_CONFIRM_MODAL.id,
|
||||
{
|
||||
nodeConfig,
|
||||
confirmModalCallBack: async () => {
|
||||
await commandService.executeCommand<NsNodeCmd.DelNode.IArgs>(
|
||||
XFlowNodeCommands.DEL_NODE.id,
|
||||
{
|
||||
nodeConfig: {
|
||||
id: target?.data?.id || '',
|
||||
type: 'dataSource',
|
||||
targetData: target.data,
|
||||
},
|
||||
},
|
||||
);
|
||||
commandService.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
|
||||
XFlowGraphCommands.SAVE_GRAPH_DATA.id,
|
||||
{
|
||||
saveGraphDataService: (meta, graphData) => GraphApi.saveGraphData!(meta, graphData),
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const VIEW_DIMENSION: IMenuOptions = {
|
||||
id: CustomCommands.VIEW_DIMENSION.id,
|
||||
label: '查看维度',
|
||||
isVisible: true,
|
||||
iconName: 'EditOutlined',
|
||||
onClick: async (args) => {
|
||||
const { target, commandService, modelService } = args as any;
|
||||
initDimensionGraphCmds({ commandService, target });
|
||||
},
|
||||
};
|
||||
|
||||
export const SEPARATOR: IMenuOptions = {
|
||||
id: 'separator',
|
||||
type: MenuItemType.Separator,
|
||||
};
|
||||
}
|
||||
|
||||
export const useMenuConfig = createCtxMenuConfig((config) => {
|
||||
config.setMenuModelService(async (target, model, modelService, toDispose) => {
|
||||
const { type, cell } = target as any;
|
||||
switch (type) {
|
||||
/** 节点菜单 */
|
||||
case 'node':
|
||||
model.setValue({
|
||||
id: 'root',
|
||||
type: MenuItemType.Root,
|
||||
submenu: [
|
||||
// NsMenuItemConfig.VIEW_DIMENSION,
|
||||
// NsMenuItemConfig.SEPARATOR,
|
||||
// NsMenuItemConfig.DELETE_NODE,
|
||||
NsMenuItemConfig.DELETE_DATASOURCE_NODE,
|
||||
// NsMenuItemConfig.RENAME_NODE,
|
||||
],
|
||||
});
|
||||
break;
|
||||
/** 边菜单 */
|
||||
case 'edge':
|
||||
model.setValue({
|
||||
id: 'root',
|
||||
type: MenuItemType.Root,
|
||||
submenu: [NsMenuItemConfig.DELETE_EDGE],
|
||||
});
|
||||
break;
|
||||
/** 画布菜单 */
|
||||
case 'blank':
|
||||
model.setValue({
|
||||
id: 'root',
|
||||
type: MenuItemType.Root,
|
||||
submenu: [NsMenuItemConfig.EMPTY_MENU],
|
||||
});
|
||||
break;
|
||||
/** 默认菜单 */
|
||||
default:
|
||||
model.setValue({
|
||||
id: 'root',
|
||||
type: MenuItemType.Root,
|
||||
submenu: [NsMenuItemConfig.EMPTY_MENU],
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Disposable, IModelService } from '@antv/xflow';
|
||||
import { createModelServiceConfig, DisposableCollection } from '@antv/xflow';
|
||||
|
||||
export namespace NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE {
|
||||
export const ID = 'NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE';
|
||||
// export const id = 'NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE';
|
||||
export interface IState {
|
||||
open: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export const useModelServiceConfig = createModelServiceConfig((config) => {
|
||||
config.registerModel((registry) => {
|
||||
const list: Disposable[] = [
|
||||
registry.registerModel({
|
||||
id: NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE.ID,
|
||||
// getInitialValue: () => {
|
||||
// open: false;
|
||||
// },
|
||||
}),
|
||||
];
|
||||
const toDispose = new DisposableCollection();
|
||||
toDispose.pushAll(list);
|
||||
return toDispose;
|
||||
});
|
||||
});
|
||||
|
||||
export const useOpenState = async (contextService: IModelService) => {
|
||||
const ctx = await contextService.awaitModel<NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE.IState>(
|
||||
NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE.ID,
|
||||
);
|
||||
return ctx.getValidValue();
|
||||
};
|
||||
@@ -0,0 +1,242 @@
|
||||
import type { IToolbarItemOptions } from '@antv/xflow';
|
||||
import { createToolbarConfig } from '@antv/xflow';
|
||||
import type { IModelService } from '@antv/xflow';
|
||||
import {
|
||||
XFlowGraphCommands,
|
||||
XFlowDagCommands,
|
||||
NsGraphStatusCommand,
|
||||
MODELS,
|
||||
IconStore,
|
||||
} from '@antv/xflow';
|
||||
import {
|
||||
UngroupOutlined,
|
||||
SaveOutlined,
|
||||
CloudSyncOutlined,
|
||||
GroupOutlined,
|
||||
GatewayOutlined,
|
||||
PlaySquareOutlined,
|
||||
StopOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { GraphApi } from './service';
|
||||
import type { NsGraphCmd } from '@antv/xflow';
|
||||
import { Radio } from 'antd';
|
||||
|
||||
export namespace NSToolbarConfig {
|
||||
/** 注册icon 类型 */
|
||||
IconStore.set('SaveOutlined', SaveOutlined);
|
||||
IconStore.set('CloudSyncOutlined', CloudSyncOutlined);
|
||||
IconStore.set('GatewayOutlined', GatewayOutlined);
|
||||
IconStore.set('GroupOutlined', GroupOutlined);
|
||||
IconStore.set('UngroupOutlined', UngroupOutlined);
|
||||
IconStore.set('PlaySquareOutlined', PlaySquareOutlined);
|
||||
IconStore.set('StopOutlined', StopOutlined);
|
||||
|
||||
/** toolbar依赖的状态 */
|
||||
export interface IToolbarState {
|
||||
isMultiSelectionActive: boolean;
|
||||
isNodeSelected: boolean;
|
||||
isGroupSelected: boolean;
|
||||
isProcessing: boolean;
|
||||
}
|
||||
|
||||
export const getDependencies = async (modelService: IModelService) => {
|
||||
return [
|
||||
await MODELS.SELECTED_CELLS.getModel(modelService),
|
||||
await MODELS.GRAPH_ENABLE_MULTI_SELECT.getModel(modelService),
|
||||
await NsGraphStatusCommand.MODEL.getModel(modelService),
|
||||
];
|
||||
};
|
||||
|
||||
/** toolbar依赖的状态 */
|
||||
export const getToolbarState = async (modelService: IModelService) => {
|
||||
// isMultiSelectionActive
|
||||
const { isEnable: isMultiSelectionActive } = await MODELS.GRAPH_ENABLE_MULTI_SELECT.useValue(
|
||||
modelService,
|
||||
);
|
||||
// isGroupSelected
|
||||
const isGroupSelected = await MODELS.IS_GROUP_SELECTED.useValue(modelService);
|
||||
// isNormalNodesSelected: node不能是GroupNode
|
||||
const isNormalNodesSelected = await MODELS.IS_NORMAL_NODES_SELECTED.useValue(modelService);
|
||||
// statusInfo
|
||||
const statusInfo = await NsGraphStatusCommand.MODEL.useValue(modelService);
|
||||
|
||||
return {
|
||||
isNodeSelected: isNormalNodesSelected,
|
||||
isGroupSelected,
|
||||
isMultiSelectionActive,
|
||||
isProcessing: statusInfo.graphStatus === NsGraphStatusCommand.StatusEnum.PROCESSING,
|
||||
} as NSToolbarConfig.IToolbarState;
|
||||
};
|
||||
|
||||
export const getToolbarItems = async () => {
|
||||
const toolbarGroup1: IToolbarItemOptions[] = [];
|
||||
const toolbarGroup2: IToolbarItemOptions[] = [];
|
||||
const toolbarGroup3: IToolbarItemOptions[] = [];
|
||||
/** 保存数据 */
|
||||
toolbarGroup1.push({
|
||||
id: XFlowGraphCommands.SAVE_GRAPH_DATA.id,
|
||||
iconName: 'SaveOutlined',
|
||||
tooltip: '保存数据',
|
||||
onClick: async ({ commandService }) => {
|
||||
commandService.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
|
||||
XFlowGraphCommands.SAVE_GRAPH_DATA.id,
|
||||
{ saveGraphDataService: (meta, graphData) => GraphApi.saveGraphData!(meta, graphData) },
|
||||
);
|
||||
},
|
||||
});
|
||||
// /** 部署服务按钮 */
|
||||
// toolbarGroup1.push({
|
||||
// iconName: 'CloudSyncOutlined',
|
||||
// tooltip: '部署服务',
|
||||
// id: CustomCommands.DEPLOY_SERVICE.id,
|
||||
// onClick: ({ commandService }) => {
|
||||
// commandService.executeCommand<NsDeployDagCmd.IArgs>(CustomCommands.DEPLOY_SERVICE.id, {
|
||||
// deployDagService: (meta, graphData) => GraphApi.deployDagService(meta, graphData),
|
||||
// });
|
||||
// },
|
||||
// });
|
||||
// /** 开启框选 */
|
||||
// toolbarGroup2.push({
|
||||
// id: XFlowGraphCommands.GRAPH_TOGGLE_MULTI_SELECT.id,
|
||||
// tooltip: '开启框选',
|
||||
// iconName: 'GatewayOutlined',
|
||||
// active: state.isMultiSelectionActive,
|
||||
// onClick: async ({ commandService }) => {
|
||||
// commandService.executeCommand<NsGraphCmd.GraphToggleMultiSelect.IArgs>(
|
||||
// XFlowGraphCommands.GRAPH_TOGGLE_MULTI_SELECT.id,
|
||||
// {},
|
||||
// );
|
||||
// },
|
||||
// });
|
||||
// /** 新建群组 */
|
||||
// toolbarGroup2.push({
|
||||
// id: XFlowGroupCommands.ADD_GROUP.id,
|
||||
// tooltip: '新建群组',
|
||||
// iconName: 'GroupOutlined',
|
||||
// isEnabled: state.isNodeSelected,
|
||||
// onClick: async ({ commandService, modelService }) => {
|
||||
// const cells = await MODELS.SELECTED_CELLS.useValue(modelService);
|
||||
// const groupChildren = cells.map((cell) => cell.id);
|
||||
// commandService.executeCommand<NsGroupCmd.AddGroup.IArgs>(XFlowGroupCommands.ADD_GROUP.id, {
|
||||
// nodeConfig: {
|
||||
// id: uuidv4(),
|
||||
// renderKey: GROUP_NODE_RENDER_ID,
|
||||
// groupChildren,
|
||||
// groupCollapsedSize: { width: 200, height: 40 },
|
||||
// label: '新建群组',
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
// });
|
||||
// /** 解散群组 */
|
||||
// toolbarGroup2.push({
|
||||
// id: XFlowGroupCommands.DEL_GROUP.id,
|
||||
// tooltip: '解散群组',
|
||||
// iconName: 'UngroupOutlined',
|
||||
// isEnabled: state.isGroupSelected,
|
||||
// onClick: async ({ commandService, modelService }) => {
|
||||
// const cell = await MODELS.SELECTED_NODE.useValue(modelService);
|
||||
// const nodeConfig = cell.getData();
|
||||
// commandService.executeCommand<NsGroupCmd.AddGroup.IArgs>(XFlowGroupCommands.DEL_GROUP.id, {
|
||||
// nodeConfig: nodeConfig,
|
||||
// });
|
||||
// },
|
||||
// });
|
||||
|
||||
// toolbarGroup3.push({
|
||||
// id: XFlowDagCommands.QUERY_GRAPH_STATUS.id + 'play',
|
||||
// tooltip: '开始执行',
|
||||
// iconName: 'PlaySquareOutlined',
|
||||
// isEnabled: !state.isProcessing,
|
||||
// onClick: async ({ commandService }) => {
|
||||
// commandService.executeCommand<NsGraphStatusCommand.IArgs>(
|
||||
// XFlowDagCommands.QUERY_GRAPH_STATUS.id,
|
||||
// {
|
||||
// graphStatusService: GraphApi.graphStatusService,
|
||||
// loopInterval: 3000,
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// });
|
||||
// toolbarGroup3.push({
|
||||
// id: XFlowDagCommands.QUERY_GRAPH_STATUS.id + 'stop',
|
||||
// tooltip: '停止执行',
|
||||
// iconName: 'StopOutlined',
|
||||
// isEnabled: state.isProcessing,
|
||||
// onClick: async ({ commandService }) => {
|
||||
// commandService.executeCommand<NsGraphStatusCommand.IArgs>(
|
||||
// XFlowDagCommands.QUERY_GRAPH_STATUS.id,
|
||||
// {
|
||||
// graphStatusService: GraphApi.stopGraphStatusService,
|
||||
// loopInterval: 5000,
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// render: (props) => {
|
||||
// return (
|
||||
// <Popconfirm
|
||||
// title="确定停止执行?"
|
||||
// onConfirm={() => {
|
||||
// props.onClick();
|
||||
// }}
|
||||
// >
|
||||
// {props.children}
|
||||
// </Popconfirm>
|
||||
// );
|
||||
// },
|
||||
// });
|
||||
|
||||
return [
|
||||
{ name: 'graphData', items: toolbarGroup1 },
|
||||
{ name: 'groupOperations', items: toolbarGroup2 },
|
||||
{
|
||||
name: 'customCmd',
|
||||
items: toolbarGroup3,
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
export const getExtraToolbarItems = async () => {
|
||||
const toolbarGroup: IToolbarItemOptions[] = [];
|
||||
/** 保存数据 */
|
||||
toolbarGroup.push({
|
||||
id: XFlowDagCommands.QUERY_GRAPH_STATUS.id + 'switchShowType',
|
||||
render: () => {
|
||||
return (
|
||||
<Radio.Group defaultValue="dataSource" buttonStyle="solid" size="small">
|
||||
<Radio.Button value="dataSource">数据源</Radio.Button>
|
||||
<Radio.Button value="dimension">维度</Radio.Button>
|
||||
<Radio.Button value="metric">指标</Radio.Button>
|
||||
</Radio.Group>
|
||||
);
|
||||
},
|
||||
// text: '添加节点',
|
||||
// tooltip: '添加节点,配置extraGroups',
|
||||
});
|
||||
|
||||
return [{ name: 'extra', items: toolbarGroup }];
|
||||
};
|
||||
|
||||
export const useToolbarConfig = createToolbarConfig((toolbarConfig) => {
|
||||
/** 生产 toolbar item */
|
||||
toolbarConfig.setToolbarModelService(async (toolbarModel, modelService, toDispose) => {
|
||||
const updateToolbarModel = async () => {
|
||||
const state = await NSToolbarConfig.getToolbarState(modelService);
|
||||
const toolbarItems = await NSToolbarConfig.getToolbarItems(state);
|
||||
// const extraToolbarItems = await getExtraToolbarItems();
|
||||
toolbarModel.setValue((toolbar) => {
|
||||
toolbar.mainGroups = toolbarItems;
|
||||
// toolbar.extraGroups = extraToolbarItems;
|
||||
});
|
||||
};
|
||||
const models = await NSToolbarConfig.getDependencies(modelService);
|
||||
|
||||
const subscriptions = models.map((model) => {
|
||||
return model.watch(async () => {
|
||||
updateToolbarModel();
|
||||
});
|
||||
});
|
||||
toDispose.pushAll(subscriptions);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import ReactDom from 'react-dom';
|
||||
import { Tooltip } from 'antd';
|
||||
import type { EdgeView } from '@antv/x6';
|
||||
import { Graph, ToolsView } from '@antv/x6';
|
||||
class TooltipTool extends ToolsView.ToolItem<EdgeView, TooltipToolOptions> {
|
||||
private knob: HTMLDivElement;
|
||||
|
||||
render() {
|
||||
if (!this.knob) {
|
||||
this.knob = ToolsView.createElement('div', false) as HTMLDivElement;
|
||||
this.knob.style.position = 'absolute';
|
||||
this.container.appendChild(this.knob);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private toggleTooltip(visible: boolean) {
|
||||
if (this.knob) {
|
||||
ReactDom.unmountComponentAtNode(this.knob);
|
||||
if (visible) {
|
||||
ReactDom.render(
|
||||
<Tooltip title={this.options.tooltip} open={visible} destroyTooltipOnHide>
|
||||
<div />
|
||||
</Tooltip>,
|
||||
this.knob,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onMosueEnter({ e }: { e: MouseEvent }) {
|
||||
this.updatePosition(e);
|
||||
this.toggleTooltip(true);
|
||||
}
|
||||
|
||||
private onMouseLeave() {
|
||||
this.updatePosition();
|
||||
this.toggleTooltip(false);
|
||||
}
|
||||
|
||||
private onMouseMove() {
|
||||
this.updatePosition();
|
||||
this.toggleTooltip(false);
|
||||
}
|
||||
|
||||
delegateEvents() {
|
||||
this.cellView.on('cell:mouseenter', this.onMosueEnter, this);
|
||||
this.cellView.on('cell:mouseleave', this.onMouseLeave, this);
|
||||
this.cellView.on('cell:mousemove', this.onMouseMove, this);
|
||||
return super.delegateEvents();
|
||||
}
|
||||
|
||||
private updatePosition(e?: MouseEvent) {
|
||||
const style = this.knob.style;
|
||||
if (e) {
|
||||
const p = this.graph.clientToGraph(e.clientX, e.clientY);
|
||||
style.display = 'block';
|
||||
style.left = `${p.x}px`;
|
||||
style.top = `${p.y}px`;
|
||||
} else {
|
||||
style.display = 'none';
|
||||
style.left = '-1000px';
|
||||
style.top = '-1000px';
|
||||
}
|
||||
}
|
||||
|
||||
protected onRemove() {
|
||||
this.toggleTooltip(false);
|
||||
this.cellView.off('cell:mouseenter', this.onMosueEnter, this);
|
||||
this.cellView.off('cell:mouseleave', this.onMouseLeave, this);
|
||||
this.cellView.off('cell:mousemove', this.onMouseMove, this);
|
||||
}
|
||||
}
|
||||
|
||||
TooltipTool.config({
|
||||
tagName: 'div',
|
||||
isSVGElement: false,
|
||||
});
|
||||
|
||||
export interface TooltipToolOptions extends ToolsView.ToolItem.Options {
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
Graph.registerEdgeTool('tooltip', TooltipTool, true);
|
||||
@@ -0,0 +1,101 @@
|
||||
@light-border: 1px solid #d9d9d9;
|
||||
@primaryColor: #c2c8d5;
|
||||
|
||||
.xflow-algo-node {
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
width: 180px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
border: 1px solid @primaryColor;
|
||||
border-radius: 2px;
|
||||
box-shadow: ~'-1px -1px 4px 0 rgba(223,223,223,0.50), -2px 2px 4px 0 rgba(244,244,244,0.50), 2px 3px 8px 2px rgba(151,151,151,0.05)';
|
||||
transition: all ease-in-out 0.15s;
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
border: 1px solid #3057e3;
|
||||
// border: 1px solid @primaryColor;
|
||||
box-shadow: 0 0 3px 3px rgba(48, 86, 227, 0.15);
|
||||
cursor: move;
|
||||
}
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
}
|
||||
.label {
|
||||
width: 108px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.status {
|
||||
width: 36px;
|
||||
}
|
||||
&.panel-node {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.x6-node-selected {
|
||||
.xflow-algo-node {
|
||||
background-color: rgba(48, 86, 227, 0.05);
|
||||
border: 1px solid #3057e3;
|
||||
box-shadow: 0 0 3px 3px rgba(48, 86, 227, 0.15);
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 5px 5px rgba(48, 86, 227, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dag-solution-layout {
|
||||
.xflow-canvas-root {
|
||||
.xflow-algo-node {
|
||||
height: 72px !important;
|
||||
line-height: 72px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dataSourceTooltipWrapper {
|
||||
max-width: 500px;
|
||||
.ant-tooltip-inner {
|
||||
padding:0;
|
||||
}
|
||||
.dataSourceTooltip {
|
||||
width: 300px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
color: #4d4d4d;
|
||||
padding: 15px;
|
||||
opacity: .9;
|
||||
font-size: 11px;
|
||||
box-shadow: 0 0 5px #d8d8d8;
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
font-size: 13px;
|
||||
padding: 4px;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.dataSourceTooltipLabel {
|
||||
display: block;
|
||||
width: 64px;
|
||||
color: grey;
|
||||
}
|
||||
.dataSourceTooltipValue{
|
||||
flex: 1 1;
|
||||
display: block;
|
||||
width: 220px;
|
||||
padding: 0 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
DatabaseOutlined,
|
||||
RedoOutlined,
|
||||
CloseCircleOutlined,
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { NsGraph } from '@antv/xflow';
|
||||
import { NsGraphStatusCommand } from '@antv/xflow';
|
||||
import { Tooltip } from 'antd';
|
||||
import moment from 'moment';
|
||||
import './algoNode.less';
|
||||
|
||||
const fontStyle = { fontSize: '16px', color: '#3057e3' };
|
||||
interface IProps {
|
||||
status: NsGraphStatusCommand.StatusEnum;
|
||||
hide: boolean;
|
||||
}
|
||||
export const AlgoIcon: React.FC<IProps> = (props) => {
|
||||
if (props.hide) {
|
||||
return null;
|
||||
}
|
||||
switch (props.status) {
|
||||
case NsGraphStatusCommand.StatusEnum.PROCESSING:
|
||||
return <RedoOutlined spin style={{ color: '#c1cdf7', fontSize: '16px' }} />;
|
||||
case NsGraphStatusCommand.StatusEnum.ERROR:
|
||||
return <CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />;
|
||||
case NsGraphStatusCommand.StatusEnum.SUCCESS:
|
||||
return <CheckCircleOutlined style={{ color: '#39ca74cc', fontSize: '16px' }} />;
|
||||
case NsGraphStatusCommand.StatusEnum.WARNING:
|
||||
return <ExclamationCircleOutlined style={{ color: '#faad14', fontSize: '16px' }} />;
|
||||
case NsGraphStatusCommand.StatusEnum.DEFAULT:
|
||||
return <InfoCircleOutlined style={{ color: '#d9d9d9', fontSize: '16px' }} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const AlgoNode: NsGraph.INodeRender = (props) => {
|
||||
const { data } = props;
|
||||
const dataSourceData = data.payload;
|
||||
|
||||
const openState = dataSourceData ? undefined : false;
|
||||
let tooltipNode = <></>;
|
||||
if (dataSourceData) {
|
||||
const { name, id, bizName, description, createdBy, updatedAt } = dataSourceData;
|
||||
const labelList = [
|
||||
{
|
||||
label: '数据源ID',
|
||||
value: id,
|
||||
},
|
||||
{
|
||||
label: '名称',
|
||||
value: name,
|
||||
},
|
||||
{
|
||||
label: '英文名',
|
||||
value: bizName,
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
value: createdBy,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
value: updatedAt ? moment(updatedAt).format('YYYY-MM-DD HH:mm:ss') : '-',
|
||||
},
|
||||
{
|
||||
label: '描述',
|
||||
value: description,
|
||||
},
|
||||
];
|
||||
tooltipNode = (
|
||||
<div className="dataSourceTooltip">
|
||||
{labelList.map(({ label, value }) => {
|
||||
return (
|
||||
<p key={value}>
|
||||
<span className="dataSourceTooltipLabel">{label}:</span>
|
||||
<span className="dataSourceTooltipValue">{value || '-'}</span>
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`xflow-algo-node ${props.isNodeTreePanel ? 'panel-node' : ''}`}>
|
||||
<span className="icon">
|
||||
<DatabaseOutlined style={fontStyle} />
|
||||
</span>
|
||||
|
||||
<span className="label">
|
||||
<Tooltip
|
||||
open={openState}
|
||||
title={tooltipNode}
|
||||
placement="right"
|
||||
color="#fff"
|
||||
overlayClassName="dataSourceTooltipWrapper"
|
||||
>
|
||||
{props.data.label}
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
<span className="status">
|
||||
<AlgoIcon status={props.data && props.data.status} hide={props.isNodeTreePanel} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
@light-border: 1px solid #d9d9d9;
|
||||
@primaryColor: #c1cdf7;
|
||||
|
||||
.xflow-group-node {
|
||||
z-index: 9;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.65);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 4px;
|
||||
box-shadow: ~'rgb(17 49 96 / 12%) 0px 1px 3px 0px, rgb(17 49 96 / 4%) 0px 0px 0px 1px';
|
||||
cursor: grab;
|
||||
&:hover {
|
||||
background-color: rgba(227, 244, 255, 0.45);
|
||||
border: 1px solid @primaryColor;
|
||||
box-shadow: 0 0 3px 3px rgba(64, 169, 255, 0.2);
|
||||
cursor: move;
|
||||
}
|
||||
.xflow-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
line-height: 38px;
|
||||
.header-left {
|
||||
width: 80%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.header-right {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
span.anticon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.x6-node-selected {
|
||||
.xflow-group-node {
|
||||
background-color: rgba(243, 249, 255, 0.92);
|
||||
border: 1px solid @primaryColor;
|
||||
box-shadow: 0 0 3px 3px rgb(64 169 255 / 20%);
|
||||
&:hover {
|
||||
background-color: rgba(243, 249, 255, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { PlusSquareOutlined, MinusSquareOutlined } from '@ant-design/icons';
|
||||
import type { NsGraph } from '@antv/xflow';
|
||||
import { useXFlowApp, XFlowGroupCommands } from '@antv/xflow';
|
||||
import './group.less';
|
||||
|
||||
export const GroupNode: NsGraph.INodeRender = (props) => {
|
||||
const { cell } = props;
|
||||
const app = useXFlowApp();
|
||||
const isCollapsed = props.data.isCollapsed || false;
|
||||
const onExpand = () => {
|
||||
app.executeCommand(XFlowGroupCommands.COLLAPSE_GROUP.id, {
|
||||
nodeId: cell.id,
|
||||
isCollapsed: false,
|
||||
collapsedSize: { width: 200, height: 40 },
|
||||
});
|
||||
};
|
||||
const onCollapse = () => {
|
||||
app.executeCommand(XFlowGroupCommands.COLLAPSE_GROUP.id, {
|
||||
nodeId: cell.id,
|
||||
isCollapsed: true,
|
||||
collapsedSize: { width: 200, height: 40 },
|
||||
gap: 3,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="xflow-group-node">
|
||||
<div className="xflow-group-header">
|
||||
<div className="header-left">{props.data.label}</div>
|
||||
<div className="header-right">
|
||||
{isCollapsed && <PlusSquareOutlined onClick={onExpand} />}
|
||||
{!isCollapsed && <MinusSquareOutlined onClick={onCollapse} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Form, Button, Drawer, Space, Input, Select, message } from 'antd';
|
||||
import { formLayout } from '@/components/FormHelper/utils';
|
||||
import { createOrUpdateDatasourceRela } from '../../service';
|
||||
import { getRelationConfigInfo } from '../utils';
|
||||
import { useXFlowApp } from '@antv/xflow';
|
||||
import { CustomCommands } from '../CmdExtensions/constants';
|
||||
|
||||
export type DataSourceRelationFormDrawerProps = {
|
||||
domainId: number;
|
||||
nodeDataSource: any;
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
const DataSourceRelationFormDrawer: React.FC<DataSourceRelationFormDrawerProps> = ({
|
||||
domainId,
|
||||
open,
|
||||
nodeDataSource,
|
||||
onClose,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const [dataSourceOptions, setDataSourceOptions] = useState<any[]>([]);
|
||||
|
||||
const app = useXFlowApp();
|
||||
|
||||
const getRelationListInfo = async () => {
|
||||
await app.commandService.executeCommand(CustomCommands.DATASOURCE_RELATION.id, {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const { sourceData, targetData } = nodeDataSource;
|
||||
const dataSourceFromIdentifiers = sourceData?.datasourceDetail?.identifiers || [];
|
||||
const dataSourceToIdentifiers = targetData?.datasourceDetail?.identifiers || [];
|
||||
const dataSourceToIdentifiersNames = dataSourceToIdentifiers.map((item) => {
|
||||
return item.name;
|
||||
});
|
||||
const keyOptions = dataSourceFromIdentifiers.reduce((options: any[], item: any) => {
|
||||
const { name } = item;
|
||||
if (dataSourceToIdentifiersNames.includes(name)) {
|
||||
options.push(item);
|
||||
}
|
||||
return options;
|
||||
}, []);
|
||||
setDataSourceOptions(
|
||||
keyOptions.map((item: any) => {
|
||||
const { name } = item;
|
||||
return {
|
||||
label: name,
|
||||
value: name,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}, [nodeDataSource]);
|
||||
|
||||
useEffect(() => {
|
||||
const { sourceData, targetData } = nodeDataSource;
|
||||
if (!sourceData || !targetData) {
|
||||
return;
|
||||
}
|
||||
const relationList = app.commandService.getGlobal('dataSourceRelationList') || [];
|
||||
const config = getRelationConfigInfo(sourceData.id, targetData.id, relationList);
|
||||
if (config) {
|
||||
form.setFieldsValue({
|
||||
joinKey: config.joinKey,
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
joinKey: '',
|
||||
});
|
||||
}
|
||||
}, [nodeDataSource]);
|
||||
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
<FormItem hidden={true} name="id" label="ID">
|
||||
<Input placeholder="id" />
|
||||
</FormItem>
|
||||
<FormItem label="主数据源:">{nodeDataSource?.sourceData?.name}</FormItem>
|
||||
<FormItem label="关联数据源:">{nodeDataSource?.targetData?.name}</FormItem>
|
||||
<FormItem
|
||||
name="joinKey"
|
||||
label="可关联Key:"
|
||||
tooltip="主从数据源中必须具有相同的主键或外键才可建立关联关系"
|
||||
rules={[{ required: true, message: '请选择关联Key' }]}
|
||||
>
|
||||
<Select placeholder="请选择关联Key">
|
||||
{dataSourceOptions.map((item) => (
|
||||
<Option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const saveRelation = async () => {
|
||||
const values = await form.validateFields();
|
||||
setSaveLoading(true);
|
||||
const { code, msg } = await createOrUpdateDatasourceRela({
|
||||
domainId,
|
||||
datasourceFrom: nodeDataSource?.sourceData?.id,
|
||||
datasourceTo: nodeDataSource?.targetData?.id,
|
||||
...values,
|
||||
});
|
||||
setSaveLoading(false);
|
||||
if (code === 200) {
|
||||
message.success('保存成功');
|
||||
getRelationListInfo();
|
||||
onClose?.();
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={saveLoading}
|
||||
onClick={() => {
|
||||
saveRelation();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
forceRender
|
||||
width={400}
|
||||
getContainer={false}
|
||||
title={'数据源关联信息'}
|
||||
mask={false}
|
||||
open={open}
|
||||
footer={renderFooter()}
|
||||
onClose={() => {
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
<Form {...formLayout} form={form}>
|
||||
{renderContent()}
|
||||
</Form>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataSourceRelationFormDrawer;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { WorkspacePanel } from '@antv/xflow';
|
||||
import type { NsJsonSchemaForm } from '@antv/xflow';
|
||||
import XflowJsonSchemaFormDrawerForm from './XflowJsonSchemaFormDrawerForm';
|
||||
|
||||
export type CreateFormProps = {
|
||||
controlMapService?: any;
|
||||
formSchemaService?: any;
|
||||
formValueUpdateService?: any;
|
||||
};
|
||||
|
||||
const XflowJsonSchemaFormDrawer: React.FC<CreateFormProps> = ({
|
||||
controlMapService,
|
||||
formSchemaService,
|
||||
formValueUpdateService,
|
||||
}) => {
|
||||
const defaultFormValueUpdateService: NsJsonSchemaForm.IFormValueUpdateService = async () => {};
|
||||
const defaultFormSchemaService: NsJsonSchemaForm.IFormSchemaService = async () => {
|
||||
return { tabs: [] };
|
||||
};
|
||||
const defaultControlMapService: NsJsonSchemaForm.IControlMapService = (controlMap) => {
|
||||
return controlMap;
|
||||
};
|
||||
return (
|
||||
<WorkspacePanel position={{}}>
|
||||
<XflowJsonSchemaFormDrawerForm
|
||||
controlMapService={controlMapService || defaultControlMapService}
|
||||
formSchemaService={formSchemaService || defaultFormSchemaService}
|
||||
formValueUpdateService={formValueUpdateService || defaultFormValueUpdateService}
|
||||
/>
|
||||
</WorkspacePanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default XflowJsonSchemaFormDrawer;
|
||||
@@ -0,0 +1,125 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Drawer } from 'antd';
|
||||
import { WorkspacePanel, useXFlowApp, useModelAsync, XFlowGraphCommands } from '@antv/xflow';
|
||||
import { useJsonSchemaFormModel } from '@antv/xflow-extension/es/canvas-json-schema-form/service';
|
||||
import { NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE } from '../ConfigModelService';
|
||||
import { connect } from 'umi';
|
||||
import { DATASOURCE_NODE_RENDER_ID } from '../constant';
|
||||
import DataSourceRelationFormDrawer from './DataSourceRelationFormDrawer';
|
||||
import { GraphApi } from '../service';
|
||||
import type { StateType } from '../../model';
|
||||
import DataSource from '../../Datasource';
|
||||
|
||||
export type CreateFormProps = {
|
||||
controlMapService: any;
|
||||
formSchemaService: any;
|
||||
formValueUpdateService: any;
|
||||
domainManger: StateType;
|
||||
};
|
||||
|
||||
const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
|
||||
const { domainManger } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||
const [dataSourceItem, setDataSourceItem] = useState<any>();
|
||||
const [nodeDataSource, setNodeDataSource] = useState<any>({
|
||||
sourceData: {},
|
||||
targetData: {},
|
||||
});
|
||||
|
||||
const app = useXFlowApp();
|
||||
// 借用JsonSchemaForm钩子函数对元素状态进行监听
|
||||
const { state, commandService, modelService } = useJsonSchemaFormModel({
|
||||
...props,
|
||||
targetType: ['node', 'edge', 'canvas', 'group'],
|
||||
position: {},
|
||||
});
|
||||
|
||||
const [modalOpenState] = useModelAsync({
|
||||
getModel: async () => {
|
||||
return await modelService.awaitModel(NS_DATA_SOURCE_RELATION_MODAL_OPEN_STATE.ID);
|
||||
},
|
||||
initialState: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const { open } = modalOpenState as any;
|
||||
setVisible(open);
|
||||
}, [modalOpenState]);
|
||||
|
||||
useEffect(() => {
|
||||
const { targetType, targetData } = state;
|
||||
if (targetType && ['node', 'edge'].includes(targetType)) {
|
||||
const { renderKey, payload } = targetData as any;
|
||||
if (renderKey === DATASOURCE_NODE_RENDER_ID) {
|
||||
setDataSourceItem(payload);
|
||||
setCreateModalVisible(true);
|
||||
} else {
|
||||
const { sourceNodeData, targetNodeData } = targetData as any;
|
||||
setNodeDataSource({
|
||||
sourceData: sourceNodeData.payload,
|
||||
targetData: targetNodeData.payload,
|
||||
});
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
const resetSelectedNode = async () => {
|
||||
const x6Graph = await app.graphProvider.getGraphInstance();
|
||||
x6Graph.resetSelection();
|
||||
};
|
||||
|
||||
const handleDataSourceRelationDrawerClose = () => {
|
||||
resetSelectedNode();
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkspacePanel position={{}}>
|
||||
<DataSourceRelationFormDrawer
|
||||
domainId={domainManger.selectDomainId}
|
||||
nodeDataSource={nodeDataSource}
|
||||
onClose={() => {
|
||||
handleDataSourceRelationDrawerClose();
|
||||
}}
|
||||
open={visible}
|
||||
/>
|
||||
<Drawer
|
||||
width={'100%'}
|
||||
destroyOnClose
|
||||
title="数据源编辑"
|
||||
open={createModalVisible}
|
||||
onClose={() => {
|
||||
resetSelectedNode();
|
||||
setCreateModalVisible(false);
|
||||
setDataSourceItem(undefined);
|
||||
}}
|
||||
footer={null}
|
||||
>
|
||||
<DataSource
|
||||
initialValues={dataSourceItem}
|
||||
domainId={Number(domainManger?.selectDomainId)}
|
||||
onSubmitSuccess={(dataSourceInfo: any) => {
|
||||
setCreateModalVisible(false);
|
||||
const { targetCell, targetData } = state;
|
||||
targetCell?.setData({
|
||||
...targetData,
|
||||
label: dataSourceInfo.name,
|
||||
payload: dataSourceInfo,
|
||||
id: `dataSource-${dataSourceInfo.id}`,
|
||||
});
|
||||
setDataSourceItem(undefined);
|
||||
commandService.executeCommand(XFlowGraphCommands.SAVE_GRAPH_DATA.id, {
|
||||
saveGraphDataService: (meta, graphData) => GraphApi.saveGraphData!(meta, graphData),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
</WorkspacePanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(XflowJsonSchemaFormDrawerForm);
|
||||
@@ -0,0 +1,5 @@
|
||||
export const DND_RENDER_ID = 'DND_NDOE';
|
||||
export const GROUP_NODE_RENDER_ID = 'GROUP_NODE_RENDER_ID';
|
||||
export const DATASOURCE_NODE_RENDER_ID = 'DATASOURCE_NODE';
|
||||
export const NODE_WIDTH = 180;
|
||||
export const NODE_HEIGHT = 72;
|
||||
29
webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticFlows/data.d.ts
vendored
Normal file
29
webapp/packages/supersonic-fe/src/pages/SemanticModel/SemanticFlows/data.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { ISODateString, GraphConfigType, UserName } from '../data';
|
||||
import type { NsGraph } from '@antv/xflow';
|
||||
|
||||
export type GraphConfigListItem = {
|
||||
id: number;
|
||||
domainId: number;
|
||||
config: string;
|
||||
type: GraphConfigType;
|
||||
createdAt: ISODateString;
|
||||
createdBy: UserName;
|
||||
updatedAt: ISODateString;
|
||||
updatedBy: UserName;
|
||||
};
|
||||
|
||||
export type GraphConfig = { id: number; config: NsGraph.IGraphData };
|
||||
|
||||
export type RelationListItem = {
|
||||
id: number;
|
||||
domainId: number;
|
||||
datasourceFrom: number;
|
||||
datasourceTo: number;
|
||||
joinKey: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
updatedAt: string;
|
||||
updatedBy: string;
|
||||
};
|
||||
|
||||
export type RelationList = RelationListItem[];
|
||||
@@ -0,0 +1,103 @@
|
||||
@body-bg: #fafafa;
|
||||
@primaryColor: #3056e3;
|
||||
@light-border: 1px solid #d9d9d9;
|
||||
|
||||
|
||||
.dag-solution {
|
||||
.__dumi-default-previewer-actions {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dag-solution-layout {
|
||||
position: relative;
|
||||
height: 610px;
|
||||
border: @light-border;
|
||||
.xflow-x6-canvas {
|
||||
background: @body-bg;
|
||||
}
|
||||
.x6-edge {
|
||||
&:hover {
|
||||
path:nth-child(2) {
|
||||
stroke: @primaryColor;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
&.x6-edge-selected {
|
||||
path:nth-child(2) {
|
||||
stroke: @primaryColor;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.xflow-canvas-dnd-node-tree {
|
||||
border-right: @light-border;
|
||||
}
|
||||
|
||||
|
||||
.xflow-workspace-toolbar-top {
|
||||
background-image: ~'linear-gradient(180deg, #ffffff 0%, #fafafa 100%)';
|
||||
border-bottom: @light-border;
|
||||
}
|
||||
|
||||
.xflow-workspace-toolbar-bottom {
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-top: @light-border;
|
||||
}
|
||||
|
||||
.xflow-modal-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.xflow-collapse-panel {
|
||||
.xflow-collapse-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
background: #f7f8fa;
|
||||
.ant-input-affix-wrapper {
|
||||
padding: 2px 11px;
|
||||
}
|
||||
}
|
||||
.xflow-collapse-panel-body {
|
||||
background: #f7f8fa;
|
||||
.xflow-collapse-header {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
}
|
||||
.xflow-node-dnd-panel-footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.xflow-json-form .tabs .ant-tabs-nav {
|
||||
box-shadow: unset;
|
||||
}
|
||||
// .xflow-json-schema-form {
|
||||
// .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
// color: #525252;
|
||||
// font-weight: 300 !important;
|
||||
// }
|
||||
// .xflow-json-schema-form-footer {
|
||||
// display: none;
|
||||
// }
|
||||
// .xflow-json-form .tabs.xTab .ant-tabs-nav .ant-tabs-nav-list,
|
||||
// .xflow-json-form .tabs.xTab .ant-tabs-nav .ant-tabs-nav-list .ant-tabs-tab {
|
||||
// background: #f7f8fa;
|
||||
// }
|
||||
// .xflow-json-schema-form-body {
|
||||
// position: relative;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background: #f7f8fa;
|
||||
// box-shadow: 0 1px 1px 0 rgb(206 201 201 / 50%);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
/** app 核心组件 */
|
||||
import { XFlow, XFlowCanvas, XFlowGraphCommands } from '@antv/xflow';
|
||||
import type { IApplication, IAppLoad, NsGraph, NsGraphCmd } from '@antv/xflow';
|
||||
/** 交互组件 */
|
||||
import {
|
||||
/** 触发Command的交互组件 */
|
||||
CanvasScaleToolbar,
|
||||
NodeCollapsePanel,
|
||||
CanvasContextMenu,
|
||||
CanvasToolbar,
|
||||
/** Graph的扩展交互组件 */
|
||||
CanvasSnapline,
|
||||
CanvasNodePortTooltip,
|
||||
DagGraphExtension,
|
||||
} from '@antv/xflow';
|
||||
/** app 组件配置 */
|
||||
/** 配置画布 */
|
||||
import { useGraphHookConfig } from './ConfigGraph';
|
||||
/** 配置Command */
|
||||
import { useCmdConfig, initGraphCmds } from './ConfigCmd';
|
||||
/** 配置Model */
|
||||
import { useModelServiceConfig } from './ConfigModelService';
|
||||
/** 配置Menu */
|
||||
import { useMenuConfig } from './ConfigMenu';
|
||||
/** 配置Toolbar */
|
||||
import { useToolbarConfig } from './ConfigToolbar';
|
||||
/** 配置Dnd组件面板 */
|
||||
import * as dndPanelConfig from './ConfigDndPanel';
|
||||
import { connect } from 'umi';
|
||||
import type { StateType } from '../model';
|
||||
import './index.less';
|
||||
import XflowJsonSchemaFormDrawer from './components/XflowJsonSchemaFormDrawer';
|
||||
import { getViewInfoList } from '../service';
|
||||
import { getGraphConfigFromList } from './utils';
|
||||
import type { GraphConfig } from './data';
|
||||
import '@antv/xflow/dist/index.css';
|
||||
|
||||
import './ReactNodes/ToolTipsNode';
|
||||
|
||||
export interface IProps {
|
||||
domainManger: StateType;
|
||||
}
|
||||
|
||||
export const SemanticFlow: React.FC<IProps> = (props) => {
|
||||
const { domainManger } = props;
|
||||
|
||||
const graphHooksConfig = useGraphHookConfig(props);
|
||||
const toolbarConfig = useToolbarConfig();
|
||||
const menuConfig = useMenuConfig();
|
||||
const cmdConfig = useCmdConfig();
|
||||
const modelServiceConfig = useModelServiceConfig();
|
||||
const [graphConfig, setGraphConfig] = useState<GraphConfig>();
|
||||
|
||||
const [meta, setMeta] = useState<NsGraph.IGraphMeta>({
|
||||
flowId: 'semanticFlow',
|
||||
domainManger,
|
||||
});
|
||||
|
||||
const cache =
|
||||
React.useMemo<{ app: IApplication } | null>(
|
||||
() => ({
|
||||
app: null as any,
|
||||
}),
|
||||
[],
|
||||
) || ({} as any);
|
||||
|
||||
const queryGraphConfig = async () => {
|
||||
const { code, data } = await getViewInfoList(domainManger.selectDomainId);
|
||||
if (code === 200) {
|
||||
const config = getGraphConfigFromList(data);
|
||||
setGraphConfig(config || ({} as GraphConfig));
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
queryGraphConfig();
|
||||
}, [domainManger.selectDomainId]);
|
||||
|
||||
useEffect(() => {
|
||||
setMeta({
|
||||
...meta,
|
||||
domainManger,
|
||||
graphConfig,
|
||||
});
|
||||
}, [graphConfig]);
|
||||
|
||||
/**
|
||||
* @param app 当前XFlow工作空间
|
||||
*/
|
||||
const onLoad: IAppLoad = async (app) => {
|
||||
cache.app = app;
|
||||
initGraphCmds(cache.app);
|
||||
};
|
||||
|
||||
const updateGraph = async (app: IApplication) => {
|
||||
await app.executeCommand(XFlowGraphCommands.LOAD_META.id, {
|
||||
meta,
|
||||
} as NsGraphCmd.GraphMeta.IArgs);
|
||||
initGraphCmds(app);
|
||||
};
|
||||
|
||||
/** 父组件meta属性更新时,执行initGraphCmds */
|
||||
React.useEffect(() => {
|
||||
if (cache.app) {
|
||||
updateGraph(cache.app);
|
||||
}
|
||||
}, [cache.app, meta]);
|
||||
return (
|
||||
<div id="semanticFlowContainer" style={{ height: '100%' }}>
|
||||
{meta.graphConfig && (
|
||||
<XFlow
|
||||
className="dag-user-custom-clz dag-solution-layout"
|
||||
hookConfig={graphHooksConfig}
|
||||
modelServiceConfig={modelServiceConfig}
|
||||
commandConfig={cmdConfig}
|
||||
onLoad={onLoad}
|
||||
meta={meta}
|
||||
>
|
||||
<DagGraphExtension layout="LR" />
|
||||
<NodeCollapsePanel
|
||||
className="xflow-node-panel"
|
||||
searchService={dndPanelConfig.searchService}
|
||||
nodeDataService={dndPanelConfig.nodeDataService}
|
||||
onNodeDrop={dndPanelConfig.onNodeDrop}
|
||||
position={{ width: 230, top: 0, bottom: 0, left: 0 }}
|
||||
footerPosition={{ height: 0 }}
|
||||
bodyPosition={{ top: 40, bottom: 0, left: 0 }}
|
||||
/>
|
||||
<CanvasToolbar
|
||||
className="xflow-workspace-toolbar-top"
|
||||
layout="horizontal"
|
||||
config={toolbarConfig}
|
||||
position={{ top: 0, left: 230, right: 0, bottom: 0 }}
|
||||
/>
|
||||
<XFlowCanvas position={{ top: 40, left: 230, right: 0, bottom: 0 }}>
|
||||
<CanvasScaleToolbar position={{ top: 60, left: 20 }} />
|
||||
<CanvasContextMenu config={menuConfig} />
|
||||
<CanvasSnapline color="#faad14" />
|
||||
<CanvasNodePortTooltip />
|
||||
</XFlowCanvas>
|
||||
|
||||
<XflowJsonSchemaFormDrawer />
|
||||
</XFlow>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||
domainManger,
|
||||
}))(SemanticFlow);
|
||||
@@ -0,0 +1,352 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { DATASOURCE_NODE_RENDER_ID, NODE_WIDTH, NODE_HEIGHT } from './constant';
|
||||
import { uuidv4, NsGraph, NsGraphStatusCommand } from '@antv/xflow';
|
||||
import type { NsRenameNodeCmd } from './CmdExtensions/CmdRenameNodeModal';
|
||||
import type { NsNodeCmd, NsEdgeCmd, NsGraphCmd } from '@antv/xflow';
|
||||
import type { NsDeployDagCmd } from './CmdExtensions/CmdDeploy';
|
||||
import { getRelationConfigInfo, addClassInfoAsDataSourceParents } from './utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import type { IDataSource } from '../data';
|
||||
import {
|
||||
getDatasourceList,
|
||||
deleteDatasource,
|
||||
getDimensionList,
|
||||
createOrUpdateViewInfo,
|
||||
getViewInfoList,
|
||||
deleteDatasourceRela,
|
||||
} from '../service';
|
||||
import { message } from 'antd';
|
||||
|
||||
/** mock 后端接口调用 */
|
||||
export namespace GraphApi {
|
||||
export const NODE_COMMON_PROPS = {
|
||||
renderKey: DATASOURCE_NODE_RENDER_ID,
|
||||
width: NODE_WIDTH,
|
||||
height: NODE_HEIGHT,
|
||||
} as const;
|
||||
|
||||
/** 查图的meta元信息 */
|
||||
export const queryGraphMeta: NsGraphCmd.GraphMeta.IArgs['graphMetaService'] = async (args) => {
|
||||
return { ...args, flowId: args.meta.flowId };
|
||||
};
|
||||
export const createPorts = (nodeId: string, count = 1, layout = 'LR') => {
|
||||
const ports = [] as NsGraph.INodeAnchor[];
|
||||
Array(count)
|
||||
.fill(1)
|
||||
.forEach((item, idx) => {
|
||||
const portIdx = idx + 1;
|
||||
ports.push(
|
||||
...[
|
||||
{
|
||||
id: `${nodeId}-input-${portIdx}`,
|
||||
type: NsGraph.AnchorType.INPUT,
|
||||
group: layout === 'TB' ? NsGraph.AnchorGroup.TOP : NsGraph.AnchorGroup.LEFT,
|
||||
tooltip: `输入桩-${portIdx}`,
|
||||
},
|
||||
{
|
||||
id: `${nodeId}-output-${portIdx}`,
|
||||
type: NsGraph.AnchorType.OUTPUT,
|
||||
group: layout === 'TB' ? NsGraph.AnchorGroup.BOTTOM : NsGraph.AnchorGroup.RIGHT,
|
||||
tooltip: `输出桩-${portIdx}`,
|
||||
},
|
||||
],
|
||||
);
|
||||
});
|
||||
return ports;
|
||||
};
|
||||
|
||||
export const createDataSourceNode = (dataSourceItem: IDataSource.IDataSourceItem) => {
|
||||
const { id, name } = dataSourceItem;
|
||||
const nodeId = `dataSource-${id}`;
|
||||
return {
|
||||
...NODE_COMMON_PROPS,
|
||||
id: nodeId,
|
||||
label: `${name}-${id}`,
|
||||
ports: createPorts(nodeId),
|
||||
payload: dataSourceItem,
|
||||
};
|
||||
};
|
||||
|
||||
/** 删除节点的api */
|
||||
export const delDataSource = async (nodeConfig: any) => {
|
||||
const dataSourceId = nodeConfig.targetData?.payload?.id;
|
||||
if (!dataSourceId) {
|
||||
// dataSourceId 不存在时,为未保存节点,直接返回true删除
|
||||
return true;
|
||||
}
|
||||
const { code, msg } = await deleteDatasource(dataSourceId);
|
||||
if (code === 200) {
|
||||
return true;
|
||||
}
|
||||
message.error(msg);
|
||||
return false;
|
||||
};
|
||||
|
||||
export const loadDataSourceData = async (args: NsGraph.IGraphMeta) => {
|
||||
const { domainManger, graphConfig } = args.meta;
|
||||
const { selectDomainId } = domainManger;
|
||||
const { code, data = [] } = await getDatasourceList({ domainId: selectDomainId });
|
||||
const dataSourceMap = data.reduce(
|
||||
(itemMap: Record<string, IDataSource.IDataSourceItem>, item: IDataSource.IDataSourceItem) => {
|
||||
const { id, name } = item;
|
||||
itemMap[`dataSource-${id}`] = item;
|
||||
|
||||
itemMap[name] = item;
|
||||
return itemMap;
|
||||
},
|
||||
{},
|
||||
);
|
||||
if (code === 200) {
|
||||
// 如果config存在,将数据源信息进行merge
|
||||
if (graphConfig?.id && graphConfig?.config) {
|
||||
const { config } = graphConfig;
|
||||
const { nodes, edges } = config;
|
||||
const nodesMap = nodes.reduce(
|
||||
(itemMap: Record<string, NsGraph.INodeConfig>, item: NsGraph.INodeConfig) => {
|
||||
itemMap[item.id] = item;
|
||||
return itemMap;
|
||||
},
|
||||
{},
|
||||
);
|
||||
let mergeNodes = nodes;
|
||||
let mergeEdges = edges;
|
||||
if (Array.isArray(nodes)) {
|
||||
mergeNodes = data.reduce(
|
||||
(mergeNodeList: NsGraph.INodeConfig[], item: IDataSource.IDataSourceItem) => {
|
||||
const { id } = item;
|
||||
const targetDataSourceItem = nodesMap[`dataSource-${id}`];
|
||||
if (targetDataSourceItem) {
|
||||
mergeNodeList.push({
|
||||
...targetDataSourceItem,
|
||||
payload: item,
|
||||
});
|
||||
} else {
|
||||
mergeNodeList.push(createDataSourceNode(item));
|
||||
}
|
||||
return mergeNodeList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
if (Array.isArray(edges)) {
|
||||
mergeEdges = edges.reduce(
|
||||
(mergeEdgeList: NsGraph.IEdgeConfig[], item: NsGraph.IEdgeConfig) => {
|
||||
const { source, target } = item;
|
||||
const sourceDataSourceItem = dataSourceMap[source];
|
||||
const targetDataSourceItem = dataSourceMap[target];
|
||||
if (sourceDataSourceItem && targetDataSourceItem) {
|
||||
const tempItem = { ...item };
|
||||
tempItem.sourceNodeData.payload = sourceDataSourceItem;
|
||||
tempItem.targetNodeData.payload = targetDataSourceItem;
|
||||
mergeEdgeList.push(tempItem);
|
||||
}
|
||||
return mergeEdgeList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
return { nodes: mergeNodes, edges: mergeEdges };
|
||||
}
|
||||
|
||||
// 如果config不存在,进行初始化
|
||||
const nodes: NsGraph.INodeConfig[] = data.map((item: IDataSource.IDataSourceItem) => {
|
||||
return createDataSourceNode(item);
|
||||
});
|
||||
return addClassInfoAsDataSourceParents({ nodes, edges: [] }, domainManger);
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const loadDimensionData = async (args: NsGraph.IGraphMeta) => {
|
||||
const { domainManger } = args.meta;
|
||||
const { domainId } = domainManger;
|
||||
const { code, data } = await getDimensionList({ domainId });
|
||||
if (code === 200) {
|
||||
const { list } = data;
|
||||
const nodes: NsGraph.INodeConfig[] = list.map((item: any) => {
|
||||
const { id, name } = item;
|
||||
const nodeId = `dimension-${id}`;
|
||||
return {
|
||||
...NODE_COMMON_PROPS,
|
||||
id: nodeId,
|
||||
label: `${name}-${id}`,
|
||||
ports: createPorts(nodeId),
|
||||
payload: item,
|
||||
};
|
||||
});
|
||||
return { nodes, edges: [] };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
/** 保存图数据的api */
|
||||
export const saveGraphData: NsGraphCmd.SaveGraphData.IArgs['saveGraphDataService'] = async (
|
||||
graphMeta: NsGraph.IGraphMeta,
|
||||
graphData: NsGraph.IGraphData,
|
||||
) => {
|
||||
const { commandService } = graphMeta;
|
||||
const initGraphCmdsState = commandService.getGlobal('initGraphCmdsSuccess');
|
||||
// 如果graph处于初始化阶段,则禁止配置文件保存操作
|
||||
if (!initGraphCmdsState) {
|
||||
return;
|
||||
}
|
||||
const tempGraphData = cloneDeep(graphData);
|
||||
const { edges, nodes } = tempGraphData;
|
||||
if (Array.isArray(nodes)) {
|
||||
tempGraphData.nodes = nodes.map((item: any) => {
|
||||
delete item.payload;
|
||||
return item;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(edges)) {
|
||||
tempGraphData.edges = edges.map((item: any) => {
|
||||
delete item.sourceNodeData.payload;
|
||||
delete item.targetNodeData.payload;
|
||||
return item;
|
||||
});
|
||||
}
|
||||
const { domainManger, graphConfig } = graphMeta.meta;
|
||||
const { code, msg } = await createOrUpdateViewInfo({
|
||||
id: graphConfig?.id,
|
||||
domainId: domainManger.selectDomainId,
|
||||
type: 'datasource',
|
||||
config: JSON.stringify(tempGraphData),
|
||||
});
|
||||
if (code !== 200) {
|
||||
message.error(msg);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: graphData,
|
||||
};
|
||||
};
|
||||
/** 部署图数据的api */
|
||||
export const deployDagService: NsDeployDagCmd.IDeployDagService = async (
|
||||
meta: NsGraph.IGraphMeta,
|
||||
graphData: NsGraph.IGraphData,
|
||||
) => {
|
||||
return {
|
||||
success: true,
|
||||
data: graphData,
|
||||
};
|
||||
};
|
||||
|
||||
/** 添加节点api */
|
||||
export const addNode: NsNodeCmd.AddNode.IArgs['createNodeService'] = async (
|
||||
args: NsNodeCmd.AddNode.IArgs,
|
||||
) => {
|
||||
console.info('addNode service running, add node:', args);
|
||||
|
||||
const { id, ports = createPorts(id, 1), groupChildren } = args.nodeConfig;
|
||||
const nodeId = id || uuidv4();
|
||||
/** 这里添加连线桩 */
|
||||
const node: NsNodeCmd.AddNode.IArgs['nodeConfig'] = {
|
||||
...NODE_COMMON_PROPS,
|
||||
...args.nodeConfig,
|
||||
id: nodeId,
|
||||
ports: ports,
|
||||
};
|
||||
/** group没有链接桩 */
|
||||
if (groupChildren && groupChildren.length) {
|
||||
node.ports = [];
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
/** 更新节点 name,可能依赖接口判断是否重名,返回空字符串时,不更新 */
|
||||
export const renameNode: NsRenameNodeCmd.IUpdateNodeNameService = async (
|
||||
name,
|
||||
node,
|
||||
graphMeta,
|
||||
) => {
|
||||
return { err: null, nodeName: name };
|
||||
};
|
||||
|
||||
/** 删除节点的api */
|
||||
export const delNode: NsNodeCmd.DelNode.IArgs['deleteNodeService'] = async (args: any) => {
|
||||
const { type } = args.nodeConfig;
|
||||
switch (type) {
|
||||
case 'dataSource':
|
||||
return await delDataSource(args.nodeConfig);
|
||||
case 'class':
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** 添加边的api */
|
||||
export const addEdge: NsEdgeCmd.AddEdge.IArgs['createEdgeService'] = async (args) => {
|
||||
console.info('addEdge service running, add edge:', args);
|
||||
const { edgeConfig } = args;
|
||||
return {
|
||||
...edgeConfig,
|
||||
id: uuidv4(),
|
||||
};
|
||||
};
|
||||
|
||||
/** 删除边的api */
|
||||
export const delEdge: NsEdgeCmd.DelEdge.IArgs['deleteEdgeService'] = async (args) => {
|
||||
console.info('delEdge service running, del edge:', args);
|
||||
const { commandService, edgeConfig } = args;
|
||||
if (!edgeConfig?.sourceNodeData || !edgeConfig?.targetNodeData) {
|
||||
return true;
|
||||
}
|
||||
const { sourceNodeData, targetNodeData } = edgeConfig as any;
|
||||
const sourceDataId = sourceNodeData.payload.id;
|
||||
const targetDataId = targetNodeData.payload.id;
|
||||
const { getGlobal } = commandService as any;
|
||||
const dataSourceRelationList = getGlobal('dataSourceRelationList');
|
||||
const relationConfig = getRelationConfigInfo(
|
||||
sourceDataId,
|
||||
targetDataId,
|
||||
dataSourceRelationList,
|
||||
);
|
||||
if (!relationConfig) {
|
||||
// 如果配置不存在则直接删除
|
||||
return true;
|
||||
}
|
||||
const { code, msg } = await deleteDatasourceRela(relationConfig.id);
|
||||
if (code === 200) {
|
||||
return true;
|
||||
}
|
||||
message.error(msg);
|
||||
return false;
|
||||
};
|
||||
|
||||
let runningNodeId = 0;
|
||||
const statusMap = {} as NsGraphStatusCommand.IStatusInfo['statusMap'];
|
||||
let graphStatus: NsGraphStatusCommand.StatusEnum = NsGraphStatusCommand.StatusEnum.DEFAULT;
|
||||
export const graphStatusService: NsGraphStatusCommand.IArgs['graphStatusService'] = async () => {
|
||||
if (runningNodeId < 4) {
|
||||
statusMap[`node${runningNodeId}`] = { status: NsGraphStatusCommand.StatusEnum.SUCCESS };
|
||||
statusMap[`node${runningNodeId + 1}`] = {
|
||||
status: NsGraphStatusCommand.StatusEnum.PROCESSING,
|
||||
};
|
||||
runningNodeId += 1;
|
||||
graphStatus = NsGraphStatusCommand.StatusEnum.PROCESSING;
|
||||
} else {
|
||||
runningNodeId = 0;
|
||||
statusMap.node4 = { status: NsGraphStatusCommand.StatusEnum.SUCCESS };
|
||||
graphStatus = NsGraphStatusCommand.StatusEnum.SUCCESS;
|
||||
}
|
||||
return {
|
||||
graphStatus: graphStatus,
|
||||
statusMap: statusMap,
|
||||
};
|
||||
};
|
||||
export const stopGraphStatusService: NsGraphStatusCommand.IArgs['graphStatusService'] =
|
||||
async () => {
|
||||
Object.entries(statusMap).forEach(([, val]) => {
|
||||
const { status } = val as { status: NsGraphStatusCommand.StatusEnum };
|
||||
if (status === NsGraphStatusCommand.StatusEnum.PROCESSING) {
|
||||
val.status = NsGraphStatusCommand.StatusEnum.ERROR;
|
||||
}
|
||||
});
|
||||
return {
|
||||
graphStatus: NsGraphStatusCommand.StatusEnum.ERROR,
|
||||
statusMap: statusMap,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import type { NsGraph } from '@antv/xflow';
|
||||
import { uuidv4 } from '@antv/xflow';
|
||||
import type { StateType } from '../model';
|
||||
import { GraphApi } from './service';
|
||||
import { NODE_WIDTH, NODE_HEIGHT } from './constant';
|
||||
import moment from 'moment';
|
||||
import { jsonParse } from '@/utils/utils';
|
||||
import type { GraphConfigListItem, RelationListItem } from './data';
|
||||
|
||||
export const getEdgesNodesIds = (edges: NsGraph.IEdgeConfig[], type?: 'source' | 'target') => {
|
||||
const hasEdgesNodesIds = edges.reduce((nodesList: string[], item: NsGraph.IEdgeConfig) => {
|
||||
const { source, target } = item;
|
||||
if (!type) {
|
||||
nodesList.push(source, target);
|
||||
} else if (type === 'source') {
|
||||
nodesList.push(source);
|
||||
} else if (type === 'target') {
|
||||
nodesList.push(target);
|
||||
}
|
||||
|
||||
return nodesList;
|
||||
}, []);
|
||||
const uniqueHasEdgesNodesIds = Array.from(new Set(hasEdgesNodesIds));
|
||||
return uniqueHasEdgesNodesIds;
|
||||
};
|
||||
|
||||
export const computedSingerNodesEdgesPosition = ({ nodes, edges }: NsGraph.IGraphData) => {
|
||||
const hasEdgesNodesIds = getEdgesNodesIds(edges);
|
||||
const defaultXPostion = 100;
|
||||
const defaultYPostion = 100;
|
||||
const paddingSize = 50;
|
||||
let xPosistion = defaultXPostion;
|
||||
const yPostition = defaultYPostion;
|
||||
const positionNodes = nodes.reduce(
|
||||
(nodesList: NsGraph.INodeConfig[], item: NsGraph.INodeConfig, index: number) => {
|
||||
const { id, width, height = NODE_HEIGHT } = item;
|
||||
if (!hasEdgesNodesIds.includes(id)) {
|
||||
xPosistion = xPosistion + (width || NODE_WIDTH + paddingSize) * index;
|
||||
}
|
||||
nodesList.push({
|
||||
...item,
|
||||
x: xPosistion,
|
||||
y: height > yPostition ? height + paddingSize : yPostition,
|
||||
});
|
||||
return nodesList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
return { nodes: positionNodes, edges };
|
||||
};
|
||||
|
||||
export const addClassInfoAsDataSourceParents = (
|
||||
{ nodes = [], edges = [] }: NsGraph.IGraphData,
|
||||
domainManger: StateType,
|
||||
) => {
|
||||
const { selectDomainId, selectDomainName } = domainManger;
|
||||
const sourceId = `classNodeId-${selectDomainId}`;
|
||||
const classNode = {
|
||||
...GraphApi.NODE_COMMON_PROPS,
|
||||
id: sourceId,
|
||||
label: selectDomainName,
|
||||
ports: GraphApi.createPorts(sourceId),
|
||||
};
|
||||
const classEdges = nodes.reduce((edgesList: NsGraph.IEdgeConfig[], item: NsGraph.INodeConfig) => {
|
||||
const { id } = item;
|
||||
|
||||
const sourcePortId = `${sourceId}-output-1`;
|
||||
const edge = {
|
||||
id: uuidv4(),
|
||||
source: sourceId,
|
||||
target: id,
|
||||
sourcePortId,
|
||||
targetPortId: `${id}-input-1`,
|
||||
};
|
||||
edgesList.push(edge);
|
||||
return edgesList;
|
||||
}, []);
|
||||
const graphData = {
|
||||
nodes: [classNode, ...nodes],
|
||||
edges: [...edges, ...classEdges],
|
||||
};
|
||||
return graphData;
|
||||
};
|
||||
|
||||
export const addDataSourceInfoAsDimensionParents = (
|
||||
{ nodes = [], edges = [] }: NsGraph.IGraphData,
|
||||
targetDataSource: NsGraph.INodeConfig,
|
||||
) => {
|
||||
const { id: sourceId } = targetDataSource;
|
||||
const dimensionEdges = nodes.reduce(
|
||||
(edgesList: NsGraph.IEdgeConfig[], item: NsGraph.INodeConfig) => {
|
||||
const { id } = item;
|
||||
|
||||
const sourcePortId = `${sourceId}-output-1`;
|
||||
const edge = {
|
||||
id: uuidv4(),
|
||||
source: sourceId,
|
||||
target: id,
|
||||
sourcePortId,
|
||||
targetPortId: `${id}-input-1`,
|
||||
};
|
||||
edgesList.push(edge);
|
||||
return edgesList;
|
||||
},
|
||||
[],
|
||||
);
|
||||
const graphData = {
|
||||
nodes: [targetDataSource, ...nodes],
|
||||
edges: [...edges, ...dimensionEdges],
|
||||
};
|
||||
return graphData;
|
||||
};
|
||||
|
||||
export const getGraphConfigFromList = (configList: GraphConfigListItem[]) => {
|
||||
configList.sort((a, b) => moment(b.updatedAt).valueOf() - moment(a.updatedAt).valueOf());
|
||||
const targetConfig = configList[0];
|
||||
if (targetConfig) {
|
||||
const { config, id } = targetConfig;
|
||||
return {
|
||||
config: jsonParse(config),
|
||||
id,
|
||||
};
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
export const getRelationConfigInfo = (
|
||||
fromDataSourceId: number,
|
||||
toDataSourceId: number,
|
||||
relationList: RelationListItem[],
|
||||
) => {
|
||||
const relationConfig = relationList.filter((item: RelationListItem) => {
|
||||
const { datasourceFrom, datasourceTo } = item;
|
||||
return fromDataSourceId === datasourceFrom && toDataSourceId === datasourceTo;
|
||||
})[0];
|
||||
return relationConfig;
|
||||
};
|
||||
Reference in New Issue
Block a user