diff --git a/apps/platform/config/routes.ts b/apps/platform/config/routes.ts index 06e7060..c3c0443 100644 --- a/apps/platform/config/routes.ts +++ b/apps/platform/config/routes.ts @@ -21,6 +21,11 @@ export const routes = [ component: 'model-submission', wrappers: ['@/wrappers/p2p-center-auth', '@/wrappers/component-wrapper'], }, + { + path: '/periodic-task-detail', + component: 'periodic-task-detail', + wrappers: ['@/wrappers/p2p-center-auth', '@/wrappers/component-wrapper'], + }, { path: '/node', component: 'new-node', diff --git a/apps/platform/package.json b/apps/platform/package.json index a911e54..52bab32 100644 --- a/apps/platform/package.json +++ b/apps/platform/package.json @@ -39,6 +39,7 @@ "react-csv": "^2.2.2", "react-syntax-highlighter": "^15.5.0", "umi": "^4.0.64", + "uuid": "^10.0.0", "valtio": "^1.10.7" }, "devDependencies": { diff --git a/apps/platform/src/app.ts b/apps/platform/src/app.ts index cabe7ab..eddd301 100644 --- a/apps/platform/src/app.ts +++ b/apps/platform/src/app.ts @@ -1,7 +1,9 @@ import { history } from 'umi'; import request from 'umi-request'; +import { v4 as uuidv4 } from 'uuid'; request.interceptors.request.use((url, options) => { + const traceId = uuidv4(); // 生成唯一的 traceId const token = localStorage.getItem('User-Token') || ''; return { url: `${url}`, @@ -13,6 +15,7 @@ request.interceptors.request.use((url, options) => { headers: { 'Content-Type': 'application/json', 'User-Token': token, + 'Trace-Id': traceId, }, }, }; diff --git a/apps/platform/src/assets/run-all.icon.svg b/apps/platform/src/assets/run-all.icon.svg index 651092c..1f00a3c 100644 --- a/apps/platform/src/assets/run-all.icon.svg +++ b/apps/platform/src/assets/run-all.icon.svg @@ -8,7 +8,7 @@ - + @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/apps/platform/src/modules/advanced-config/advanced-config-entry.tsx b/apps/platform/src/modules/advanced-config/advanced-config-entry.tsx index b5f4924..45b044f 100644 --- a/apps/platform/src/modules/advanced-config/advanced-config-entry.tsx +++ b/apps/platform/src/modules/advanced-config/advanced-config-entry.tsx @@ -1,13 +1,14 @@ import { SettingOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; + +import { DefaultModalManager } from '@/modules/dag-modal-manager'; +import { useModel } from '@/util/valtio-helper'; + import { AdvancedConfig, AdvancedConfigDrawer, } from './advanced-config-drawer/advanced-config-view'; -import { useModel } from '@/util/valtio-helper'; -import { DefaultModalManager } from '@/modules/dag-modal-manager'; - import styles from './index.less'; -import { Tooltip } from 'antd'; export const AdvancedConfigComponent: React.FC = () => { const modalManager = useModel(DefaultModalManager); diff --git a/apps/platform/src/modules/component-config/component-config-protocol.ts b/apps/platform/src/modules/component-config/component-config-protocol.ts index a76ee42..7105719 100644 --- a/apps/platform/src/modules/component-config/component-config-protocol.ts +++ b/apps/platform/src/modules/component-config/component-config-protocol.ts @@ -67,6 +67,7 @@ export const codeNameRenderKey = { 'preprocessing/psi': 'UNION_KEY_SELECT', 'preprocessing/sqlite': 'SQL', 'data_filter/sample': 'SAMPLE', + 'ml.predict/read_model': 'MODEL_SELECT', // TODO:修改名称 }; export interface ComponentConfig { diff --git a/apps/platform/src/modules/component-config/config-form-view.tsx b/apps/platform/src/modules/component-config/config-form-view.tsx index 1f77ce9..f26b663 100644 --- a/apps/platform/src/modules/component-config/config-form-view.tsx +++ b/apps/platform/src/modules/component-config/config-form-view.tsx @@ -169,7 +169,10 @@ export const ConfigFormComponent: React.FC = (prop) => { }, [node, nodeId, savedNode, mode, nodeName]); useEffect(() => { - if (pathname !== '/dag') setIsEditable(false); + if (pathname !== '/dag') { + setIsEditable(false); + return; + } if (projectEditService.canEdit.configFormDisabled) { setIsEditable(false); } else { diff --git a/apps/platform/src/modules/component-config/config-item-render/config-render-contribution.ts b/apps/platform/src/modules/component-config/config-item-render/config-render-contribution.ts index e6eaaad..54c230c 100644 --- a/apps/platform/src/modules/component-config/config-item-render/config-render-contribution.ts +++ b/apps/platform/src/modules/component-config/config-item-render/config-render-contribution.ts @@ -9,6 +9,7 @@ import { LinearModelParametersModificationRender } from './custom-render/linear- import ObservationsQuantilesRender from './custom-render/observations-quantiles-render'; import { DefaultColSelection } from './default-col-selection-template'; import { DefaultMultiTableFeatureSelection } from './default-feature-selection/default-feature-selection'; +import { DefaultModelSelect } from './default-model-selection-template'; import { DefaultNodeSelect } from './default-node-selection-template'; import { DefaultInputNumber, @@ -98,6 +99,12 @@ export class DefaultConfigRender implements ConfigRenderProtocol { }, component: DefaultTableSelect, }, + { + canHandle: (node: AtomicConfigNode, renderKey?: string) => { + return renderKey === 'MODEL_SELECT' ? 3 : false; + }, + component: DefaultModelSelect, + }, { canHandle: (node: AtomicConfigNode) => { return node.allowed_values ? 2 : false; diff --git a/apps/platform/src/modules/component-config/config-item-render/custom-render/groupby-render/index.tsx b/apps/platform/src/modules/component-config/config-item-render/custom-render/groupby-render/index.tsx index c60166d..9995c69 100644 --- a/apps/platform/src/modules/component-config/config-item-render/custom-render/groupby-render/index.tsx +++ b/apps/platform/src/modules/component-config/config-item-render/custom-render/groupby-render/index.tsx @@ -140,7 +140,7 @@ export const GroupByRender = (prop: { node: AtomicConfigNode }) => { ); const form = Form.useFormInstance(); - const groupCols = Form.useWatch('input/input_data/by', form); + const groupCols = Form.useWatch('input/input_ds/by', form); return ( @@ -169,7 +169,7 @@ export const GroupByRender = (prop: { node: AtomicConfigNode }) => { {...restField} className={styles.col} name={[name, 'column_name']} - dependencies={['input/input_data/by']} + dependencies={['input/input_ds/by']} rules={[ { required: true, message: '请选择值列' }, { diff --git a/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/default-feature-selection.tsx b/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/default-feature-selection.tsx index dc7cf45..74719d0 100644 --- a/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/default-feature-selection.tsx +++ b/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/default-feature-selection.tsx @@ -12,6 +12,7 @@ import type { AtomicConfigNode } from '../../component-config-protocol'; import styles from '../../index.less'; import type { RenderProp } from '../config-render-protocol'; +import { SingleTableFeatureSelection } from './feature-selection-only-one'; import { MultiTableFeatureSelection } from './table-feature-selection'; interface IDataTable { @@ -151,17 +152,22 @@ export const DefaultMultiTableFeatureSelection: React.FC> = ( initialValue={defaultVal} colon={false} > - + {node.col_max_cnt_inclusive === 1 ? ( + // 最多只能选择一列则变为单选下拉框 + + ) : ( + + )} ); }; diff --git a/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/feature-selection-only-one.tsx b/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/feature-selection-only-one.tsx new file mode 100644 index 0000000..638799b --- /dev/null +++ b/apps/platform/src/modules/component-config/config-item-render/default-feature-selection/feature-selection-only-one.tsx @@ -0,0 +1,103 @@ +import { useDeepCompareEffect } from 'ahooks'; +import { Select } from 'antd'; +import { uniqBy } from 'lodash'; +import { parse } from 'query-string'; +import { useState } from 'react'; +import { useLocation } from 'umi'; + +import { getGraphNodeOutput } from '@/services/secretpad/GraphController'; +import { getProjectDatatable } from '@/services/secretpad/ProjectController'; + +import type { TableInfoType } from './type'; + +export interface IProps { + /** 上游的输出表 */ + tableKeys: TableInfoType[]; + /** 上游输入的样本表 */ + fromTableKey?: TableInfoType; + onChange?: (values: string[]) => void; + value?: string[]; +} + +export const SingleTableFeatureSelection = (props: IProps) => { + const { tableKeys: tables, fromTableKey: fromTable, value = [], onChange } = props; + const [colsOptions, setCols] = useState<{ value: string; label: string }[]>([]); + const { search } = useLocation(); + + const { projectId, dagId } = parse(search) as { projectId: string; dagId: string }; + + useDeepCompareEffect(() => { + const getSchema = async () => { + const tableFields: { colName: string; colType: string }[] = []; + // 利用最上游输入的样本表 fromTable 和 上游的输出表(tables) 来进行获取特征 + const schemaArr = fromTable ? [fromTable, ...tables] : tables; + await Promise.all( + schemaArr.map(async (s) => { + const { datatableId, nodeId, graphNodeId } = s; + + if (!nodeId && graphNodeId) { + const { data } = await getGraphNodeOutput({ + projectId, + graphId: dagId, + graphNodeId, + outputId: datatableId, + }); + + if (data?.meta?.rows?.length) { + data.meta.rows.forEach( + ({ fieldTypes, fields }: { fieldTypes: string; fields: string }) => { + if (fieldTypes && fields) { + const fieldList = fields.split(','); + const fieldTypeList = fieldTypes.split(','); + tableFields.push( + ...fieldList.map((f, i) => ({ + colName: f, + colType: fieldTypeList[i], + })), + ); + } + }, + ); + } + } else { + const { data } = await getProjectDatatable({ + datatableId, + nodeId, + projectId, + type: 'CSV', + }); + if (!data) return; + const { configs } = data; + configs.map((c) => { + tableFields.push({ colName: c.colName, colType: c.colType }); + }); + } + }), + ); + const selectOptions = tableFields.map( + (option: { colName: string; colType: string }) => ({ + value: option.colName, + label: option.colName, + key: `${option.colName}_${option.colType}`, + }), + ); + const cols = uniqBy(selectOptions, 'key'); + setCols(cols); + }; + getSchema(); + }, [fromTable, tables]); + + return ( + { + onChange(val); + }} + showSearch + filterOption={(input: string, option?: { label: string; value: string }) => + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + options={modelList.map((item) => ({ + label: item.modelName, + value: item.modelId, + }))} + > + + + ); +}; diff --git a/apps/platform/src/modules/component-config/config-item-render/default-table-selection-temple.tsx b/apps/platform/src/modules/component-config/config-item-render/default-table-selection-temple.tsx index 368adf6..95d41e5 100644 --- a/apps/platform/src/modules/component-config/config-item-render/default-table-selection-temple.tsx +++ b/apps/platform/src/modules/component-config/config-item-render/default-table-selection-temple.tsx @@ -145,10 +145,21 @@ export const DefaultTableSelect: React.FC> = (config) => { return ( - {`1. 目前解析支持 “>=|<=|<>|!=|=|>|<| LIKE | like ”,不建议使用 like 以及 ' != - '\n2. 多个条件使用,隔开,多个条件使用and聚合`} - + <> +
{`1. 填写dt=maxpt,则获取最新分区;dt为分区字段`}
+
+ {'2. 如自定义规则获取分区表,可填写如:dt=${yyyymmdd+/- 3}'} +
+
+ {'3. 如选择多表自动union,则填写如dt=20240607 or dt=20240608'} +
+
+ { + '4. 支持and or 作为多个分区列条件聚合,支持 = != < > >= <= 作为分区列比较条件,其他暂不支持' + } +
+
{'5. 条件列必须是添加数据表时指定的一级或二级分区字段'}
+ } label={
分区
} name={'datatable_partition'} diff --git a/apps/platform/src/modules/component-config/config-modal.tsx b/apps/platform/src/modules/component-config/config-modal.tsx index a37998d..a6a03b0 100644 --- a/apps/platform/src/modules/component-config/config-modal.tsx +++ b/apps/platform/src/modules/component-config/config-modal.tsx @@ -29,8 +29,8 @@ export const ComponentConfigDrawer: React.FC = () => { inputNodes, label, upstreamSampleNodes, + nodeDef, } = data || {}; - const [, idNum] = id?.match(/.*-([0-9]+)$/) || []; const [isRecordDrawerOpen, setRecordDrawerStatus] = useState(false); @@ -73,6 +73,10 @@ export const ComponentConfigDrawer: React.FC = () => { 组件类型: 纵向 +
+ 组件版本: + {nodeDef?.version} +
组件 ID: {idNum} diff --git a/apps/platform/src/modules/component-config/template-quick-config/quick-config-psi.tsx b/apps/platform/src/modules/component-config/template-quick-config/quick-config-psi.tsx index 68f36ca..c760122 100644 --- a/apps/platform/src/modules/component-config/template-quick-config/quick-config-psi.tsx +++ b/apps/platform/src/modules/component-config/template-quick-config/quick-config-psi.tsx @@ -1,6 +1,6 @@ import { PlusCircleFilled, DeleteOutlined } from '@ant-design/icons'; import type { FormInstance } from 'antd'; -import { Form, Select, Tag, Typography } from 'antd'; +import { Form, Input, Select, Tag, Typography } from 'antd'; import { parse } from 'query-string'; import type { Dispatch, SetStateAction } from 'react'; import { useState, useEffect } from 'react'; @@ -18,7 +18,12 @@ import styles from './index.less'; const { Option } = Select; type QuickConfigPSIComponentProps = { - tables: { datatableId: string; nodeName: string; datatableName: string }[]; + tables: { + datatableId: string; + nodeName: string; + datatableName: string; + isPartitionTable?: boolean; + }[]; tableList: (API.ProjectDatatableBaseVO & { nodeId: string | undefined; nodeName: string | undefined; @@ -131,7 +136,7 @@ export const QuickConfigPSIComponent = (props: QuickConfigPSIComponentProps) => }} > + + {({ getFieldValue }) => { + const selectId = getFieldValue('dataTableReceiver'); + const selected = tables.find( + (t) => t.datatableId === selectId?.s, + )?.isPartitionTable; + if (selected && type === 'MPC') { + return ( + +
{`1. 填写dt=maxpt,则获取最新分区;dt为分区字段`}
+
+ {'2. 如自定义规则获取分区表,可填写如:dt=${yyyymmdd+/- 3}'} +
+
+ {'3. 如选择多表自动union,则填写如dt=20240607 or dt=20240608'} +
+
+ { + '4. 支持and or 作为多个分区列条件聚合,支持 = != < > >= <= 作为分区列比较条件,其他暂不支持' + } +
+
{'5. 条件列必须是添加数据表时指定的一级或二级分区字段'}
+ + } + label={
分区
} + name={'dataTableReceiverPartition'} + > + +
+ ); + } + return false; + }} +
{(fields, { add, remove }, { errors }) => @@ -224,7 +281,7 @@ export const QuickConfigPSIComponent = (props: QuickConfigPSIComponentProps) => }} > + + {({ getFieldValue }) => { + const selectId = getFieldValue('dataTableSender'); + const selected = tables.find( + (t) => t.datatableId === selectId?.s, + )?.isPartitionTable; + if (selected && type === 'MPC') { + return ( + +
{`1. 填写dt=maxpt,则获取最新分区;dt为分区字段`}
+
+ {'2. 如自定义规则获取分区表,可填写如:dt=${yyyymmdd+/- 3}'} +
+
+ {'3. 如选择多表自动union,则填写如dt=20240607 or dt=20240608'} +
+
+ { + '4. 支持and or 作为多个分区列条件聚合,支持 = != < > >= <= 作为分区列比较条件,其他暂不支持' + } +
+
{'5. 条件列必须是添加数据表时指定的一级或二级分区字段'}
+ + } + label={
分区
} + name={'dataTableSenderPartition'} + > + +
+ ); + } + return false; + }} +
+ {(fields, { add, remove }, { errors }) => fields.map((field, index) => ( @@ -376,6 +486,7 @@ export const QuickConfigPSI = (props: { type?: 'MPC' | 'TEE' }) => { datatableId: string; nodeName: string; datatableName: string; + isPartitionTable: boolean; // 是否是ODPS分区表 }[] = []; const dataTableList: (API.ProjectDatatableBaseVO & { nodeId: string | undefined; @@ -397,6 +508,8 @@ export const QuickConfigPSI = (props: { type?: 'MPC' | 'TEE' }) => { datatableId: table.datatableId, nodeName: nodeName as string, datatableName: table.datatableName, + isPartitionTable: + table.partition?.type === 'odps' && table.partition?.fields, }); }); }); diff --git a/apps/platform/src/modules/component-config/template-quick-config/quick-config-risk.tsx b/apps/platform/src/modules/component-config/template-quick-config/quick-config-risk.tsx index 4b0386c..fe5df8a 100644 --- a/apps/platform/src/modules/component-config/template-quick-config/quick-config-risk.tsx +++ b/apps/platform/src/modules/component-config/template-quick-config/quick-config-risk.tsx @@ -13,7 +13,12 @@ import { QuickConfigPSIComponent } from './quick-config-psi'; export const QuickConfigRisk = () => { const form = Form.useFormInstance(); const [tables, setTables] = useState< - { datatableId: string; nodeName: string; datatableName: string }[] + { + datatableId: string; + nodeName: string; + datatableName: string; + isPartitionTable?: boolean; + }[] >([]); const [nodeOptions, setNodeOptions] = useState< { @@ -71,6 +76,7 @@ export const QuickConfigRisk = () => { datatableId: string; nodeName: string; datatableName: string; + isPartitionTable: boolean; // 是否是ODPS分区表 }[] = []; const dataTableList: (API.ProjectDatatableBaseVO & { nodeId: string | undefined; @@ -91,6 +97,8 @@ export const QuickConfigRisk = () => { datatableId: table.datatableId, nodeName: nodeName as string, datatableName: table.datatableName, + isPartitionTable: + table.partition?.type === 'odps' && table.partition?.fields, }); }); }); diff --git a/apps/platform/src/modules/cooperative-node-list/cooperative-node-detail-modal.tsx b/apps/platform/src/modules/cooperative-node-list/cooperative-node-detail-modal.tsx index 9302434..d3da79a 100644 --- a/apps/platform/src/modules/cooperative-node-list/cooperative-node-detail-modal.tsx +++ b/apps/platform/src/modules/cooperative-node-list/cooperative-node-detail-modal.tsx @@ -154,6 +154,9 @@ export const CooperativeNodeDetailDrawer = ({
+ + {cooperativeNodeDetail.dstNode?.nodeName} + {cooperativeNodeDetail.dstNetAddress} diff --git a/apps/platform/src/modules/cooperative-node-list/index.tsx b/apps/platform/src/modules/cooperative-node-list/index.tsx index 699e309..e5123cb 100644 --- a/apps/platform/src/modules/cooperative-node-list/index.tsx +++ b/apps/platform/src/modules/cooperative-node-list/index.tsx @@ -105,6 +105,22 @@ export const CooperativeNodeListComponent = () => { ), }, + { + title: '本方节点', + dataIndex: 'dstNode', + key: 'dstNode', + ellipsis: true, + width: '10%', + render: (dstNode: string, record) => ( + + {record.dstNode?.nodeName || '- -'} + + ), + }, { title: ( <> diff --git a/apps/platform/src/modules/dag-log/dag-log.service.ts b/apps/platform/src/modules/dag-log/dag-log.service.ts index 6904d4d..977b30c 100644 --- a/apps/platform/src/modules/dag-log/dag-log.service.ts +++ b/apps/platform/src/modules/dag-log/dag-log.service.ts @@ -192,9 +192,10 @@ export class DagLogService extends Model { graphNodeId: id, }); } else if (from === 'record') { + const jobId = id.split('-')[0]; logResponse = await getJobLog({ projectId, - jobId: graphId, + jobId: jobId, taskId: id, }); } diff --git a/apps/platform/src/modules/dag-log/log.drawer.layout.tsx b/apps/platform/src/modules/dag-log/log.drawer.layout.tsx index e60c2ad..8bd37f4 100644 --- a/apps/platform/src/modules/dag-log/log.drawer.layout.tsx +++ b/apps/platform/src/modules/dag-log/log.drawer.layout.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import classnames from 'classnames'; import { parse } from 'query-string'; import React, { useEffect } from 'react'; +import { useLocation } from 'umi'; import { hasAccess, Platform } from '@/components/platform-wrapper'; import { DefaultModalManager } from '@/modules/dag-modal-manager'; @@ -15,7 +16,6 @@ import { LoginService } from '../login/login.service'; import { DagLogService } from './dag-log.service'; import styles from './index.less'; import { SlsService } from './sls-service'; -import { useLocation } from 'umi'; const CONFIG_MIN_WIDTH = 30; const CONFIG_MAX_WIDTH = 600; @@ -46,7 +46,8 @@ export const DagLogDrawer = ({ children }: IDagLogDrawer) => { }; const isAutonomyMode = hasAccess({ type: [Platform.AUTONOMY] }); - const { search } = useLocation(); + const { search, pathname } = useLocation(); + slsService.currentPathName = pathname; const { ownerId } = parse(search); useEffect(() => { diff --git a/apps/platform/src/modules/dag-log/sls-service.ts b/apps/platform/src/modules/dag-log/sls-service.ts index 6a4c96b..40fa773 100644 --- a/apps/platform/src/modules/dag-log/sls-service.ts +++ b/apps/platform/src/modules/dag-log/sls-service.ts @@ -7,6 +7,9 @@ import { Model } from '@/util/valtio-helper'; import { StatusMap } from './dag-log.service'; +const RECORD_TYPES = ['/record', '/model-submission']; +const PIPELINE_TYPES = ['/dag']; + export class SlsService extends Model { // 获取当前算子的参与方列表 nodePartiesList: { @@ -31,6 +34,8 @@ export class SlsService extends Model { slsLogContent = ''; + currentPathName = ''; + logTipContent: { status: string; //StatusType; current?: number; @@ -63,23 +68,30 @@ export class SlsService extends Model { const { mode } = parse(search); if (mode === PadMode.TEE) return; if (!this.slsLogIsConfig) return; - if (!currentNodePartiesId) return; const { label, id } = data; + if (!currentNodePartiesId) { + this.slsLogContent = ''; + this.logTipContent = { + status: 'default', + name: label, + }; + return; + } let logResponse = { data: { logs: [] }, } as API.SecretPadResponse_GraphNodeTaskLogsVO_; - - if (from === 'pipeline') { + if (from === 'pipeline' || PIPELINE_TYPES.includes(this.currentPathName)) { logResponse = await API.CloudLogController.getCloudLog({ projectId, graphId, graphNodeId: id, nodeId: currentNodePartiesId, }); - } else if (from === 'record') { + } else if (from === 'record' || RECORD_TYPES.includes(this.currentPathName)) { + const jobId = id.split('-')[0]; logResponse = await API.CloudLogController.getCloudLog({ projectId, - jobId: graphId, + jobId: jobId, taskId: id, nodeId: currentNodePartiesId, }); diff --git a/apps/platform/src/modules/dag-record/graph-request-service.ts b/apps/platform/src/modules/dag-record/graph-request-service.ts index a836f60..f27c391 100644 --- a/apps/platform/src/modules/dag-record/graph-request-service.ts +++ b/apps/platform/src/modules/dag-record/graph-request-service.ts @@ -1,12 +1,15 @@ import type { GraphModel, NodeStatus } from '@secretflow/dag'; import { parse } from 'query-string'; +import { history } from 'umi'; import { ComponentConfigRegistry } from '@/modules/component-config/component-config-registry'; import { DefaultComponentTreeService } from '@/modules/component-tree/component-tree-service'; import { GraphRequestService } from '@/modules/main-dag/graph-request-service'; import { nodeStatus } from '@/modules/main-dag/util'; +import { PeriodicDetailType } from '@/modules/periodic-task/type'; import { DefaultRecordService } from '@/modules/pipeline-record-list/record-service'; import { getJob } from '@/services/secretpad/ProjectController'; +import { info, taskInfo } from '@/services/secretpad/ScheduledController'; import { getModel } from '@/util/valtio-helper'; export class GraphRecordRequestService extends GraphRequestService { @@ -25,10 +28,7 @@ export class GraphRecordRequestService extends GraphRequestService { // stopRun: (dagId: string, componentId: string) => Promise; // getMaxNodeIndex: (dagId: string) => Promise; async queryStatus(dagId: string) { - const { data } = await getJob({ - projectId: getProjectId(), - jobId: dagId, - }); + const { data } = await getInfoQuery(dagId); const { graph, finished } = data || {}; if (!graph) return { @@ -48,10 +48,7 @@ export class GraphRecordRequestService extends GraphRequestService { } async queryDag(dagId: string) { - const { data } = await getJob({ - projectId: getProjectId(), - jobId: dagId, - }); + const { data } = await getInfoQuery(dagId); const { graph } = data || {}; if (!graph) return { nodes: [], edges: [] }; const graphData: { nodes: any[]; edges: any[] } = {} as any; @@ -84,12 +81,7 @@ export class GraphRecordRequestService extends GraphRequestService { const { search } = window.location; const { projectId, dagId } = parse(search); if (!projectId || !dagId) return; - - const { data } = await getJob({ - projectId: projectId as string, - jobId: dagId as string, - }); - + const { data } = await getInfoQuery(dagId as string); const { graph = {} } = data || {}; const { nodes } = graph; const node = nodes?.find((n) => n.graphNodeId === nodeId); @@ -101,11 +93,7 @@ export class GraphRecordRequestService extends GraphRequestService { const { search } = window.location; const { projectId, dagId } = parse(search) as { projectId: string; dagId: string }; if (!graphNodeId || !projectId || !dagId) return []; - const { data } = await getJob({ - projectId: getProjectId(), - jobId: dagId, - graphNodeId, - }); + const { data } = await getInfoQuery(dagId, graphNodeId); const nodes = data?.graph?.nodes || []; return nodes.find((item) => item.graphNodeId === graphNodeId)?.parties || []; } @@ -116,3 +104,63 @@ const getProjectId = () => { const { projectId } = parse(search); return projectId as string; }; + +const getInfoQuery = async (dagId: string, graphNodeId?: string) => { + let queryDagInfo; + // periodicType 存在证明是从周期任务那边跳转过来 + const { periodicType, scheduleId } = getPeriodicHistoryState(); + if (periodicType && periodicType === PeriodicDetailType.TASK) { + // 周期任务 + queryDagInfo = await info({ + scheduleId: dagId, + }); + } else if (periodicType === PeriodicDetailType.CHILDTASK) { + // 周期子任务 + queryDagInfo = await taskInfo({ + scheduleId, + scheduleTaskId: dagId, + }); + } else { + // record 详情 + queryDagInfo = await getJob({ + projectId: getProjectId(), + jobId: dagId, + graphNodeId, + }); + } + return queryDagInfo; +}; + +/** + * periodicType 周期任务类型 + * scheduleId 周期任务主任务ID + * scheduleTaskId 周期任务子任务ID + * historyDagId 点击周期任务详情跳转前url上的dagID + * periodicJobId 周期任务详情实际的jobID + * periodicGraphId 周期任务详情实际的画布ID + */ +export const getPeriodicHistoryState = () => { + const { + periodicType, + scheduleId, + scheduleTaskId, + historyDagId, + periodicJobId, + periodicGraphId, + } = (history.location.state || {}) as { + periodicType: PeriodicDetailType | undefined; + scheduleId: string | undefined; + scheduleTaskId: string | undefined; + historyDagId: string | undefined; + periodicJobId: string | undefined; + periodicGraphId: string | undefined; + }; + return { + periodicType, + scheduleId, + scheduleTaskId, + historyDagId, + periodicJobId, + periodicGraphId, + }; +}; diff --git a/apps/platform/src/modules/dag-record/graph-service.ts b/apps/platform/src/modules/dag-record/graph-service.ts index 4685d40..b4cfc1c 100644 --- a/apps/platform/src/modules/dag-record/graph-service.ts +++ b/apps/platform/src/modules/dag-record/graph-service.ts @@ -15,6 +15,7 @@ import { getJobTaskOutput } from '@/services/secretpad/ProjectController'; import { getModel, Model } from '@/util/valtio-helper'; import mainDag from './dag'; +import { getPeriodicHistoryState } from './graph-request-service'; export class RecordGraphService extends Model implements GraphEventHandlerProtocol { componentService = getModel(DefaultComponentTreeService); @@ -34,10 +35,14 @@ export class RecordGraphService extends Model implements GraphEventHandlerProtoc const { projectId } = parse(search); const [, nodeId] = outputId.match(/(.*)-output-([0-9]+)$/) || []; if (!nodeId) return; + + const { periodicType, periodicJobId } = getPeriodicHistoryState(); + const currentJobId = periodicType ? periodicJobId : graphId; + const { data, status } = await getJobTaskOutput({ projectId: projectId as string, - jobId: graphId, - taskId: `${graphId}-${nodeId}`, + jobId: currentJobId, + taskId: `${currentJobId}-${nodeId}`, outputId, }); diff --git a/apps/platform/src/modules/dag-record/graph.tsx b/apps/platform/src/modules/dag-record/graph.tsx index 74faaa3..38f9568 100644 --- a/apps/platform/src/modules/dag-record/graph.tsx +++ b/apps/platform/src/modules/dag-record/graph.tsx @@ -1,5 +1,6 @@ import type { GraphManager } from '@secretflow/dag'; import { splitPortId } from '@secretflow/dag'; +import { history } from 'umi'; import { DefaultModalManager } from '@/modules/dag-modal-manager'; import { GraphComponents, GraphView } from '@/modules/main-dag/graph'; @@ -24,6 +25,11 @@ export class RecordGraphView extends GraphView { graphService = getModel(RecordGraphService); onViewMount() { + // 如果 periodicType 有值,代表是周期任务那边打开的详情,则不需要自动打开 RecordListDrawerItem + const { periodicType } = (history.location.state || {}) as { + periodicType: string | undefined; + }; + if (periodicType) return; setTimeout(() => { this.modelManager.openModal(RecordListDrawerItem.id); }, 100); diff --git a/apps/platform/src/modules/dag-record/record-result-view.tsx b/apps/platform/src/modules/dag-record/record-result-view.tsx index 63a43d7..6a2607c 100644 --- a/apps/platform/src/modules/dag-record/record-result-view.tsx +++ b/apps/platform/src/modules/dag-record/record-result-view.tsx @@ -20,22 +20,24 @@ import { DefaultRecordService } from '@/modules/pipeline-record-list/record-serv import { getModel, Model, useModel } from '@/util/valtio-helper'; import mainDag from './dag'; +import { getPeriodicHistoryState } from './graph-request-service'; import styles from './index.less'; export const RecordResultComponent = () => { const { search } = useLocation(); const recordId = parse(search)?.dagId as string; + const { periodicJobId, periodicType } = getPeriodicHistoryState(); + const viewInstance = useModel(RecordResultView); const [record, setRecord] = useState(); - useEffect(() => { const getRecord = async (id: string) => { const res = await viewInstance.getRecord(id); if (res) setRecord(res); }; - - getRecord(recordId); + // periodicType 存在代表是周期任务跳转的详情,需要使用 periodicJobId + getRecord(periodicType ? (periodicJobId as string) : recordId); viewInstance.setResultTypeSelected(); }, [recordId, viewInstance.recordList]); diff --git a/apps/platform/src/modules/dag-result/index.less b/apps/platform/src/modules/dag-result/index.less index 1cf8bfb..ac4dfaa 100644 --- a/apps/platform/src/modules/dag-result/index.less +++ b/apps/platform/src/modules/dag-result/index.less @@ -48,6 +48,10 @@ margin-bottom: 0; } + :global(.ant-typography-copy) { + margin-bottom: 10px !important; + } + :global { .ant-table-thead, .ant-table-tbody { diff --git a/apps/platform/src/modules/dag-result/result-modal.tsx b/apps/platform/src/modules/dag-result/result-modal.tsx index 29dc3be..6091707 100644 --- a/apps/platform/src/modules/dag-result/result-modal.tsx +++ b/apps/platform/src/modules/dag-result/result-modal.tsx @@ -30,7 +30,6 @@ export const ResultDrawer = () => { const { visible, data, close } = modalManager.modals[resultDrawer.id]; const { data: resultData, codeName, outputId } = data || {}; - const handleClose = () => { modalManager.closeModal(resultDrawer.id); }; diff --git a/apps/platform/src/modules/dag-result/result-model.tsx b/apps/platform/src/modules/dag-result/result-model.tsx index efe69a3..8468197 100644 --- a/apps/platform/src/modules/dag-result/result-model.tsx +++ b/apps/platform/src/modules/dag-result/result-model.tsx @@ -13,14 +13,15 @@ import { ResultManagerService } from '../result-manager/result-manager.service'; import { Download } from './apply-download'; import styles from './index.less'; import type { ResultComponentProps } from './types'; -import { formatTimestamp } from './utils'; +import { formatTimestamp, getDownloadBtnTitle } from './utils'; const { Paragraph } = Typography; export const ResultModelComponent = (props: ResultComponentProps<'model'>) => { const { data, id } = props; const { mode, projectId } = parse(window.location.search); - const { gmtCreate, meta, jobId, taskId, type } = data; + const { gmtCreate, meta, jobId, taskId, type, codeName } = data; + const isReadModal = codeName === 'ml.predict/read_model'; // 读模型产出的模型不允许下载 const { rows } = meta; const resultManagerService = getModel(ResultManagerService); const { pathname } = useLocation(); @@ -36,7 +37,8 @@ export const ResultModelComponent = (props: ResultComponentProps<'model'>) => { if ( datasourceType === DataSourceType.OSS || - datasourceType === DataSourceType.ODPS + datasourceType === DataSourceType.ODPS || + datasourceType === DataSourceType.MYSQL ) { setDownloadBtnDisabled({ disable: true, @@ -89,38 +91,44 @@ export const ResultModelComponent = (props: ResultComponentProps<'model'>) => { )} - {!hasAccess({ type: [Platform.AUTONOMY] }) && mode === 'TEE' && ( - - )} - {/* p2p 模式下不用申请,直接下载 */} - {hasAccess({ type: [Platform.AUTONOMY] }) && ( - - - + {!isReadModal && ( + <> + {!hasAccess({ type: [Platform.AUTONOMY] }) && mode === 'TEE' && ( + + )} + {/* p2p 模式下不用申请,直接下载 */} + {hasAccess({ type: [Platform.AUTONOMY] }) && ( + + + + )} + )} ); diff --git a/apps/platform/src/modules/dag-result/result-report.tsx b/apps/platform/src/modules/dag-result/result-report.tsx index ed52c06..ac916df 100644 --- a/apps/platform/src/modules/dag-result/result-report.tsx +++ b/apps/platform/src/modules/dag-result/result-report.tsx @@ -20,6 +20,7 @@ export const getTabsName = (name: string | undefined, index: number) => { eq_frequent_bin_report: '等频', eq_range_bin_report: '等宽', head_report: '头表', + eq_range_bin_tbl: '等宽', }; return reportMapZh[name] || name; }; diff --git a/apps/platform/src/modules/dag-result/result-rules.tsx b/apps/platform/src/modules/dag-result/result-rules.tsx index d63da8e..2fecc5b 100644 --- a/apps/platform/src/modules/dag-result/result-rules.tsx +++ b/apps/platform/src/modules/dag-result/result-rules.tsx @@ -13,7 +13,7 @@ import { ResultManagerService } from '../result-manager/result-manager.service'; import style from './index.less'; import type { ResultComponentProps } from './types'; -import { formatTimestamp } from './utils'; +import { formatTimestamp, getDownloadBtnTitle } from './utils'; const { Paragraph } = Typography; @@ -37,7 +37,8 @@ export const ResultRuleComponent = (props: ResultComponentProps<'rule'>) => { if ( datasourceType === DataSourceType.OSS || - datasourceType === DataSourceType.ODPS + datasourceType === DataSourceType.ODPS || + datasourceType === DataSourceType.MYSQL ) { setDownloadBtnDisabled({ disable: true, @@ -109,7 +110,7 @@ export const ResultRuleComponent = (props: ResultComponentProps<'rule'>) => { diff --git a/apps/platform/src/modules/dag-result/result-table.tsx b/apps/platform/src/modules/dag-result/result-table.tsx index fab1b5d..ee46db1 100644 --- a/apps/platform/src/modules/dag-result/result-table.tsx +++ b/apps/platform/src/modules/dag-result/result-table.tsx @@ -28,7 +28,7 @@ import { ResultManagerService } from '../result-manager/result-manager.service'; import styles from './index.less'; import type { DataType, ResultComponentProps } from './types'; -import { formatTimestamp } from './utils'; +import { formatTimestamp, getDownloadBtnTitle } from './utils'; export const ResultTableComponent = (props: ResultComponentProps<'table'>) => { const [searchText, setSearchText] = useState(''); @@ -140,7 +140,8 @@ export const ResultTableComponent = (props: ResultComponentProps<'table'>) => { if ( datasourceType === DataSourceType.OSS || - datasourceType === DataSourceType.ODPS + datasourceType === DataSourceType.ODPS || + datasourceType === DataSourceType.MYSQL ) { setDownloadBtnDisabled({ disable: true, @@ -273,7 +274,7 @@ export const ResultTableComponent = (props: ResultComponentProps<'table'>) => { diff --git a/apps/platform/src/modules/dag-result/utils.ts b/apps/platform/src/modules/dag-result/utils.ts index b4da7b2..fea9502 100644 --- a/apps/platform/src/modules/dag-result/utils.ts +++ b/apps/platform/src/modules/dag-result/utils.ts @@ -1,10 +1,25 @@ import dayjs from 'dayjs'; +import { DataSourceType } from '@/modules/data-source-list/type'; + export const formatTimestamp = (timestamp: string) => { + if (!timestamp) return ''; const min = new Date(timestamp).getTime() / 1000 / 60; const localNow = new Date().getTimezoneOffset(); const localTime = min - localNow; - return dayjs(new Date(localTime * 1000 * 60)).format('YYYY-MM-DD HH:mm:ss'); }; + +export const getDownloadBtnTitle = (type: DataSourceType, path?: string) => { + switch (type) { + case DataSourceType.OSS: + return `OSS 文件不支持直接下载,请到 OSS 对应 bucket 的预设路径下找到文件下载,地址:${path}`; + case DataSourceType.ODPS: + return `ODPS 文件不支持直接下载,请到 ODPS 对应项目下找到文件下载,地址:${path}`; + case DataSourceType.MYSQL: + return `MYSQL 文件不支持直接下载,请到 MYSQL 对应的数据库下找到文件下载,地址:${path}`; + default: + return ''; + } +}; diff --git a/apps/platform/src/modules/data-manager/data-manager.service.ts b/apps/platform/src/modules/data-manager/data-manager.service.ts index 9011f43..bff595c 100644 --- a/apps/platform/src/modules/data-manager/data-manager.service.ts +++ b/apps/platform/src/modules/data-manager/data-manager.service.ts @@ -41,4 +41,5 @@ export enum DataSourceType { HTTP = 'HTTP', ODPS = 'ODPS', LOCAL = 'LOCAL', + MYSQL = 'MYSQL', } diff --git a/apps/platform/src/modules/data-manager/data-manager.view.tsx b/apps/platform/src/modules/data-manager/data-manager.view.tsx index b54d235..b69761c 100644 --- a/apps/platform/src/modules/data-manager/data-manager.view.tsx +++ b/apps/platform/src/modules/data-manager/data-manager.view.tsx @@ -96,6 +96,7 @@ export const DataManagerComponent: React.FC = () => { { text: 'HTTP', value: DataSourceType.HTTP }, { text: 'LOCAL', value: DataSourceType.LOCAL }, { text: 'ODPS', value: DataSourceType.ODPS }, + { text: 'MYSQL', value: DataSourceType.MYSQL }, ], }, { @@ -289,6 +290,7 @@ export const DataManagerComponent: React.FC = () => { record.datatableId || '', messageApi, record.type as string, + record.datasourceType, currentNodeId, ); }, @@ -417,15 +419,13 @@ export const DataManagerComponent: React.FC = () => { /> )} - {viewInstance.showDataAddDrawer && ( - { - viewInstance.getTableList(); - viewInstance.showDataAddDrawer = false; - }} - visible={viewInstance.showDataAddDrawer} - /> - )} + { + viewInstance.getTableList(); + viewInstance.showDataAddDrawer = false; + }} + visible={viewInstance.showDataAddDrawer} + /> {contextHolder} ); @@ -528,6 +528,7 @@ export class DataManagerView extends Model { dataId: string, messageApi: MessageInstance, type: string, + datasourceType: string, currentNodeId?: string, ) => { if (!currentNodeId) return; @@ -535,12 +536,14 @@ export class DataManagerView extends Model { nodeId: currentNodeId, datatableId: dataId, type, + datasourceType: datasourceType, }); if (status && status.code !== 0) { messageApi.error(status.msg); return; } messageApi.success(`「${datatableName}」删除成功!`); + this.pageNumber = 1; this.getTableList(); }; @@ -574,6 +577,7 @@ export class DataManagerView extends Model { } else { this.statusFilter = 'UnAvailable'; } + this.pageNumber = 1; this.getTableList('', true); } @@ -594,6 +598,7 @@ export class DataManagerView extends Model { datatableId: record.datatableId, nodeId: refreshTableNodeId, type: record.type, + datasourceType: record.datasourceType, }); if (status?.code === 0) { message.success('数据状态刷新成功'); diff --git a/apps/platform/src/modules/data-source-list/components/create-data-source/index.tsx b/apps/platform/src/modules/data-source-list/components/create-data-source/index.tsx index f885f97..a500a37 100644 --- a/apps/platform/src/modules/data-source-list/components/create-data-source/index.tsx +++ b/apps/platform/src/modules/data-source-list/components/create-data-source/index.tsx @@ -4,6 +4,7 @@ import { PlusOutlined, } from '@ant-design/icons'; import { + Alert, Button, Descriptions, Drawer, @@ -44,12 +45,12 @@ export const CreateDataSourceModal: React.FC<{ const [form] = Form.useForm(); const values = Form.useWatch([], form); const nodeIdsValue = Form.useWatch('nodeIds', form); - const [disabled, setDisabled] = useState(true); // const [nodeStatus, setNodeStatus] = useState('pending'); const closeHandler = () => { onClose(); + form.resetFields(); }; const handleOk = async () => { @@ -74,7 +75,7 @@ export const CreateDataSourceModal: React.FC<{ if (errorNodeList.length !== 0) { notificationApi.info({ message: '部分节点创建失败', - duration: null, + duration: 3, description: ( {errorNodeList.map((node) => { @@ -89,13 +90,14 @@ export const CreateDataSourceModal: React.FC<{ ), }); + closeHandler(); return; } } - onClose(); + closeHandler(); } else { message.error(status?.msg || '注册失败'); - onClose(); + closeHandler(); } }); }; @@ -261,6 +263,9 @@ export const CreateDataSourceModal: React.FC<{ ODPS + + MYSQL +
@@ -353,6 +358,69 @@ export const CreateDataSourceModal: React.FC<{ > + ) : getFieldValue('type') === DataSourceType.MYSQL ? ( + <> + + + + + + + + + + + + + + + + + ) : ( <> { + const { data } = props; + + return ( +
+ + {data.type} + + {data.info?.endpoint || '--'} + + {data?.info?.user || '--'} + ****** + + {data?.info?.database || '--'} + + +
+ ); +}; + const DataSourceInfoRender = (props: { item: API.DatasourceDetailAggregateVO }) => { const { item } = props; if (!item.type) return <>{'--'}; @@ -81,6 +101,7 @@ const DataSourceInfoRender = (props: { item: API.DatasourceDetailAggregateVO }) [DataSourceType.OSS]: , [DataSourceType.HTTP]: , [DataSourceType.ODPS]: , + [DataSourceType.MYSQL]: , }), [item], ); diff --git a/apps/platform/src/modules/data-source-list/data-source-list.service.ts b/apps/platform/src/modules/data-source-list/data-source-list.service.ts index aa22b5a..087ee04 100644 --- a/apps/platform/src/modules/data-source-list/data-source-list.service.ts +++ b/apps/platform/src/modules/data-source-list/data-source-list.service.ts @@ -71,6 +71,7 @@ export enum DataSourceType { 'OSS' = 'OSS', 'HTTP' = 'HTTP', 'ODPS' = 'ODPS', // 未支持 + 'MYSQL' = 'MYSQL', } export enum DataSourceStatus { diff --git a/apps/platform/src/modules/data-source-list/index.tsx b/apps/platform/src/modules/data-source-list/index.tsx index 65f627c..1efdb3a 100644 --- a/apps/platform/src/modules/data-source-list/index.tsx +++ b/apps/platform/src/modules/data-source-list/index.tsx @@ -69,6 +69,7 @@ export const DataSourceListComponent = () => { { text: 'OSS', value: DataSourceType.OSS }, { text: 'HTTP', value: DataSourceType.HTTP }, { text: 'ODPS', value: DataSourceType.ODPS }, + { text: 'MYSQL', value: DataSourceType.MYSQL }, ], }, { @@ -188,7 +189,12 @@ export const DataSourceListComponent = () => { onChange={(pagination, filters) => { viewInstance.typeFilters = filters?.type === null - ? ([DataSourceType.OSS, DataSourceType.HTTP] as FilterValue) + ? ([ + DataSourceType.OSS, + DataSourceType.HTTP, + DataSourceType.MYSQL, + DataSourceType.ODPS, + ] as FilterValue) : filters?.type; viewInstance.getDataSourceList(); }} @@ -207,16 +213,13 @@ export const DataSourceListComponent = () => { size="small" /> - - {viewInstance.showAddDataSourceDrawer && ( - { - viewInstance.getDataSourceList(); - viewInstance.showAddDataSourceDrawer = false; - }} - visible={viewInstance.showAddDataSourceDrawer} - /> - )} + { + viewInstance.getDataSourceList(); + viewInstance.showAddDataSourceDrawer = false; + }} + visible={viewInstance.showAddDataSourceDrawer} + /> {viewInstance.showDataSourceInfoDrawer && ( { @@ -246,6 +249,7 @@ export class DataSourceView extends Model { DataSourceType.OSS, DataSourceType.HTTP, DataSourceType.ODPS, + DataSourceType.MYSQL, ]; search = ''; diff --git a/apps/platform/src/modules/data-source-list/type.ts b/apps/platform/src/modules/data-source-list/type.ts index 5e838f8..3358d73 100644 --- a/apps/platform/src/modules/data-source-list/type.ts +++ b/apps/platform/src/modules/data-source-list/type.ts @@ -3,4 +3,5 @@ export enum DataSourceType { 'HTTP' = 'HTTP', 'ODPS' = 'ODPS', // 未支持 'LOCAL' = 'LOCAL', + 'MYSQL' = 'MYSQL', } diff --git a/apps/platform/src/modules/data-table-add/add-data/add-data-service.ts b/apps/platform/src/modules/data-table-add/add-data/add-data-service.ts index 874d64f..3732c14 100644 --- a/apps/platform/src/modules/data-table-add/add-data/add-data-service.ts +++ b/apps/platform/src/modules/data-table-add/add-data/add-data-service.ts @@ -3,7 +3,6 @@ import { parse } from 'query-string'; import { list, nodes, detail } from '@/services/secretpad/DataSourceController'; import { createDataTable } from '@/services/secretpad/DatatableController'; -import { createFeatureDatasource } from '@/services/secretpad/FeatureDatasourceController'; import { listNode } from '@/services/secretpad/InstController'; import { Model } from '@/util/valtio-helper'; @@ -12,6 +11,7 @@ export enum DataSourceType { HTTP = 'HTTP', ODPS = 'ODPS', LOCAL = 'LOCAL', + MYSQL = 'MYSQL', } type OptionType = { @@ -79,7 +79,12 @@ export class AddDataSheetService extends Model { queryDataSourceList = async (ownerId: string) => { const { data, status } = await list({ ownerId: ownerId, - types: [DataSourceType.OSS, DataSourceType.HTTP, DataSourceType.ODPS], + types: [ + DataSourceType.OSS, + DataSourceType.HTTP, + DataSourceType.ODPS, + DataSourceType.MYSQL, + ], page: 1, size: 1000, status: '', @@ -154,11 +159,34 @@ export class AddDataSheetService extends Model { return this.addOssData(params); case DataSourceType.ODPS: return this.addOdpsData(params); + case DataSourceType.MYSQL: + return this.addMysqlData(params); default: return { status: {} }; } }; + addMysqlData = async (value: AddDataSheetParams) => { + const params = { + ownerId: value.ownerId, + nodeIds: value.nodeIds, + datatableName: value.tableName, + datasourceId: value.dataSource, + type: value.type, + desc: value.tableDesc, + relativeUri: value.address, + columns: value.features.map((item: Structure) => ({ + colName: item.featureName, + colType: item.featureType, + colComment: item.featureDescription || '', + })), + datasourceType: value.datasourceType, + datasourceName: value.datasourceName, + nullStrs: value.nullStrs, + }; + return await createDataTable(params); + }; + addOdpsData = async (value: AddDataSheetParams) => { const params = { nodeIds: value.nodeIds, @@ -204,19 +232,22 @@ export class AddDataSheetService extends Model { addHttpData = async (value: AddDataSheetParams) => { const params = { - featureTableName: value.tableName, + datasourceType: value.datasourceType, + datasourceId: 'http-data-source', + datasourceName: 'http-data-source', + datatableName: value.tableName, ownerId: value.ownerId, nodeIds: value.nodeIds, type: value.type, desc: value.tableDesc, - url: value.address, + relativeUri: value.address, columns: value.features.map((item: Structure) => ({ colName: item.featureName, colType: item.featureType, colComment: item.featureDescription, })), }; - return await createFeatureDatasource(params); + return await createDataTable(params); }; addOssData = async (value: AddDataSheetParams) => { diff --git a/apps/platform/src/modules/data-table-add/add-data/add-data.view.tsx b/apps/platform/src/modules/data-table-add/add-data/add-data.view.tsx index 69bacbd..67b0e4a 100644 --- a/apps/platform/src/modules/data-table-add/add-data/add-data.view.tsx +++ b/apps/platform/src/modules/data-table-add/add-data/add-data.view.tsx @@ -125,6 +125,8 @@ export const DataAddDrawer = ({ onClose(); form.resetFields(); dataTableStructureService.featuresError = []; + UploadInstance.step = 0; + UploadInstance.fileInfo = undefined; }; const validateForm = async (options = {}) => { @@ -148,7 +150,8 @@ export const DataAddDrawer = ({ const nullStrs = currentDataSource?.type === DataSourceType.OSS || - currentDataSource?.type === DataSourceType.ODPS + currentDataSource?.type === DataSourceType.ODPS || + currentDataSource?.type === DataSourceType.MYSQL ? { nullStrs: validateRes.tableNullStrs ? JSON.parse(`[${validateRes.tableNullStrs}]`) @@ -184,7 +187,7 @@ export const DataAddDrawer = ({ if (errorNodeList.length !== 0) { notificationApi.info({ message: '部分节点创建失败', - duration: null, + duration: 3, description: ( {errorNodeList.map((node) => { @@ -199,13 +202,14 @@ export const DataAddDrawer = ({ ), }); + handleClose(); return; } } - onClose(); + handleClose(); } else { message.error(status?.msg || '添加失败'); - onClose(); + handleClose(); } } catch (error) { addDataSheetService.addDataSheetLoading = false; @@ -246,7 +250,7 @@ export const DataAddDrawer = ({ onClick={debounce(async () => { try { await UploadInstance.submit(); - onClose(); + handleClose(); } catch (e) { return; } @@ -311,6 +315,15 @@ export const DataAddDrawer = ({ />
)} + {dataSourceFormValueType === DataSourceType.MYSQL && ( + + + + )} {dataSourceFormValueType && dataSourceFormValueType !== DataSourceType.LOCAL && ( @@ -350,7 +363,8 @@ export const DataAddDrawer = ({ {dataSourceFormValueType === DataSourceType.OSS || - dataSourceFormValueType === DataSourceType.ODPS ? ( + dataSourceFormValueType === DataSourceType.ODPS || + dataSourceFormValueType === DataSourceType.MYSQL ? ( = ({ }) => { const [form] = Form.useForm(); const values = Form.useWatch([], form); + const { ownerId: centerOwnerId } = parse(window.location.search); const isAutonomy = hasAccess({ type: [Platform.AUTONOMY] }); @@ -65,7 +67,7 @@ export const UploadTable: React.FC = ({ */ const NodeId = isAutonomy ? nodeNameOptions[0]?.value || '' - : nodeService.currentNode?.nodeId || ''; + : nodeService.currentNode?.nodeId || (centerOwnerId as string); viewInstance.ownerId = NodeId; @@ -672,15 +674,27 @@ export class UploadTableView extends Model { this.submitting = true; const validateRes = await this.validateForm(); const values = validateRes; - const res = await createData({ - nodeId: this.ownerId, - name: this.fileInfo?.name, - tableName: values.tbl_name, - description: values.tbl_desc, - datatableSchema: values.schema, - realName: this.fileInfo?.realName, + const { ownerId: currentOwnerId } = parse(window.location.search); + const res = await createDataTable({ + ownerId: currentOwnerId as string, + nodeIds: [this.ownerId], + datatableName: values.tbl_name, + desc: values.tbl_desc, + columns: values.schema.map( + (item: { + featureName: string; + featureType: string; + featureDescription: string; + }) => ({ + colName: item.featureName, + colType: item.featureType, + colComment: item.featureDescription || '', + }), + ), + relativeUri: this.fileInfo?.realName, datasourceType: 'LOCAL', datasourceName: 'default-data-source', + datasourceId: 'default-data-source', nullStrs: values.tbl_nullStrs ? JSON.parse(`[${values.tbl_nullStrs}]`) : [], }); diff --git a/apps/platform/src/modules/data-table-info/data-table-auth/data-tabel-auth.view.tsx b/apps/platform/src/modules/data-table-info/data-table-auth/data-tabel-auth.view.tsx index fea3ca0..5d05658 100644 --- a/apps/platform/src/modules/data-table-info/data-table-auth/data-tabel-auth.view.tsx +++ b/apps/platform/src/modules/data-table-info/data-table-auth/data-tabel-auth.view.tsx @@ -3,6 +3,7 @@ import { Button, message, Popconfirm, Space, Tag, Table } from 'antd'; import { parse } from 'query-string'; import React, { useEffect } from 'react'; +import { hasAccess, Platform } from '@/components/platform-wrapper'; import { formatTimestamp } from '@/modules/dag-result/utils'; import { DataManagerView } from '@/modules/data-manager/data-manager.view'; import { ComputeModeType, computeModeText } from '@/modules/project-list'; @@ -13,7 +14,6 @@ import { getModel, Model, useModel } from '@/util/valtio-helper'; import styles from './index.less'; import type { ProjectAuthConfigType } from './project-auth-config'; import { ProjectAuthConfigDrawer } from './project-auth-config'; -import { hasAccess, Platform } from '@/components/platform-wrapper'; interface IProps { tableInfo: API.DatatableVO; @@ -140,6 +140,7 @@ export class DataTableAuthModel extends Model { datatableId: tableInfo.datatableId, nodeId: currentNodeId as string, type: tableInfo.type, + datasourceType: tableInfo.datasourceType, }); this.tableInfo = { ...(response.data?.datatableVO || {}), diff --git a/apps/platform/src/modules/data-table-info/data-table-info.view.tsx b/apps/platform/src/modules/data-table-info/data-table-info.view.tsx index 868c08c..8319a2e 100644 --- a/apps/platform/src/modules/data-table-info/data-table-info.view.tsx +++ b/apps/platform/src/modules/data-table-info/data-table-info.view.tsx @@ -35,7 +35,6 @@ export const DataTableInfoDrawer: React.FC> = (props) => { const isAutonomy = hasAccess({ type: [Platform.AUTONOMY] }); const tableInfo = viewInstance.tableInfo || {}; - console.log(viewInstance.tableInfo); useEffect(() => { viewInstance.tableInfo = data.tableInfo || {}; @@ -132,6 +131,7 @@ export class DataTableInfoDrawerView extends Model { datatableId: tableInfo.datatableId, nodeId: refreshTableNodeId, type: tableInfo.type, + datasourceType: tableInfo.datasourceType, }); setTimeout(() => { diff --git a/apps/platform/src/modules/layout/dag-layout/index.tsx b/apps/platform/src/modules/layout/dag-layout/index.tsx index eb08854..af6ca55 100644 --- a/apps/platform/src/modules/layout/dag-layout/index.tsx +++ b/apps/platform/src/modules/layout/dag-layout/index.tsx @@ -28,10 +28,13 @@ import { DatatableTreeComponent } from '@/modules/data-table-tree/datatable-tree import { LoginService } from '@/modules/login/login.service'; import { GraphComponents } from '@/modules/main-dag/graph'; import { ModelSubmissionEntry } from '@/modules/main-dag/model-submission-entry'; +import { PeriodicTaskEntry } from '@/modules/main-dag/periodic-task-entry'; import { RecordComponent } from '@/modules/main-dag/record'; import { ToolbarComponent } from '@/modules/main-dag/toolbar'; import { ToolbuttonComponent } from '@/modules/main-dag/toolbutton'; import { ModelListComponent } from '@/modules/model-manager'; +import { PeriodicTaskCreate } from '@/modules/periodic-task/periodic-task-drawer/create-periodic-task.view'; +import { PeriodicTaskComponent } from '@/modules/periodic-task/periodic-task-list/task.view'; import { PipelineCreationComponent } from '@/modules/pipeline/pipeline-creation-view'; import { PipelineViewComponent } from '@/modules/pipeline/pipeline-view'; import { RecordListDrawerItem } from '@/modules/pipeline-record-list/record-list-drawer-view'; @@ -64,6 +67,7 @@ export enum DagLayoutMenu { PROJECTDATA = 'project-data', MODELTRAIN = 'model-train', MODELMANAGER = 'model-manager', + PERIODICTASK = 'periodic-task', } export const DagLayout = () => { @@ -73,6 +77,7 @@ export const DagLayout = () => { const { type = 'DAG', mode, projectId } = parse(window.location.search); const goBack = async () => { + viewInstance.setInitActiveMenu(DagLayoutMenu.MODELTRAIN); const userInfo = await loginService.getUserInfo(); if (userInfo.platformType === Platform.AUTONOMY) { const { origin } = (history.location.state as { origin: string }) || {}; @@ -118,6 +123,17 @@ export const DagLayout = () => { isInit: false, projectMode: ['MPC'], }, + { + key: '周期任务', + label: '周期任务', + id: DagLayoutMenu.PERIODICTASK, + callBack: () => { + viewInstance.setPeriodicTaskShow(); + viewInstance.setActiveKey('pipeline'); + }, + isInit: false, + projectMode: ['MPC'], + }, ], PSI: [], ALL: [], @@ -201,6 +217,7 @@ export const DagLayout = () => { {/*
*/} {viewInstance.modelManagerShow && } + {viewInstance.periodicTaskShow && } {/*
*/} {viewInstance.dagShow && (
@@ -239,6 +256,8 @@ export const DagLayout = () => { + + {!isTeeProject() && }
@@ -322,6 +341,9 @@ export class DagLayoutView extends Model { initActiveMenu: string | null = ''; modelManagerShow = false; + + periodicTaskShow = false; + dagShow = true; setActiveKey = (key: string) => { @@ -339,14 +361,23 @@ export class DagLayoutView extends Model { setModelManagerShow = () => { this.modelManagerShow = true; this.dagShow = false; + this.periodicTaskShow = false; this.modalManager.closeAllModals(); }; setDagShow = () => { this.modelManagerShow = false; + this.periodicTaskShow = false; this.dagShow = true; }; + setPeriodicTaskShow = () => { + this.modelManagerShow = false; + this.dagShow = false; + this.periodicTaskShow = true; + this.modalManager.closeAllModals(); + }; + toggleLeftPanel() { this.leftPanelShow = !this.leftPanelShow; } diff --git a/apps/platform/src/modules/layout/periodic-task-detail-layout/index.less b/apps/platform/src/modules/layout/periodic-task-detail-layout/index.less new file mode 100644 index 0000000..e147e81 --- /dev/null +++ b/apps/platform/src/modules/layout/periodic-task-detail-layout/index.less @@ -0,0 +1,78 @@ +.wrap { + overflow: hidden; + width: 100%; + height: 100%; +} + +.header { + display: flex; + width: 100%; + height: 56px; + align-items: center; + padding-left: 16px; + border-bottom: 1px solid #eaebed; + + .back { + display: flex; + width: 24px; + height: 24px; + align-items: center; + justify-content: center; + border-radius: 50%; + cursor: pointer; + + &:hover { + background-color: #eee; + } + } + + .title { + color: #1d2129; + font-size: 20px; + font-weight: 500; + } + + .slot { + margin-left: 16px; + } +} + +.content { + display: flex; + width: 100%; + height: calc(100% - 56px); + box-sizing: border-box; + + .center { + position: relative; + width: 100%; + height: 100%; + + .header { + display: flex; + width: 100%; + height: 42px; + box-sizing: border-box; + align-items: center; + justify-content: space-between; + padding: 0 16px; + background-color: #f6f8fa; + + .left { + color: rgb(0 0 0 / 85%); + font-size: 14px; + } + } + + .graph { + width: calc(100% + 42px); + height: calc(100% - 42px); + } + + .toolbutton { + position: absolute; + right: 20px; + bottom: 36px; + } + } +} diff --git a/apps/platform/src/modules/layout/periodic-task-detail-layout/index.tsx b/apps/platform/src/modules/layout/periodic-task-detail-layout/index.tsx new file mode 100644 index 0000000..afcd1ab --- /dev/null +++ b/apps/platform/src/modules/layout/periodic-task-detail-layout/index.tsx @@ -0,0 +1,148 @@ +import { ArrowLeftOutlined, NodeIndexOutlined } from '@ant-design/icons'; +import { Breadcrumb, Divider } from 'antd'; +import { parse, stringify } from 'query-string'; +import { useEffect } from 'react'; +import { history } from 'umi'; + +import { PadMode } from '@/components/platform-wrapper'; +import { ComponentConfigDrawer } from '@/modules/component-config/config-modal'; +import { Log, LogLabel } from '@/modules/dag-log/log-viewer.view'; +import { DagLogDrawer } from '@/modules/dag-log/log.drawer.layout'; +import { DagLog } from '@/modules/dag-log/log.view'; +import { SlsLog, SlsLogLabel } from '@/modules/dag-log/sls-log-viewer.view'; +import { SlsService } from '@/modules/dag-log/sls-service'; +import { RecordGraphComponent } from '@/modules/dag-record/graph'; +import { RecordResultComponent } from '@/modules/dag-record/record-result-view'; +import { ResultDrawer } from '@/modules/dag-result/result-modal'; +import { PeriodicDetailType } from '@/modules/periodic-task/type'; +import { DefaultRecordService } from '@/modules/pipeline-record-list/record-service'; +import { useModel } from '@/util/valtio-helper'; + +import { DagLayoutMenu, DagLayoutView } from '../dag-layout'; + +import styles from './index.less'; + +export const PeriodicTaskDetailLayout = () => { + const slsLogService = useModel(SlsService); + const dagLayoutView = useModel(DagLayoutView); + const recordService = useModel(DefaultRecordService); + + const searchDagParams = window.location.search; + const { projectId, mode, type } = parse(searchDagParams); + const { periodicType, scheduleId, scheduleTaskId, historyDagId, periodicGraphId } = + (history.location.state || {}) as { + periodicType: string; + scheduleId: string; + scheduleTaskId: string; + historyDagId: string; + periodicGraphId: string; + }; + + const goBack = async () => { + if (!projectId) return; + const searchParams = { + dagId: historyDagId, + projectId, + mode, + type, + }; + history.push({ + pathname: '/dag', + search: stringify(searchParams), + }); + dagLayoutView.setInitActiveMenu(DagLayoutMenu.PERIODICTASK); + }; + + const logItems = [ + { + key: '1', + label: , + children: , + disabled: false, + }, + ]; + + if (mode === PadMode.MPC) { + logItems.push({ + key: '2', + label: , + disabled: !slsLogService.slsLogIsConfig, + children: , + }); + } + + const breadCrumbItem = [ + { + title: scheduleId || '', + }, + ]; + + if (periodicType === PeriodicDetailType.CHILDTASK) { + breadCrumbItem.push({ + title: scheduleTaskId || '', + }); + } + + let pollFlag: NodeJS.Timeout; + useEffect(() => { + if (periodicType && periodicGraphId) { + const fetchRecordList = async () => { + const res = await recordService.getRecordList( + projectId as string, + periodicGraphId, + 100, + 1, + ); + const data = res?.data.filter((item) => item.status === 'RUNNING'); + if (data.length) { + pollFlag = setTimeout(() => { + fetchRecordList(); + }, 5000); + } + }; + fetchRecordList(); + } + return () => { + clearTimeout(pollFlag); + }; + }, [projectId, periodicGraphId]); + + return ( +
+
+ + + + + + +
+
+
+
+
+ + {`「${ + periodicType === PeriodicDetailType.CHILDTASK + ? scheduleTaskId + : scheduleId + }」执行结果`} +
+
+ +
+
+
+
+ +
+
+
+ + + + + +
+ ); +}; diff --git a/apps/platform/src/modules/main-dag/index.less b/apps/platform/src/modules/main-dag/index.less index 3521956..f5f50d0 100644 --- a/apps/platform/src/modules/main-dag/index.less +++ b/apps/platform/src/modules/main-dag/index.less @@ -78,12 +78,19 @@ align-items: center; border-radius: 4px; margin-right: 8px; - background-color: #0068fa; - color: white; + color: rgb(0 0 0 / 65%); + + svg > g > path:last-child { + fill: rgb(0 0 0 / 65%); + } &:hover { - background-color: #0068fa !important; - color: white !important; + background-color: #f6f8fa !important; + color: #0068fa !important; + + svg > g > path:last-child { + fill: #0068fa !important; + } } } diff --git a/apps/platform/src/modules/main-dag/periodic-task-entry.tsx b/apps/platform/src/modules/main-dag/periodic-task-entry.tsx new file mode 100644 index 0000000..6eaa5dc --- /dev/null +++ b/apps/platform/src/modules/main-dag/periodic-task-entry.tsx @@ -0,0 +1,92 @@ +import { Button, Tooltip } from 'antd'; +import { parse } from 'query-string'; +import { useEffect } from 'react'; + +import { DefaultModalManager } from '@/modules/dag-modal-manager'; +import { PeriodicTaskCreateDrawer } from '@/modules/periodic-task/periodic-task-drawer/create-periodic-task.view'; +import { onceSuccess } from '@/services/secretpad/ScheduledController'; +import { Model, useModel } from '@/util/valtio-helper'; + +import mainDag from './dag'; +import type { IGraphNodeType } from './graph.protocol'; +import styles from './index.less'; + +export const PeriodicTaskEntry = () => { + const viewInstance = useModel(PeriodicTaskEntryView); + const modalManager = useModel(DefaultModalManager); + + const { disabled } = viewInstance; + + const { search } = window.location; + const { projectId, dagId } = parse(search); + + const { finished } = viewInstance.statusObj; + + useEffect(() => { + if (!projectId || !dagId) return; + viewInstance.getGraphHistoryReady(projectId as string, dagId as string); + return () => { + clearTimeout(viewInstance.graphHistoryReadyTimer); + }; + }, [dagId, finished]); + + const handleClick = () => { + modalManager.openModal(PeriodicTaskCreateDrawer.id, viewInstance.nodes); + }; + + return ( +
+ + + +
+ ); +}; + +export class PeriodicTaskEntryView extends Model { + constructor() { + super(); + mainDag.requestService.onNodeStatusChanged(this.onNodeStatusChanged.bind(this)); + mainDag.requestService.onNodeChanged(this.onNodeChanged.bind(this)); + } + + disabled = true; + + nodes: IGraphNodeType[] = []; + + statusObj: API.GraphStatus = { nodes: [], finished: true }; + + graphHistoryReadyTimer: ReturnType | undefined; + + onNodeStatusChanged = (statusObj: API.GraphStatus) => { + this.statusObj = statusObj; + }; + + onNodeChanged = (data: IGraphNodeType[]) => { + this.nodes = data; + }; + + getGraphHistoryReady = async (projectId: string, dagId: string) => { + const { data, status } = await onceSuccess({ + projectId: projectId, + graphId: dagId, + }); + if (status && status.code === 0) { + if (data) { + this.disabled = false; + } else { + this.disabled = true; + if (!this.statusObj.finished) { + clearTimeout(this.graphHistoryReadyTimer); + this.graphHistoryReadyTimer = setTimeout(() => { + this.getGraphHistoryReady(projectId, dagId); + }, 5000); + } else { + clearTimeout(this.graphHistoryReadyTimer); + } + } + } + }; +} diff --git a/apps/platform/src/modules/periodic-task/periodic-child-task-list/child-task.service.tsx b/apps/platform/src/modules/periodic-task/periodic-child-task-list/child-task.service.tsx new file mode 100644 index 0000000..3375466 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-child-task-list/child-task.service.tsx @@ -0,0 +1,177 @@ +import { message } from 'antd'; +import dayjs from 'dayjs'; + +import { + taskPage, + taskStop, + taskRerun, + taskInfo, +} from '@/services/secretpad/ScheduledController'; +import { Model } from '@/util/valtio-helper'; + +export enum TaskStatus { + SUCCESS = 'SUCCEED', + FAILED = 'FAILED', + RUNNING = 'RUNNING', + COMMITTED = 'TO_BE_RUN', + STOPPED = 'STOPPED', + STOPPING = 'STOPPING', +} + +export const TaskStatusText = { + [TaskStatus.SUCCESS]: { + label: '成功', + iconColor: 'success', + value: 'success', + }, + [TaskStatus.FAILED]: { + label: '失败', + iconColor: 'error', + value: 'error', + }, + [TaskStatus.RUNNING]: { + label: '执行中', + iconColor: 'processing', + value: 'doing', + }, + [TaskStatus.COMMITTED]: { + label: '待运行', + iconColor: 'warning', + value: 'warning', + }, + [TaskStatus.STOPPED]: { + label: '已停止', + iconColor: 'error', + value: 'error', + }, + [TaskStatus.STOPPING]: { + label: '停止中', + iconColor: 'processing', + value: 'doing', + }, +}; + +export class PeriodicChildTaskService extends Model { + periodicChildTaskTimer: ReturnType | undefined; + + periodicTaskChildList: API.TaskPageScheduledVO[] = []; + + loading = false; + + reRunLoading = false; + + pageNumber = 1; + + pageSize = 10; + + totalNum = 1; + + onViewUnMount(): void { + this.periodicChildTaskTimer && clearTimeout(this.periodicChildTaskTimer); + } + + getPeriodicTaskChildList = async ( + scheduleId?: string, + options?: { isRefresh: boolean }, + ) => { + if (!scheduleId) return; + const { isRefresh = false } = options || {}; + this.loading = true; + const params: API.TaskPageScheduledRequest = { + scheduleId, + size: isRefresh ? 10 : this.pageSize, + page: isRefresh ? 1 : this.pageNumber, + sort: { + scheduleTaskExpectStartTime: 'ASC', + }, + search: '', + }; + const { data, status } = await taskPage(params); + this.loading = false; + if (status && status.code === 0 && data) { + this.totalNum = data?.total || 0; + this.periodicTaskChildList = data?.list || []; + const flag = this.periodicTaskChildList.some( + (item) => + item.scheduleTaskStatus === TaskStatus.RUNNING || + item.scheduleTaskStatus === TaskStatus.COMMITTED || + item.scheduleTaskStatus === TaskStatus.STOPPING, + ); + if (flag) { + clearTimeout(this.periodicChildTaskTimer); + this.periodicChildTaskTimer = setTimeout(() => { + this.getPeriodicTaskChildList(scheduleId); + }, 10000); + } else { + clearTimeout(this.periodicChildTaskTimer); + } + } + }; + + /** 重跑子任务 */ + reRunTask = async (scheduleId?: string, scheduleTaskId?: string, type?: number) => { + if (!scheduleId || !scheduleTaskId || type === undefined) return; + this.reRunLoading = true; + const { status } = await taskRerun({ + scheduleId, + scheduleTaskId, + type, + }); + this.reRunLoading = false; + if (status && status.code === 0) { + message.success('任务重跑成功'); + } else { + message.error(status?.msg); + } + }; + + /** 停止子任务 */ + stopChildTask = async (scheduleId?: string, scheduleTaskId?: string) => { + if (!scheduleId || !scheduleTaskId) return; + const { status } = await taskStop({ + scheduleId, + scheduleTaskId, + }); + if (status && status.code === 0) { + message.success('任务停止成功'); + } else { + message.error(status?.msg); + } + }; + + /** 获取jobId */ + getJobId = async (scheduleId: string, scheduleTaskId: string) => { + return await taskInfo({ + scheduleId, + scheduleTaskId, + }); + }; +} + +export const renderOptionsTitle = (record: API.TaskPageScheduledVO) => { + // 运行失败且没有超过30天,则可以重跑失败, 否则需要重跑(全部重跑) + const overThirtyTime = + dayjs().diff(dayjs(record.scheduleTaskStartTime), 'day', true) > 30; + let title = ''; + if ( + (record.scheduleTaskStatus === TaskStatus.FAILED || + record.scheduleTaskStatus === TaskStatus.STOPPED) && + overThirtyTime + ) { + title = '如存在部分成功执行结果,重跑后会覆盖已产出的结果'; + } else if ( + (record.scheduleTaskStatus === TaskStatus.FAILED || + record.scheduleTaskStatus === TaskStatus.STOPPED) && + !overThirtyTime + ) { + title = '可选重跑或重跑失败部分,重跑后会覆盖该任务产出的结果。'; + } else if (record.scheduleTaskStatus === TaskStatus.SUCCESS) { + title = '重跑后会覆盖已产出的结果,确定要重跑吗?'; + } else { + title = ''; + } + return { + title, + overThirtyTime, + }; +}; diff --git a/apps/platform/src/modules/periodic-task/periodic-child-task-list/child-task.view.tsx b/apps/platform/src/modules/periodic-task/periodic-child-task-list/child-task.view.tsx new file mode 100644 index 0000000..a642535 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-child-task-list/child-task.view.tsx @@ -0,0 +1,298 @@ +import type { BadgeProps } from 'antd'; +import { Badge, Button, Drawer, Popconfirm, Space, Table, Typography } from 'antd'; +import classNames from 'classnames'; +import { useEffect, useRef } from 'react'; +import { history } from 'umi'; + +import { formatTimestamp } from '@/modules/dag-result/utils'; +import { useModel } from '@/util/valtio-helper'; + +import type { PeriodicTaskInfo } from '../type'; +import { PeriodicDetailType } from '../type'; + +import { renderOptionsTitle, TaskStatus } from './child-task.service'; +import { PeriodicChildTaskService, TaskStatusText } from './child-task.service'; +import styles from './index.less'; + +export const PeriodicChildTaskDrawer = (props: { + visible: boolean; + data?: PeriodicTaskInfo; + close: () => void; +}) => { + const { visible, data = {}, close } = props; + + const service = useModel(PeriodicChildTaskService); + + const popRef = useRef<{ closePopConfirm: VoidFunction }>(null); + + const queryChildTask = async (scheduleId: string) => { + await service.getPeriodicTaskChildList(scheduleId); + }; + + useEffect(() => { + if (!visible) return; + if (data.scheduleId) { + queryChildTask(data.scheduleId); + } + if (popRef.current) { + popRef.current?.closePopConfirm(); + } + return () => { + popRef.current?.closePopConfirm(); + }; + }, [visible, data.scheduleId]); + + const goToPeriodicTaskChildDetail = async (record: API.TaskPageScheduledVO) => { + if (!record.scheduleTaskId || !data.scheduleId) return; + const { data: taskInfo } = await service.getJobId( + data.scheduleId, + record.scheduleTaskId, + ); + const searchParams = new URL(window.location.toString()).searchParams; + const currentDagId = searchParams?.get('dagId'); + searchParams.set('dagId', record.scheduleTaskId); + history.push( + { + pathname: '/periodic-task-detail', + search: searchParams.toString(), + }, + { + periodicType: PeriodicDetailType.CHILDTASK, + scheduleId: data.scheduleId, + scheduleTaskId: record.scheduleTaskId, + historyDagId: currentDagId, + periodicJobId: taskInfo?.jobId, + periodicGraphId: taskInfo?.graph?.graphId, + }, + ); + close(); + }; + + const columns = [ + { + title: '子任务ID', + dataIndex: 'scheduleTaskId', + key: 'scheduleTaskId', + ellipsis: true, + render: (text: string, record: API.TaskPageScheduledVO) => { + return ( + { + goToPeriodicTaskChildDetail(record); + }} + > + {text} + + ); + }, + }, + { + title: '期望开始时间', + dataIndex: 'scheduleTaskExpectStartTime', + key: 'scheduleTaskExpectStartTime', + ellipsis: true, + width: 180, + render: (scheduleTaskExpectStartTime: string) => ( + + {formatTimestamp(scheduleTaskExpectStartTime as string) || '-'} + + ), + }, + { + title: '实际开始时间', + dataIndex: 'scheduleTaskStartTime', + key: 'scheduleTaskStartTime', + ellipsis: true, + render: (scheduleTaskStartTime: string) => ( + + {formatTimestamp(scheduleTaskStartTime as string) || '-'} + + ), + }, + { + title: '任务结束时间', + dataIndex: 'scheduleTaskEndTime', + key: 'scheduleTaskEndTime', + width: 180, + ellipsis: true, + render: (scheduleTaskEndTime: string) => ( + + {formatTimestamp(scheduleTaskEndTime as string) || '-'} + + ), + }, + { + title: '状态', + dataIndex: 'scheduleTaskStatus', + key: 'scheduleTaskStatus', + ellipsis: true, + render: (status: TaskStatus) => { + return ( + + + {TaskStatusText[status]?.label || '-'} + + ); + }, + }, + { + title: '操作', + key: 'actions', + render: (_: string, record: API.TaskPageScheduledVO) => { + if (!data.showOptions) { + return '-'; + } + const { title, overThirtyTime } = renderOptionsTitle(record); + // overThirtyTime false 没有大于三十天 + // 展示重跑失败 + const showReRunAll = + (record.scheduleTaskStatus === TaskStatus.FAILED || + record.scheduleTaskStatus === TaskStatus.STOPPED) && + !overThirtyTime; + + return ( + + {(record.scheduleTaskStatus === TaskStatus.COMMITTED || + record.scheduleTaskStatus === TaskStatus.STOPPING) && + '-'} + {record.scheduleTaskStatus === TaskStatus.RUNNING && ( + { + await service.stopChildTask(data.scheduleId, record.scheduleTaskId); + await service.getPeriodicTaskChildList(data.scheduleId, { + isRefresh: true, + }); + }} + > + 停止 + + )} + {(record.scheduleTaskStatus === TaskStatus.SUCCESS || + record.scheduleTaskStatus === TaskStatus.FAILED || + record.scheduleTaskStatus === TaskStatus.STOPPED) && ( + { + await service.reRunTask( + data.scheduleId, + record.scheduleTaskId, + 0, // 重跑失败1 / 全部重跑 0 + ); + await service.getPeriodicTaskChildList(data.scheduleId, { + isRefresh: true, + }); + setTimeout(() => { + const button = document.querySelector( + '.ant-btn.cancelText', + ) as HTMLButtonElement; + button?.click(); + }); + }} + > + 重跑 + + ) + } + onConfirm={async () => { + await service.reRunTask( + data.scheduleId, + record.scheduleTaskId, + showReRunAll ? 1 : 0, // 重跑失败1 / 全部重跑 0 + ); + await service.getPeriodicTaskChildList(data.scheduleId, { + isRefresh: true, + }); + }} + > + 重跑 + + )} + + ); + }, + }, + ]; + + return ( + + { + service.pageNumber = page; + service.pageSize = pageSize; + }, + size: 'default', + showSizeChanger: false, + }} + rowKey={(record) => record.scheduleTaskId as string} + /> + + ); +}; diff --git a/apps/platform/src/modules/periodic-task/periodic-child-task-list/index.less b/apps/platform/src/modules/periodic-task/periodic-child-task-list/index.less new file mode 100644 index 0000000..b8ccfb4 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-child-task-list/index.less @@ -0,0 +1,17 @@ +.rerunBtn { + position: absolute; + right: 92px; + bottom: 12px; +} + +.rerunBtnLoading { + right: 110px; +} + +.showReRunBtn { + margin-right: 55px; +} + +.showReRunBtnLoading { + margin-right: 90px; +} diff --git a/apps/platform/src/modules/periodic-task/periodic-task-drawer/create-periodic-task-service.ts b/apps/platform/src/modules/periodic-task/periodic-task-drawer/create-periodic-task-service.ts new file mode 100644 index 0000000..64fed27 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-task-drawer/create-periodic-task-service.ts @@ -0,0 +1,51 @@ +import { message } from 'antd'; + +import { id, create } from '@/services/secretpad/ScheduledController'; +import { Model } from '@/util/valtio-helper'; + +export enum CycleTaskType { + Week = 'W', + Day = 'D', + Month = 'M', +} + +/** 自然周从周日开始,1为周日 */ +export const weekMapping = { + '1': '周一', + '2': '周二', + '3': '周三', + '4': '周四', + '5': '周五', + '6': '周六', + '7': '周日', +}; + +export const monthMapping: Record = { + '-1': '最后一天', +}; +for (let i = 1; i < 32; i++) { + monthMapping[String(i)] = String(i); +} + +export class PeriodicTaskCreateServive extends Model { + loading = false; + + scheduledId = ''; + + getScheduledId = async (projectId: string, graphId: string) => { + const { status, data } = await id({ + graphId, + projectId, + }); + if (status && status.code === 0) { + this.scheduledId = data || ''; + } else { + this.scheduledId = ''; + message.error(status?.msg); + } + }; + + createScheduled = async (value: API.ScheduledGraphCreateRequest) => { + return await create(value); + }; +} diff --git a/apps/platform/src/modules/periodic-task/periodic-task-drawer/create-periodic-task.view.tsx b/apps/platform/src/modules/periodic-task/periodic-task-drawer/create-periodic-task.view.tsx new file mode 100644 index 0000000..71ec8ee --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-task-drawer/create-periodic-task.view.tsx @@ -0,0 +1,320 @@ +import { Button, Drawer, Form, Input, DatePicker, Select, Space, message } from 'antd'; +import dayjs from 'dayjs'; +import { parse } from 'query-string'; +import { useCallback, useEffect } from 'react'; +import { useLocation } from 'umi'; + +import { DefaultModalManager } from '@/modules/dag-modal-manager'; +import { getModel, useModel } from '@/util/valtio-helper'; + +import { + CycleTaskType, + monthMapping, + PeriodicTaskCreateServive, + weekMapping, +} from './create-periodic-task-service'; +import { getScheduleDateStr, getSpecifiedDatesWithinRange, range } from './utils'; + +export const PeriodicTaskCreate = () => { + const modalManager = useModel(DefaultModalManager); + const service = useModel(PeriodicTaskCreateServive); + + const { RangePicker, TimePicker } = DatePicker; + const { Option } = Select; + + const [form] = Form.useForm(); + const periodic = Form.useWatch('periodic', form); + const days = Form.useWatch('days', form); + + const { visible, data: graphNodes } = + modalManager.modals[PeriodicTaskCreateDrawer.id]; + + const onClose = () => { + modalManager.closeModal(PeriodicTaskCreateDrawer.id); + form.resetFields(); + service.loading = false; + }; + + const { search } = useLocation(); + const { projectId, dagId } = parse(search) as { projectId: string; dagId: string }; + + useEffect(() => { + if (!visible || !projectId || !dagId) return; + service.getScheduledId(projectId, dagId); + }, [visible, projectId, dagId]); + + const onSave = async () => { + const values = await form.validateFields(); + // 校验提交时间是否小于结束时间 + if (dayjs(values.start[1]).isBefore(dayjs())) { + form.setFields([ + { + name: 'start', + value: values.start, + errors: ['调度范围结束时间不得小于保存时间'], + }, + ]); + return; + } + + const startTime = dayjs(values.start[0]).format('YYYY-MM-DD HH:mm:ss'); + const endTime = dayjs(values.start[1]).format('YYYY-MM-DD HH:mm:ss'); + const currentTime = dayjs(values.time).format('HH:mm:ss'); + const scheduleDate = + values.periodic === CycleTaskType.Week + ? getScheduleDateStr(values.periodic, values.days).map((item) => + item === 7 ? 0 : item, + ) + : getScheduleDateStr(values.periodic, values.days); + // 判断调度选择日期内有没有可调度的日期 + const hasDayCount = getSpecifiedDatesWithinRange( + startTime, + endTime, + values.periodic, + scheduleDate, + currentTime, + ); + + if (new Set(hasDayCount).size === 0) { + message.warning('调度时间范围内无可调度的时间,请检查'); + return; + } + const params = { + scheduleId: values.scheduledId, + scheduleDesc: values.scheduleDesc, + cron: { + startTime, + endTime, + scheduleCycle: values.periodic, + scheduleDate: getScheduleDateStr(values.periodic, values.days) + .join(',') + .replace('-1', 'end'), + scheduleTime: dayjs(values.time).format('HH:mm:ss'), + }, + projectId: projectId, + graphId: dagId, + nodes: graphNodes.map((item: { id: string }) => item.id), + }; + service.loading = true; + const { status } = await service.createScheduled(params); + service.loading = false; + if (status && status.code === 0) { + message.success('周期任务创建成功'); + onClose(); + } else { + message.error(status && status.msg); + } + }; + + const disabledDate = (current: dayjs.Dayjs) => { + return current && current < dayjs().subtract(1, 'days').endOf('day'); + }; + + const disabledDateTime = (now: dayjs.Dayjs) => { + const selectDay = dayjs(now).add(2, 'minute').format('YYYY-MM-DD'); + const selectHours = dayjs(now).add(2, 'minute').format('H'); + const currentDay = dayjs().format('YYYY-MM-DD'); + + const hours = dayjs().format('H'); // 0~23 + // 添加两分钟误差时间 + const minutes = dayjs().add(2, 'minute').format('m'); // 0~59 + //当日只能选择当前时间之后的时间点 + return { + disabledHours: () => + selectDay === currentDay ? range(0, 60).splice(0, Number(hours)) : [], + disabledMinutes: () => + selectDay === currentDay && selectHours === hours + ? range(0, 60).splice(0, Number(minutes)) + : [], + }; + }; + + // 获取可调度的日期 + const getDispatchDaysOptions = useCallback(() => { + const cycle = form.getFieldValue('periodic'); + const selectDays = form.getFieldValue('days') || []; + + if (cycle === CycleTaskType.Day) { + return null; // 占位符 + } else if (cycle === CycleTaskType.Week) { + const dispatchDaysList = Object.keys(weekMapping).map((key) => ({ + key, + label: weekMapping[key as keyof typeof weekMapping], + })); + + return dispatchDaysList.map((item) => { + // 如果已经选择了 5 个,其他选项禁用, 否则选项一直可选 + const disabled = + selectDays.length >= 5 + ? !selectDays.includes(weekMapping[item.key as keyof typeof weekMapping]) + : false; + + return ( + + ); + }); + } else if (cycle === CycleTaskType.Month) { + const dispatchDaysList = Object.keys(monthMapping).map((key) => ({ + key, + label: monthMapping[key], + })); + + return dispatchDaysList.map((item) => { + // 如果已经选择了 5 个,其他选项禁用, 否则选项一直可选 + const disabled = + selectDays.length >= 5 + ? !selectDays.includes(monthMapping[item.key as keyof typeof monthMapping]) + : false; + + return ( + + {monthMapping[item.key]} + {Number(item.key) > 28 && ( + + (没有这一天的月份将按最后一天执行) + + )} + + ); + }); + } + }, [periodic, days]); + + useEffect(() => { + visible && form.setFieldValue('scheduledId', service.scheduledId); + }, [service.scheduledId, visible]); + + return ( + + + + + } + > +
+ + + + + + + + disabledDateTime(now as dayjs.Dayjs)} + allowClear={false} + placeholder={['开始日期', '结束日期']} + defaultValue={[dayjs().add(2, 'minute'), null]} + showTime + /> + + + + + {form.getFieldValue('periodic') !== CycleTaskType.Day && ( + + + + )} + + + ( + + )} + /> + + +
+ ); +}; + +export const PeriodicTaskCreateDrawer = { + id: 'PeriodicTaskCreate', + visible: false, + data: {}, +}; + +getModel(DefaultModalManager).registerModal(PeriodicTaskCreateDrawer); diff --git a/apps/platform/src/modules/periodic-task/periodic-task-drawer/utils.ts b/apps/platform/src/modules/periodic-task/periodic-task-drawer/utils.ts new file mode 100644 index 0000000..2183252 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-task-drawer/utils.ts @@ -0,0 +1,104 @@ +import dayjs from 'dayjs'; + +import { + CycleTaskType, + monthMapping, + weekMapping, +} from './create-periodic-task-service'; + +export const range = (start: number, end: number) => { + const result = []; + for (let i = start; i < end; i++) { + result.push(i); + } + return result; +}; + +export const getScheduleDateStr = (periodic: CycleTaskType, dayList: string[] = []) => { + let days: number[] = []; + + const findKey = (mappingObj: Record, value: string) => { + if (value === '最后一天') return '-1'; + return Object.keys(mappingObj).find((k) => { + return mappingObj[k] === value; + }); + }; + + if (periodic === CycleTaskType.Week) { + days = dayList.map((value: string) => Number(findKey(weekMapping, value))); + } else if (periodic === CycleTaskType.Month) { + days = dayList.map((value: string) => Number(findKey(monthMapping, value))); + } + + return days; +}; + +// 格式化函数 +const formatDate = (date: dayjs.Dayjs) => date.format('YYYYMMDD'); + +// 获取每个月的最后一天 +const getLastDayOfMonth = (year: number, month: number) => { + return dayjs(new Date(year, month + 1, 0)).date(); // 修改为 month + 1 +}; + +/** + * 判断日期范围内有没有符合条件的日期 + * @param startTime 开始日期 + * @param endTime 结束日期 + * @param type 日期类型 D / M / W + * @param days 选择的日期, D -> [], W -> [1,2,3,4,5,6,0]:周一到周日,周日为0 , M -> [1~31, -1] + * @param currentTime 当天的某一时刻 + * @returns + */ +export const getSpecifiedDatesWithinRange = ( + startTime: string, + endTime: string, + type: CycleTaskType, + days: number[] = [], + currentTime: string, +) => { + const startTimestamp = dayjs(startTime).valueOf(); + const endTimestamp = dayjs(endTime).valueOf(); + const result: string[] = []; + let currentDateTimestamp = dayjs(startTime).startOf('day').valueOf(); + + while (currentDateTimestamp <= endTimestamp) { + const currentDate = dayjs(currentDateTimestamp); + // 查找并形成特定日期时刻 + const relevantDates = []; + if (type === CycleTaskType.Week && days.includes(currentDate.day())) { + relevantDates.push(currentDate); + } else if (type === CycleTaskType.Day) { + relevantDates.push(currentDate); + } else if (type === CycleTaskType.Month) { + days.forEach((day) => { + const lastDayOfMonth = getLastDayOfMonth( + currentDate.year(), + currentDate.month(), + ); + const validDay = Math.min(day, lastDayOfMonth); + relevantDates.push( + dayjs(new Date(currentDate.year(), currentDate.month(), validDay)), + ); + }); + } + + // 针对上述类型进一步筛选特定时刻,并检查是否在时间范围内 + relevantDates.forEach((relevantDate) => { + const currentSpecifiedMoment = dayjs( + `${relevantDate.format('YYYY-MM-DD')} ${currentTime}`, + ).valueOf(); + if ( + currentSpecifiedMoment >= startTimestamp && + currentSpecifiedMoment <= endTimestamp + ) { + result.push(formatDate(relevantDate)); + } + }); + + // 移动到下一个日期 + currentDateTimestamp += 24 * 60 * 60 * 1000; // 增加一天的时间戳(以毫秒计) + } + + return result; +}; diff --git a/apps/platform/src/modules/periodic-task/periodic-task-list/index.less b/apps/platform/src/modules/periodic-task/periodic-task-list/index.less new file mode 100644 index 0000000..4b8b19d --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-task-list/index.less @@ -0,0 +1,23 @@ +.periodicTasksContainer { + height: calc(100% - 89px); + padding: 16px 32px; + background: #f7f8fa; + + .content { + overflow: auto; + width: 100%; + height: 100%; + border-radius: 8px; + background: #fff; + } + + .header { + display: flex; + justify-content: space-between; + padding: 16px 24px; + } + + .table { + padding: 0 24px; + } +} diff --git a/apps/platform/src/modules/periodic-task/periodic-task-list/task.service.tsx b/apps/platform/src/modules/periodic-task/periodic-task-list/task.service.tsx new file mode 100644 index 0000000..835ee57 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-task-list/task.service.tsx @@ -0,0 +1,139 @@ +import { message } from 'antd'; +import type { FilterValue } from 'antd/es/table/interface'; +import { parse } from 'query-string'; +import type { ChangeEvent } from 'react'; + +import { page, offline, del, info } from '@/services/secretpad/ScheduledController'; +import { Model } from '@/util/valtio-helper'; + +export enum TaskStatus { + ALL = '', + UP = 'UP', + DOWN = 'DOWN', +} + +export const taskStatusText = { + [TaskStatus.ALL]: { + label: '全部状态', + iconColor: 'green', + value: '', + }, + [TaskStatus.UP]: { + label: '生效中', + iconColor: 'green', + value: 'success', + }, + [TaskStatus.DOWN]: { + label: '已下线', + iconColor: 'gray', + value: 'offline', + }, +}; + +export class PeriodicTaskListService extends Model { + periodicTaskList: API.PageScheduledVO[] = []; + + periodicJobId = ''; + + loading = false; + + search = ''; + + taskStats = ''; + + sortRule = {}; + + pageNumber = 1; + + pageSize = 10; + + totalNum = 1; + + searchDebounce: number | undefined = undefined; + + onViewUnMount() { + this.search = ''; + } + + /** 搜索周期任务 */ + searchTask = (e: ChangeEvent) => { + this.search = e.target.value; + clearTimeout(this.searchDebounce); + this.searchDebounce = setTimeout(() => { + this.getPeriodicTaskList(); + }, 300) as unknown as number; + }; + + /** 部署时间排序 */ + deployTimeFilter = ( + _: Record, + sort: { order: string; field: string }, + ) => { + if (sort?.order) { + this.sortRule = { + [sort.field]: sort.order === 'ascend' ? 'ASC' : 'DESC', + }; + } else { + this.sortRule = {}; + } + this.getPeriodicTaskList(); + }; + + /** 模型状态过滤 */ + taskStatusFilter = (value: string) => { + this.taskStats = value; + this.getPeriodicTaskList(); + }; + + /** 获取周期任务列表 */ + getPeriodicTaskList = async () => { + const { projectId } = parse(window.location.search); + if (!projectId) return []; + this.loading = true; + const list = await page({ + projectId: projectId as string, + status: this.taskStats, + search: this.search, + page: this.pageNumber, + size: this.pageSize, + sort: this.sortRule, + }); + this.loading = false; + if (list.status && list.status.code === 0 && list.data) { + this.periodicTaskList = list.data.list || []; + this.totalNum = list?.data?.total || 0; + } + return this.periodicTaskList; + }; + + /** 下线周期任务 */ + offlineTask = async (scheduleId: string) => { + const { status } = await offline({ + scheduleId, + }); + if (status && status.code === 0) { + message.success('周期任务下线成功'); + } else { + message.error(status?.msg); + } + }; + + /** 删除周期任务 */ + deleteTask = async (scheduleId: string) => { + const { status } = await del({ + scheduleId, + }); + if (status && status.code === 0) { + message.success('周期任务删除成功'); + } else { + message.error(status?.msg); + } + }; + + /** 获取jobId */ + getJobId = async (scheduleId: string) => { + return await info({ + scheduleId, + }); + }; +} diff --git a/apps/platform/src/modules/periodic-task/periodic-task-list/task.view.tsx b/apps/platform/src/modules/periodic-task/periodic-task-list/task.view.tsx new file mode 100644 index 0000000..4511c9f --- /dev/null +++ b/apps/platform/src/modules/periodic-task/periodic-task-list/task.view.tsx @@ -0,0 +1,310 @@ +import { ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons'; +import type { RadioChangeEvent } from 'antd'; +import { + Space, + Input, + Table, + Badge, + Typography, + Popconfirm, + Radio, + Tooltip, +} from 'antd'; +import { parse } from 'query-string'; +import { useEffect } from 'react'; +import { useLocation, history } from 'umi'; + +import { formatTimestamp } from '@/modules/dag-result/utils'; +import { Model, useModel } from '@/util/valtio-helper'; + +import { PeriodicChildTaskDrawer } from '../periodic-child-task-list/child-task.view'; +import { PeriodicDetailType, PeriodicTaskInfo } from '../type'; + +import styles from './index.less'; +import { TaskStatus } from './task.service'; +import { PeriodicTaskListService, taskStatusText } from './task.service'; +import { hasAccess, Platform } from '@/components/platform-wrapper'; +import { LoginService } from '@/modules/login/login.service'; + +const { Link } = Typography; + +export const PeriodicTaskComponent = () => { + const viewInstance = useModel(PeriodicTaskView); + const service = useModel(PeriodicTaskListService); + const loginService = useModel(LoginService); + + const { search } = useLocation(); + const { projectId } = parse(search) as { projectId: string }; + + const showOptions = (record: API.PageScheduledVO) => { + const isAutonomyMode = hasAccess({ type: [Platform.AUTONOMY] }); + + // p2p 模式下只有当前 owner 才可以操作 + if (isAutonomyMode) { + const loginOwnerId = loginService?.userInfo?.ownerId; + if (loginOwnerId === record.owner) { + return true; + } + return false; + } else { + return true; + } + }; + + useEffect(() => { + if (projectId) { + service.getPeriodicTaskList(); + } + }, [projectId]); + + const goToPeriodicTaskDetail = async (record: API.PageScheduledVO) => { + if (!record.scheduleId) return; + const { data } = await service.getJobId(record.scheduleId); + const searchParams = new URL(window.location.toString()).searchParams; + const currentDagId = searchParams?.get('dagId'); + searchParams.set('dagId', record.scheduleId); + history.push( + { + pathname: '/periodic-task-detail', + search: searchParams.toString(), + }, + { + periodicType: PeriodicDetailType.TASK, + scheduleId: record.scheduleId, + historyDagId: currentDagId, + periodicJobId: data?.jobId, + periodicGraphId: data?.graph?.graphId, + }, + ); + }; + + const columns = [ + { + title: '任务ID', + dataIndex: 'scheduleId', + key: 'scheduleId', + ellipsis: true, + render: (text: string, record: API.PageScheduledVO) => { + return ( + { + goToPeriodicTaskDetail(record); + }} + > + {text} + + ); + }, + }, + { + title: '描述', + dataIndex: 'scheduleDesc', + key: 'scheduleDesc', + ellipsis: true, + render: (text: string) => {text || '-'}, + }, + + { + title: '状态', + dataIndex: 'scheduleStats', + key: 'scheduleStats', + ellipsis: true, + render(text: TaskStatus) { + return ( + + + {taskStatusText[text]?.label} + + ); + }, + }, + { + title: '创建人', + dataIndex: 'creator', + key: 'creator', + ellipsis: true, + render: (text: string) => {text || '-'}, + }, + { + title: '创建方', + dataIndex: 'ownerName', + key: 'ownerName', + ellipsis: true, + render: (text: string) => {text || '-'}, + }, + { + title: '部署时间', + dataIndex: 'createTime', + key: 'createTime', + sorter: true, + ellipsis: true, + render: (createTime: string) => ( + + {formatTimestamp(createTime as string)} + + ), + }, + { + title: '操作', + key: 'actions', + render(_: string, record: API.PageScheduledVO) { + return ( + + { + viewInstance.setPeriodicTaskInfo({ + ...record, + showOptions: showOptions(record), + }); + viewInstance.setPeriodicChildTaskVisible(true); + }} + > + 调度历史 + + {showOptions(record) && ( + + {record.scheduleStats === TaskStatus.UP ? ( + { + await service.offlineTask(record?.scheduleId || ''); + await service.getPeriodicTaskList(); + }} + okText="下线" + cancelText="取消" + disabled={record.taskRunning} + > + {!record.taskRunning ? ( + 下线 + ) : ( + + 下线 + + )} + + ) : ( + } + description={'删除后已运行历史任务将不可查看'} + overlayStyle={{ + maxWidth: 300, + }} + onConfirm={async () => { + await service.deleteTask(record.scheduleId!); + await service.getPeriodicTaskList(); + }} + okText="删除" + cancelText="取消" + okButtonProps={{ + danger: true, + ghost: true, + }} + > + 删除 + + )} + + )} + + ); + }, + }, + ]; + + return ( +
+
+
+ + service.searchTask(e)} + style={{ width: 200 }} + suffix={ + + } + /> + + + service.taskStatusFilter(e.target.value) + } + > + 全部 + 生效中 + 已下线 + + +
+
+
+ service.deployTimeFilter( + filters, + sorter as { order: string; field: string }, + ) + } + pagination={{ + total: service.totalNum || 1, + current: service.pageNumber, + pageSize: service.pageSize, + onChange: (page, pageSize) => { + service.pageNumber = page; + service.pageSize = pageSize; + }, + size: 'default', + showSizeChanger: true, + }} + rowKey={(record) => record.scheduleId as string} + /> + + {viewInstance.periodicChildTaskVisible && ( + { + viewInstance.setPeriodicChildTaskVisible(false); + service.getPeriodicTaskList(); + }} + data={viewInstance.periodicTaskInfo} + /> + )} + + + ); +}; + +export class PeriodicTaskView extends Model { + periodicChildTaskVisible = false; + + periodicTaskInfo: PeriodicTaskInfo = {}; + + setPeriodicTaskInfo = (record: PeriodicTaskInfo) => { + this.periodicTaskInfo = record; + }; + + setPeriodicChildTaskVisible = (visible: boolean) => { + this.periodicChildTaskVisible = visible; + }; +} diff --git a/apps/platform/src/modules/periodic-task/type.ts b/apps/platform/src/modules/periodic-task/type.ts new file mode 100644 index 0000000..635c683 --- /dev/null +++ b/apps/platform/src/modules/periodic-task/type.ts @@ -0,0 +1,10 @@ +export enum PeriodicDetailType { + /** 周期任务 */ + TASK = 'periodicTask', + /** 周期子任务 */ + CHILDTASK = 'periodicTaskChild', +} + +export interface PeriodicTaskInfo extends API.PageScheduledVO { + showOptions?: boolean; +} diff --git a/apps/platform/src/modules/pipeline-record-list/record-service.ts b/apps/platform/src/modules/pipeline-record-list/record-service.ts index dff9427..90c9057 100644 --- a/apps/platform/src/modules/pipeline-record-list/record-service.ts +++ b/apps/platform/src/modules/pipeline-record-list/record-service.ts @@ -1,6 +1,9 @@ import { Emitter } from '@secretflow/utils'; +import { history } from 'umi'; +import { PeriodicDetailType } from '@/modules/periodic-task/type'; import { listJob } from '@/services/secretpad/ProjectController'; +import { listJob as periodicTasklistJob } from '@/services/secretpad/ScheduledController'; import { Model } from '@/util/valtio-helper'; import type { @@ -28,12 +31,23 @@ export class DefaultRecordService extends Model { pageSize?: number, pageNum?: number, ) => { - const { data } = await listJob({ - projectId: projectId, + const { periodicType, scheduleTaskId } = (history.location.state || {}) as { + periodicType: string; + scheduleTaskId: string; + }; + // 周期子任务的详情需要调用不同的接口 + const queryListJob = + periodicType === PeriodicDetailType.CHILDTASK ? periodicTasklistJob : listJob; + const params: API.ScheduleListProjectJobRequest = { + projectId, graphId: pipelineId, pageNum, pageSize, - }); + }; + if (periodicType === PeriodicDetailType.CHILDTASK) { + params.scheduleTaskId = scheduleTaskId; + } + const { data } = await queryListJob(params); this.recordList = data as ExecutionRecord; this.onRecordListUpdatedEmitter.fire(); diff --git a/apps/platform/src/modules/pipeline/templates/pipeline-template-psi-guide.ts b/apps/platform/src/modules/pipeline/templates/pipeline-template-psi-guide.ts index 48faf82..518c9bc 100644 --- a/apps/platform/src/modules/pipeline/templates/pipeline-template-psi-guide.ts +++ b/apps/platform/src/modules/pipeline/templates/pipeline-template-psi-guide.ts @@ -125,7 +125,7 @@ export class TemplateGuidePSI extends Model implements PipelineTemplateContribut ], domain: 'data_prep', name: 'psi', - version: '0.0.7', + version: '0.0.8', }, inputs: [`${graphId}-node-1-output-0`, `${graphId}-node-2-output-0`], codeName: `data_prep/psi`, @@ -140,8 +140,8 @@ export class TemplateGuidePSI extends Model implements PipelineTemplateContribut nodeDef: { domain: `stats`, name: `table_statistics`, - version: `0.0.2`, - attrPaths: ['input/input_data/features'], + version: `1.0.0`, + attrPaths: ['input/input_ds/features'], attrs: [ { ss: ['y', 'age', 'education', 'default'], diff --git a/apps/platform/src/modules/pipeline/templates/pipeline-template-psi.ts b/apps/platform/src/modules/pipeline/templates/pipeline-template-psi.ts index 05e194e..76872b8 100644 --- a/apps/platform/src/modules/pipeline/templates/pipeline-template-psi.ts +++ b/apps/platform/src/modules/pipeline/templates/pipeline-template-psi.ts @@ -15,7 +15,9 @@ export class TemplatePSI extends Model implements PipelineTemplateContribution { content = (graphId: string, quickConfigs?: any) => { const { dataTableReceiver, + dataTableReceiverPartition, dataTableSender, + dataTableSenderPartition, receiverKey, senderKey, featureSelects, @@ -50,12 +52,7 @@ export class TemplatePSI extends Model implements PipelineTemplateContribution { { outputs: [`${graphId}-node-1-output-0`], nodeDef: { - ...(dataTableReceiver - ? { - attrPaths: ['datatable_selected'], - attrs: [{ ...dataTableReceiver, is_na: false }], - } - : {}), + ...getDataTableDef(dataTableReceiver, dataTableReceiverPartition), domain: `read_data`, name: `datatable`, version: `0.0.1`, @@ -71,12 +68,7 @@ export class TemplatePSI extends Model implements PipelineTemplateContribution { { outputs: [`${graphId}-node-2-output-0`], nodeDef: { - ...(dataTableSender - ? { - attrPaths: ['datatable_selected'], - attrs: [{ ...dataTableSender, is_na: false }], - } - : {}), + ...getDataTableDef(dataTableSender, dataTableSenderPartition), domain: `read_data`, name: `datatable`, version: `0.0.1`, @@ -139,12 +131,12 @@ export class TemplatePSI extends Model implements PipelineTemplateContribution { ], domain: 'data_prep', name: 'psi', - version: '0.0.7', + version: '0.0.8', } : { domain: 'data_prep', name: 'psi', - version: '0.0.7', + version: '0.0.8', }), }, inputs: [`${graphId}-node-1-output-0`, `${graphId}-node-2-output-0`], @@ -160,13 +152,13 @@ export class TemplatePSI extends Model implements PipelineTemplateContribution { nodeDef: { ...(featureSelects ? { - attrPaths: ['input/input_data/features'], + attrPaths: ['input/input_ds/features'], attrs: [{ ...featureSelects, is_na: false }], } : {}), domain: `stats`, name: `table_statistics`, - version: `0.0.2`, + version: `1.0.0`, }, inputs: [`${graphId}-node-3-output-0`], codeName: `stats/table_statistics`, @@ -180,3 +172,23 @@ export class TemplatePSI extends Model implements PipelineTemplateContribution { }; }; } + +const getDataTableDef = (receiver: { s: string }, partition: string) => { + if (receiver) { + if (partition) { + // 分区表 + return { + attrPaths: ['datatable_selected', 'datatable_partition'], + attrs: [ + { ...receiver, is_na: false }, + { s: partition, is_na: false }, + ], + }; + } + return { + attrPaths: ['datatable_selected'], + attrs: [{ ...receiver, is_na: false }], + }; + } + return {}; +}; diff --git a/apps/platform/src/modules/pipeline/templates/pipeline-template-risk-guide.ts b/apps/platform/src/modules/pipeline/templates/pipeline-template-risk-guide.ts index 3a0a484..b53d66e 100644 --- a/apps/platform/src/modules/pipeline/templates/pipeline-template-risk-guide.ts +++ b/apps/platform/src/modules/pipeline/templates/pipeline-template-risk-guide.ts @@ -49,18 +49,11 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu target: `${graphId}-node-6`, }, { - edgeId: `${graphId}-node-5-output-0__${graphId}-node-7-input-0`, - sourceAnchor: `${graphId}-node-5-output-0`, - targetAnchor: `${graphId}-node-7-input-0`, - source: `${graphId}-node-5`, - target: `${graphId}-node-7`, - }, - { - edgeId: `${graphId}-node-6-output-0__${graphId}-node-7-input-1`, - sourceAnchor: `${graphId}-node-6-output-0`, - targetAnchor: `${graphId}-node-7-input-1`, + edgeId: `${graphId}-node-6-output-1__${graphId}-node-8-input-1`, + sourceAnchor: `${graphId}-node-6-output-1`, + targetAnchor: `${graphId}-node-8-input-1`, source: `${graphId}-node-6`, - target: `${graphId}-node-7`, + target: `${graphId}-node-8`, }, { edgeId: `${graphId}-node-5-output-1__${graphId}-node-8-input-0`, @@ -70,38 +63,31 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu target: `${graphId}-node-8`, }, { - edgeId: `${graphId}-node-6-output-0__${graphId}-node-8-input-1`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-9-input-0`, sourceAnchor: `${graphId}-node-6-output-0`, - targetAnchor: `${graphId}-node-8-input-1`, - source: `${graphId}-node-6`, - target: `${graphId}-node-8`, - }, - { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-9-input-0`, - sourceAnchor: `${graphId}-node-7-output-0`, targetAnchor: `${graphId}-node-9-input-0`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-9`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-10-input-0`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-10-input-0`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-10-input-0`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-10`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-11-input-0`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-11-input-0`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-11-input-0`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-11`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-12-input-1`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-12-input-1`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-12-input-1`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-12`, }, { @@ -168,15 +154,15 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `stats`, name: `ss_vif`, - version: `0.0.1`, - attrPaths: ['input/input_data/feature_selects'], + version: `1.0.0`, + attrPaths: ['input/input_ds/feature_selects'], attrs: [ { ss: ['duration'], }, ], }, - inputs: [`${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-6-output-0`], codeName: `stats/ss_vif`, x: -240, y: 190, @@ -188,8 +174,8 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu outputs: [`${graphId}-node-11-output-0`, `${graphId}-node-11-output-1`], nodeDef: { attrPaths: [ - 'input/train_dataset/feature_selects', - 'input/train_dataset/label', + 'input/input_ds/feature_selects', + 'input/input_ds/label', 'epochs', 'learning_rate', 'batch_size', @@ -243,9 +229,9 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu ], domain: 'ml.train', name: 'ss_sgd_train', - version: '0.0.1', + version: '1.0.0', }, - inputs: [`${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-6-output-0`], codeName: `ml.train/ss_sgd_train`, x: -40, y: 220, @@ -258,9 +244,9 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `ml.eval`, name: `ss_pvalue`, - version: `0.0.1`, + version: `1.0.0`, }, - inputs: [`${graphId}-node-11-output-0`, `${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-11-output-0`, `${graphId}-node-6-output-0`], codeName: `ml.eval/ss_pvalue`, x: -250, y: 310, @@ -272,7 +258,7 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu outputs: [`${graphId}-node-13-output-0`], nodeDef: { attrPaths: [ - 'input/feature_dataset/saved_features', + 'input/input_ds/saved_features', 'batch_size', 'receiver', 'pred_name', @@ -307,7 +293,7 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu ], domain: 'ml.predict', name: 'ss_sgd_predict', - version: '0.0.2', + version: '1.0.0', }, inputs: [`${graphId}-node-11-output-0`, `${graphId}-node-8-output-0`], codeName: `ml.predict/ss_sgd_predict`, @@ -322,8 +308,8 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `ml.eval`, name: `biclassification_eval`, - version: `0.0.1`, - attrPaths: ['input/in_ds/label', 'input/in_ds/prediction'], + version: `1.0.0`, + attrPaths: ['input/input_ds/label', 'input/input_ds/prediction'], attrs: [ { ss: ['y'], is_na: false }, { ss: ['pred'], is_na: false }, @@ -342,8 +328,8 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `ml.eval`, name: `prediction_bias_eval`, - version: `0.0.1`, - attrPaths: ['input/in_ds/label', 'input/in_ds/prediction'], + version: `1.0.0`, + attrPaths: ['input/input_ds/label', 'input/input_ds/prediction'], attrs: [ { ss: ['y'], is_na: false }, { ss: ['pred'], is_na: false }, @@ -424,7 +410,7 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu ], domain: 'data_prep', name: 'psi', - version: '0.0.7', + version: '0.0.8', }, inputs: [`${graphId}-node-1-output-0`, `${graphId}-node-2-output-0`], codeName: `data_prep/psi`, @@ -439,8 +425,8 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `stats`, name: `table_statistics`, - version: `0.0.2`, - attrPaths: ['input/input_data/features'], + version: `1.0.0`, + attrPaths: ['input/input_ds/features'], attrs: [ { ss: ['y', 'age', 'education', 'default'], @@ -460,7 +446,7 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `data_prep`, name: `train_test_split`, - version: `0.0.1`, + version: `1.0.0`, }, inputs: [`${graphId}-node-3-output-0`], codeName: `data_prep/train_test_split`, @@ -471,14 +457,18 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu status: `STAGING`, }, { - outputs: [`${graphId}-node-6-output-0`, `${graphId}-node-6-output-1`], + outputs: [ + `${graphId}-node-6-output-0`, + `${graphId}-node-6-output-1`, + `${graphId}-node-6-output-2`, + ], nodeDef: { - domain: `feature`, + domain: `preprocessing`, name: `vert_woe_binning`, - version: `0.0.2`, + version: `1.0.0`, attrPaths: [ - 'input/input_data/feature_selects', - 'input/input_data/label', + 'input/input_ds/feature_selects', + 'input/input_ds/label', 'secure_device_type', 'binning_method', 'bin_num', @@ -486,6 +476,7 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu 'chimerge_init_bins', 'chimerge_target_bins', 'chimerge_target_pvalue', + 'report_rules', ], attrs: [ { @@ -498,62 +489,57 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu }, { s: 'spu', + is_na: false, }, { s: 'quantile', + is_na: false, }, { i64: 10, + is_na: false, }, { s: '1', + is_na: false, }, { i64: 100, + is_na: false, }, { i64: 10, + is_na: false, }, { f: 0.1, + is_na: false, + }, + { + is_na: true, }, ], }, inputs: [`${graphId}-node-5-output-0`], - codeName: `feature/vert_woe_binning`, + codeName: `preprocessing/vert_woe_binning`, x: -140, y: 20, label: `WOE分箱`, graphNodeId: `${graphId}-node-6`, status: `STAGING`, }, - { - outputs: [`${graphId}-node-7-output-0`], - nodeDef: { - domain: `preprocessing`, - name: `vert_bin_substitution`, - version: `0.0.1`, - }, - inputs: [`${graphId}-node-5-output-0`, `${graphId}-node-6-output-0`], - codeName: `preprocessing/vert_bin_substitution`, - x: -320, - y: 110, - label: `分箱转换`, - graphNodeId: `${graphId}-node-7`, - status: `STAGING`, - }, { outputs: [`${graphId}-node-8-output-0`], nodeDef: { domain: `preprocessing`, - name: `vert_bin_substitution`, - version: `0.0.1`, + name: `substitution`, + version: `1.0.0`, }, - inputs: [`${graphId}-node-5-output-1`, `${graphId}-node-6-output-0`], - codeName: `preprocessing/vert_bin_substitution`, + inputs: [`${graphId}-node-5-output-1`, `${graphId}-node-6-output-1`], + codeName: `preprocessing/substitution`, x: -10, y: 100, - label: `分箱转换`, + label: `特征工程应用`, graphNodeId: `${graphId}-node-8`, status: `STAGING`, }, @@ -562,9 +548,16 @@ export class TemplateGuideRisk extends Model implements PipelineTemplateContribu nodeDef: { domain: `stats`, name: `ss_pearsonr`, - version: `0.0.1`, + version: `1.0.0`, + attrPaths: ['input/input_ds/feature_selects'], + attrs: [ + { + ss: ['contact_cellular'], + is_na: false, + }, + ], }, - inputs: [`${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-6-output-0`], codeName: `stats/ss_pearsonr`, x: -450, y: 190, diff --git a/apps/platform/src/modules/pipeline/templates/pipeline-template-risk.ts b/apps/platform/src/modules/pipeline/templates/pipeline-template-risk.ts index 650ee95..660a355 100644 --- a/apps/platform/src/modules/pipeline/templates/pipeline-template-risk.ts +++ b/apps/platform/src/modules/pipeline/templates/pipeline-template-risk.ts @@ -15,7 +15,9 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution content = (graphId: string, quickConfigs?: any) => { const { dataTableReceiver, + dataTableReceiverPartition, dataTableSender, + dataTableSenderPartition, receiverKey, senderKey, featureSelects, @@ -61,20 +63,6 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution source: `${graphId}-node-5`, target: `${graphId}-node-6`, }, - { - edgeId: `${graphId}-node-5-output-0__${graphId}-node-7-input-0`, - sourceAnchor: `${graphId}-node-5-output-0`, - targetAnchor: `${graphId}-node-7-input-0`, - source: `${graphId}-node-5`, - target: `${graphId}-node-7`, - }, - { - edgeId: `${graphId}-node-6-output-0__${graphId}-node-7-input-1`, - sourceAnchor: `${graphId}-node-6-output-0`, - targetAnchor: `${graphId}-node-7-input-1`, - source: `${graphId}-node-6`, - target: `${graphId}-node-7`, - }, { edgeId: `${graphId}-node-5-output-1__${graphId}-node-8-input-0`, sourceAnchor: `${graphId}-node-5-output-1`, @@ -83,38 +71,38 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution target: `${graphId}-node-8`, }, { - edgeId: `${graphId}-node-6-output-0__${graphId}-node-8-input-1`, - sourceAnchor: `${graphId}-node-6-output-0`, + edgeId: `${graphId}-node-6-output-1__${graphId}-node-8-input-1`, + sourceAnchor: `${graphId}-node-6-output-1`, targetAnchor: `${graphId}-node-8-input-1`, source: `${graphId}-node-6`, target: `${graphId}-node-8`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-9-input-0`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-9-input-0`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-9-input-0`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-9`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-10-input-0`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-10-input-0`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-10-input-0`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-10`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-11-input-0`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-11-input-0`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-11-input-0`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-11`, }, { - edgeId: `${graphId}-node-7-output-0__${graphId}-node-12-input-1`, - sourceAnchor: `${graphId}-node-7-output-0`, + edgeId: `${graphId}-node-6-output-0__${graphId}-node-12-input-1`, + sourceAnchor: `${graphId}-node-6-output-0`, targetAnchor: `${graphId}-node-12-input-1`, - source: `${graphId}-node-7`, + source: `${graphId}-node-6`, target: `${graphId}-node-12`, }, { @@ -157,12 +145,7 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution { outputs: [`${graphId}-node-1-output-0`], nodeDef: { - ...(dataTableReceiver - ? { - attrPaths: ['datatable_selected'], - attrs: [{ ...dataTableReceiver, is_na: false }], - } - : {}), + ...getDataTableDef(dataTableReceiver, dataTableReceiverPartition), domain: `read_data`, name: `datatable`, version: `0.0.1`, @@ -180,15 +163,15 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { ...(featureSelects ? { - attrPaths: ['input/input_data/feature_selects'], + attrPaths: ['input/input_ds/feature_selects'], attrs: [{ ...featureSelects, is_na: false }], } : {}), domain: `stats`, name: `ss_vif`, - version: `0.0.1`, + version: `1.0.0`, }, - inputs: [`${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-6-output-0`], codeName: `stats/ss_vif`, x: -240, y: 190, @@ -201,13 +184,10 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { domain: `ml.train`, name: `ss_sgd_train`, - version: `0.0.1`, + version: `1.0.0`, ...(labelSelects && featureSelects ? { - attrPaths: [ - 'input/train_dataset/label', - 'input/train_dataset/feature_selects', - ], + attrPaths: ['input/input_ds/label', 'input/input_ds/feature_selects'], attrs: [ { ...labelSelects, is_na: false }, { ...featureSelects, is_na: false }, @@ -215,7 +195,7 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution } : {}), }, - inputs: [`${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-6-output-0`], codeName: `ml.train/ss_sgd_train`, x: -40, y: 220, @@ -228,9 +208,9 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { domain: `ml.eval`, name: `ss_pvalue`, - version: `0.0.1`, + version: `1.0.0`, }, - inputs: [`${graphId}-node-11-output-0`, `${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-11-output-0`, `${graphId}-node-6-output-0`], codeName: `ml.eval/ss_pvalue`, x: -250, y: 310, @@ -254,7 +234,7 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution domain: `ml.predict`, name: `ss_sgd_predict`, - version: `0.0.2`, + version: '1.0.0', }, inputs: [`${graphId}-node-11-output-0`, `${graphId}-node-8-output-0`], codeName: `ml.predict/ss_sgd_predict`, @@ -269,10 +249,10 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { domain: `ml.eval`, name: `biclassification_eval`, - version: `0.0.1`, + version: `1.0.0`, ...(labelSelects && pred ? { - attrPaths: ['input/in_ds/label', 'input/in_ds/prediction'], + attrPaths: ['input/input_ds/label', 'input/input_ds/prediction'], attrs: [ { ...labelSelects, is_na: false }, { ...{ ss: [pred.s] }, is_na: false }, @@ -293,10 +273,10 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { domain: `ml.eval`, name: `prediction_bias_eval`, - version: `0.0.1`, + version: `1.0.0`, ...(labelSelects && pred ? { - attrPaths: ['input/in_ds/label', 'input/in_ds/prediction'], + attrPaths: ['input/input_ds/label', 'input/input_ds/prediction'], attrs: [ { ...labelSelects, is_na: false }, { ...{ ss: [pred.s] }, is_na: false }, @@ -315,12 +295,7 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution { outputs: [`${graphId}-node-2-output-0`], nodeDef: { - ...(dataTableSender - ? { - attrPaths: ['datatable_selected'], - attrs: [{ ...dataTableSender, is_na: false }], - } - : {}), + ...getDataTableDef(dataTableSender, dataTableSenderPartition), domain: `read_data`, name: `datatable`, version: `0.0.1`, @@ -383,12 +358,12 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution ], domain: 'data_prep', name: 'psi', - version: '0.0.7', + version: '0.0.8', } : { domain: 'data_prep', name: 'psi', - version: '0.0.7', + version: '0.0.8', }), }, inputs: [`${graphId}-node-1-output-0`, `${graphId}-node-2-output-0`], @@ -404,13 +379,13 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { ...(featureSelects ? { - attrPaths: ['input/input_data/features'], + attrPaths: ['input/input_ds/features'], attrs: [{ ...featureSelects, is_na: false }], } : {}), domain: `stats`, name: `table_statistics`, - version: `0.0.2`, + version: `1.0.0`, }, inputs: [`${graphId}-node-3-output-0`], codeName: `stats/table_statistics`, @@ -425,7 +400,7 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { domain: `data_prep`, name: `train_test_split`, - version: `0.0.1`, + version: `1.0.0`, }, inputs: [`${graphId}-node-3-output-0`], codeName: `data_prep/train_test_split`, @@ -436,59 +411,45 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution status: `STAGING`, }, { - outputs: [`${graphId}-node-6-output-0`, `${graphId}-node-6-output-1`], + outputs: [ + `${graphId}-node-6-output-0`, + `${graphId}-node-6-output-1`, + `${graphId}-node-6-output-2`, + ], nodeDef: { ...(featureSelects ? { - attrPaths: [ - 'input/input_data/feature_selects', - 'input/input_data/label', - ], + attrPaths: ['input/input_ds/feature_selects', 'input/input_ds/label'], attrs: [ { ...featureSelects, is_na: false }, { ...labelSelects, is_na: false }, ], } : {}), - domain: `feature`, + domain: `preprocessing`, name: `vert_woe_binning`, - version: `0.0.2`, + version: `1.0.0`, }, inputs: [`${graphId}-node-5-output-0`], - codeName: `feature/vert_woe_binning`, + codeName: `preprocessing/vert_woe_binning`, x: -140, y: 20, label: `WOE分箱`, graphNodeId: `${graphId}-node-6`, status: `STAGING`, }, - { - outputs: [`${graphId}-node-7-output-0`], - nodeDef: { - domain: `preprocessing`, - name: `vert_bin_substitution`, - version: `0.0.1`, - }, - inputs: [`${graphId}-node-5-output-0`, `${graphId}-node-6-output-0`], - codeName: `preprocessing/vert_bin_substitution`, - x: -320, - y: 110, - label: `分箱转换`, - graphNodeId: `${graphId}-node-7`, - status: `STAGING`, - }, { outputs: [`${graphId}-node-8-output-0`], nodeDef: { domain: `preprocessing`, - name: `vert_bin_substitution`, - version: `0.0.1`, + name: `substitution`, + version: `1.0.0`, }, - inputs: [`${graphId}-node-5-output-1`, `${graphId}-node-6-output-0`], - codeName: `preprocessing/vert_bin_substitution`, + inputs: [`${graphId}-node-5-output-1`, `${graphId}-node-6-output-1`], + codeName: `preprocessing/substitution`, x: -10, y: 100, - label: `分箱转换`, + label: `特征工程应用`, graphNodeId: `${graphId}-node-8`, status: `STAGING`, }, @@ -497,15 +458,15 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution nodeDef: { ...(featureSelects ? { - attrPaths: ['input/input_data/feature_selects'], + attrPaths: ['input/input_ds/feature_selects'], attrs: [{ ...featureSelects, is_na: false }], } : {}), domain: `stats`, name: `ss_pearsonr`, - version: `0.0.1`, + version: `1.0.0`, }, - inputs: [`${graphId}-node-7-output-0`], + inputs: [`${graphId}-node-6-output-0`], codeName: `stats/ss_pearsonr`, x: -450, y: 190, @@ -517,3 +478,23 @@ export class TemplateRisk extends Model implements PipelineTemplateContribution }; }; } + +const getDataTableDef = (receiver: { s: string }, partition: string) => { + if (receiver) { + if (partition) { + // 分区表 + return { + attrPaths: ['datatable_selected', 'datatable_partition'], + attrs: [ + { ...receiver, is_na: false }, + { s: partition, is_na: false }, + ], + }; + } + return { + attrPaths: ['datatable_selected'], + attrs: [{ ...receiver, is_na: false }], + }; + } + return {}; +}; diff --git a/apps/platform/src/modules/result-details/result-details-drawer.tsx b/apps/platform/src/modules/result-details/result-details-drawer.tsx index f03487d..7a7045f 100644 --- a/apps/platform/src/modules/result-details/result-details-drawer.tsx +++ b/apps/platform/src/modules/result-details/result-details-drawer.tsx @@ -17,7 +17,7 @@ import React, { useEffect } from 'react'; import { DefaultModalManager } from '@/modules/dag-modal-manager'; import { getTabsName, getVisComponents } from '@/modules/dag-result/result-report'; -import { formatTimestamp } from '@/modules/dag-result/utils'; +import { formatTimestamp, getDownloadBtnTitle } from '@/modules/dag-result/utils'; import type { ResultOriginData } from '@/modules/dag-result/vis/typing'; import type { TableType } from '@/modules/result-manager/result-manager.protocol'; import { @@ -104,8 +104,12 @@ export const ResultDetailsDrawer: React.FC = () => { @@ -113,7 +117,8 @@ export const ResultDetailsDrawer: React.FC = () => { type="primary" disabled={ nodeResultsVO?.datasourceType === DataSourceType.OSS || - nodeResultsVO?.datasourceType === DataSourceType.ODPS + nodeResultsVO?.datasourceType === DataSourceType.ODPS || + nodeResultsVO?.datasourceType === DataSourceType.MYSQL } onClick={() => viewInstance.download(data?.nodeId)} > @@ -125,8 +130,12 @@ export const ResultDetailsDrawer: React.FC = () => { diff --git a/apps/platform/src/modules/result-manager/result-manager.view.tsx b/apps/platform/src/modules/result-manager/result-manager.view.tsx index e15f2fd..6682c08 100644 --- a/apps/platform/src/modules/result-manager/result-manager.view.tsx +++ b/apps/platform/src/modules/result-manager/result-manager.view.tsx @@ -8,7 +8,7 @@ import { history } from 'umi'; import { hasAccess, Platform } from '@/components/platform-wrapper'; import type { ComputeMode } from '@/modules/component-tree/component-protocol'; import { DefaultModalManager } from '@/modules/dag-modal-manager'; -import { formatTimestamp } from '@/modules/dag-result/utils'; +import { formatTimestamp, getDownloadBtnTitle } from '@/modules/dag-result/utils'; import { resultDetailsDrawer, ResultDetailsDrawer, @@ -55,8 +55,9 @@ export const ResultManagerComponent = () => { @@ -66,7 +67,8 @@ export const ResultManagerComponent = () => { onClick={() => viewInstance.download(record, isAutonomy)} disabled={ record?.datasourceType === DataSourceType.OSS || - record?.datasourceType === DataSourceType.ODPS + record?.datasourceType === DataSourceType.ODPS || + record?.datasourceType === DataSourceType.MYSQL } > 下载 @@ -89,8 +91,9 @@ export const ResultManagerComponent = () => { @@ -100,7 +103,8 @@ export const ResultManagerComponent = () => { onClick={() => viewInstance.download(record, isAutonomy)} disabled={ record?.datasourceType === DataSourceType.OSS || - record?.datasourceType === DataSourceType.ODPS + record?.datasourceType === DataSourceType.ODPS || + record?.datasourceType === DataSourceType.MYSQL } > 下载 diff --git a/apps/platform/src/pages/periodic-task-detail.tsx b/apps/platform/src/pages/periodic-task-detail.tsx new file mode 100644 index 0000000..52f4100 --- /dev/null +++ b/apps/platform/src/pages/periodic-task-detail.tsx @@ -0,0 +1,7 @@ +import { PeriodicTaskDetailLayout } from '@/modules/layout/periodic-task-detail-layout'; + +const PeriodicTaskDetailPage = () => { + return ; +}; + +export default PeriodicTaskDetailPage; diff --git a/apps/platform/src/services/secretpad/ScheduledController.ts b/apps/platform/src/services/secretpad/ScheduledController.ts new file mode 100644 index 0000000..4ef8c37 --- /dev/null +++ b/apps/platform/src/services/secretpad/ScheduledController.ts @@ -0,0 +1,198 @@ +/* eslint-disable */ +// 该文件由 OneAPI 自动生成,请勿手动修改! +import request from 'umi-request'; + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/graph/create */ +export async function create( + body?: API.ScheduledGraphCreateRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/graph/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/del */ +export async function del( + body?: API.ScheduledDelRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/del', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/id */ +export async function id( + body?: API.ScheduledIdRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/id', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/info */ +export async function info( + body?: API.ScheduledInfoRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/info', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/job/list */ +export async function listJob( + body?: API.ScheduleListProjectJobRequest, + options?: { [key: string]: any }, +) { + return request( + '/api/v1alpha1/scheduled/job/list', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/offline */ +export async function offline( + body?: API.ScheduledOfflineRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/offline', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/graph/once/success */ +export async function onceSuccess( + body?: API.ScheduledGraphOnceSuccessRequest, + options?: { [key: string]: any }, +) { + return request( + '/api/v1alpha1/scheduled/graph/once/success', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/page */ +export async function page( + body?: API.PageScheduledRequest, + options?: { [key: string]: any }, +) { + return request( + '/api/v1alpha1/scheduled/page', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/task/info */ +export async function taskInfo( + body?: API.TaskInfoScheduledRequest, + options?: { [key: string]: any }, +) { + return request( + '/api/v1alpha1/scheduled/task/info', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/task/page */ +export async function taskPage( + body?: API.TaskPageScheduledRequest, + options?: { [key: string]: any }, +) { + return request( + '/api/v1alpha1/scheduled/task/page', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }, + ); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/task/rerun */ +export async function taskRerun( + body?: API.TaskReRunScheduledRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/task/rerun', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + +/** 此处后端没有提供注释 POST /api/v1alpha1/scheduled/task/stop */ +export async function taskStop( + body?: API.TaskStopScheduledRequest, + options?: { [key: string]: any }, +) { + return request('/api/v1alpha1/scheduled/task/stop', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} diff --git a/apps/platform/src/services/secretpad/index.ts b/apps/platform/src/services/secretpad/index.ts index b35a345..5adcf02 100644 --- a/apps/platform/src/services/secretpad/index.ts +++ b/apps/platform/src/services/secretpad/index.ts @@ -1,6 +1,6 @@ /* eslint-disable */ -// API 更新时间:2024-08-09 11:26:19 -// API 唯一标识:66b58c5b3f8f03846ba9342f +// API 更新时间:2024-09-02 18:26:14 +// API 唯一标识:66d592c66eb33d73d5acbdf8 // 该文件由 OneAPI 自动生成,请勿手动修改! import * as ApprovalController from './ApprovalController'; @@ -26,6 +26,7 @@ import * as P2PProjectController from './P2PProjectController'; import * as P2pNodeController from './P2pNodeController'; import * as ProjectController from './ProjectController'; import * as RemoteUserController from './RemoteUserController'; +import * as ScheduledController from './ScheduledController'; import * as UserController from './UserController'; import * as VoteSyncController from './VoteSyncController'; export default { @@ -52,6 +53,7 @@ export default { P2pNodeController, ProjectController, RemoteUserController, + ScheduledController, UserController, VoteSyncController, }; diff --git a/apps/platform/src/services/secretpad/typings.d.ts b/apps/platform/src/services/secretpad/typings.d.ts index 151a0f7..11c2a5b 100644 --- a/apps/platform/src/services/secretpad/typings.d.ts +++ b/apps/platform/src/services/secretpad/typings.d.ts @@ -252,6 +252,8 @@ manipulate, derived from the value returned by the back end in the uplink mouth projectId?: string; } + type Cron = Record; + type DataErrorCode = | 202011801 | 'FILE_NAME_EMPTY' @@ -1404,22 +1406,18 @@ result management list interface */ interface OdpsPartitionParam { type?: string; - fields?: Array; + fields?: Array; } type OdpsPartitionParam$Field = Record; - type OdpsPartitionParamField = Record; - interface OdpsPartitionRequest { type?: string; - fields?: Array; + fields?: Array; } type OdpsPartitionRequest$Field = Record; - type OdpsPartitionRequestField = Record; - type OneApiResult_object_ = Record; type OneApiResult_string_ = Record; @@ -1470,11 +1468,26 @@ result management list interface */ data?: OrgSecretflowSecretpadCommonDtoSecretPadResponse_SyncDataDTO; } + type OrgSecretflowSecretpadManagerIntegrationModelOdpsPartitionParamField = Record< + string, + any + >; + + type OrgSecretflowSecretpadPersistenceModelParticipantNodeInstVONodeInstVO = Record< + string, + any + >; + type OrgSecretflowSecretpadServiceModelApprovalParticipantVoteInfo = Record< string, any >; + type OrgSecretflowSecretpadServiceModelDatatableOdpsPartitionRequestField = Record< + string, + any + >; + type OrgSecretflowSecretpadServiceModelGraphFullUpdateGraphRequestGraphDataSourceConfig = Record; @@ -1578,6 +1591,28 @@ result management list interface */ data?: Array; } + interface PageScheduledRequest { + /** page num default 1 */ + page?: number; + /** page size default 10 */ + size?: number; + /** sort,property,property(,ASC|DESC) "createdDate,desc" */ + sort?: Record; + /** scheduleId,status */ + search?: string; + status?: string; + projectId?: string; + } + + interface PageScheduledVO { + scheduleId?: string; + scheduleDesc?: string; + scheduleStats?: string; + creator?: string; + createTime?: string; + taskRunning?: boolean; + } + interface Participant { /** this id means the node who initiate a vote */ nodeID?: string; @@ -1594,13 +1629,11 @@ result management list interface */ interface ParticipantNodeInstVO { initiatorNodeId?: string; initiatorNodeName?: string; - invitees?: Array; + invitees?: Array; } type ParticipantNodeInstVO$NodeInstVO = Record; - type ParticipantNodeInstVONodeInstVO = Record; - type Parties = Record; type Party = Record; @@ -1608,8 +1641,8 @@ result management list interface */ type PartyConfig = Record; interface PartyVoteInfoVO { - instId?: string; - instName?: string; + partyId?: string; + partyName?: string; action?: string; reason?: string; } @@ -1933,6 +1966,77 @@ result management list interface */ routerId?: string; } + interface ScheduleListProjectJobRequest { + /** What page is currently requested? Note that starting at 1 represents the first page */ + pageNum?: number; + /** How many pieces of data are in each page */ + pageSize?: number; + projectId?: string; + graphId?: string; + } + + interface ScheduledDelRequest { + scheduleId?: string; + } + + type ScheduledErrorCode = + | 202015001 + | 'PROJECT_JOB_NEED_SUCCESS_ONCE' + | 202015002 + | 'PROJECT_JOB_NOT_EXIST' + | 202015003 + | 'SCHEDULE_NOT_EXIST' + | 202015004 + | 'SCHEDULE_UP_NOT_DEL' + | 202015005 + | 'SCHEDULE_RUNNING_NOT_OFFLINE' + | 202015006 + | 'REQUEST_IS_NULL' + | 202015007 + | 'SCHEDULE_TASK_NOT_EXIST' + | 202015008 + | 'SCHEDULE_TASK_STATUS_NOT_RUNNING' + | 202015009 + | 'PROJECT_JOB_RESTART_ERROR'; + + interface ScheduledGraphCreateRequest { + scheduleId?: string; + scheduleDesc?: string; + cron?: ScheduledGraphCreateRequestCron; + projectId?: string; + graphId?: string; + nodes?: Array; + } + + type ScheduledGraphCreateRequestCron = Record; + + interface ScheduledGraphOnceSuccessRequest { + projectId?: string; + graphId?: string; + } + + interface ScheduledIdRequest { + projectId?: string; + graphId?: string; + } + + interface ScheduledInfoRequest { + scheduleId?: string; + } + + interface ScheduledOfflineRequest { + scheduleId?: string; + } + + type ScheduledStatus = + | 'UP' + | 'DOWN' + | 'TO_BE_RUN' + | 'RUNNING' + | 'STOPPED' + | 'SUCCEED' + | 'FAILED'; + interface SecretPadPageRequest { /** page num default 1 */ page?: number; @@ -1963,6 +2067,20 @@ result management list interface */ total?: number; } + interface SecretPadPageResponse_PageScheduledVO_ { + /** page list */ + list?: Array; + /** total */ + total?: number; + } + + interface SecretPadPageResponse_TaskPageScheduledVO_ { + /** page list */ + list?: Array; + /** total */ + total?: number; + } + interface SecretPadResponse { status?: SecretPadResponseSecretPadResponseStatus; data?: Record; @@ -2202,6 +2320,16 @@ result management list interface */ data?: SecretPadPageResponse_NodeVO_; } + interface SecretPadResponse_SecretPadPageResponse_PageScheduledVO__ { + status?: SecretPadResponseSecretPadResponseStatus; + data?: SecretPadPageResponse_PageScheduledVO_; + } + + interface SecretPadResponse_SecretPadPageResponse_TaskPageScheduledVO__ { + status?: SecretPadResponseSecretPadResponseStatus; + data?: SecretPadPageResponse_TaskPageScheduledVO_; + } + interface SecretPadResponse_ServingDetailVO_ { status?: SecretPadResponseSecretPadResponseStatus; data?: ServingDetailVO; @@ -2340,6 +2468,43 @@ result management list interface */ colComment?: string; } + interface TaskInfoScheduledRequest { + scheduleId?: string; + scheduleTaskId?: string; + } + + interface TaskPageScheduledRequest { + /** page num default 1 */ + page?: number; + /** page size default 10 */ + size?: number; + /** sort,property,property(,ASC|DESC) "createdDate,desc" */ + sort?: Record; + /** scheduleTaskId */ + search?: string; + /** scheduleId */ + scheduleId?: string; + } + + interface TaskPageScheduledVO { + scheduleTaskId?: string; + scheduleTaskExpectStartTime?: string; + scheduleTaskStartTime?: string; + scheduleTaskEndTime?: string; + scheduleTaskStatus?: string; + } + + interface TaskReRunScheduledRequest { + scheduleId?: string; + scheduleTaskId?: string; + type?: string; + } + + interface TaskStopScheduledRequest { + scheduleId?: string; + scheduleTaskId?: string; + } + type TeeJobKind = 'PushAuth' | 'Push' | 'Auth' | 'CancelAuth' | 'Pull' | 'Delete'; type TeeJobStatus = 'RUNNING' | 'SUCCESS' | 'FAILED'; diff --git a/packages/dag/src/shapes/descriptions.tsx b/packages/dag/src/shapes/descriptions.tsx index 5a8ac23..1ddb4a4 100644 --- a/packages/dag/src/shapes/descriptions.tsx +++ b/packages/dag/src/shapes/descriptions.tsx @@ -106,6 +106,7 @@ interface IProps { const resultInfo: Record = { table: { text: '输出表', icon: }, model: { text: '模型', icon: }, + serving: { text: '模型', icon: }, rule: { text: '规则', icon: }, report: { text: '报告', icon: }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1de022a..bbfba41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,6 +147,9 @@ importers: umi: specifier: ^4.0.64 version: 4.3.18(@babel/core@7.25.2)(@types/node@20.5.1)(@types/react@18.3.5)(eslint@8.57.0)(jest@29.7.0)(prettier@2.8.8)(react-dom@18.3.1)(react@18.3.1)(stylelint@14.16.1)(typescript@4.9.5)(webpack@5.94.0) + uuid: + specifier: ^10.0.0 + version: 10.0.0 valtio: specifier: ^1.10.7 version: 1.13.2(@types/react@18.3.5)(react@18.3.1) @@ -18766,6 +18769,11 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + /uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + dev: false + /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true