Browse Source

[Feature][UI Next] Add Crontab Component (#8409)

* support crontab component

* fix time-modal form data init bug
3.0.0/version-upgrade
Devosend 3 years ago committed by GitHub
parent
commit
53869cb0eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 193
      dolphinscheduler-ui-next/src/components/crontab/common.ts
  2. 29
      dolphinscheduler-ui-next/src/components/crontab/index.module.scss
  3. 131
      dolphinscheduler-ui-next/src/components/crontab/index.tsx
  4. 640
      dolphinscheduler-ui-next/src/components/crontab/modules/day.tsx
  5. 296
      dolphinscheduler-ui-next/src/components/crontab/modules/time.tsx
  6. 30
      dolphinscheduler-ui-next/src/components/crontab/types.ts
  7. 65
      dolphinscheduler-ui-next/src/locales/modules/en_US.ts
  8. 63
      dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
  9. 58
      dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/timing-modal.tsx
  10. 5
      dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/use-modal.ts

193
dolphinscheduler-ui-next/src/components/crontab/common.ts

@ -0,0 +1,193 @@
/*
* 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 _ from 'lodash'
const timeI18n = {
second: {
everyTime: 'crontab.every_second',
every: 'crontab.every',
timeCarriedOut: 'crontab.second_carried_out',
timeStart: 'crontab.second_start',
cycleFrom: 'crontab.cycle_from',
specificTime: 'crontab.specific_second',
specificTimeTip: 'crontab.specific_second_tip',
to: 'crontab.to',
time: 'crontab.second'
},
minute: {
everyTime: 'crontab.every_minute',
every: 'crontab.every',
timeCarriedOut: 'crontab.minute_carried_out',
timeStart: 'crontab.minute_start',
cycleFrom: 'crontab.cycle_from',
specificTime: 'crontab.specific_minute',
specificTimeTip: 'crontab.specific_minute_tip',
to: 'crontab.to',
time: 'crontab.minute'
},
hour: {
everyTime: 'crontab.every_hour',
every: 'crontab.every',
timeCarriedOut: 'crontab.hour_carried_out',
timeStart: 'crontab.hour_start',
cycleFrom: 'crontab.cycle_from',
specificTime: 'crontab.specific_hour',
specificTimeTip: 'crontab.specific_hour_tip',
to: 'crontab.to',
time: 'crontab.hour'
},
month: {
everyTime: 'crontab.every_month',
every: 'crontab.every',
timeCarriedOut: 'crontab.month_carried_out',
timeStart: 'crontab.month_start',
cycleFrom: 'crontab.cycle_from',
specificTime: 'crontab.specific_month',
specificTimeTip: 'crontab.specific_month_tip',
to: 'crontab.to',
time: 'crontab.month'
},
year: {
everyTime: 'crontab.every_year',
every: 'crontab.every',
timeCarriedOut: 'crontab.year_carried_out',
timeStart: 'crontab.year_start',
cycleFrom: 'crontab.cycle_from',
specificTime: 'crontab.specific_year',
specificTimeTip: 'crontab.specific_year_tip',
to: 'crontab.to',
time: 'crontab.year'
}
}
const week = [
{
label: 'crontab.sunday',
value: 1
},
{
label: 'crontab.monday',
value: 2
},
{
label: 'crontab.tuesday',
value: 3
},
{
label: 'crontab.wednesday',
value: 4
},
{
label: 'crontab.thursday',
value: 5
},
{
label: 'crontab.friday',
value: 6
},
{
label: 'crontab.saturday',
value: 7
}
]
const specificWeek = [
{
label: 'SUN',
value: 'SUN'
},
{
label: 'MON',
value: 'MON'
},
{
label: 'TUE',
value: 'TUE'
},
{
label: 'WED',
value: 'WED'
},
{
label: 'THU',
value: 'THU'
},
{
label: 'FRI',
value: 'FRI'
},
{
label: 'SAT',
value: 'SAT'
}
]
const lastWeeks = [
{
label: 'crontab.sunday',
value: '?'
},
{
label: 'crontab.monday',
value: '2L'
},
{
label: 'crontab.tuesday',
value: '3L'
},
{
label: 'crontab.wednesday',
value: '4L'
},
{
label: 'crontab.thursday',
value: '5L'
},
{
label: 'crontab.friday',
value: '6L'
},
{
label: 'crontab.saturday',
value: '7L'
}
]
const isStr = (str: string, v: string) => {
let flag
if (str.indexOf(v) !== -1) {
flag = str.split(v)
}
return flag
}
const isWeek = (str: string) => {
let flag = false
const data = str.split(',')
const isSpecificWeek = (key: string) => {
return _.findIndex(specificWeek, (v) => v.value === key) !== -1
}
_.map(data, (v) => {
if (isSpecificWeek(v)) {
flag = true
}
})
return flag
}
export { isStr, isWeek, timeI18n, week, specificWeek, lastWeeks }

29
dolphinscheduler-ui-next/src/components/crontab/index.module.scss

@ -0,0 +1,29 @@
/*
* 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.
*/
.crontab-list {
display: flex;
.crontab-list-item {
display: flex;
vertical-align: middle;
align-items: center;
width: 460px;
> div {
margin: 5px;
}
}
}

131
dolphinscheduler-ui-next/src/components/crontab/index.tsx

@ -0,0 +1,131 @@
/*
* 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 { computed, defineComponent, ref, watch, PropType } from 'vue'
import { NTabPane, NTabs } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import CrontabTime from './modules/time'
import CrontabDay from './modules/day'
import { timeI18n } from './common'
const props = {
value: {
type: String as PropType<String>,
default: '* * * * * ? *'
}
}
export default defineComponent({
name: 'Crontab',
props,
emits: ['update:value'],
setup(props, ctx) {
const cron = props.value.split(' ')
const secondRef = ref(cron[0])
const minuteRef = ref(cron[1])
const hourRef = ref(cron[2])
const dayRef = ref(cron[3])
const monthRef = ref(cron[4])
const weekRef = ref(cron[5])
const yearRef = ref(cron[6])
const crontabValue = computed(
() =>
`${secondRef.value} ${minuteRef.value} ${hourRef.value} ${dayRef.value} ${monthRef.value} ${weekRef.value} ${yearRef.value}`
)
const reset = () => {
const cron = props.value.split(' ')
secondRef.value = cron[0]
minuteRef.value = cron[1]
hourRef.value = cron[2]
dayRef.value = cron[3]
monthRef.value = cron[4]
weekRef.value = cron[5]
yearRef.value = cron[6]
}
watch(
() => crontabValue.value,
() => {
ctx.emit('update:value', crontabValue.value)
}
)
watch(
() => props.value,
() => {
reset()
}
)
return {
secondRef,
minuteRef,
hourRef,
dayRef,
weekRef,
monthRef,
yearRef,
crontabValue
}
},
render() {
const { t } = useI18n()
return (
<NTabs type='line'>
<NTabPane name='seconde' tab={t('crontab.second')}>
<CrontabTime
v-model:timeValue={this.secondRef}
timeI18n={timeI18n.second}
/>
</NTabPane>
<NTabPane name='minute' tab={t('crontab.minute')}>
<CrontabTime
v-model:timeValue={this.minuteRef}
timeI18n={timeI18n.minute}
/>
</NTabPane>
<NTabPane name='hour' tab={t('crontab.hour')}>
<CrontabTime
v-model:timeValue={this.hourRef}
timeI18n={timeI18n.hour}
/>
</NTabPane>
<NTabPane name='day' tab={t('crontab.day')}>
<CrontabDay
v-model:dayValue={this.dayRef}
v-model:weekValue={this.weekRef}
/>
</NTabPane>
<NTabPane name='month' tab={t('crontab.month')}>
<CrontabTime
v-model:timeValue={this.monthRef}
timeI18n={timeI18n.month}
/>
</NTabPane>
<NTabPane name='year' tab={t('crontab.year')}>
<CrontabTime
v-model:timeValue={this.yearRef}
timeI18n={timeI18n.year}
/>
</NTabPane>
</NTabs>
)
}
})

640
dolphinscheduler-ui-next/src/components/crontab/modules/day.tsx

@ -0,0 +1,640 @@
/*
* 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, PropType, ref, watch } from 'vue'
import { NInputNumber, NRadio, NRadioGroup, NSelect } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { isStr, isWeek, week, specificWeek, lastWeeks } from '../common'
import styles from '../index.module.scss'
const props = {
dayValue: {
type: String as PropType<string>,
default: '*'
},
weekValue: {
type: String as PropType<string>,
default: '?'
}
}
export default defineComponent({
name: 'CrontabDay',
props,
emits: ['update:dayValue', 'update:weekValue'],
setup(props, ctx) {
const { t } = useI18n()
const options = Array.from({ length: 60 }, (x, i) => ({
label: i.toString(),
value: i
}))
const weekOptions = week.map((v) => ({
label: t(v.label),
value: v.value
}))
const lastWeekOptions = lastWeeks.map((v) => ({
label: t(v.label),
value: v.value
}))
const radioRef = ref()
const dayRef = ref()
const weekRef = ref()
const WkintervalWeekStartRef = ref(2)
const WkintervalWeekPerformRef = ref(2)
const intervalDayStartRef = ref(1)
const intervalDayPerformRef = ref(1)
const WkspecificDayRef = ref<Array<number>>([])
const WkspecificWeekRef = ref<Array<number>>([])
const monthLastDaysRef = ref('L')
const monthLastWorkingDaysRef = ref('LW')
const monthLastWeeksRef = ref('?')
const monthTailBeforeRef = ref(1)
const recentlyWorkingDaysMonthRef = ref(1)
const WkmonthNumWeeksDayRef = ref(1)
const WkmonthNumWeeksWeekRef = ref(1)
/**
* Parse parameter value
*/
const analyticalValue = () => {
const $dayVal = props.dayValue
const $weekVal = props.weekValue
const isWeek1 = $weekVal.indexOf('/') !== -1
const isWeek2 = $weekVal.indexOf('#') !== -1
// Initialization
if ($dayVal === '*' && $weekVal === '?') {
radioRef.value = 'everyDay'
return
}
// week
if (isWeek1 || isWeek2 || isWeek($weekVal)) {
dayRef.value = '?'
/**
* Processing by sequence number (excluding days)
* @param [
* WkintervalWeek=>(/),
* WkspecificWeek=>(TUE,WED),
* WkmonthNumWeeks=>(#)
* ]
*/
const hanleWeekOne = () => {
const a = isStr($weekVal, '/') as string[]
WkintervalWeekStartRef.value = parseInt(a[0])
WkintervalWeekPerformRef.value = parseInt(a[1])
dayRef.value = '?'
weekRef.value = `${WkintervalWeekPerformRef.value}/${WkintervalWeekStartRef.value}`
radioRef.value = 'WkintervalWeek'
}
const hanleWeekTwo = () => {
WkspecificWeekRef.value = $weekVal
.split(',')
.map((item) => parseInt(item))
radioRef.value = 'WkspecificWeek'
}
const hanleWeekThree = () => {
const a = isStr($weekVal, '#') as string[]
WkmonthNumWeeksDayRef.value = parseInt(a[0])
WkmonthNumWeeksDayRef.value = parseInt(a[1])
radioRef.value = 'WkmonthNumWeeks'
}
// Processing week
if (isStr($weekVal, '/')) {
hanleWeekOne()
} else if (isStr($weekVal, '#')) {
hanleWeekThree()
} else if (isWeek($weekVal)) {
hanleWeekTwo()
}
} else {
weekRef.value = '?'
/**
* Processing by sequence number (excluding week)
* @param [
* everyDay=>(*),
* intervalDay=>(1/1),
* specificDay=>(1,2,5,3,4),
* monthLastDays=>(L),
* monthLastWorkingDays=>(LW),
* monthLastWeeks=>(3L),
* monthTailBefore=>(L-4),
* recentlyWorkingDaysMonth=>(6W)
* ]
*/
const hanleDayOne = () => {
radioRef.value = 'everyDay'
}
const hanleDayTwo = () => {
const a = isStr($dayVal, '/') as string[]
intervalDayStartRef.value = parseInt(a[0])
intervalDayPerformRef.value = parseInt(a[1])
radioRef.value = 'intervalDay'
}
const hanleDayThree = () => {
WkspecificDayRef.value = $dayVal
.split(',')
.map((item) => parseInt(item))
radioRef.value = 'specificDay'
}
const hanleDayFour = () => {
radioRef.value = 'monthLastDays'
}
const hanleDayFive = () => {
radioRef.value = 'monthLastWorkingDays'
}
const hanleDaySix = () => {
monthLastWeeksRef.value = $dayVal
radioRef.value = 'monthLastWeeks'
}
const hanleDaySeven = () => {
const a = isStr($dayVal, '-') as string[]
monthTailBeforeRef.value = parseInt(a[1])
radioRef.value = 'monthTailBefore'
}
const hanleDayEight = () => {
recentlyWorkingDaysMonthRef.value = parseInt(
$dayVal.slice(0, $dayVal.length - 1)
)
radioRef.value = 'recentlyWorkingDaysMonth'
}
if ($dayVal === '*') {
hanleDayOne()
} else if (isStr($dayVal, '/')) {
hanleDayTwo()
} else if ($dayVal === 'L') {
hanleDayFour()
} else if ($dayVal === 'LW') {
hanleDayFive()
} else if ($dayVal.charAt($dayVal.length - 1) === 'L') {
hanleDaySix()
} else if (isStr($dayVal, '-')) {
hanleDaySeven()
} else if ($dayVal.charAt($dayVal.length - 1) === 'W') {
hanleDayEight()
} else {
hanleDayThree()
}
}
}
// Every few weeks
const onWkintervalWeekPerform = (value: number | null) => {
WkintervalWeekPerformRef.value = value || 0
if (radioRef.value === 'WkintervalWeek') {
dayRef.value = '?'
weekRef.value = `${WkintervalWeekStartRef.value}/${WkintervalWeekPerformRef.value}`
}
}
// Every few weeks
const onWkintervalWeekStart = (value: number | null) => {
WkintervalWeekStartRef.value = value || 0
if (radioRef.value === 'WkintervalWeek') {
dayRef.value = '?'
weekRef.value = `${WkintervalWeekStartRef.value}/${WkintervalWeekPerformRef.value}`
}
}
// Interval start time(1)
const onIntervalDayStart = (value: number | null) => {
intervalDayStartRef.value = value || 0
if (radioRef.value === 'intervalDay') {
intervalDaySet()
}
}
// Interval execution time(2)
const onIntervalDayPerform = (value: number | null) => {
intervalDayPerformRef.value = value || 0
if (radioRef.value === 'intervalDay') {
intervalDaySet()
}
}
// Specific day of the week (multiple choice)
const onWkspecificWeek = (arr: Array<number>) => {
WkspecificWeekRef.value = arr
if (radioRef.value === 'WkspecificWeek') {
dayRef.value = '?'
weekRef.value = arr.join(',')
}
}
// Specific days (multiple choices)
const onWkspecificDay = (arr: Array<number>) => {
WkspecificDayRef.value = arr
if (radioRef.value === 'specificDay') {
weekRef.value = '?'
dayRef.value = arr.join(',')
}
}
const onMonthLastWeeks = (value: string | null) => {
monthLastWeeksRef.value = value || '?'
if (radioRef.value === 'monthLastWeeks') {
weekRef.value = value
dayRef.value = '?'
}
}
// Specific days
const onSpecificDays = (arr: Array<number>) => {
WkspecificDayRef.value = arr
if (radioRef.value === 'specificDay') {
specificSet()
}
}
// By the end of this month
const onMonthTailBefore = (value: number | null) => {
monthTailBeforeRef.value = value || 0
if (radioRef.value === 'monthTailBefore') {
dayRef.value = `L-${monthTailBeforeRef.value}`
}
}
// Last working day
const onRecentlyWorkingDaysMonth = (value: number | null) => {
recentlyWorkingDaysMonthRef.value = value || 0
if (radioRef.value === 'recentlyWorkingDaysMonth') {
dayRef.value = `${recentlyWorkingDaysMonthRef.value}W`
}
}
// On the day of this month
const onWkmonthNumWeeksDay = (value: number | null) => {
WkmonthNumWeeksDayRef.value = value || 0
if (radioRef.value === 'WkmonthNumWeeks') {
weekRef.value = `${WkmonthNumWeeksWeekRef.value}#${WkmonthNumWeeksDayRef.value}`
}
}
// On the week of this month
const onWkmonthNumWeeksWeek = (value: number | null) => {
if (radioRef.value === 'WkmonthNumWeeks') {
dayRef.value = '?'
weekRef.value = `${value}#${WkmonthNumWeeksDayRef.value}`
}
}
// Reset every day
const everyDaySet = () => {
dayRef.value = '*'
}
// Reset interval week starts from *
const WkintervalWeekReset = () => {
weekRef.value = `${WkintervalWeekStartRef.value}/${WkintervalWeekPerformRef.value}`
}
// Reset interval days
const intervalDaySet = () => {
dayRef.value = `${intervalDayStartRef.value}/${intervalDayPerformRef.value}`
}
// Specific week (multiple choices)
const WkspecificWeekReset = () => {
weekRef.value = WkspecificWeekRef.value.length
? WkspecificWeekRef.value.join(',')
: '*'
}
// Reset specific days
const specificSet = () => {
if (WkspecificDayRef.value.length) {
dayRef.value = WkspecificDayRef.value.join(',')
} else {
dayRef.value = '*'
}
}
// On the last day of the month
const monthLastDaysReset = () => {
dayRef.value = monthLastDaysRef.value
}
// On the last working day of the month
const monthLastWorkingDaysReset = () => {
dayRef.value = monthLastWorkingDaysRef.value
}
// At the end of the month*
const monthLastWeeksReset = () => {
dayRef.value = monthLastWeeksRef.value
}
// By the end of this month
const monthTailBeforeReset = () => {
dayRef.value = `L-${monthTailBeforeRef.value}`
}
// Last working day (Monday to Friday) to this month
const recentlyWorkingDaysMonthReset = () => {
dayRef.value = `${recentlyWorkingDaysMonthRef.value}W`
}
// On the day of this month
const WkmonthNumReset = () => {
weekRef.value = `${WkmonthNumWeeksWeekRef.value}#${WkmonthNumWeeksDayRef.value}`
}
const updateRadioDay = (value: string) => {
switch (value) {
case 'everyDay':
weekRef.value = '?'
everyDaySet()
break
case 'WkintervalWeek':
dayRef.value = '?'
WkintervalWeekReset()
break
case 'intervalDay':
weekRef.value = '?'
intervalDaySet()
break
case 'WkspecificWeek':
dayRef.value = '?'
WkspecificWeekReset()
break
case 'specificDay':
weekRef.value = '?'
specificSet()
break
case 'monthLastDays':
weekRef.value = '?'
monthLastDaysReset()
break
case 'monthLastWorkingDays':
weekRef.value = '?'
monthLastWorkingDaysReset()
break
case 'monthLastWeeks':
weekRef.value = '1L'
monthLastWeeksReset()
break
case 'monthTailBefore':
weekRef.value = '?'
monthTailBeforeReset()
break
case 'recentlyWorkingDaysMonth':
weekRef.value = '?'
recentlyWorkingDaysMonthReset()
break
case 'WkmonthNumWeeks':
dayRef.value = '?'
WkmonthNumReset()
break
}
}
watch(
() => dayRef.value,
() => ctx.emit('update:dayValue', dayRef.value.toString())
)
watch(
() => weekRef.value,
() => ctx.emit('update:weekValue', weekRef.value.toString())
)
onMounted(() => analyticalValue())
return {
options,
weekOptions,
lastWeekOptions,
radioRef,
WkintervalWeekStartRef,
WkintervalWeekPerformRef,
intervalDayStartRef,
intervalDayPerformRef,
WkspecificWeekRef,
WkspecificDayRef,
monthLastWeeksRef,
monthTailBeforeRef,
recentlyWorkingDaysMonthRef,
WkmonthNumWeeksDayRef,
WkmonthNumWeeksWeekRef,
updateRadioDay,
onWkintervalWeekStart,
onWkintervalWeekPerform,
onIntervalDayStart,
onIntervalDayPerform,
onSpecificDays,
onWkspecificWeek,
onWkspecificDay,
onMonthLastWeeks,
onMonthTailBefore,
onRecentlyWorkingDaysMonth,
onWkmonthNumWeeksDay,
onWkmonthNumWeeksWeek
}
},
render() {
const { t } = useI18n()
return (
<NRadioGroup
v-model:value={this.radioRef}
onUpdateValue={this.updateRadioDay}
>
<NRadio class={styles['crontab-list']} value={'everyDay'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.every_day')}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'WkintervalWeek'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.every')}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={7}
v-model:value={this.WkintervalWeekPerformRef}
onUpdateValue={this.onWkintervalWeekPerform}
/>
</div>
<div>{t('crontab.day_carried_out')}</div>
<div>
<NSelect
style={{ width: '150px' }}
options={this.weekOptions}
defaultValue={this.WkintervalWeekStartRef}
v-model:value={this.WkintervalWeekStartRef}
onUpdateValue={this.onWkintervalWeekStart}
/>
</div>
<div>{t('crontab.start')}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'intervalDay'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.every')}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={31}
v-model:value={this.intervalDayPerformRef}
onUpdateValue={this.onIntervalDayPerform}
/>
</div>
<div>{t('crontab.day_carried_out')}</div>
<div>
<NInputNumber
defaultValue={0}
min={1}
max={31}
v-model:value={this.intervalDayStartRef}
onUpdateValue={this.onIntervalDayStart}
/>
</div>
<div>{t('crontab.day_start')}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'WkspecificWeek'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.specific_week')}</div>
<div>
<NSelect
style={{ width: '300px' }}
multiple
options={specificWeek}
placeholder={t('crontab.specific_week_tip')}
v-model:value={this.WkspecificWeekRef}
onUpdateValue={this.onWkspecificWeek}
/>
</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'specificDay'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.specific_day')}</div>
<div>
<NSelect
style={{ width: '300px' }}
multiple
options={this.options}
placeholder={t('crontab.specific_day_tip')}
v-model:value={this.WkspecificDayRef}
onUpdateValue={this.onWkspecificDay}
/>
</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'monthLastDays'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.last_day_of_month')}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'monthLastWorkingDays'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.last_work_day_of_month')}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'monthLastWeeks'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.last_of_month')}</div>
<div>
<NSelect
style={{ width: '150px' }}
options={this.lastWeekOptions}
defaultValue={this.monthLastWeeksRef}
v-model:value={this.monthLastWeeksRef}
onUpdateValue={this.onMonthLastWeeks}
/>
</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'monthTailBefore'}>
<div class={styles['crontab-list-item']}>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={31}
v-model:value={this.monthTailBeforeRef}
onUpdateValue={this.onMonthTailBefore}
/>
</div>
<div>{t('crontab.before_end_of_month')}</div>
</div>
</NRadio>
<NRadio
class={styles['crontab-list']}
value={'recentlyWorkingDaysMonth'}
>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.recent_business_day_to_month')}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={31}
v-model:value={this.recentlyWorkingDaysMonthRef}
onUpdateValue={this.onRecentlyWorkingDaysMonth}
/>
</div>
<div>{t('crontab.one_day')}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'WkmonthNumWeeks'}>
<div class={styles['crontab-list-item']}>
<div>{t('crontab.in_this_months')}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={31}
v-model:value={this.WkmonthNumWeeksDayRef}
onUpdateValue={this.onWkmonthNumWeeksDay}
/>
</div>
<div>
<NSelect
style={{ width: '150px' }}
options={this.weekOptions}
defaultValue={this.WkmonthNumWeeksWeekRef}
v-model:value={this.WkmonthNumWeeksWeekRef}
onUpdateValue={this.onWkmonthNumWeeksWeek}
/>
</div>
</div>
</NRadio>
</NRadioGroup>
)
}
})

296
dolphinscheduler-ui-next/src/components/crontab/modules/time.tsx

@ -0,0 +1,296 @@
/*
* 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 _ from 'lodash'
import { defineComponent, onMounted, PropType, ref, toRefs, watch } from 'vue'
import { NInputNumber, NRadio, NRadioGroup, NSelect } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { ICrontabI18n } from '../types'
import { isStr } from '../common'
import styles from '../index.module.scss'
const props = {
timeValue: {
type: String as PropType<string>,
default: '*'
},
timeI18n: {
type: Object as PropType<ICrontabI18n>,
require: true
}
}
export default defineComponent({
name: 'CrontabTime',
props,
emits: ['update:timeValue'],
setup(props, ctx) {
const options = Array.from({ length: 60 }, (x, i) => ({
label: i.toString(),
value: i
}))
const timeRef = ref()
const radioRef = ref()
const intervalStartRef = ref(0)
const intervalPerformRef = ref(0)
const specificTimesRef = ref<Array<number>>([])
const cycleStartRef = ref(0)
const cycleEndRef = ref(0)
/**
* Parse parameter value
*/
const analyticalValue = () => {
const $timeVal = props.timeValue
// Interval time
const $interval = isStr($timeVal, '/')
// Specific time
const $specific = isStr($timeVal, ',')
// Cycle time
const $cycle = isStr($timeVal, '-')
// Every time
if ($timeVal === '*') {
radioRef.value = 'everyTime'
timeRef.value = '*'
return
}
// Positive integer (times)
if (
($timeVal.length === 1 && _.isInteger(parseInt($timeVal))) ||
($timeVal.length === 2 && _.isInteger(parseInt($timeVal)))
) {
radioRef.value = 'specificTime'
specificTimesRef.value = [parseInt($timeVal)]
return
}
// Interval times
if ($interval) {
radioRef.value = 'intervalTime'
intervalStartRef.value = parseInt($interval[0])
intervalPerformRef.value = parseInt($interval[1])
timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
return
}
// Specific times
if ($specific) {
radioRef.value = 'specificTime'
specificTimesRef.value = $specific.map((item) => parseInt(item))
return
}
// Cycle time
if ($cycle) {
radioRef.value = 'cycleTime'
cycleStartRef.value = parseInt($cycle[0])
cycleEndRef.value = parseInt($cycle[1])
timeRef.value = `${cycleStartRef.value}-${cycleEndRef.value}`
return
}
}
// Interval start time(1)
const onIntervalStart = (value: number | null) => {
intervalStartRef.value = value || 0
if (radioRef.value === 'intervalTime') {
timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
}
}
// Interval execution time(2)
const onIntervalPerform = (value: number | null) => {
intervalPerformRef.value = value || 0
if (radioRef.value === 'intervalTime') {
timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
}
}
// Specific time
const onSpecificTimes = (arr: Array<number>) => {
specificTimesRef.value = arr
if (radioRef.value === 'specificTime') {
specificReset()
}
}
// Cycle start value
const onCycleStart = (value: number | null) => {
cycleStartRef.value = value || 0
if (radioRef.value === 'cycleTime') {
timeRef.value = `${cycleStartRef.value}-${cycleEndRef.value}`
}
}
// Cycle end value
const onCycleEnd = (value: number | null) => {
cycleEndRef.value = value || 0
if (radioRef.value === 'cycleTime') {
timeRef.value = `${cycleStartRef.value}-${cycleEndRef.value}`
}
}
// Reset every time
const everyReset = () => {
timeRef.value = '*'
}
// Reset interval time
const intervalReset = () => {
timeRef.value = `${intervalStartRef.value}/${intervalPerformRef.value}`
}
// Reset specific time
const specificReset = () => {
let timeValue = '*'
if (specificTimesRef.value.length) {
timeValue = specificTimesRef.value.join(',')
}
timeRef.value = timeValue
}
// Reset cycle time
const cycleReset = () => {
timeRef.value = `${cycleStartRef.value}-${cycleEndRef.value}`
}
const updateRadioTime = (value: string) => {
switch (value) {
case 'everyTime':
everyReset()
break
case 'intervalTime':
intervalReset()
break
case 'specificTime':
specificReset()
break
case 'cycleTime':
cycleReset()
break
}
}
watch(
() => timeRef.value,
() => ctx.emit('update:timeValue', timeRef.value.toString())
)
onMounted(() => analyticalValue())
return {
options,
radioRef,
intervalStartRef,
intervalPerformRef,
specificTimesRef,
cycleStartRef,
cycleEndRef,
updateRadioTime,
onIntervalStart,
onIntervalPerform,
onSpecificTimes,
onCycleStart,
onCycleEnd,
...toRefs(props)
}
},
render() {
const { t } = useI18n()
return (
<NRadioGroup
v-model:value={this.radioRef}
onUpdateValue={this.updateRadioTime}
>
<NRadio class={styles['crontab-list']} value={'everyTime'}>
<div class={styles['crontab-list-item']}>
<div>{t(this.timeI18n!.everyTime)}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'intervalTime'}>
<div class={styles['crontab-list-item']}>
<div>{t(this.timeI18n!.every)}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={59}
v-model:value={this.intervalStartRef}
onUpdateValue={this.onIntervalStart}
/>
</div>
<div>{t(this.timeI18n!.timeCarriedOut)}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={59}
v-model:value={this.intervalPerformRef}
onUpdateValue={this.onIntervalPerform}
/>
</div>
<div>{t(this.timeI18n!.timeStart)}</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'specificTime'}>
<div class={styles['crontab-list-item']}>
<div>{t(this.timeI18n!.specificTime)}</div>
<div>
<NSelect
style={{ width: '300px' }}
multiple
options={this.options}
placeholder={t(this.timeI18n!.specificTimeTip)}
v-model:value={this.specificTimesRef}
onUpdateValue={this.onSpecificTimes}
/>
</div>
</div>
</NRadio>
<NRadio class={styles['crontab-list']} value={'cycleTime'}>
<div class={styles['crontab-list-item']}>
<div>{t(this.timeI18n!.cycleFrom)}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={59}
v-model:value={this.cycleStartRef}
onUpdateValue={this.onCycleStart}
/>
</div>
<div>{t(this.timeI18n!.to)}</div>
<div>
<NInputNumber
defaultValue={0}
min={0}
max={59}
v-model:value={this.cycleEndRef}
onUpdateValue={this.onCycleEnd}
/>
</div>
<div>{t(this.timeI18n!.time)}</div>
</div>
</NRadio>
</NRadioGroup>
)
}
})

30
dolphinscheduler-ui-next/src/components/crontab/types.ts

@ -0,0 +1,30 @@
/*
* 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 ICrontabI18n {
everyTime: string
every: string
timeCarriedOut: string
timeStart: string
cycleFrom: string
specificTime: string
specificTimeTip: string
to: string
time: string
}
export { ICrontabI18n }

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

@ -951,6 +951,68 @@ const data_quality = {
}
}
const crontab = {
second: 'second',
minute: 'minute',
hour: 'hour',
day: 'day',
month: 'month',
year: 'year',
monday: 'Monday',
tuesday: 'Tuesday',
wednesday: 'Wednesday',
thursday: 'Thursday',
friday: 'Friday',
saturday: 'Saturday',
sunday: 'Sunday',
every_second: 'Every second',
every: 'Every',
second_carried_out: 'second carried out',
second_start: 'Start',
specific_second: 'Specific second(multiple)',
specific_second_tip: 'Please enter a specific second',
cycle_from: 'Cycle from',
to: 'to',
every_minute: 'Every minute',
minute_carried_out: 'minute carried out',
minute_start: 'Start',
specific_minute: 'Specific minute(multiple)',
specific_minute_tip: 'Please enter a specific minute',
every_hour: 'Every hour',
hour_carried_out: 'hour carried out',
hour_start: 'Start',
specific_hour: 'Specific hour(multiple)',
specific_hour_tip: 'Please enter a specific hour',
every_day: 'Every day',
week_carried_out: 'week carried out',
start: 'Start',
day_carried_out: 'day carried out',
day_start: 'Start',
specific_week: 'Specific day of the week(multiple)',
specific_week_tip: 'Please enter a specific week',
specific_day: 'Specific days(multiple)',
specific_day_tip: 'Please enter a days',
last_day_of_month: 'On the last day of the month',
last_work_day_of_month: 'On the last working day of the month',
last_of_month: 'At the last of this month',
before_end_of_month: 'Before the end of this month',
recent_business_day_to_month:
'The most recent business day (Monday to Friday) to this month',
in_this_months: 'In this months',
every_month: 'Every month',
month_carried_out: 'month carried out',
month_start: 'Start',
specific_month: 'Specific months(multiple)',
specific_month_tip: 'Please enter a months',
every_year: 'Every year',
year_carried_out: 'year carried out',
year_start: 'Start',
specific_year: 'Specific year(multiple)',
specific_year_tip: 'Please enter a year',
one_hour: 'hour',
one_day: 'day'
}
export default {
login,
modal,
@ -965,5 +1027,6 @@ export default {
project,
security,
datasource,
data_quality
data_quality,
crontab
}

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

@ -937,6 +937,66 @@ const data_quality = {
TargetTableTotalRows: '目标表总行数'
}
}
const crontab = {
second: '秒',
minute: '分',
hour: '时',
day: '天',
month: '月',
year: '年',
monday: '星期一',
tuesday: '星期二',
wednesday: '星期三',
thursday: '星期四',
friday: '星期五',
saturday: '星期六',
sunday: '星期天',
every_second: '每一秒钟',
every: '每隔',
second_carried_out: '秒执行 从',
second_start: '秒开始',
specific_second: '具体秒数(可多选)',
specific_second_tip: '请选择具体秒数',
cycle_from: '周期从',
to: '到',
every_minute: '每一分钟',
minute_carried_out: '分执行 从',
minute_start: '分开始',
specific_minute: '具体分钟数(可多选)',
specific_minute_tip: '请选择具体分钟数',
every_hour: '每一小时',
hour_carried_out: '小时执行 从',
hour_start: '小时开始',
specific_hour: '具体小时数(可多选)',
specific_hour_tip: '请选择具体小时数',
every_day: '每一天',
week_carried_out: '周执行 从',
start: '开始',
day_carried_out: '天执行 从',
day_start: '天开始',
specific_week: '具体星期几(可多选)',
specific_week_tip: '请选择具体周几',
specific_day: '具体天数(可多选)',
specific_day_tip: '请选择具体天数',
last_day_of_month: '在这个月的最后一天',
last_work_day_of_month: '在这个月的最后一个工作日',
last_of_month: '在这个月的最后一个',
before_end_of_month: '在本月底前',
recent_business_day_to_month: '最近的工作日(周一至周五)至本月',
in_this_months: '在这个月的第',
every_month: '每一月',
month_carried_out: '月执行 从',
month_start: '月开始',
specific_month: '具体月数(可多选)',
specific_month_tip: '请选择具体月数',
every_year: '每一年',
year_carried_out: '年执行 从',
year_start: '年开始',
specific_year: '具体年数(可多选)',
specific_year_tip: '请选择具体年数',
one_hour: '小时',
one_day: '日'
}
export default {
login,
@ -952,5 +1012,6 @@ export default {
project,
security,
datasource,
data_quality
data_quality,
crontab
}

58
dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/timing-modal.tsx

@ -15,7 +15,15 @@
* limitations under the License.
*/
import { defineComponent, PropType, toRefs, h, onMounted, ref } from 'vue'
import {
defineComponent,
PropType,
toRefs,
h,
onMounted,
ref,
watch
} from 'vue'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
@ -34,10 +42,12 @@ import {
NInputGroup,
NList,
NListItem,
NThing
NThing,
NPopover
} from 'naive-ui'
import { ArrowDownOutlined, ArrowUpOutlined } from '@vicons/antd'
import { timezoneList } from '@/utils/timezone'
import Crontab from '@/components/crontab'
const props = {
row: {
@ -59,6 +69,7 @@ export default defineComponent({
props,
emits: ['update:show', 'update:row', 'updateList'],
setup(props, ctx) {
const crontabRef = ref()
const parallelismRef = ref(false)
const { t } = useI18n()
const { timingState } = useForm()
@ -173,8 +184,28 @@ export default defineComponent({
getEnvironmentList()
})
watch(
() => props.row,
() => {
timingState.timingForm.startEndTime = [
new Date(props.row.startTime),
new Date(props.row.endTime)
]
timingState.timingForm.crontab = props.row.crontab
timingState.timingForm.timezoneId = props.row.timezoneId
timingState.timingForm.failureStrategy = props.row.failureStrategy
timingState.timingForm.warningType = props.row.warningType
timingState.timingForm.processInstancePriority =
props.row.processInstancePriority
timingState.timingForm.warningGroupId = props.row.warningGroupId
timingState.timingForm.workerGroup = props.row.workerGroup
timingState.timingForm.environmentCode = props.row.environmentCode
}
)
return {
t,
crontabRef,
parallelismRef,
hideModal,
handleTiming,
@ -216,10 +247,25 @@ export default defineComponent({
</NFormItem>
<NFormItem label={t('project.workflow.timing')} path='crontab'>
<NInputGroup>
<NInput
style={{ width: '80%' }}
v-model:value={this.timingForm.crontab}
></NInput>
<NPopover
trigger='click'
showArrow={false}
placement='bottom'
style={{ width: '500px' }}
>
{{
trigger: () => (
<NInput
style={{ width: '80%' }}
readonly={true}
v-model:value={this.timingForm.crontab}
></NInput>
),
default: () => (
<Crontab v-model:value={this.timingForm.crontab} />
)
}}
</NPopover>
<NButton type='primary' ghost onClick={this.handlePreview}>
{t('project.workflow.execute_time')}
</NButton>

5
dolphinscheduler-ui-next/src/views/projects/workflow/definition/components/use-modal.ts

@ -160,7 +160,8 @@ export function useModal(
schedule: JSON.stringify({
startTime: start,
endTime: end,
crontab: state.timingForm.crontab
crontab: state.timingForm.crontab,
timezoneId: state.timingForm.timezoneId
}),
failureStrategy: state.timingForm.failureStrategy,
warningType: state.timingForm.warningType,
@ -169,7 +170,7 @@ export function useModal(
state.timingForm.warningGroupId === ''
? 0
: state.timingForm.warningGroupId,
workerGroup: state.timingForm.workerGroups,
workerGroup: state.timingForm.workerGroup,
environmentCode: state.timingForm.environmentCode
}
return data

Loading…
Cancel
Save