This is an automated email from the ASF dual-hosted git repository.

jinyleechina pushed a commit to branch 2.0.4-prepare
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/2.0.4-prepare by this push:
     new 2aa5b4c  [Cherry-pick-to-204] pick to 204-prepare (#8429)
2aa5b4c is described below

commit 2aa5b4c6cccb2dfa5de02215041d18d63580b5be
Author: JinYong Li <[email protected]>
AuthorDate: Fri Feb 18 20:30:36 2022 +0800

    [Cherry-pick-to-204] pick to 204-prepare (#8429)
    
    * fix relation unbinding bug
    
    * [Bug-8053] Fix CronUtils.getMaxCycle return null (#8086)
    
    * [bug] Fix datax task type error parse clickhouse reader sql (#8091)
    
    * [Fix-8119][API] Update ProcessInstance error and without reason output 
(#8122)
    
    * fix bug_8119
    
    * fix bug_8119
    
    * fix bug_8119
    
    * fix bug_8119
    
    * fix bug_8119
    
    * pick-8086/8091/8122/8139
    
    * [Bug-8169] [dolphinscheduler-ui] Fix `After you save the workflow, it… 
(#8198)
    
    * [Fix-8187][UI] Add the function to the module of resource that you can 
re-upload the file on the page of the file management. (#8359)
    
    * add reupload file
    
    * develop reupload file
    
    * fix this issue
    
    * [Improvement-8284][Alert] Dingtalk alert plugin supports markdown message 
type (#8285)
    
    * add msgtype in the dingtalk alert plugin
    
    * update markdown msgtype 'at persion'
    
    * fix sudo.enable=false Is invalid (#8388)
    
    * [Fix][UI] Rectify this issue with missing the re-uploading file button 
when the file is under the root directory.
    
    Co-authored-by: springmonster <[email protected]>
    Co-authored-by: seagle <[email protected]>
    Co-authored-by: xiangzihao <[email protected]>
    Co-authored-by: Kerwin <[email protected]>
    Co-authored-by: calvin <[email protected]>
    Co-authored-by: ShuiMuNianHuaLP 
<[email protected]>
---
 .../dingtalk/DingTalkAlertChannelFactory.java      |  35 ++++-
 .../alert/dingtalk/DingTalkParamsConstants.java    |  36 +++--
 .../plugin/alert/dingtalk/DingTalkSender.java      | 157 +++++++++++++++++----
 .../dingtalk/DingTalkAlertChannelFactoryTest.java  |   2 +-
 .../plugin/alert/dingtalk/DingTalkSenderTest.java  |   4 +-
 .../api/service/ProcessDefinitionService.java      |   4 +-
 .../service/impl/ProcessDefinitionServiceImpl.java |  10 +-
 .../service/impl/ProcessInstanceServiceImpl.java   |   2 +-
 .../api/service/ProcessDefinitionServiceTest.java  |  16 ++-
 .../api/service/ProcessInstanceServiceTest.java    |  24 +++-
 .../service/quartz/cron/AbstractCycle.java         |  10 ++
 .../service/quartz/cron/CronUtils.java             |   7 +-
 .../service/quartz/cron/CycleFactory.java          |  57 ++++++++
 .../service/quartz/cron/CronUtilsTest.java         |  26 +++-
 .../plugin/task/api/AbstractCommandExecutor.java   |  10 +-
 .../dolphinscheduler/plugin/task/util/OSUtils.java |  10 ++
 .../plugin/task/datax/DataxUtils.java              |   3 +
 .../src/js/conf/home/pages/dag/_source/dag.vue     |   4 +-
 .../pages/file/pages/list/_source/list.vue         |  10 +-
 .../pages/file/pages/subdirectory/_source/list.vue |   9 +-
 .../module/components/fileUpdate/fileReUpload.vue  | 110 +++++++++------
 .../src/js/module/components/nav/nav.vue           |  46 ++++--
 .../src/js/module/i18n/locale/en_US.js             |   4 +
 .../src/js/module/i18n/locale/zh_CN.js             |   4 +
 24 files changed, 481 insertions(+), 119 deletions(-)

diff --git 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactory.java
 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactory.java
index 01b9060..2f7afee 100644
--- 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactory.java
+++ 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactory.java
@@ -71,6 +71,39 @@ public final class DingTalkAlertChannelFactory implements 
AlertChannelFactory {
                         .setRequired(false)
                         .build())
                 .build();
+
+        RadioParam msgTypeParam = RadioParam
+                .newBuilder(DingTalkParamsConstants.NAME_DING_TALK_MSG_TYPE, 
DingTalkParamsConstants.DING_TALK_MSG_TYPE)
+                .addParamsOptions(new 
ParamsOptions(DingTalkParamsConstants.DING_TALK_MSG_TYPE_TEXT, 
DingTalkParamsConstants.DING_TALK_MSG_TYPE_TEXT, false))
+                .addParamsOptions(new 
ParamsOptions(DingTalkParamsConstants.DING_TALK_MSG_TYPE_MARKDOWN, 
DingTalkParamsConstants.DING_TALK_MSG_TYPE_MARKDOWN, false))
+                .setValue(DingTalkParamsConstants.DING_TALK_MSG_TYPE_TEXT)
+                .addValidate(Validate.newBuilder()
+                        .setRequired(false)
+                        .build())
+                .build();
+
+        InputParam atMobilesParam = InputParam
+                .newBuilder(DingTalkParamsConstants.NAME_DING_TALK_AT_MOBILES, 
DingTalkParamsConstants.DING_TALK_AT_MOBILES)
+                .addValidate(Validate.newBuilder()
+                        .setRequired(false)
+                        .build())
+                .build();
+        InputParam atUserIdsParam = InputParam
+                .newBuilder(DingTalkParamsConstants.NAME_DING_TALK_AT_USERIDS, 
DingTalkParamsConstants.DING_TALK_AT_USERIDS)
+                .addValidate(Validate.newBuilder()
+                        .setRequired(false)
+                        .build())
+                .build();
+        RadioParam isAtAll = RadioParam
+                .newBuilder(DingTalkParamsConstants.NAME_DING_TALK_AT_ALL, 
DingTalkParamsConstants.DING_TALK_AT_ALL)
+                .addParamsOptions(new ParamsOptions(STRING_YES, STRING_TRUE, 
false))
+                .addParamsOptions(new ParamsOptions(STRING_NO, STRING_FALSE, 
false))
+                .setValue(STRING_FALSE)
+                .addValidate(Validate.newBuilder()
+                        .setRequired(false)
+                        .build())
+                .build();
+
         RadioParam isEnableProxy = RadioParam
                 
.newBuilder(DingTalkParamsConstants.NAME_DING_TALK_PROXY_ENABLE, 
DingTalkParamsConstants.DING_TALK_PROXY_ENABLE)
                 .addParamsOptions(new ParamsOptions(STRING_YES, STRING_TRUE, 
false))
@@ -105,7 +138,7 @@ public final class DingTalkAlertChannelFactory implements 
AlertChannelFactory {
                 .setPlaceholder("if enable use authentication, you need input 
password")
                 .build();
 
-        return Arrays.asList(webHookParam, keywordParam, secretParam, 
isEnableProxy, proxyParam, portParam, userParam, passwordParam);
+        return Arrays.asList(webHookParam, keywordParam, secretParam, 
msgTypeParam, atMobilesParam, atUserIdsParam, isAtAll, isEnableProxy, 
proxyParam, portParam, userParam, passwordParam);
     }
 
 }
diff --git 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkParamsConstants.java
 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkParamsConstants.java
index fa47b0c..e5b667a 100644
--- 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkParamsConstants.java
+++ 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkParamsConstants.java
@@ -18,25 +18,43 @@
 package org.apache.dolphinscheduler.plugin.alert.dingtalk;
 
 public final class DingTalkParamsConstants {
-
-    static final String DING_TALK_PROXY_ENABLE = "isEnableProxy";
+    static final String DING_TALK_PROXY_ENABLE = "$t('isEnableProxy')";
     static final String NAME_DING_TALK_PROXY_ENABLE = "IsEnableProxy";
 
-    static final String DING_TALK_WEB_HOOK = "webhook";
+    static final String DING_TALK_WEB_HOOK = "$t('webhook')";
     static final String NAME_DING_TALK_WEB_HOOK = "WebHook";
-    static final String DING_TALK_KEYWORD = "keyword";
+
+    static final String DING_TALK_KEYWORD = "$t('keyword')";
     static final String NAME_DING_TALK_KEYWORD = "Keyword";
 
-    static final String DING_TALK_SECRET = "secret";
+    static final String DING_TALK_SECRET = "$t('secret')";
     static final String NAME_DING_TALK_SECRET = "Secret";
 
-    static final String DING_TALK_PROXY = "proxy";
+    static final String DING_TALK_MSG_TYPE = "$t('msgType')";
+    static final String NAME_DING_TALK_MSG_TYPE = "MsgType";
+
+    static final String DING_TALK_MSG_TYPE_TEXT = "text";
+    static final String DING_TALK_MSG_TYPE_MARKDOWN = "markdown";
+
+    static final String DING_TALK_AT_MOBILES = "$t('atMobiles')";
+    static final String NAME_DING_TALK_AT_MOBILES = "AtMobiles";
+
+    static final String DING_TALK_AT_USERIDS = "$t('atUserIds')";
+    static final String NAME_DING_TALK_AT_USERIDS = "AtUserIds";
+
+    static final String DING_TALK_AT_ALL = "$t('isAtAll')";
+    static final String NAME_DING_TALK_AT_ALL = "IsAtAll";
+
+    static final String DING_TALK_PROXY = "$t('proxy')";
     static final String NAME_DING_TALK_PROXY = "Proxy";
-    static final String DING_TALK_PORT = "port";
+
+    static final String DING_TALK_PORT = "$t('port')";
     static final String NAME_DING_TALK_PORT = "Port";
-    static final String DING_TALK_USER = "user";
+
+    static final String DING_TALK_USER = "$t('user')";
     static final String NAME_DING_TALK_USER = "User";
-    static final String DING_TALK_PASSWORD = "password";
+
+    static final String DING_TALK_PASSWORD = "$t('password')";
     static final String NAME_DING_TALK_PASSWORD = "Password";
 
     private DingTalkParamsConstants() {
diff --git 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSender.java
 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSender.java
index eeca8c0..8519e54 100644
--- 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSender.java
+++ 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/main/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSender.java
@@ -39,8 +39,10 @@ import org.apache.http.util.EntityUtils;
 import java.io.IOException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
@@ -61,6 +63,12 @@ public final class DingTalkSender {
     private final String url;
     private final String keyword;
     private final String secret;
+    private String msgType;
+
+    private final String atMobiles;
+    private final String atUserIds;
+    private final Boolean atAll;
+
     private final Boolean enableProxy;
 
     private String proxy;
@@ -75,11 +83,17 @@ public final class DingTalkSender {
         url = config.get(DingTalkParamsConstants.NAME_DING_TALK_WEB_HOOK);
         keyword = config.get(DingTalkParamsConstants.NAME_DING_TALK_KEYWORD);
         secret = config.get(DingTalkParamsConstants.NAME_DING_TALK_SECRET);
+        msgType = config.get(DingTalkParamsConstants.NAME_DING_TALK_MSG_TYPE);
+
+        atMobiles = 
config.get(DingTalkParamsConstants.NAME_DING_TALK_AT_MOBILES);
+        atUserIds = 
config.get(DingTalkParamsConstants.NAME_DING_TALK_AT_USERIDS);
+        atAll = 
Boolean.valueOf(config.get(DingTalkParamsConstants.NAME_DING_TALK_AT_ALL));
+
         enableProxy = 
Boolean.valueOf(config.get(DingTalkParamsConstants.NAME_DING_TALK_PROXY_ENABLE));
         if (Boolean.TRUE.equals(enableProxy)) {
             port = 
Integer.parseInt(config.get(DingTalkParamsConstants.NAME_DING_TALK_PORT));
             proxy = config.get(DingTalkParamsConstants.NAME_DING_TALK_PROXY);
-            user = config.get(DingTalkParamsConstants.DING_TALK_USER);
+            user = config.get(DingTalkParamsConstants.NAME_DING_TALK_USER);
             password = 
config.get(DingTalkParamsConstants.NAME_DING_TALK_PASSWORD);
         }
     }
@@ -108,18 +122,7 @@ public final class DingTalkSender {
         return RequestConfig.custom().setProxy(httpProxy).build();
     }
 
-    private static String textToJsonString(String text) {
-        Map<String, Object> items = new HashMap<>();
-        items.put("msgtype", "text");
-        Map<String, String> textContent = new HashMap<>();
-        byte[] byt = StringUtils.getBytesUtf8(text);
-        String txt = StringUtils.newStringUtf8(byt);
-        textContent.put("content", txt);
-        items.put("text", textContent);
-        return JSONUtils.toJsonString(items);
-    }
-
-    private static AlertResult checkSendDingTalkSendMsgResult(String result) {
+    private AlertResult checkSendDingTalkSendMsgResult(String result) {
         AlertResult alertResult = new AlertResult();
         alertResult.setStatus("false");
 
@@ -144,6 +147,13 @@ public final class DingTalkSender {
         return alertResult;
     }
 
+    /**
+     * send dingtalk msg handler
+     *
+     * @param title title
+     * @param content content
+     * @return
+     */
     public AlertResult sendDingTalkMsg(String title, String content) {
         AlertResult alertResult;
         try {
@@ -160,18 +170,9 @@ public final class DingTalkSender {
 
     private String sendMsg(String title, String content) throws IOException {
 
-        StringBuilder text = new StringBuilder();
-        if 
(org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(keyword)) {
-            text.append(keyword);
-            text.append(":");
-        }
-        text.append(title);
-        text.append("\n");
-        text.append(content);
-
-        String msgToJson = textToJsonString(text.toString());
+        String msg = generateMsgJson(title, content);
 
-        HttpPost httpPost = 
constructHttpPost(org.apache.dolphinscheduler.spi.utils.StringUtils.isBlank(secret)
 ? url : generateSignedUrl(), msgToJson);
+        HttpPost httpPost = 
constructHttpPost(org.apache.dolphinscheduler.spi.utils.StringUtils.isBlank(secret)
 ? url : generateSignedUrl(), msg);
 
         CloseableHttpClient httpClient;
         if (Boolean.TRUE.equals(enableProxy)) {
@@ -192,13 +193,119 @@ public final class DingTalkSender {
             } finally {
                 response.close();
             }
-            logger.info("Ding Talk send title :{},content : {}, resp: {}", 
title, content, resp);
+            logger.info("Ding Talk send msg :{}, resp: {}", msg, resp);
             return resp;
         } finally {
             httpClient.close();
         }
     }
 
+    /**
+     * generate msg json
+     *
+     * @param title title
+     * @param content content
+     * @return msg
+     */
+    private String generateMsgJson(String title, String content) {
+        if 
(org.apache.dolphinscheduler.spi.utils.StringUtils.isBlank(msgType)) {
+            msgType = DingTalkParamsConstants.DING_TALK_MSG_TYPE_TEXT;
+        }
+        Map<String, Object> items = new HashMap<>();
+        items.put("msgtype", msgType);
+        Map<String, Object> text = new HashMap<>();
+        items.put(msgType, text);
+
+        if 
(DingTalkParamsConstants.DING_TALK_MSG_TYPE_MARKDOWN.equals(msgType)) {
+            generateMarkdownMsg(title, content, text);
+        } else {
+            generateTextMsg(title, content, text);
+        }
+
+        setMsgAt(items);
+        return JSONUtils.toJsonString(items);
+
+    }
+
+    /**
+     * generate text msg
+     *
+     * @param title title
+     * @param content content
+     * @param text text
+     */
+    private void generateTextMsg(String title, String content, Map<String, 
Object> text) {
+        StringBuilder builder = new StringBuilder(title);
+        builder.append("\n");
+        builder.append(content);
+        if 
(org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(keyword)) {
+            builder.append(" ");
+            builder.append(keyword);
+        }
+        byte[] byt = StringUtils.getBytesUtf8(builder.toString());
+        String txt = StringUtils.newStringUtf8(byt);
+        text.put("content", txt);
+    }
+
+    /**
+     * generate markdown msg
+     *
+     * @param title title
+     * @param content content
+     * @param text text
+     */
+    private void generateMarkdownMsg(String title, String content, Map<String, 
Object> text) {
+        StringBuilder builder = new StringBuilder(content);
+        if 
(org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(keyword)) {
+            builder.append(" ");
+            builder.append(keyword);
+        }
+        builder.append("\n\n");
+        if 
(org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(atMobiles)) {
+            Arrays.stream(atMobiles.split(",")).forEach(value -> {
+                builder.append("@");
+                builder.append(value);
+                builder.append(" ");
+            });
+        }
+        if 
(org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(atUserIds)) {
+            Arrays.stream(atUserIds.split(",")).forEach(value -> {
+                builder.append("@");
+                builder.append(value);
+                builder.append(" ");
+            });
+        }
+
+        byte[] byt = StringUtils.getBytesUtf8(builder.toString());
+        String txt = StringUtils.newStringUtf8(byt);
+        text.put("title", title);
+        text.put("text", txt);
+    }
+
+    /**
+     * configure msg @person
+     *
+     * @param items items
+     */
+    private void setMsgAt(Map<String, Object> items) {
+        Map<String, Object> at = new HashMap<>();
+
+        String[] atMobileArray = 
org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(atMobiles) ? 
atMobiles.split(",") : new String[0];
+        String[] atUserArray = 
org.apache.dolphinscheduler.spi.utils.StringUtils.isNotBlank(atUserIds) ? 
atUserIds.split(",") : new String[0];
+        boolean isAtAll = Objects.isNull(atAll) ? false : atAll;
+
+        at.put("atMobiles", atMobileArray);
+        at.put("atUserIds", atUserArray);
+        at.put("isAtAll", isAtAll);
+
+        items.put("at", at);
+    }
+
+    /**
+     * generate sign url
+     *
+     * @return sign url
+     */
     private String generateSignedUrl() {
         Long timestamp = System.currentTimeMillis();
         String stringToSign = timestamp + "\n" + secret;
diff --git 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactoryTest.java
 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactoryTest.java
index 9905195..ea1cd1a 100644
--- 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactoryTest.java
+++ 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkAlertChannelFactoryTest.java
@@ -32,7 +32,7 @@ public class DingTalkAlertChannelFactoryTest {
         DingTalkAlertChannelFactory dingTalkAlertChannelFactory = new 
DingTalkAlertChannelFactory();
         List<PluginParams> params = dingTalkAlertChannelFactory.params();
         JSONUtils.toJsonString(params);
-        Assert.assertEquals(8, params.size());
+        Assert.assertEquals(12, params.size());
     }
 
     @Test
diff --git 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSenderTest.java
 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSenderTest.java
index d2267a4..791a96f 100644
--- 
a/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSenderTest.java
+++ 
b/dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-dingtalk/src/test/java/org/apache/dolphinscheduler/plugin/alert/dingtalk/DingTalkSenderTest.java
@@ -33,8 +33,10 @@ public class DingTalkSenderTest {
     @Before
     public void initDingTalkConfig() {
 
-        dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_KEYWORD, 
"keyWord");
+        dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_KEYWORD, 
"keyword");
         dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_WEB_HOOK, 
"url");
+        dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_MSG_TYPE, 
DingTalkParamsConstants.DING_TALK_MSG_TYPE_MARKDOWN);
+
         
dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_PROXY_ENABLE, 
"false");
         dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_PASSWORD, 
"password");
         dingTalkConfig.put(DingTalkParamsConstants.NAME_DING_TALK_PORT, 
"9988");
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionService.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionService.java
index 755d697..4e7abf3 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionService.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionService.java
@@ -19,8 +19,10 @@ package org.apache.dolphinscheduler.api.service;
 
 import org.apache.dolphinscheduler.api.utils.Result;
 import org.apache.dolphinscheduler.common.enums.ReleaseState;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
 import org.apache.dolphinscheduler.dao.entity.User;
 
+import java.util.List;
 import java.util.Map;
 
 import javax.servlet.http.HttpServletResponse;
@@ -245,7 +247,7 @@ public interface ProcessDefinitionService {
      * @param processTaskRelationJson process task relation json
      * @return check result code
      */
-    Map<String, Object> checkProcessNodeList(String processTaskRelationJson);
+    Map<String, Object> checkProcessNodeList(String processTaskRelationJson, 
List<TaskDefinitionLog> taskDefinitionLogs);
 
     /**
      * get task node details based on process definition
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java
index 58c8ab4..ad38475 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessDefinitionServiceImpl.java
@@ -76,6 +76,7 @@ import 
org.apache.dolphinscheduler.service.process.ProcessService;
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.fs.Stat;
 
 import java.io.BufferedOutputStream;
 import java.io.IOException;
@@ -1071,7 +1072,7 @@ public class ProcessDefinitionServiceImpl extends 
BaseServiceImpl implements Pro
      * @return check result code
      */
     @Override
-    public Map<String, Object> checkProcessNodeList(String 
processTaskRelationJson) {
+    public Map<String, Object> checkProcessNodeList(String 
processTaskRelationJson, List<TaskDefinitionLog> taskDefinitionLogsList) {
         Map<String, Object> result = new HashMap<>();
         try {
             if (processTaskRelationJson == null) {
@@ -1082,7 +1083,7 @@ public class ProcessDefinitionServiceImpl extends 
BaseServiceImpl implements Pro
 
             List<ProcessTaskRelation> taskRelationList = 
JSONUtils.toList(processTaskRelationJson, ProcessTaskRelation.class);
             // Check whether the task node is normal
-            List<TaskNode> taskNodes = 
processService.transformTask(taskRelationList, Lists.newArrayList());
+            List<TaskNode> taskNodes = 
processService.transformTask(taskRelationList, taskDefinitionLogsList);
 
             if (CollectionUtils.isEmpty(taskNodes)) {
                 logger.error("process node info is empty");
@@ -1110,8 +1111,9 @@ public class ProcessDefinitionServiceImpl extends 
BaseServiceImpl implements Pro
             }
             putMsg(result, Status.SUCCESS);
         } catch (Exception e) {
-            result.put(Constants.STATUS, 
Status.REQUEST_PARAMS_NOT_VALID_ERROR);
-            result.put(Constants.MSG, e.getMessage());
+            result.put(Constants.STATUS, Status.INTERNAL_SERVER_ERROR_ARGS);
+            putMsg(result, Status.INTERNAL_SERVER_ERROR_ARGS, e.getMessage());
+            logger.error(Status.INTERNAL_SERVER_ERROR_ARGS.getMsg(), e);
         }
         return result;
     }
diff --git 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessInstanceServiceImpl.java
 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessInstanceServiceImpl.java
index 57171c7..5189616 100644
--- 
a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessInstanceServiceImpl.java
+++ 
b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ProcessInstanceServiceImpl.java
@@ -506,7 +506,7 @@ public class ProcessInstanceServiceImpl extends 
BaseServiceImpl implements Proce
         ProcessDefinition processDefinition = 
processDefineMapper.queryByCode(processInstance.getProcessDefinitionCode());
         List<ProcessTaskRelationLog> taskRelationList = 
JSONUtils.toList(taskRelationJson, ProcessTaskRelationLog.class);
         //check workflow json is valid
-        result = 
processDefinitionService.checkProcessNodeList(taskRelationJson);
+        result = 
processDefinitionService.checkProcessNodeList(taskRelationJson, 
taskDefinitionLogs);
         if (result.get(Constants.STATUS) != Status.SUCCESS) {
             return result;
         }
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java
index 497c3d3..5d7e87a 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessDefinitionServiceTest.java
@@ -30,11 +30,13 @@ import 
org.apache.dolphinscheduler.common.enums.ReleaseState;
 import org.apache.dolphinscheduler.common.enums.UserType;
 import org.apache.dolphinscheduler.common.enums.WarningType;
 import org.apache.dolphinscheduler.common.graph.DAG;
+import org.apache.dolphinscheduler.common.utils.JSONUtils;
 import org.apache.dolphinscheduler.dao.entity.DagData;
 import org.apache.dolphinscheduler.dao.entity.ProcessDefinition;
 import org.apache.dolphinscheduler.dao.entity.ProcessTaskRelation;
 import org.apache.dolphinscheduler.dao.entity.Project;
 import org.apache.dolphinscheduler.dao.entity.Schedule;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
 import org.apache.dolphinscheduler.dao.entity.Tenant;
 import org.apache.dolphinscheduler.dao.entity.User;
 import org.apache.dolphinscheduler.dao.mapper.DataSourceMapper;
@@ -82,6 +84,14 @@ public class ProcessDefinitionServiceTest {
             + 
"\"postTaskVersion\":1,\"conditionType\":0,\"conditionParams\":\"{}\"},{\"name\":\"\",\"preTaskCode\":123456789,"
             + 
"\"preTaskVersion\":1,\"postTaskCode\":123451234,\"postTaskVersion\":1,\"conditionType\":0,\"conditionParams\":\"{}\"}]";
 
+    private static final String taskDefinitionJson = 
"[{\"code\":123456789,\"name\":\"test1\",\"version\":1,\"description\":\"\",\"delayTime\":0,\"taskType\":\"SHELL\","
 +
+        
"\"taskParams\":{\"resourceList\":[],\"localParams\":[],\"rawScript\":\"echo 
1\",\"dependence\":{},\"conditionResult\":{\"successNode\":[],\"failedNode\":[]},\"waitStartTimeout\":{},"
 +
+        
"\"switchResult\":{}},\"flag\":\"YES\",\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":1,\"timeoutFlag\":\"CLOSE\","
 +
+        
"\"timeoutNotifyStrategy\":null,\"timeout\":0,\"environmentCode\":-1},{\"code\":123451234,\"name\":\"test2\",\"version\":1,\"description\":\"\",\"delayTime\":0,\"taskType\":\"SHELL\","
 +
+        
"\"taskParams\":{\"resourceList\":[],\"localParams\":[],\"rawScript\":\"echo 
2\",\"dependence\":{},\"conditionResult\":{\"successNode\":[],\"failedNode\":[]},\"waitStartTimeout\":{},"
 +
+        
"\"switchResult\":{}},\"flag\":\"YES\",\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":1,\"timeoutFlag\":\"CLOSE\","
 +
+        
"\"timeoutNotifyStrategy\":\"WARN\",\"timeout\":0,\"environmentCode\":-1}]";
+
     @InjectMocks
     private ProcessDefinitionServiceImpl processDefinitionService;
 
@@ -483,10 +493,12 @@ public class ProcessDefinitionServiceTest {
 
     @Test
     public void testCheckProcessNodeList() {
-        Map<String, Object> dataNotValidRes = 
processDefinitionService.checkProcessNodeList(null);
+        Map<String, Object> dataNotValidRes = 
processDefinitionService.checkProcessNodeList(null, null);
         Assert.assertEquals(Status.DATA_IS_NOT_VALID, 
dataNotValidRes.get(Constants.STATUS));
 
-        Map<String, Object> taskEmptyRes = 
processDefinitionService.checkProcessNodeList(taskRelationJson);
+        List<TaskDefinitionLog> taskDefinitionLogs = 
JSONUtils.toList(taskDefinitionJson, TaskDefinitionLog.class);
+
+        Map<String, Object> taskEmptyRes = 
processDefinitionService.checkProcessNodeList(taskRelationJson, 
taskDefinitionLogs);
         Assert.assertEquals(Status.PROCESS_DAG_IS_EMPTY, 
taskEmptyRes.get(Constants.STATUS));
     }
 
diff --git 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessInstanceServiceTest.java
 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessInstanceServiceTest.java
index 2074621..248ca68 100644
--- 
a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessInstanceServiceTest.java
+++ 
b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ProcessInstanceServiceTest.java
@@ -36,11 +36,13 @@ import org.apache.dolphinscheduler.common.graph.DAG;
 import org.apache.dolphinscheduler.common.model.TaskNode;
 import org.apache.dolphinscheduler.common.model.TaskNodeRelation;
 import org.apache.dolphinscheduler.common.utils.DateUtils;
+import org.apache.dolphinscheduler.common.utils.JSONUtils;
 import org.apache.dolphinscheduler.dao.entity.ProcessDefinition;
 import org.apache.dolphinscheduler.dao.entity.ProcessDefinitionLog;
 import org.apache.dolphinscheduler.dao.entity.ProcessInstance;
 import org.apache.dolphinscheduler.dao.entity.Project;
 import org.apache.dolphinscheduler.dao.entity.TaskDefinition;
+import org.apache.dolphinscheduler.dao.entity.TaskDefinitionLog;
 import org.apache.dolphinscheduler.dao.entity.TaskInstance;
 import org.apache.dolphinscheduler.dao.entity.Tenant;
 import org.apache.dolphinscheduler.dao.entity.User;
@@ -128,6 +130,17 @@ public class ProcessInstanceServiceTest {
         + 
":[\"\"],\"failedNode\":[\"\"]},\"dependence\":{}},\"flag\":\"NORMAL\",\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\","
         + 
"\"failRetryTimes\":\"0\",\"failRetryInterval\":\"1\",\"timeoutFlag\":\"CLOSE\",\"timeoutNotifyStrategy\":\"\",\"timeout\":null,\"delayTime\":\"0\"}]";
 
+    private String taskRelationJson = 
"[{\"name\":\"\",\"preTaskCode\":4254865123776,\"preTaskVersion\":1,\"postTaskCode\":4254862762304,\"postTaskVersion\":1,\"conditionType\":0,"
 +
+        
"\"conditionParams\":{}},{\"name\":\"\",\"preTaskCode\":0,\"preTaskVersion\":0,\"postTaskCode\":4254865123776,\"postTaskVersion\":1,\"conditionType\":0,\"conditionParams\":{}}]";
+
+    private String taskDefinitionJson = 
"[{\"code\":4254862762304,\"name\":\"test1\",\"version\":1,\"description\":\"\",\"delayTime\":0,\"taskType\":\"SHELL\",\"taskParams\":{\"resourceList\":[],"
 +
+        "\"localParams\":[],\"rawScript\":\"echo 
1\",\"dependence\":{},\"conditionResult\":{\"successNode\":[],\"failedNode\":[]},\"waitStartTimeout\":{},\"switchResult\":{}},\"flag\":\"YES\","
 +
+        
"\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":1,\"timeoutFlag\":\"CLOSE\",\"timeoutNotifyStrategy\":null,\"timeout\":0,"
 +
+        
"\"environmentCode\":-1},{\"code\":4254865123776,\"name\":\"test2\",\"version\":1,\"description\":\"\",\"delayTime\":0,\"taskType\":\"SHELL\",\"taskParams\":{\"resourceList\":[],"
 +
+        "\"localParams\":[],\"rawScript\":\"echo 
2\",\"dependence\":{},\"conditionResult\":{\"successNode\":[],\"failedNode\":[]},\"waitStartTimeout\":{},\"switchResult\":{}},\"flag\":\"YES\","
 +
+        
"\"taskPriority\":\"MEDIUM\",\"workerGroup\":\"default\",\"failRetryTimes\":0,\"failRetryInterval\":1,\"timeoutFlag\":\"CLOSE\",\"timeoutNotifyStrategy\":\"WARN\",\"timeout\":0,"
 +
+        "\"environmentCode\":-1}]";
+
     @Test
     public void testQueryProcessInstanceList() {
         long projectCode = 1L;
@@ -425,10 +438,12 @@ public class ProcessInstanceServiceTest {
         when(processService.getTenantForProcess(Mockito.anyInt(), 
Mockito.anyInt())).thenReturn(tenant);
         
when(processService.updateProcessInstance(processInstance)).thenReturn(1);
         when(processService.saveProcessDefine(loginUser, processDefinition, 
Boolean.TRUE, Boolean.FALSE)).thenReturn(1);
-        
when(processDefinitionService.checkProcessNodeList(shellJson)).thenReturn(result);
+
+        List<TaskDefinitionLog> taskDefinitionLogs = 
JSONUtils.toList(taskDefinitionJson, TaskDefinitionLog.class);
+        when(processDefinitionService.checkProcessNodeList(taskRelationJson, 
taskDefinitionLogs)).thenReturn(result);
         putMsg(result, Status.SUCCESS, projectCode);
         Map<String, Object> processInstanceFinishRes = 
processInstanceService.updateProcessInstance(loginUser, projectCode, 1,
-            shellJson, taskJson,"2020-02-21 00:00:00", true, "", "", 0, 
"root");
+            taskRelationJson, taskDefinitionJson,"2020-02-21 00:00:00", true, 
"", "", 0, "root");
         Assert.assertEquals(Status.SUCCESS, 
processInstanceFinishRes.get(Constants.STATUS));
 
         //success
@@ -437,7 +452,7 @@ public class ProcessInstanceServiceTest {
 
         when(processService.saveProcessDefine(loginUser, processDefinition, 
Boolean.FALSE, Boolean.FALSE)).thenReturn(1);
         Map<String, Object> successRes = 
processInstanceService.updateProcessInstance(loginUser, projectCode, 1,
-            shellJson, taskJson,"2020-02-21 00:00:00", Boolean.FALSE, "", "", 
0, "root");
+            taskRelationJson, taskDefinitionJson,"2020-02-21 00:00:00", 
Boolean.FALSE, "", "", 0, "root");
         Assert.assertEquals(Status.SUCCESS, successRes.get(Constants.STATUS));
     }
 
@@ -638,5 +653,4 @@ public class ProcessInstanceServiceTest {
             result.put(Constants.MSG, status.getMsg());
         }
     }
-
-}
\ No newline at end of file
+}
diff --git 
a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/AbstractCycle.java
 
b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/AbstractCycle.java
index 60c8623..b00f147 100644
--- 
a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/AbstractCycle.java
+++ 
b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/AbstractCycle.java
@@ -173,6 +173,16 @@ public abstract class AbstractCycle {
         FieldExpression dayOfWeekFieldExpression = 
dayOfWeekField.getExpression();
         return (dayOfWeekFieldExpression instanceof Every || 
dayOfWeekFieldExpression instanceof Always);
     }
+    
+    /**
+     * whether the year field has a value of every or always
+     *
+     * @return if year field has a value of every or always return true,else 
return false
+     */
+    protected boolean yearFieldIsEvery() {
+        FieldExpression yearFieldExpression = yearField.getExpression();
+        return (yearFieldExpression instanceof Every || yearFieldExpression 
instanceof Always);
+    }
 
     /**
      * get cycle enum
diff --git 
a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtils.java
 
b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtils.java
index d784722..49810cd 100644
--- 
a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtils.java
+++ 
b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtils.java
@@ -22,6 +22,7 @@ import static 
org.apache.dolphinscheduler.service.quartz.cron.CycleFactory.hour;
 import static org.apache.dolphinscheduler.service.quartz.cron.CycleFactory.min;
 import static 
org.apache.dolphinscheduler.service.quartz.cron.CycleFactory.month;
 import static 
org.apache.dolphinscheduler.service.quartz.cron.CycleFactory.week;
+import static 
org.apache.dolphinscheduler.service.quartz.cron.CycleFactory.year;
 
 import static com.cronutils.model.CronType.QUARTZ;
 
@@ -90,7 +91,7 @@ public class CronUtils {
      * @return CycleEnum
      */
     public static CycleEnum getMaxCycle(Cron cron) {
-        return 
min(cron).addCycle(hour(cron)).addCycle(day(cron)).addCycle(week(cron)).addCycle(month(cron)).getCycle();
+        return 
min(cron).addCycle(hour(cron)).addCycle(day(cron)).addCycle(week(cron)).addCycle(month(cron)).addCycle(year(cron)).getCycle();
     }
 
     /**
@@ -100,7 +101,7 @@ public class CronUtils {
      * @return CycleEnum
      */
     public static CycleEnum getMiniCycle(Cron cron) {
-        return 
min(cron).addCycle(hour(cron)).addCycle(day(cron)).addCycle(week(cron)).addCycle(month(cron)).getMiniCycle();
+        return 
min(cron).addCycle(hour(cron)).addCycle(day(cron)).addCycle(week(cron)).addCycle(month(cron)).addCycle(year(cron)).getMiniCycle();
     }
 
     /**
@@ -186,7 +187,7 @@ public class CronUtils {
      */
     public static List<Date> getSelfFireDateList(final Date startTime, final 
Date endTime, final List<Schedule> schedules) {
         List<Date> result = new ArrayList<>();
-        if(startTime.equals(endTime)){
+        if (startTime.equals(endTime)) {
             result.add(startTime);
             return result;
         }
diff --git 
a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CycleFactory.java
 
b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CycleFactory.java
index 1f807dc..9f931d2 100644
--- 
a/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CycleFactory.java
+++ 
b/dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/quartz/cron/CycleFactory.java
@@ -72,6 +72,15 @@ public class CycleFactory {
     public static AbstractCycle month(Cron cron) {
       return new MonthCycle(cron);
     }
+    
+    /**
+     * year
+     * @param cron cron
+     * @return AbstractCycle
+     */
+    public static AbstractCycle year(Cron cron) {
+        return new YearCycle(cron);
+    }
 
   /**
    * day cycle
@@ -275,4 +284,52 @@ public class CycleFactory {
           return null;
         }
   }
+    
+    /**
+     * year cycle
+     */
+    public static class YearCycle extends AbstractCycle {
+        public YearCycle(Cron cron) {
+            super(cron);
+        }
+        
+        /**
+         * get cycle
+         * @return CycleEnum
+         */
+        @Override
+        protected CycleEnum getCycle() {
+            boolean flag = (minFiledIsSetAll()
+                    && hourFiledIsSetAll()
+                    && dayOfMonthFieldIsSetAll()
+                    && dayOfWeekField.getExpression() instanceof QuestionMark
+                    && monthFieldIsSetAll())
+                    && yearFieldIsEvery() ||
+                    (minFiledIsSetAll()
+                            && hourFiledIsSetAll()
+                            && dayOfMonthField.getExpression() instanceof 
QuestionMark
+                            && dayofWeekFieldIsSetAll()
+                            && monthFieldIsSetAll()
+                            && yearFieldIsEvery());
+            
+            if (flag) {
+                return CycleEnum.YEAR;
+            }
+            
+            return null;
+        }
+        
+        /**
+         * get mini cycle
+         * @return CycleEnum
+         */
+        @Override
+        protected CycleEnum getMiniCycle() {
+            if (yearFieldIsEvery()) {
+                return CycleEnum.YEAR;
+            }
+            
+            return null;
+        }
+    }
 }
diff --git 
a/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtilsTest.java
 
b/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtilsTest.java
index 55cc19d..4fbcd8f 100644
--- 
a/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtilsTest.java
+++ 
b/dolphinscheduler-service/src/test/java/org/apache/dolphinscheduler/service/quartz/cron/CronUtilsTest.java
@@ -95,6 +95,20 @@ public class CronUtilsTest {
 
         CycleEnum cycleEnum3 = CronUtils.getMiniCycle(CronUtils.parse2Cron("0 
* * * * ? *"));
         Assert.assertEquals("MINUTE", cycleEnum3.name());
+    
+        CycleEnum cycleEnum4 = CronUtils.getMaxCycle(CronUtils.parse2Cron("0 0 
7 * 1 ? *"));
+        Assert.assertEquals("YEAR", cycleEnum4.name());
+        cycleEnum4 = CronUtils.getMiniCycle(CronUtils.parse2Cron("0 0 7 * 1 ? 
*"));
+        Assert.assertEquals("DAY", cycleEnum4.name());
+    
+        CycleEnum cycleEnum5 = CronUtils.getMaxCycle(CronUtils.parse2Cron("0 0 
7 * 1/1 ? *"));
+        Assert.assertEquals("MONTH", cycleEnum5.name());
+    
+        CycleEnum cycleEnum6 = CronUtils.getMaxCycle(CronUtils.parse2Cron("0 0 
7 * 1-2 ? *"));
+        Assert.assertEquals("YEAR", cycleEnum6.name());
+    
+        CycleEnum cycleEnum7 = CronUtils.getMaxCycle(CronUtils.parse2Cron("0 0 
7 * 1,2 ? *"));
+        Assert.assertEquals("YEAR", cycleEnum7.name());
     }
 
     /**
@@ -113,7 +127,7 @@ public class CronUtilsTest {
                 .instance();
         // minute cycle
         String[] cronArayy = new String[]{"* * * * * ? *","* 0 * * * ? *",
-                "* 5 * * 3/5 ? *","0 0 * * * ? *"};
+                "* 5 * * 3/5 ? *","0 0 * * * ? *", "0 0 7 * 1 ? *", "0 0 7 * 
1/1 ? *", "0 0 7 * 1-2 ? *" , "0 0 7 * 1,2 ? *"};
         for(String minCrontab:cronArayy){
             if (!org.quartz.CronExpression.isValidExpression(minCrontab)) {
                 throw new RuntimeException(minCrontab+" verify failure, cron 
expression not valid");
@@ -155,6 +169,14 @@ public class CronUtilsTest {
             logger.info("dayOfWeekField instanceof 
On:"+(dayOfWeekField.getExpression() instanceof On));
             logger.info("dayOfWeekField instanceof 
And:"+(dayOfWeekField.getExpression() instanceof And));
             logger.info("dayOfWeekField instanceof 
QuestionMark:"+(dayOfWeekField.getExpression() instanceof QuestionMark));
+    
+            CronField yearField = cron.retrieve(CronFieldName.YEAR);
+            logger.info("yearField instanceof 
Between:"+(yearField.getExpression() instanceof Between));
+            logger.info("yearField instanceof 
Always:"+(yearField.getExpression() instanceof Always));
+            logger.info("yearField instanceof 
Every:"+(yearField.getExpression() instanceof Every));
+            logger.info("yearField instanceof On:"+(yearField.getExpression() 
instanceof On));
+            logger.info("yearField instanceof And:"+(yearField.getExpression() 
instanceof And));
+            logger.info("yearField instanceof 
QuestionMark:"+(yearField.getExpression() instanceof QuestionMark));
 
             CycleEnum cycleEnum = CronUtils.getMaxCycle(minCrontab);
             if(cycleEnum !=null){
@@ -204,4 +226,4 @@ public class CronUtilsTest {
         expirationTime = CronUtils.getExpirationTime(startTime, 
CycleEnum.YEAR);
         Assert.assertEquals("2020-02-07 18:30:00", 
DateUtils.dateToString(expirationTime));
     }
-}
\ No newline at end of file
+}
diff --git 
a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/AbstractCommandExecutor.java
 
b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/AbstractCommandExecutor.java
index 805d035..4112e2c 100644
--- 
a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/AbstractCommandExecutor.java
+++ 
b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/api/AbstractCommandExecutor.java
@@ -122,10 +122,12 @@ public abstract class AbstractCommandExecutor {
         // merge error information to standard output stream
         processBuilder.redirectErrorStream(true);
 
-        // setting up user to run commands
-        command.add("sudo");
-        command.add("-u");
-        command.add(taskRequest.getTenantCode());
+        // if sudo.enable=true,setting up user to run commands
+        if (OSUtils.isSudoEnable()) {
+            command.add("sudo");
+            command.add("-u");
+            command.add(taskRequest.getTenantCode());
+        }
         command.add(commandInterpreter());
         command.addAll(Collections.emptyList());
         command.add(commandFile);
diff --git 
a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/util/OSUtils.java
 
b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/util/OSUtils.java
index e8c66a7..f34268d 100644
--- 
a/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/util/OSUtils.java
+++ 
b/dolphinscheduler-task-plugin/dolphinscheduler-task-api/src/main/java/org/apache/dolphinscheduler/plugin/task/util/OSUtils.java
@@ -18,6 +18,7 @@
 package org.apache.dolphinscheduler.plugin.task.util;
 
 import org.apache.dolphinscheduler.plugin.task.api.ShellExecutor;
+import org.apache.dolphinscheduler.spi.utils.PropertyUtils;
 import org.apache.dolphinscheduler.spi.utils.StringUtils;
 
 import java.io.IOException;
@@ -41,6 +42,15 @@ public class OSUtils {
     }
 
     /**
+     * use sudo or not
+     *
+     * @return true is use sudo
+     */
+    public static boolean isSudoEnable() {
+        return PropertyUtils.getBoolean("sudo.enable", Boolean.TRUE);
+    }
+
+    /**
      * whether is macOS
      *
      * @return true if mac
diff --git 
a/dolphinscheduler-task-plugin/dolphinscheduler-task-datax/src/main/java/org/apache/dolphinscheduler/plugin/task/datax/DataxUtils.java
 
b/dolphinscheduler-task-plugin/dolphinscheduler-task-datax/src/main/java/org/apache/dolphinscheduler/plugin/task/datax/DataxUtils.java
index c4edc4e..42ba975 100644
--- 
a/dolphinscheduler-task-plugin/dolphinscheduler-task-datax/src/main/java/org/apache/dolphinscheduler/plugin/task/datax/DataxUtils.java
+++ 
b/dolphinscheduler-task-plugin/dolphinscheduler-task-datax/src/main/java/org/apache/dolphinscheduler/plugin/task/datax/DataxUtils.java
@@ -19,6 +19,7 @@ package org.apache.dolphinscheduler.plugin.task.datax;
 
 import org.apache.dolphinscheduler.spi.enums.DbType;
 
+import 
com.alibaba.druid.sql.dialect.clickhouse.parser.ClickhouseStatementParser;
 import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
 import com.alibaba.druid.sql.dialect.oracle.parser.OracleStatementParser;
 import com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser;
@@ -91,6 +92,8 @@ public class DataxUtils {
                 return new OracleStatementParser(sql);
             case SQLSERVER:
                 return new SQLServerStatementParser(sql);
+            case CLICKHOUSE:
+                return new ClickhouseStatementParser(sql);
             default:
                 return null;
         }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue 
b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
index c79a59f..bac2aa1 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.vue
@@ -330,11 +330,11 @@
                     })
                     if (this.type === 'instance') {
                       this.$router.push({
-                        path: `/projects/${this.projectCode}/instance/list`
+                        path: 
`/projects/${this.projectCode}/instance/list/${methodParam}`
                       })
                     } else {
                       this.$router.push({
-                        path: `/projects/${this.projectCode}/definition/list`
+                        path: 
`/projects/${this.projectCode}/definition/list/${methodParam}`
                       })
                     }
                   })
diff --git 
a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/_source/list.vue
 
b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/_source/list.vue
index ab68e5a..98ab713 100755
--- 
a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/_source/list.vue
+++ 
b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/list/_source/list.vue
@@ -51,8 +51,11 @@
             <span>{{scope.row.updateTime | formatDate}}</span>
           </template>
         </el-table-column>
-        <el-table-column :label="$t('Operation')" width="150">
+        <el-table-column :label="$t('Operation')" width="180">
           <template slot-scope="scope">
+            <el-tooltip :content="$t('ReUpload File')" placement="top" 
:enterable="false">
+              <span><el-button type="primary" size="mini" 
icon="el-icon-refresh-right" @click="_reUploadFile(scope.row)" 
v-show="scope.row.directory? false: true" circle></el-button></span>
+            </el-tooltip>
             <el-tooltip :content="$t('Edit')" placement="top">
               <span><el-button type="primary" size="mini" 
icon="el-icon-edit-outline" @click="_edit(scope.row)" 
:disabled="_rtDisb(scope.row)" circle></el-button></span>
             </el-tooltip>
@@ -92,7 +95,9 @@
   import { filtTypeArr } from '../../_source/common'
   import { bytesToSize } from '@/module/util/util'
   import { downloadFile } from '@/module/download'
+  import { findComponentDownward } from '@/module/util/'
   import localStore from '@/module/util/localStorage'
+
   export default {
     name: 'file-manage-list',
     data () {
@@ -123,6 +128,9 @@
           this.$router.push({ path: `/resource/file/list/${item.id}` })
         }
       },
+      _reUploadFile (item) {
+        findComponentDownward(this.$root, 'roof-nav')._fileReUpload(item)
+      },
       _downloadFile (item) {
         downloadFile(`resources/${item.id}/download`)
       },
diff --git 
a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/list.vue
 
b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/list.vue
index 3e1eac1..517e5e0 100755
--- 
a/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/list.vue
+++ 
b/dolphinscheduler-ui/src/js/conf/home/pages/resource/pages/file/pages/subdirectory/_source/list.vue
@@ -50,8 +50,11 @@
             <span>{{scope.row.updateTime | formatDate}}</span>
           </template>
         </el-table-column>
-        <el-table-column :label="$t('Operation')" width="150">
+        <el-table-column :label="$t('Operation')" width="180">
           <template slot-scope="scope">
+            <el-tooltip :content="$t('ReUpload File')" placement="top" 
:enterable="false">
+              <span><el-button type="primary" size="mini" 
icon="el-icon-refresh-right" @click="_reUploadFile(scope.row)" 
v-show="scope.row.directory? false: true" circle></el-button></span>
+            </el-tooltip>
             <el-tooltip :content="$t('Edit')" placement="top" 
:enterable="false">
               <span><el-button type="primary" size="mini" 
icon="el-icon-edit-outline" @click="_edit(scope.row)" 
:disabled="_rtDisb(scope.row)" circle></el-button></span>
             </el-tooltip>
@@ -90,6 +93,7 @@
   import { mapActions } from 'vuex'
   import { filtTypeArr } from '../../_source/common'
   import { bytesToSize } from '@/module/util/util'
+  import { findComponentDownward } from '@/module/util/'
   import { downloadFile } from '@/module/download'
   import localStore from '@/module/util/localStorage'
 
@@ -122,6 +126,9 @@
           this.$router.push({ path: `/resource/file/list/${item.id}` })
         }
       },
+      _reUploadFile (item) {
+        findComponentDownward(this.$root, 'roof-nav')._fileReUpload(item)
+      },
       _downloadFile (item) {
         downloadFile(`resources/${item.id}/download`)
       },
diff --git 
a/dolphinscheduler-ui/src/js/module/components/fileUpdate/fileReUpload.vue 
b/dolphinscheduler-ui/src/js/module/components/fileUpdate/fileReUpload.vue
index 4b64d66..c58f43b 100644
--- a/dolphinscheduler-ui/src/js/module/components/fileUpdate/fileReUpload.vue
+++ b/dolphinscheduler-ui/src/js/module/components/fileUpdate/fileReUpload.vue
@@ -1,26 +1,27 @@
 /*
- * 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.
- */
+* 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.
+*/
 <template>
   <m-popup
-          ref="popup"
-          :ok-text="$t('Upload')"
-          :nameText="$t('ReUpload File')"
-          @ok="_ok"
-          :disabled="progress === 0 ? false : true">
+    ref="popup"
+    :ok-text="$t('Upload')"
+    :nameText="$t('ReUpload File')"
+    @ok="_ok"
+    @close="_close"
+    :disabled="progress === 0 ? false : true">
     <template slot="content">
       <form name="files" enctype="multipart/form-data" method="post">
         <div class="file-update-model"
@@ -44,11 +45,11 @@
             <template slot="name"><strong>*</strong>{{$t('File 
Name')}}</template>
             <template slot="content">
               <el-input
-                      type="input"
-                      size="small"
-                      v-model="name"
-                      :disabled="progress !== 0"
-                      :placeholder="$t('Please enter name')">
+                type="input"
+                size="small"
+                v-model="name"
+                :disabled="true"
+                :placeholder="$t('Please enter name')">
               </el-input>
             </template>
           </m-list-box-f>
@@ -56,11 +57,11 @@
             <template slot="name">{{$t('Description')}}</template>
             <template slot="content">
               <el-input
-                      type="textarea"
-                      size="small"
-                      v-model="description"
-                      :disabled="progress !== 0"
-                      :placeholder="$t('Please enter description')">
+                type="textarea"
+                size="small"
+                v-model="description"
+                :disabled="progress !== 0"
+                :placeholder="$t('Please enter description')">
               </el-input>
             </template>
           </m-list-box-f>
@@ -92,7 +93,7 @@
   import mProgressBar from '@/module/components/progressBar/progressBar'
 
   export default {
-    name: 'file-update',
+    name: 'file-upload',
     data () {
       return {
         store,
@@ -105,17 +106,21 @@
         // file
         file: null,
         currentDir: '/',
+        id: null,
         // Whether to drag upload
         dragOver: false
       }
     },
     watch: {
+      originalFileData: {
+        deep: true,
+        handler () {
+          this._init()
+        }
+      }
     },
     props: {
-      type: String,
-      fileName: String,
-      desc: String,
-      id: Number
+      originalFileData: Object
     },
     methods: {
       /**
@@ -124,7 +129,7 @@
       _ok () {
         this.$refs.popup.spinnerLoading = true
         if (this._validation()) {
-          if (this.fileName === this.name) {
+          if (this.originalFileData.fileName === this.name) {
             const isLt1024M = this.file.size / 1024 / 1024 < 1024
             if (isLt1024M) {
               this._formDataUpdate().then(res => {
@@ -139,10 +144,8 @@
               this.$refs.popup.spinnerLoading = false
             }
           } else {
-            this.store.dispatch('resource/resourceVerifyName', {
-              fullName: '/' + this.name,
-              type: this.type
-            }).then(res => {
+            const params = { fullName: this.currentDir + this.name, type: 
'FILE' }
+            this.store.dispatch('resource/resourceVerifyName', 
params).then(res => {
               const isLt1024M = this.file.size / 1024 / 1024 < 1024
               if (isLt1024M) {
                 this._formDataUpdate().then(res => {
@@ -165,6 +168,9 @@
           this.$refs.popup.spinnerLoading = false
         }
       },
+      _close () {
+        this.$emit('closeFileUpload')
+      },
       /**
        * validation
        */
@@ -186,19 +192,19 @@
         return new Promise((resolve, reject) => {
           let self = this
           let formData = new FormData()
+
           formData.append('file', this.file)
           formData.append('name', this.name)
           formData.append('description', this.description)
-          formData.append('id', this.id)
-          formData.append('type', this.type)
-          io.post('resources/update', res => {
+          formData.append('type', 'FILE')
+          io.put('resources/' + this.id, res => {
             this.$message.success(res.msg)
             resolve()
-            self.$emit('onUpdate')
+            self.$emit('onUploadFile')
             this.reset()
           }, e => {
             reject(e)
-            self.$emit('close')
+            self.$emit('closeFileUpload')
             this.$message.error(e.msg || '')
             this.reset()
           }, {
@@ -244,11 +250,21 @@
         this.file = file
         this.name = file.name
         this.$refs.file.value = null
+      },
+      _init () {
+        if (this.originalFileData) {
+          this.id = this.originalFileData.id
+          this.name = this.originalFileData.fileName
+          if (this.originalFileData.desc) {
+            this.description = this.originalFileData.desc
+          }
+          this.currentDir = this.originalFileData.fullName.substring(0, 
this.originalFileData.fullName.length - this.originalFileData.fileName.length)
+        }
       }
     },
     mounted () {
-      this.name = this.fileName
-      this.description = this.desc
+      this.reset()
+      this._init()
     },
     components: { mPopup, mListBoxF, mProgressBar }
   }
diff --git a/dolphinscheduler-ui/src/js/module/components/nav/nav.vue 
b/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
index 68ff673..27b0211 100644
--- a/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
+++ b/dolphinscheduler-ui/src/js/module/components/nav/nav.vue
@@ -163,6 +163,17 @@
       width="auto">
       <m-resource-child-update :type="type" :id="id" 
@onProgressResourceChildUpdate="onProgressResourceChildUpdate" 
@onUpdateResourceChildUpdate="onUpdateResourceChildUpdate" 
@onArchiveFileChildUpdate="onArchiveResourceChildUpdate" 
@closeResourceChildUpdate="closeResourceChildUpdate"></m-resource-child-update>
     </el-dialog>
+
+    <el-dialog
+      :visible.sync="fileUploadDialog"
+      append-to-body="true"
+      width="auto">
+      <m-file-upload
+        :originalFileData="originalFileData"
+        @onUploadFile="onUploadFile"
+        @closeFileUpload="closeFileUpload">
+      </m-file-upload>
+    </el-dialog>
   </div>
 </template>
 <script>
@@ -174,6 +185,7 @@
   import mFileChildUpdate from '@/module/components/fileUpdate/fileChildUpdate'
   import mResourceChildUpdate from 
'@/module/components/fileUpdate/resourceChildUpdate'
   import mDefinitionUpdate from 
'@/module/components/fileUpdate/definitionUpdate'
+  import mFileUpload from '@/module/components/fileUpdate/fileReUpload'
   import mProgressBar from '@/module/components/progressBar/progressBar'
   import { findLocale, localeList } from '@/module/i18n/config'
 
@@ -202,7 +214,11 @@
         fileUpdateDialog: false,
         fileChildUpdateDialog: false,
         id: null,
-        resourceChildUpdateDialog: false
+        fileName: '',
+        description: '',
+        originalFileData: {},
+        resourceChildUpdateDialog: false,
+        fileUploadDialog: false
       }
     },
 
@@ -250,16 +266,13 @@
         this.progress = 0
         this.definitionUpdateDialog = false
       },
-
       onArchiveDefinition () {
         this.isUpdate = true
       },
-
       closeDefinition () {
         this.progress = 0
         this.definitionUpdateDialog = false
       },
-
       onProgressFileUpdate (val) {
         this.progress = val
       },
@@ -270,6 +283,17 @@
         this.progress = 0
         this.fileUpdateDialog = false
       },
+      onUploadFile () {
+        let self = this
+        findComponentDownward(self.$root, 
'resource-list-index-FILE')._updateList()
+        this.isUpdate = false
+        this.progress = 0
+        this.fileUploadDialog = false
+      },
+      closeFileUpload () {
+        this.progress = 0
+        this.fileUploadDialog = false
+      },
       onArchiveFileUpdate () {
         this.isUpdate = true
       },
@@ -277,7 +301,6 @@
         this.progress = 0
         this.fileUpdateDialog = false
       },
-
       _fileChildUpdate (type, data) {
         if (this.progress) {
           this._toggleArchive()
@@ -287,7 +310,14 @@
         this.id = data
         this.fileChildUpdateDialog = true
       },
-
+      _fileReUpload (item) {
+        if (this.progress) {
+          this._toggleArchive()
+          return
+        }
+        this.originalFileData = item
+        this.fileUploadDialog = true
+      },
       onProgressFileChildUpdate (val) {
         this.progress = val
       },
@@ -307,7 +337,6 @@
         this.progress = 0
         this.fileChildUpdateDialog = false
       },
-
       _resourceChildUpdate (type, data) {
         if (this.progress) {
           this._toggleArchive()
@@ -350,7 +379,6 @@
        * Language switching
        */
       _toggleLanguage (language) {
-        console.log(language)
         cookies.set('language', language, { path: '/' })
         setTimeout(() => {
           window.location.reload()
@@ -365,7 +393,7 @@
     computed: {
       ...mapState('user', ['userInfo'])
     },
-    components: { mFileUpdate, mProgressBar, mDefinitionUpdate, 
mFileChildUpdate, mResourceChildUpdate }
+    components: { mFileUpdate, mProgressBar, mDefinitionUpdate, 
mFileChildUpdate, mResourceChildUpdate, mFileUpload }
   }
 </script>
 
diff --git a/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js 
b/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
index c749142..05caaf3 100755
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/en_US.js
@@ -727,6 +727,10 @@ export default {
   webHook: 'WebHook',
   Keyword: 'Keyword',
   Secret: 'Secret',
+  MsgType: 'MsgType',
+  AtMobiles: '@Mobiles',
+  AtUserIds: '@UserIds',
+  IsAtAll: '@All',
   Proxy: 'Proxy',
   receivers: 'Receivers',
   receiverCcs: 'ReceiverCcs',
diff --git a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js 
b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
index 4d62bb9..8d76c72 100644
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
@@ -726,6 +726,10 @@ export default {
   webHook: 'Web钩子',
   Keyword: '关键词',
   Secret: '密钥',
+  MsgType: '消息类型',
+  AtMobiles: '@手机号',
+  AtUserIds: '@用户ID',
+  IsAtAll: '@所有人',
   Proxy: '代理',
   receivers: '收件人',
   receiverCcs: '抄送人',

Reply via email to