Browse Source

[Feature][UI Next] Add task result. (#8277)

songjianet 3 years ago
parent
commit
1f87eb6760

+ 2 - 1
dolphinscheduler-ui-next/src/layouts/content/index.tsx

@@ -61,7 +61,8 @@ const Content = defineComponent({
     const getSideMenu = (state: any) => {
       const key = menuStore.getMenuKey
       state.sideMenuOptions =
-        state.menuOptions.filter((menu: { key: string }) => menu.key === key)[0]?.children || state.menuOptions
+        state.menuOptions.filter((menu: { key: string }) => menu.key === key)[0]
+          ?.children || state.menuOptions
       state.isShowSide = menuStore.getShowSideStatus
     }
 

+ 16 - 1
dolphinscheduler-ui-next/src/layouts/content/use-dataList.ts

@@ -42,7 +42,9 @@ import {
   EnvironmentOutlined,
   KeyOutlined,
   SafetyOutlined,
-  GroupOutlined
+  GroupOutlined,
+  ContainerOutlined,
+  ApartmentOutlined
 } from '@vicons/antd'
 import { useMenuStore } from '@/store/menu/menu'
 
@@ -172,6 +174,19 @@ export function useDataList() {
           }
         ]
       },
+      {
+        label: t('menu.data_quality'),
+        key: 'data-quality',
+        icon: renderIcon(ContainerOutlined),
+        isShowSide: true,
+        children: [
+          {
+            label: t('menu.task_result'),
+            key: `/data-quality/task-result`,
+            icon: renderIcon(ApartmentOutlined)
+          }
+        ]
+      },
       {
         label: t('menu.datasource'),
         key: 'datasource',

+ 37 - 2
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@@ -76,7 +76,9 @@ const menu = {
   token_manage: 'Token Manage',
   task_group_manage: 'Task Group Manage',
   task_group_option: 'Task Group Option',
-  task_group_queue: 'Task Group Queue'
+  task_group_queue: 'Task Group Queue',
+  data_quality: 'Data Quality',
+  task_result: 'Task Result'
 }
 
 const home = {
@@ -715,6 +717,38 @@ const datasource = {
   user_password_tips: 'Please enter your password'
 }
 
+const data_quality = {
+  task_result: {
+    task_name: 'Task Name',
+    workflow_instance: 'Workflow Instance',
+    rule_type: 'Rule Type',
+    rule_name: 'Rule Name',
+    state: 'State',
+    actual_value: 'Actual Value',
+    excepted_value: 'Excepted Value',
+    check_type: 'Check Type',
+    operator: 'Operator',
+    threshold: 'Threshold',
+    failure_strategy: 'Failure Strategy',
+    excepted_value_type: 'Excepted Value Type',
+    error_output_path: 'Error Output Path',
+    username: 'Username',
+    create_time: 'Create Time',
+    update_time: 'Update Time',
+    undone: 'Undone',
+    success: 'Success',
+    failure: 'Failure',
+    single_table: 'Single Table',
+    single_table_custom_sql: 'Single Table Custom Sql',
+    multi_table_accuracy: 'Multi Table Accuracy',
+    multi_table_comparison: 'Multi Table Comparison',
+    expected_and_actual_or_expected: '(Expected - Actual) / Expected x 100%',
+    expected_and_actual: 'Expected - Actual',
+    actual_and_expected: 'Actual - Expected',
+    actual_or_expected: 'Actual / Expected x 100%'
+  }
+}
+
 export default {
   login,
   modal,
@@ -728,5 +762,6 @@ export default {
   resource,
   project,
   security,
-  datasource
+  datasource,
+  data_quality
 }

+ 37 - 2
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@@ -76,7 +76,9 @@ const menu = {
   token_manage: '令牌管理',
   task_group_manage: '任务组管理',
   task_group_option: '任务组配置',
-  task_group_queue: '任务组队列'
+  task_group_queue: '任务组队列',
+  data_quality: '数据质量',
+  task_result: '任务结果'
 }
 
 const home = {
@@ -708,6 +710,38 @@ const datasource = {
   user_password_tips: '请输入密码'
 }
 
+const data_quality = {
+  task_result: {
+    task_name: '任务名称',
+    workflow_instance: '工作流实例',
+    rule_type: '规则类型',
+    rule_name: '规则名称',
+    state: '状态',
+    actual_value: '实际值',
+    excepted_value: '期望值',
+    check_type: '检测类型',
+    operator: '操作符',
+    threshold: '阈值',
+    failure_strategy: '失败策略',
+    excepted_value_type: '期望值类型',
+    error_output_path: '错误数据路径',
+    username: '用户名',
+    create_time: '创建时间',
+    update_time: '更新时间',
+    undone: '未完成',
+    success: '成功',
+    failure: '失败',
+    single_table: '单表检测',
+    single_table_custom_sql: '自定义SQL',
+    multi_table_accuracy: '多表准确性',
+    multi_table_comparison: '两表值对比',
+    expected_and_actual_or_expected: '(期望值-实际值)/实际值 x 100%',
+    expected_and_actual: '期望值-实际值',
+    actual_and_expected: '实际值-期望值',
+    actual_or_expected: '实际值/期望值 x 100%'
+  }
+}
+
 export default {
   login,
   modal,
@@ -721,5 +755,6 @@ export default {
   resource,
   project,
   security,
-  datasource
+  datasource,
+  data_quality
 }

+ 42 - 0
dolphinscheduler-ui-next/src/router/modules/data-quality.ts

@@ -0,0 +1,42 @@
+/*
+ * 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 type { Component } from 'vue'
+import utils from '@/utils'
+
+// All TSX files under the views folder automatically generate mapping relationship
+const modules = import.meta.glob('/src/views/**/**.tsx')
+const components: { [key: string]: Component } = utils.mapping(modules)
+
+export default {
+  path: '/data-quality',
+  name: 'data-quality',
+  meta: { title: 'data-quality' },
+  redirect: { name: 'task-result' },
+  component: () => import('@/layouts/content'),
+  children: [
+    {
+      path: '/data-quality/task-result',
+      name: 'task-result',
+      component: components['data-quality-task-result'],
+      meta: {
+        title: '数据质量-task-result',
+        showSide: true
+      }
+    }
+  ]
+}

+ 3 - 1
dolphinscheduler-ui-next/src/router/routes.ts

@@ -23,6 +23,7 @@ import resourcesPage from './modules/resources'
 import datasourcePage from './modules/datasource'
 import monitorPage from './modules/monitor'
 import securityPage from './modules/security'
+import dataQualityPage from './modules/data-quality'
 
 // All TSX files under the views folder automatically generate mapping relationship
 const modules = import.meta.glob('/src/views/**/**.tsx')
@@ -68,7 +69,8 @@ const basePage: RouteRecordRaw[] = [
   resourcesPage,
   datasourcePage,
   monitorPage,
-  securityPage
+  securityPage,
+  dataQualityPage
 ]
 
 /**

+ 35 - 0
dolphinscheduler-ui-next/src/service/modules/data-quality/index.ts

@@ -0,0 +1,35 @@
+/*
+ * 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 { axios } from '@/service/service'
+import type { RuleListReq, ResultListReq } from './types'
+
+export function queryRuleListPaging(params: RuleListReq): any {
+  return axios({
+    url: '/data-quality/rule/page',
+    method: 'get',
+    params
+  })
+}
+
+export function queryExecuteResultListPaging(params: ResultListReq): any {
+  return axios({
+    url: '/data-quality/result/page',
+    method: 'get',
+    params
+  })
+}

+ 74 - 0
dolphinscheduler-ui-next/src/service/modules/data-quality/types.ts

@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+interface ListReq {
+  pageNo: number
+  pageSize: number
+  searchVal?: string
+}
+
+interface RuleListReq extends ListReq {
+  endDate?: string
+  startDate?: string
+  ruleType?: string
+}
+
+interface ResultListReq extends ListReq {
+  endDate?: string
+  startDate?: string
+  ruleType?: string
+  state?: string
+}
+
+interface ResultItem {
+  id: number
+  processDefinitionId: number
+  processDefinitionName: string
+  processDefinitionCode: number
+  processInstanceId: number
+  processInstanceName: string
+  projectCode: number
+  taskInstanceId: number
+  taskName: string
+  ruleType: number
+  ruleName: string
+  statisticsValue: number
+  comparisonValue: number
+  comparisonType: number
+  comparisonTypeName: string
+  checkType: number
+  threshold: number
+  operator: number
+  failureStrategy: number
+  userId: number
+  userName: string
+  state: number
+  errorOutputPath: string
+  createTime: string
+  updateTime: string
+}
+
+interface ResultListRes {
+  totalList: ResultItem[]
+  total: number
+  totalPage: number
+  pageSize: number
+  currentPage: number
+  start: number
+}
+
+export { RuleListReq, ResultListReq, ResultItem, ResultListRes }

+ 26 - 0
dolphinscheduler-ui-next/src/views/data-quality/task-result/index.module.scss

@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+.table-card {
+  margin-top: 8px;
+
+  .pagination {
+    margin-top: 20px;
+    display: flex;
+    justify-content: center;
+  }
+}

+ 177 - 0
dolphinscheduler-ui-next/src/views/data-quality/task-result/index.tsx

@@ -0,0 +1,177 @@
+/*
+ * 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, onMounted, toRefs, watch } from 'vue'
+import {
+  NSpace,
+  NInput,
+  NSelect,
+  NDatePicker,
+  NButton,
+  NIcon,
+  NDataTable,
+  NPagination,
+  NCard
+} from 'naive-ui'
+import { SearchOutlined } from '@vicons/antd'
+import { useTable } from './use-table'
+import { useI18n } from 'vue-i18n'
+import Card from '@/components/card'
+import styles from './index.module.scss'
+
+const TaskResult = defineComponent({
+  name: 'task-result',
+  setup() {
+    const { t, variables, getTableData, createColumns } = useTable()
+
+    const requestTableData = () => {
+      getTableData({
+        pageSize: variables.pageSize,
+        pageNo: variables.page,
+        ruleType: variables.ruleType,
+        state: variables.state,
+        searchVal: variables.searchVal,
+        datePickerRange: variables.datePickerRange
+      })
+    }
+
+    const onUpdatePageSize = () => {
+      variables.page = 1
+      requestTableData()
+    }
+
+    const onSearch = () => {
+      variables.page = 1
+      requestTableData()
+    }
+
+    onMounted(() => {
+      createColumns(variables)
+      requestTableData()
+    })
+
+    watch(useI18n().locale, () => {
+      createColumns(variables)
+    })
+
+    return {
+      t,
+      ...toRefs(variables),
+      requestTableData,
+      onUpdatePageSize,
+      onSearch
+    }
+  },
+  render() {
+    const { t, requestTableData, onUpdatePageSize, onSearch } = this
+
+    return (
+      <>
+        <NCard>
+          <NSpace justify='end'>
+            <NInput
+              v-model={[this.searchVal, 'value']}
+              size='small'
+              placeholder={t('data_quality.task_result.task_name')}
+              clearable
+            />
+            <NSelect
+              v-model={[this.ruleType, 'value']}
+              size='small'
+              options={[
+                {
+                  value: 0,
+                  label: t('data_quality.task_result.single_table')
+                },
+                {
+                  value: 1,
+                  label: t('data_quality.task_result.single_table_custom_sql')
+                },
+                {
+                  value: 2,
+                  label: t('data_quality.task_result.multi_table_accuracy')
+                },
+                {
+                  value: 3,
+                  label: t('data_quality.task_result.multi_table_comparison')
+                }
+              ]}
+              placeholder={t('data_quality.task_result.rule_type')}
+              style={{ width: '180px' }}
+              clearable
+            />
+            <NSelect
+              v-model={[this.state, 'value']}
+              size='small'
+              options={[
+                {
+                  value: 0,
+                  label: t('data_quality.task_result.undone')
+                },
+                {
+                  value: 1,
+                  label: t('data_quality.task_result.success')
+                },
+                {
+                  value: 2,
+                  label: t('data_quality.task_result.failure')
+                }
+              ]}
+              placeholder={t('data_quality.task_result.state')}
+              style={{ width: '180px' }}
+              clearable
+            />
+            <NDatePicker
+              v-model={[this.datePickerRange, 'value']}
+              type='datetimerange'
+              size='small'
+              start-placeholder={t('monitor.audit_log.start_time')}
+              end-placeholder={t('monitor.audit_log.end_time')}
+              clearable
+            />
+            <NButton size='small' type='primary' onClick={onSearch}>
+              {{
+                icon: () => (
+                  <NIcon>
+                    <SearchOutlined />
+                  </NIcon>
+                )
+              }}
+            </NButton>
+          </NSpace>
+        </NCard>
+        <Card class={styles['table-card']}>
+          <NDataTable columns={this.columns} data={this.tableData} />
+          <div class={styles.pagination}>
+            <NPagination
+              v-model:page={this.page}
+              v-model:page-size={this.pageSize}
+              page-count={this.totalPage}
+              show-size-picker
+              page-sizes={[10, 30, 50]}
+              show-quick-jumper
+              onUpdatePage={requestTableData}
+              onUpdatePageSize={onUpdatePageSize}
+            />
+          </div>
+        </Card>
+      </>
+    )
+  }
+})
+
+export default TaskResult

+ 201 - 0
dolphinscheduler-ui-next/src/views/data-quality/task-result/use-table.ts

@@ -0,0 +1,201 @@
+/*
+ * 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 { useI18n } from 'vue-i18n'
+import { reactive, ref } from 'vue'
+import { useAsyncState } from '@vueuse/core'
+import { queryExecuteResultListPaging } from '@/service/modules/data-quality'
+import { format } from 'date-fns'
+import type {
+  ResultItem,
+  ResultListRes
+} from '@/service/modules/data-quality/types'
+
+export function useTable() {
+  const { t } = useI18n()
+
+  const variables = reactive({
+    columns: [],
+    tableData: [],
+    page: ref(1),
+    pageSize: ref(10),
+    ruleType: ref(null),
+    state: ref(null),
+    searchVal: ref(null),
+    datePickerRange: ref(null),
+    totalPage: ref(1)
+  })
+
+  const createColumns = (variables: any) => {
+    variables.columns = [
+      {
+        title: '#',
+        key: 'index'
+      },
+      {
+        title: t('data_quality.task_result.task_name'),
+        key: 'userName'
+      },
+      {
+        title: t('data_quality.task_result.workflow_instance'),
+        key: 'processInstanceName'
+      },
+      {
+        title: t('data_quality.task_result.rule_type'),
+        key: 'ruleType',
+        render: (row: ResultItem) => {
+          if (row.ruleType === 0) {
+            return t('data_quality.task_result.single_table')
+          } else if (row.ruleType === 1) {
+            return t('data_quality.task_result.single_table_custom_sql')
+          } else if (row.ruleType === 2) {
+            return t('data_quality.task_result.multi_table_accuracy')
+          } else if (row.ruleType === 3) {
+            return t('data_quality.task_result.multi_table_comparison')
+          }
+        }
+      },
+      {
+        title: t('data_quality.task_result.rule_name'),
+        key: 'ruleName'
+      },
+      {
+        title: t('data_quality.task_result.state'),
+        key: 'state',
+        render: (row: ResultItem) => {
+          if (row.state === 0) {
+            return t('data_quality.task_result.undone')
+          } else if (row.state === 1) {
+            return t('data_quality.task_result.success')
+          } else if (row.state === 2) {
+            return t('data_quality.task_result.failure')
+          }
+        }
+      },
+      {
+        title: t('data_quality.task_result.actual_value'),
+        key: 'statisticsValue'
+      },
+      {
+        title: t('data_quality.task_result.excepted_value'),
+        key: 'comparisonValue'
+      },
+      {
+        title: t('data_quality.task_result.check_type'),
+        key: 'checkType',
+        render: (row: ResultItem) => {
+          if (row.checkType === 0) {
+            return t('data_quality.task_result.expected_and_actual')
+          } else if (row.checkType === 1) {
+            return t('data_quality.task_result.actual_and_expected')
+          } else if (row.checkType === 2) {
+            return t('data_quality.task_result.actual_or_expected')
+          } else if (row.checkType === 3) {
+            return t('data_quality.task_result.expected_and_actual_or_expected')
+          }
+        }
+      },
+      {
+        title: t('data_quality.task_result.operator'),
+        key: 'operator',
+        render: (row: ResultItem) => {
+          if (row.operator === 0) {
+            return '='
+          } else if (row.operator === 1) {
+            return '<'
+          } else if (row.operator === 2) {
+            return '<='
+          } else if (row.operator === 3) {
+            return '>'
+          } else if (row.operator === 4) {
+            return '>='
+          } else if (row.operator === 5) {
+            return '!='
+          }
+        }
+      },
+      {
+        title: t('data_quality.task_result.threshold'),
+        key: 'threshold'
+      },
+      {
+        title: t('data_quality.task_result.failure_strategy'),
+        key: 'failureStrategy'
+      },
+      {
+        title: t('data_quality.task_result.excepted_value_type'),
+        key: 'comparisonTypeName'
+      },
+      {
+        title: t('data_quality.task_result.error_output_path'),
+        key: 'errorOutputPath',
+        render: (row: ResultItem) => {
+          return row.errorOutputPath ? row : '-'
+        }
+      },
+      {
+        title: t('data_quality.task_result.username'),
+        key: 'userName'
+      },
+      {
+        title: t('data_quality.task_result.create_time'),
+        key: 'createTime'
+      },
+      {
+        title: t('data_quality.task_result.update_time'),
+        key: 'updateTime'
+      }
+    ]
+  }
+
+  const getTableData = (params: any) => {
+    const data = {
+      pageSize: params.pageSize,
+      pageNo: params.pageNo,
+      ruleType: params.ruleType,
+      state: params.state,
+      searchVal: params.searchVal,
+      startDate: params.datePickerRange
+        ? format(new Date(params.datePickerRange[0]), 'yyyy-MM-dd HH:mm:ss')
+        : '',
+      endDate: params.datePickerRange
+        ? format(new Date(params.datePickerRange[1]), 'yyyy-MM-dd HH:mm:ss')
+        : ''
+    }
+
+    const { state } = useAsyncState(
+      queryExecuteResultListPaging(data).then((res: ResultListRes) => {
+        variables.tableData = res.totalList.map((item, index) => {
+          return {
+            index: index + 1,
+            ...item
+          }
+        }) as any
+      }),
+      {}
+    )
+
+    return state
+  }
+
+  return {
+    t,
+    variables,
+    getTableData,
+    createColumns
+  }
+}