Browse Source

[Feature][UI Next] Add project workflow relation. (#8155)

* [Feature][UI Next] Add project workflow relation.

* [Feature][UI Next] Add tooltip component.
3.0.0/version-upgrade
songjianet 3 years ago committed by GitHub
parent
commit
8c65b194c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dolphinscheduler-ui-next/src/components/chart/index.ts
  2. 152
      dolphinscheduler-ui-next/src/components/chart/modules/Graph.tsx
  3. 16
      dolphinscheduler-ui-next/src/locales/modules/en_US.ts
  4. 16
      dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
  5. 18
      dolphinscheduler-ui-next/src/service/modules/lineages/index.ts
  6. 32
      dolphinscheduler-ui-next/src/service/modules/lineages/types.ts
  7. 189
      dolphinscheduler-ui-next/src/views/projects/workflow/relation/components/Graph.tsx
  8. 106
      dolphinscheduler-ui-next/src/views/projects/workflow/relation/index.tsx
  9. 97
      dolphinscheduler-ui-next/src/views/projects/workflow/relation/use-relation.ts

2
dolphinscheduler-ui-next/src/components/chart/index.ts

@ -38,7 +38,7 @@ function initChart<Opt extends ECBasicOption>(
const init = () => { const init = () => {
chart = globalProperties?.echarts.init( chart = globalProperties?.echarts.init(
domRef.value, domRef.value,
themeStore.darkTheme ? 'dark-bold' : 'macarons' themeStore.darkTheme && 'dark-bold'
) )
chart && chart.setOption(option) chart && chart.setOption(option)
} }

152
dolphinscheduler-ui-next/src/components/chart/modules/Graph.tsx

@ -1,152 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, PropType, ref } from 'vue'
import initChart from '@/components/chart'
import type { Ref } from 'vue'
const props = {
height: {
type: [String, Number] as PropType<string | number>,
default: 400
},
width: {
type: [String, Number] as PropType<string | number>,
default: '100%'
},
tooltipFormat: {
type: String as PropType<string>,
default: ''
},
legendData: {
type: Array as PropType<Array<string>>,
default: () => []
},
seriesData: {
type: Array as PropType<Array<string>>,
default: () => []
},
labelShow: {
type: Array as PropType<Array<string>>,
default: () => []
},
linksData: {
type: Array as PropType<Array<string>>,
default: () => []
},
labelFormat: {
type: String as PropType<string>,
default: ''
}
}
const GraphChart = defineComponent({
name: 'GraphChart',
props,
setup(props) {
const graphChartRef: Ref<HTMLDivElement | null> = ref(null)
const option = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
backgroundColor: '#2D303A',
padding: [8, 12],
formatter: props.tooltipFormat,
color: '#2D303A',
textStyle: {
rich: {
a: {
fontSize: 12,
color: '#2D303A',
lineHeight: 12,
align: 'left',
padding: [4, 4, 4, 4]
}
}
}
},
legend: [
{
orient: 'horizontal',
top: 6,
left: 6,
data: props.legendData
}
],
series: [
{
type: 'graph',
layout: 'force',
nodeScaleRatio: 1.2,
draggable: true,
animation: false,
data: props.seriesData,
roam: true,
symbol: 'roundRect',
symbolSize: 70,
categories: props.legendData,
label: {
show: props.labelShow,
position: 'inside',
formatter: props.labelFormat,
color: '#222222',
textStyle: {
rich: {
a: {
fontSize: 12,
color: '#222222',
lineHeight: 12,
align: 'left',
padding: [4, 4, 4, 4]
}
}
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 12],
force: {
repulsion: 1000,
edgeLength: 300
},
links: props.linksData,
lineStyle: {
color: '#999999'
}
}
]
}
initChart(graphChartRef, option)
return { graphChartRef }
},
render() {
const { height, width } = this
return (
<div
ref='graphChartRef'
style={{
height: typeof height === 'number' ? height + 'px' : height,
width: typeof width === 'number' ? width + 'px' : width
}}
/>
)
}
})
export default GraphChart

16
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@ -323,6 +323,22 @@ const project = {
workflow: { workflow: {
workflow_relation: 'Workflow Relation', workflow_relation: 'Workflow Relation',
create_workflow: 'Create Workflow', create_workflow: 'Create Workflow',
workflow_name: 'Workflow Name',
current_selection: 'Current Selection',
online: 'Online',
offline: 'Offline',
refresh: 'Refresh',
show_hide_label: 'Show / Hide Label',
workflow_offline: 'Workflow Offline',
schedule_offline: 'Schedule Offline',
schedule_start_time: 'Schedule Start Time',
schedule_end_time: 'Schedule End Time',
crontab_expression: 'Crontab',
workflow_publish_status: 'Workflow Publish Status',
schedule_publish_status: 'Schedule Publish Status'
},
dag: {
createWorkflow: 'Create Workflow',
search: 'Search', search: 'Search',
download_png: 'Download PNG', download_png: 'Download PNG',
fullscreen_open: 'Open Fullscreen', fullscreen_open: 'Open Fullscreen',

16
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@ -322,6 +322,22 @@ const project = {
workflow: { workflow: {
workflow_relation: '工作流关系', workflow_relation: '工作流关系',
create_workflow: '创建工作流', create_workflow: '创建工作流',
workflow_name: '工作流名称',
current_selection: '当前选择',
online: '已上线',
offline: '已下线',
refresh: '刷新',
show_hide_label: '显示 / 隐藏标签',
workflow_offline: '工作流下线',
schedule_offline: '调度下线',
schedule_start_time: '定时开始时间',
schedule_end_time: '定时结束时间',
crontab_expression: 'Crontab',
workflow_publish_status: '工作流上线状态',
schedule_publish_status: '定时状态'
},
dag: {
createWorkflow: '创建工作流',
search: '搜索', search: '搜索',
download_png: '下载工作流图片', download_png: '下载工作流图片',
fullscreen_open: '全屏', fullscreen_open: '全屏',

18
dolphinscheduler-ui-next/src/service/modules/lineages/index.ts

@ -16,32 +16,28 @@
*/ */
import { axios } from '@/service/service' import { axios } from '@/service/service'
import { ProjectCodeReq, WorkFlowNameReq } from './types' import { ProjectCodeReq, WorkFlowNameReq, WorkflowCodeReq } from './types'
export function queryWorkFlowList(projectCode: ProjectCodeReq): any { export function queryWorkFlowList(projectCode: ProjectCodeReq): any {
return axios({ return axios({
url: `/projects/${projectCode}/lineages/list`, url: `/projects/${projectCode.projectCode}/lineages/list`,
method: 'get' method: 'get'
}) })
} }
export function queryLineageByWorkFlowName( export function queryLineageByWorkFlowName(projectCode: ProjectCodeReq): any {
params: WorkFlowNameReq,
projectCode: ProjectCodeReq
): any {
return axios({ return axios({
url: `/projects/${projectCode}/lineages/query-by-name`, url: `/projects/${projectCode.projectCode}/lineages/query-by-name`,
method: 'get', method: 'get'
params
}) })
} }
export function queryLineageByWorkFlowCode( export function queryLineageByWorkFlowCode(
workFlowCode: WorkFlowNameReq, workFlowCode: WorkflowCodeReq,
projectCode: ProjectCodeReq projectCode: ProjectCodeReq
): any { ): any {
return axios({ return axios({
url: `/projects/${projectCode}/lineages/${workFlowCode}`, url: `/projects/${projectCode.projectCode}/lineages/${workFlowCode.workFlowCode}`,
method: 'get' method: 'get'
}) })
} }

32
dolphinscheduler-ui-next/src/service/modules/lineages/types.ts

@ -19,8 +19,38 @@ interface ProjectCodeReq {
projectCode: number projectCode: number
} }
interface WorkflowCodeReq {
workFlowCode: number
}
interface WorkFlowNameReq { interface WorkFlowNameReq {
workFlowName: string workFlowName: string
} }
export { ProjectCodeReq, WorkFlowNameReq } interface WorkFlowListRes extends WorkflowCodeReq {
workFlowName: string
workFlowPublishStatus: string
scheduleStartTime?: any
scheduleEndTime?: any
crontab?: any
schedulePublishStatus: number
sourceWorkFlowCode: string
}
interface WorkFlowRelationList {
sourceWorkFlowCode: number
targetWorkFlowCode: number
}
interface WorkflowRes {
workFlowList: WorkFlowListRes[]
workFlowRelationList: WorkFlowRelationList[]
}
export {
ProjectCodeReq,
WorkflowCodeReq,
WorkFlowNameReq,
WorkflowRes,
WorkFlowListRes
}

189
dolphinscheduler-ui-next/src/views/projects/workflow/relation/components/Graph.tsx

@ -0,0 +1,189 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, PropType, ref } from 'vue'
import initChart from '@/components/chart'
import { useI18n } from 'vue-i18n'
import type { Ref } from 'vue'
import { format } from 'date-fns'
const props = {
height: {
type: [String, Number] as PropType<string | number>,
default: window.innerHeight - 174
},
width: {
type: [String, Number] as PropType<string | number>,
default: '100%'
},
seriesData: {
type: Array as PropType<Array<any>>,
default: () => []
},
labelShow: {
type: Boolean as PropType<boolean>,
default: true
}
}
const GraphChart = defineComponent({
name: 'GraphChart',
props,
setup(props) {
const graphChartRef: Ref<HTMLDivElement | null> = ref(null)
const { t } = useI18n()
console.log(props.seriesData)
const legendData = [
{ name: t('project.workflow.online') },
{ name: t('project.workflow.workflow_offline') },
{ name: t('project.workflow.schedule_offline') }
]
const getCategory = (schedulerStatus: number, workflowStatus: number) => {
console.log(schedulerStatus, workflowStatus)
switch (true) {
case workflowStatus === 0:
return 1
case workflowStatus === 1 && schedulerStatus === 0:
return 2
case workflowStatus === 1 && schedulerStatus === 1:
default:
return 0
}
}
const option: any = {
tooltip: {
confine: true,
backgroundColor: '#fff',
formatter: (params: any) => {
if (!params.data.name) {
return false
}
const {
name,
scheduleStartTime,
scheduleEndTime,
crontab,
workFlowPublishStatus,
schedulePublishStatus
} = params.data
return `
${t('project.workflow.workflow_name')}${name}<br/>
${t(
'project.workflow.schedule_start_time'
)}${scheduleStartTime}<br/>
${t('project.workflow.schedule_end_time')}${scheduleEndTime}<br/>
${t('project.workflow.crontab_expression')}${
crontab ? crontab : ' - '
}<br/>
${t(
'project.workflow.workflow_publish_status'
)}${workFlowPublishStatus}<br/>
${t(
'project.workflow.schedule_publish_status'
)}${schedulePublishStatus}<br/>
`
}
},
legend: [
{
data: legendData?.map((item) => item.name)
}
],
series: [
{
type: 'graph',
layout: 'force',
draggable: true,
force: {
repulsion: 300,
edgeLength: 100
},
symbol: 'roundRect',
symbolSize: 70,
roam: false,
label: {
show: props.labelShow,
formatter: (val: any) => {
let newStr = ''
const str = val.data.name.split('')
for (let i = 0, s; (s = str[i++]); ) {
newStr += s
if (!(i % 10)) newStr += '\n'
}
return newStr.length > 60 ? newStr.slice(0, 60) + '...' : newStr
}
},
data: props.seriesData.map((item) => {
return {
name: item.name,
id: item.id,
category: getCategory(
Number(item.schedulePublishStatus),
Number(item.workFlowPublishStatus)
),
workFlowPublishStatus: format(
new Date(item.workFlowPublishStatus),
'yyyy-MM-dd HH:mm:ss'
),
schedulePublishStatus: format(
new Date(item.schedulePublishStatus),
'yyyy-MM-dd HH:mm:ss'
),
crontab: item.crontab,
scheduleStartTime:
Number(item.scheduleStartTime) === 0
? t('project.workflow.offline')
: t('project.workflow.online'),
scheduleEndTime:
Number(item.scheduleEndTime) === 0
? t('project.workflow.offline')
: t('project.workflow.online')
}
}),
categories: legendData
}
]
}
initChart(graphChartRef, option)
return { graphChartRef }
},
render() {
const { height, width } = this
return (
<div
ref='graphChartRef'
style={{
height: typeof height === 'number' ? height + 'px' : height,
width: typeof width === 'number' ? width + 'px' : width
}}
/>
)
}
})
export default GraphChart

106
dolphinscheduler-ui-next/src/views/projects/workflow/relation/index.tsx

@ -15,37 +15,105 @@
* limitations under the License. * limitations under the License.
*/ */
import { defineComponent } from 'vue' import { defineComponent, onMounted, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { NSelect, NButton, NIcon } from 'naive-ui' import { useRoute } from 'vue-router'
import { ReloadOutlined, EyeOutlined } from '@vicons/antd' import {NSelect, NButton, NIcon, NSpace, NTooltip} from 'naive-ui'
import {ReloadOutlined, EyeOutlined, EditOutlined} from '@vicons/antd'
import { useRelation } from './use-relation'
import Card from '@/components/card' import Card from '@/components/card'
import Graph from './components/Graph'
const workflowRelation = defineComponent({ const workflowRelation = defineComponent({
name: 'workflow-relation', name: 'workflow-relation',
setup() { setup() {
const { t } = useI18n() const { t, locale } = useI18n()
const route = useRoute()
const { variables, getWorkflowName, getOneWorkflow, getWorkflowList } =
useRelation()
return { t } onMounted(() => {
getWorkflowList(Number(route.params.projectCode))
getWorkflowName(Number(route.params.projectCode))
})
const handleResetDate = () => {
variables.seriesData = []
variables.workflow && variables.workflow !== 0
? getOneWorkflow(
Number(variables.workflow),
Number(route.params.projectCode)
)
: getWorkflowList(Number(route.params.projectCode))
}
watch(
() => [variables.workflow, variables.labelShow, locale.value],
() => {
handleResetDate()
}
)
return { t, handleResetDate, ...toRefs(variables) }
}, },
render() { render() {
const { t } = this const { t, handleResetDate } = this
return ( return (
<Card title={t('project.workflow.workflow_relation')}> <Card title={t('project.workflow.workflow_relation')}>
<div> {{
<NSelect /> default: () =>
<NButton strong secondary circle type='info'> Object.keys(this.seriesData).length > 0 && (
<NIcon> <Graph seriesData={this.seriesData} labelShow={this.labelShow} />
<ReloadOutlined /> ),
</NIcon> 'header-extra': () => (
</NButton> <NSpace>
<NButton strong secondary circle type='info'> <NSelect
<NIcon> clearable
<EyeOutlined /> style={{ width: '300px' }}
</NIcon> placeholder={t('project.workflow.workflow_name')}
</NButton> options={this.workflowOptions}
</div> v-model={[this.workflow, 'value']}
/>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.refresh'),
trigger: () => (
<NButton
strong
secondary
circle
type='info'
onClick={handleResetDate}
>
<NIcon>
<ReloadOutlined />
</NIcon>
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.show_hide_label'),
trigger: () => (
<NButton
strong
secondary
circle
type='info'
onClick={() => (this.labelShow = !this.labelShow)}
>
<NIcon>
<EyeOutlined />
</NIcon>
</NButton>
)
}}
</NTooltip>
</NSpace>
)
}}
</Card> </Card>
) )
} }

97
dolphinscheduler-ui-next/src/views/projects/workflow/relation/use-relation.ts

@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reactive, ref } from 'vue'
import { useAsyncState } from '@vueuse/core'
import {
queryWorkFlowList,
queryLineageByWorkFlowCode,
queryLineageByWorkFlowName
} from '@/service/modules/lineages'
import type {
WorkflowRes,
WorkFlowListRes
} from '@/service/modules/lineages/types'
export function useRelation() {
const variables = reactive({
workflowOptions: [],
workflow: ref(null),
seriesData: [],
labelShow: ref(true)
})
const formatWorkflow = (obj: Array<WorkFlowListRes>) => {
variables.seriesData = []
variables.seriesData = obj.map((item) => {
console.log(item)
return {
name: item.workFlowName,
id: item.workFlowCode,
...item
}
}) as any
}
const getWorkflowName = (projectCode: number) => {
const { state } = useAsyncState(
queryLineageByWorkFlowName({ projectCode }).then(
(res: Array<WorkFlowListRes>) => {
variables.workflowOptions = res.map((item) => {
return {
label: item.workFlowName,
value: item.workFlowCode
}
}) as any
}
),
{}
)
return state
}
const getOneWorkflow = (workflowCode: number, projectCode: number) => {
const { state } = useAsyncState(
queryLineageByWorkFlowCode(
{ workFlowCode: workflowCode },
{ projectCode }
).then((res: WorkflowRes) => {
formatWorkflow(res.workFlowList)
}),
{}
)
return state
}
const getWorkflowList = (projectCode: number) => {
const { state } = useAsyncState(
queryWorkFlowList({
projectCode
}).then((res: WorkflowRes) => {
formatWorkflow(res.workFlowList)
}),
{}
)
return state
}
return { variables, getWorkflowName, getOneWorkflow, getWorkflowList }
}
Loading…
Cancel
Save