Browse Source

[Feature][UI Next] Add the page of rule management in the module of data quality. (#8317)

calvin 3 years ago
parent
commit
b75f67a56a

File diff suppressed because it is too large
+ 1988 - 696
dolphinscheduler-ui-next/pnpm-lock.yaml


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

@@ -44,7 +44,8 @@ import {
   SafetyOutlined,
   GroupOutlined,
   ContainerOutlined,
-  ApartmentOutlined
+  ApartmentOutlined,
+  BarsOutlined
 } from '@vicons/antd'
 import { useMenuStore } from '@/store/menu/menu'
 
@@ -184,6 +185,11 @@ export function useDataList() {
             label: t('menu.task_result'),
             key: `/data-quality/task-result`,
             icon: renderIcon(ApartmentOutlined)
+          },
+          {
+            label: t('menu.rule'),
+            key: `/data-quality/rule`,
+            icon: renderIcon(BarsOutlined)
           }
         ]
       },

+ 66 - 1
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@@ -78,7 +78,8 @@ const menu = {
   task_group_option: 'Task Group Option',
   task_group_queue: 'Task Group Queue',
   data_quality: 'Data Quality',
-  task_result: 'Task Result'
+  task_result: 'Task Result',
+  rule: 'Rule management'
 }
 
 const home = {
@@ -771,6 +772,70 @@ const data_quality = {
     expected_and_actual: 'Expected - Actual',
     actual_and_expected: 'Actual - Expected',
     actual_or_expected: 'Actual / Expected x 100%'
+  },
+  rule: {
+    actions: 'Actions',
+    name: 'Rule Name',
+    type: 'Rule Type',
+    username: 'User Name',
+    create_time: 'Create Time',
+    update_time: 'Update Time',
+    input_item: 'Rule input item',
+    view_input_item: 'View input items',
+    input_item_title: 'Input item title',
+    input_item_placeholder: 'Input item placeholder',
+    input_item_type: 'Input item type',
+    src_connector_type: 'SrcConnType',
+    src_datasource_id: 'SrcSource',
+    src_table: 'SrcTable',
+    src_filter: 'SrcFilter',
+    src_field: 'SrcField',
+    statistics_name: 'ActualValName',
+    check_type: 'CheckType',
+    operator: 'Operator',
+    threshold: 'Threshold',
+    failure_strategy: 'FailureStrategy',
+    target_connector_type: 'TargetConnType',
+    target_datasource_id: 'TargetSourceId',
+    target_table: 'TargetTable',
+    target_filter: 'TargetFilter',
+    mapping_columns: 'OnClause',
+    statistics_execute_sql: 'ActualValExecSql',
+    comparison_name: 'ExceptedValName',
+    comparison_execute_sql: 'ExceptedValExecSql',
+    comparison_type: 'ExceptedValType',
+    writer_connector_type: 'WriterConnType',
+    writer_datasource_id: 'WriterSourceId',
+    target_field: 'TargetField',
+    field_length: 'FieldLength',
+    logic_operator: 'LogicOperator',
+    regexp_pattern: 'RegexpPattern',
+    deadline: 'Deadline',
+    datetime_format: 'DatetimeFormat',
+    enum_list: 'EnumList',
+    begin_time: 'BeginTime',
+    fix_value: 'FixValue',
+    null_check: 'NullCheck',
+    custom_sql: 'Custom Sql',
+    single_table: 'Single Table',
+    single_table_custom_sql: 'Single Table Custom Sql',
+    multi_table_accuracy: 'Multi Table Accuracy',
+    multi_table_value_comparison: 'Multi Table Compare',
+    field_length_check: 'FieldLengthCheck',
+    uniqueness_check: 'UniquenessCheck',
+    regexp_check: 'RegexpCheck',
+    timeliness_check: 'TimelinessCheck',
+    enumeration_check: 'EnumerationCheck',
+    table_count_check: 'TableCountCheck',
+    All: 'All',
+    FixValue: 'FixValue',
+    DailyFluctuation: 'DailyFluctuation',
+    WeeklyFluctuation: 'WeeklyFluctuation',
+    MonthlyFluctuation: 'MonthlyFluctuation',
+    Last7DayFluctuation: 'Last7DayFluctuation',
+    Last30DayFluctuation: 'Last30DayFluctuation',
+    SrcTableTotalRows: 'SrcTableTotalRows',
+    TargetTableTotalRows: 'TargetTableTotalRows'
   }
 }
 

+ 65 - 1
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@@ -78,7 +78,8 @@ const menu = {
   task_group_option: '任务组配置',
   task_group_queue: '任务组队列',
   data_quality: '数据质量',
-  task_result: '任务结果'
+  task_result: '任务结果',
+  rule: '规则管理'
 }
 
 const home = {
@@ -765,6 +766,69 @@ const data_quality = {
     expected_and_actual: '期望值-实际值',
     actual_and_expected: '实际值-期望值',
     actual_or_expected: '实际值/期望值 x 100%'
+  },
+  rule: {
+    actions: '操作',
+    name: '规则名称',
+    type: '规则类型',
+    username: '用户名',
+    create_time: '创建时间',
+    update_time: '更新时间',
+    input_item: '规则输入项',
+    view_input_item: '查看规则输入项信息',
+    input_item_title: '输入项标题',
+    input_item_placeholder: '输入项占位符',
+    input_item_type: '输入项类型',
+    src_connector_type: '源数据类型',
+    src_datasource_id: '源数据源',
+    src_table: '源数据表',
+    src_filter: '源表过滤条件',
+    src_field: '源表检测列',
+    statistics_name: '实际值名',
+    check_type: '校验方式',
+    operator: '校验操作符',
+    threshold: '阈值',
+    failure_strategy: '失败策略',
+    target_connector_type: '目标数据类型',
+    target_datasource_id: '目标数据源',
+    target_table: '目标数据表',
+    target_filter: '目标表过滤条件',
+    mapping_columns: 'ON语句',
+    statistics_execute_sql: '实际值计算SQL',
+    comparison_name: '期望值名',
+    comparison_execute_sql: '期望值计算SQL',
+    comparison_type: '期望值类型',
+    writer_connector_type: '输出数据类型',
+    writer_datasource_id: '输出数据源',
+    target_field: '目标表检测列',
+    field_length: '字段长度限制',
+    logic_operator: '逻辑操作符',
+    regexp_pattern: '正则表达式',
+    deadline: '截止时间',
+    datetime_format: '时间格式',
+    enum_list: '枚举值列表',
+    begin_time: '起始时间',
+    fix_value: '固定值',
+    null_check: '空值检测',
+    custom_sql: '自定义SQL',
+    single_table: '单表检测',
+    multi_table_accuracy: '多表准确性',
+    multi_table_value_comparison: '两表值比对',
+    field_length_check: '字段长度校验',
+    uniqueness_check: '唯一性校验',
+    regexp_check: '正则表达式',
+    timeliness_check: '及时性校验',
+    enumeration_check: '枚举值校验',
+    table_count_check: '表行数校验',
+    all: '全部',
+    FixValue: '固定值',
+    DailyFluctuation: '日波动',
+    WeeklyFluctuation: '周波动',
+    MonthlyFluctuation: '月波动',
+    Last7DayFluctuation: '最近7天波动',
+    Last30DayFluctuation: '最近30天波动',
+    SrcTableTotalRows: '源表总行数',
+    TargetTableTotalRows: '目标表总行数'
   }
 }
 

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

@@ -37,6 +37,15 @@ export default {
         title: '数据质量-task-result',
         showSide: true
       }
+    },
+    {
+      path: '/data-quality/rule',
+      name: 'data-quality-rule',
+      component: components['data-quality-rule'],
+      meta: {
+        title: '数据质量-rule',
+        showSide: true
+      }
     }
   ]
 }

+ 21 - 1
dolphinscheduler-ui-next/src/service/modules/data-quality/types.ts

@@ -71,4 +71,24 @@ interface ResultListRes {
   start: number
 }
 
-export { RuleListReq, ResultListReq, ResultItem, ResultListRes }
+interface Rule {
+  id: number
+  name: string
+  ruleJson: string
+  type: number
+  userId: number
+  userName: string
+  createTime: string
+  updateTime: string
+}
+
+interface RuleRes {
+  totalList: Rule[]
+  total: number
+  totalPage: number
+  pageSize: number
+  currentPage: number
+  start: number
+}
+
+export { RuleListReq, ResultListReq, ResultItem, ResultListRes, Rule, RuleRes }

+ 102 - 0
dolphinscheduler-ui-next/src/views/data-quality/rule/components/rule-modal.tsx

@@ -0,0 +1,102 @@
+/*
+ * 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, h, PropType, reactive, ref, toRefs, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { NDataTable } from 'naive-ui'
+import Modal from '@/components/modal'
+import styles from '../index.module.scss'
+import { TableColumns } from 'naive-ui/es/data-table/src/interface'
+
+const props = {
+  show: {
+    type: Boolean as PropType<boolean>,
+    default: false
+  },
+  data: {
+    type: String as PropType<string>,
+    default: ''
+  }
+}
+
+export default defineComponent({
+  name: 'ruleInputEntry',
+  props,
+  emits: ['cancel', 'confirm'],
+  setup(props, ctx) {
+    const { t } = useI18n()
+
+    const ruleInputEntryList = JSON.parse(props.data).ruleInputEntryList
+
+    ruleInputEntryList.forEach((item: any) => {
+      item.title = t(
+        'data_quality.rule.' + item.title.substring(3, item.title.length - 1)
+      )
+    })
+
+    const columns: TableColumns<any> = [
+      {
+        title: t('data_quality.rule.input_item_title'),
+        key: 'title'
+      },
+      {
+        title: t('data_quality.rule.input_item_placeholder'),
+        key: 'field'
+      },
+      {
+        title: t('data_quality.rule.input_item_type'),
+        key: 'type'
+      }
+    ]
+
+    const onCancel = () => {
+      ctx.emit('cancel')
+    }
+
+    const onConfirm = () => {
+      ctx.emit('confirm')
+    }
+
+    return {
+      onCancel,
+      onConfirm,
+      columns,
+      ruleInputEntryList
+    }
+  },
+
+  render() {
+    const { t } = useI18n()
+
+    return (
+      <Modal
+        show={this.$props.show}
+        title={t('data_quality.rule.input_item')}
+        cancelShow={false}
+        onConfirm={this.onConfirm}
+      >
+        <NDataTable
+          columns={this.columns}
+          data={this.ruleInputEntryList}
+          striped
+          size={'small'}
+          class={styles.table}
+        />
+      </Modal>
+    )
+  }
+})

+ 74 - 0
dolphinscheduler-ui-next/src/views/data-quality/rule/components/table-action.tsx

@@ -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.
+ */
+
+import { defineComponent, PropType } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { NSpace, NTooltip, NButton, NIcon } from 'naive-ui'
+import { InfoCircleFilled } from '@vicons/antd'
+import type { Rule } from '@/service/modules/data-quality/types'
+
+interface ItemRow extends Rule {}
+
+const props = {
+  row: {
+    type: Object as PropType<ItemRow>,
+    default: {}
+  }
+}
+
+const TableAction = defineComponent({
+  name: 'TableAction',
+  props,
+  emits: ['viewRuleEntry'],
+  setup(props, { emit }) {
+    const { t } = useI18n()
+
+    const viewRuleEntryDetails = (detail: string) => {
+      emit('viewRuleEntry', detail)
+    }
+
+    return { t, viewRuleEntryDetails }
+  },
+  render() {
+    const { t, viewRuleEntryDetails } = this
+
+    return (
+      <NSpace>
+        <NTooltip trigger={'hover'}>
+          {{
+            default: () => t('data_quality.rule.view_input_item'),
+            trigger: () => (
+              <NButton
+                size='small'
+                type='primary'
+                tag='div'
+                onClick={() => viewRuleEntryDetails(this.row.ruleJson)}
+                circle
+              >
+                <NIcon>
+                  <InfoCircleFilled />
+                </NIcon>
+              </NButton>
+            )
+          }}
+        </NTooltip>
+      </NSpace>
+    )
+  }
+})
+
+export default TableAction

+ 26 - 0
dolphinscheduler-ui-next/src/views/data-quality/rule/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;
+  }
+}

+ 157 - 0
dolphinscheduler-ui-next/src/views/data-quality/rule/index.tsx

@@ -0,0 +1,157 @@
+/*
+ * 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, ref, toRefs, watch } from 'vue'
+import {
+  NSpace,
+  NInput,
+  NButton,
+  NIcon,
+  NDataTable,
+  NPagination,
+  NCard
+} from 'naive-ui'
+import { SearchOutlined } from '@vicons/antd'
+import { useTable } from './use-table'
+import Card from '@/components/card'
+import styles from './index.module.scss'
+import RuleModal from './components/rule-modal'
+
+const TaskResult = defineComponent({
+  name: 'rule',
+  setup() {
+    const { t, variables, getTableData } = useTable()
+
+    const showModalRef = ref(false)
+
+    const ruleEntryData = ref('')
+
+    const requestTableData = () => {
+      getTableData({
+        pageSize: variables.pageSize,
+        pageNo: variables.page,
+        startDate: '',
+        endDate: '',
+        searchVal: variables.searchVal
+      })
+    }
+
+    const onUpdatePageSize = () => {
+      variables.page = 1
+      requestTableData()
+    }
+
+    const onSearch = () => {
+      variables.page = 1
+      requestTableData()
+    }
+
+    const onCancel = () => {
+      showModalRef.value = false
+    }
+
+    const onConfirm = () => {
+      showModalRef.value = false
+    }
+
+    const viewRuleEntry = (ruleJson: string) => {
+      showModalRef.value = true
+      ruleEntryData.value = ruleJson
+    }
+
+    onMounted(() => {
+      requestTableData()
+    })
+
+    return {
+      t,
+      ...toRefs(variables),
+      requestTableData,
+      onUpdatePageSize,
+      showModalRef,
+      onCancel,
+      onConfirm,
+      onSearch,
+      ruleEntryData,
+      viewRuleEntry
+    }
+  },
+  render() {
+    const {
+      t,
+      showModalRef,
+      requestTableData,
+      onUpdatePageSize,
+      onSearch,
+      onCancel,
+      onConfirm,
+      viewRuleEntry,
+      ruleEntryData
+    } = this
+
+    const { columns } = useTable(viewRuleEntry)
+
+    return (
+      <div>
+        <NCard>
+          <NSpace justify='end'>
+            <NInput
+              v-model={[this.searchVal, 'value']}
+              size='small'
+              placeholder={t('data_quality.rule.name')}
+              clearable
+            />
+            <NButton size='small' type='primary' onClick={onSearch}>
+              {{
+                icon: () => (
+                  <NIcon>
+                    <SearchOutlined />
+                  </NIcon>
+                )
+              }}
+            </NButton>
+          </NSpace>
+        </NCard>
+        <Card class={styles['table-card']}>
+          <NDataTable columns={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>
+        {showModalRef && (
+          <RuleModal
+            show={showModalRef}
+            onCancel={onCancel}
+            onConfirm={onConfirm}
+            data={ruleEntryData}
+          />
+        )}
+      </div>
+    )
+  }
+})
+
+export default TaskResult

+ 146 - 0
dolphinscheduler-ui-next/src/views/data-quality/rule/use-table.ts

@@ -0,0 +1,146 @@
+/*
+ * 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 { h, reactive, ref } from 'vue'
+import { useAsyncState } from '@vueuse/core'
+import { queryRuleListPaging } from '@/service/modules/data-quality'
+import type { Rule, RuleRes } from '@/service/modules/data-quality/types'
+import TableAction from './components/table-action'
+import _ from 'lodash'
+import { format } from 'date-fns'
+import { TableColumns } from 'naive-ui/es/data-table/src/interface'
+
+export function useTable(viewRuleEntry = (ruleJson: string): void => {}) {
+  const { t } = useI18n()
+
+  const variables = reactive({
+    tableData: [],
+    page: ref(1),
+    pageSize: ref(10),
+    state: ref(null),
+    searchVal: ref(null),
+    totalPage: ref(1)
+  })
+
+  const columns: TableColumns<any> = [
+    {
+      title: t('data_quality.rule.name'),
+      key: 'ruleName'
+    },
+    {
+      title: t('data_quality.rule.type'),
+      key: 'ruleTypeName'
+    },
+    {
+      title: t('data_quality.rule.username'),
+      key: 'userName'
+    },
+    {
+      title: t('data_quality.rule.create_time'),
+      key: 'createTime'
+    },
+    {
+      title: t('data_quality.rule.update_time'),
+      key: 'updateTime'
+    },
+    {
+      title: t('data_quality.rule.actions'),
+      key: 'actions',
+      width: 150,
+      render: (row: any) =>
+        h(TableAction, {
+          row,
+          onViewRuleEntry: (ruleJson: string) => {
+            viewRuleEntry(ruleJson)
+          }
+        })
+    }
+  ]
+
+  const ruleTypeMapping = [
+    {
+      code: -1,
+      label: t('data_quality.rule.all')
+    },
+    {
+      code: 0,
+      label: t('data_quality.rule.single_table')
+    },
+    {
+      code: 1,
+      label: t('data_quality.rule.custom_sql')
+    },
+    {
+      code: 2,
+      label: t('data_quality.rule.multi_table_accuracy')
+    },
+    {
+      code: 3,
+      label: t('data_quality.rule.multi_table_value_comparison')
+    }
+  ]
+
+  const getTableData = (params: any) => {
+    const data = {
+      pageSize: params.pageSize,
+      pageNo: params.pageNo,
+      searchVal: params.searchVal,
+      startDate: params.startDate,
+      endDate: params.endDate
+    }
+
+    const { state } = useAsyncState(
+      queryRuleListPaging(data).then((res: RuleRes) => {
+        variables.tableData = res.totalList.map((item, index) => {
+          const ruleName =
+            'data_quality.rule.' + item.name.substring(3, item.name.length - 1)
+          const ruleNameLocale = t(ruleName)
+
+          const ruleType = _.find(ruleTypeMapping, { code: item.type })
+
+          let ruleTypeName = ''
+
+          if (ruleType) {
+            ruleTypeName = ruleType.label
+          }
+
+          item.createTime = format(
+            new Date(item.createTime),
+            'yyyy-MM-dd HH:mm:ss'
+          )
+          item.updateTime = format(
+            new Date(item.updateTime),
+            'yyyy-MM-dd HH:mm:ss'
+          )
+
+          return {
+            index: index + 1,
+            ...item,
+            ruleName: ruleNameLocale,
+            ruleTypeName: ruleTypeName
+          }
+        }) as any
+      }),
+      {}
+    )
+
+    return state
+  }
+
+  return { t, variables, getTableData, columns }
+}

+ 2 - 1
dolphinscheduler-ui-next/src/views/projects/task/definition/index.module.scss

@@ -26,7 +26,8 @@
     align-items: center;
     width: 300px;
 
-    button, input {
+    button,
+    input {
       margin-left: 10px;
     }
   }

+ 32 - 32
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss

@@ -96,104 +96,104 @@ $bgLight: #ffffff;
     background-size: 100% 100%;
     margin-right: 10px;
     &.icon-shell {
-      background-image: url("@/assets/images/task-icons/shell.png");
+      background-image: url('@/assets/images/task-icons/shell.png');
     }
     &.icon-sub_process {
-      background-image: url("@/assets/images/task-icons/sub_process.png");
+      background-image: url('@/assets/images/task-icons/sub_process.png');
     }
     &.icon-procedure {
-      background-image: url("@/assets/images/task-icons/procedure.png");
+      background-image: url('@/assets/images/task-icons/procedure.png');
     }
     &.icon-sql {
-      background-image: url("@/assets/images/task-icons/sql.png");
+      background-image: url('@/assets/images/task-icons/sql.png');
     }
     &.icon-flink {
-      background-image: url("@/assets/images/task-icons/flink.png");
+      background-image: url('@/assets/images/task-icons/flink.png');
     }
     &.icon-mr {
-      background-image: url("@/assets/images/task-icons/mr.png");
+      background-image: url('@/assets/images/task-icons/mr.png');
     }
     &.icon-python {
-      background-image: url("@/assets/images/task-icons/python.png");
+      background-image: url('@/assets/images/task-icons/python.png');
     }
     &.icon-dependent {
-      background-image: url("@/assets/images/task-icons/dependent.png");
+      background-image: url('@/assets/images/task-icons/dependent.png');
     }
     &.icon-http {
-      background-image: url("@/assets/images/task-icons/http.png");
+      background-image: url('@/assets/images/task-icons/http.png');
     }
     &.icon-datax {
-      background-image: url("@/assets/images/task-icons/datax.png");
+      background-image: url('@/assets/images/task-icons/datax.png');
     }
     &.icon-pigeon {
-      background-image: url("@/assets/images/task-icons/pigeon.png");
+      background-image: url('@/assets/images/task-icons/pigeon.png');
     }
     &.icon-sqoop {
-      background-image: url("@/assets/images/task-icons/sqoop.png");
+      background-image: url('@/assets/images/task-icons/sqoop.png');
     }
     &.icon-conditions {
-      background-image: url("@/assets/images/task-icons/conditions.png");
+      background-image: url('@/assets/images/task-icons/conditions.png');
     }
     &.icon-seatunnel {
-      background-image: url("@/assets/images/task-icons/seatunnel.png");
+      background-image: url('@/assets/images/task-icons/seatunnel.png');
     }
     &.icon-spark {
-      background-image: url("@/assets/images/task-icons/spark.png");
+      background-image: url('@/assets/images/task-icons/spark.png');
     }
     &.icon-switch {
-      background-image: url("@/assets/images/task-icons/switch.png");
+      background-image: url('@/assets/images/task-icons/switch.png');
     }
   }
 
   &:hover {
     .sidebar-icon {
       &.icon-shell {
-        background-image: url("@/assets/images/task-icons/shell_hover.png");
+        background-image: url('@/assets/images/task-icons/shell_hover.png');
       }
       &.icon-sub_process {
-        background-image: url("@/assets/images/task-icons/sub_process_hover.png");
+        background-image: url('@/assets/images/task-icons/sub_process_hover.png');
       }
       &.icon-procedure {
-        background-image: url("@/assets/images/task-icons/procedure_hover.png");
+        background-image: url('@/assets/images/task-icons/procedure_hover.png');
       }
       &.icon-sql {
-        background-image: url("@/assets/images/task-icons/sql_hover.png");
+        background-image: url('@/assets/images/task-icons/sql_hover.png');
       }
       &.icon-flink {
-        background-image: url("@/assets/images/task-icons/flink_hover.png");
+        background-image: url('@/assets/images/task-icons/flink_hover.png');
       }
       &.icon-mr {
-        background-image: url("@/assets/images/task-icons/mr_hover.png");
+        background-image: url('@/assets/images/task-icons/mr_hover.png');
       }
       &.icon-python {
-        background-image: url("@/assets/images/task-icons/python_hover.png");
+        background-image: url('@/assets/images/task-icons/python_hover.png');
       }
       &.icon-dependent {
-        background-image: url("@/assets/images/task-icons/dependent_hover.png");
+        background-image: url('@/assets/images/task-icons/dependent_hover.png');
       }
       &.icon-http {
-        background-image: url("@/assets/images/task-icons/http_hover.png");
+        background-image: url('@/assets/images/task-icons/http_hover.png');
       }
       &.icon-datax {
-        background-image: url("@/assets/images/task-icons/datax_hover.png");
+        background-image: url('@/assets/images/task-icons/datax_hover.png');
       }
       &.icon-pigeon {
-        background-image: url("@/assets/images/task-icons/pigeon_hover.png");
+        background-image: url('@/assets/images/task-icons/pigeon_hover.png');
       }
       &.icon-sqoop {
-        background-image: url("@/assets/images/task-icons/sqoop_hover.png");
+        background-image: url('@/assets/images/task-icons/sqoop_hover.png');
       }
       &.icon-conditions {
-        background-image: url("@/assets/images/task-icons/conditions_hover.png");
+        background-image: url('@/assets/images/task-icons/conditions_hover.png');
       }
       &.icon-seatunnel {
-        background-image: url("@/assets/images/task-icons/seatunnel_hover.png");
+        background-image: url('@/assets/images/task-icons/seatunnel_hover.png');
       }
       &.icon-spark {
-        background-image: url("@/assets/images/task-icons/spark_hover.png");
+        background-image: url('@/assets/images/task-icons/spark_hover.png');
       }
       &.icon-switch {
-        background-image: url("@/assets/images/task-icons/switch_hover.png");
+        background-image: url('@/assets/images/task-icons/switch_hover.png');
       }
     }
   }

+ 2 - 2
dolphinscheduler-ui-next/src/views/projects/workflow/definition/index.module.scss

@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
- .content {
+.content {
   width: 100%;
 
   .card {
@@ -85,4 +85,4 @@
   > div:first-child {
     width: 86%;
   }
-}
+}