You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by ki...@apache.org on 2021/08/27 13:07:59 UTC

[dolphinscheduler] branch task-plugin updated: [Feature-5992] Enhance using experience of DataX in DS by integeration with TIS (#6015)

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

kirs pushed a commit to branch task-plugin
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/task-plugin by this push:
     new b04bceb  [Feature-5992] Enhance using experience of DataX in DS by integeration with TIS (#6015)
b04bceb is described below

commit b04bceb03c9e777377f721c10ba75de4d6ca452c
Author: 百岁 <mo...@126.com>
AuthorDate: Fri Aug 27 21:07:51 2021 +0800

    [Feature-5992] Enhance using experience of DataX in DS by integeration with TIS (#6015)
---
 dolphinscheduler-dist/release-docs/LICENSE         |   2 +-
 .../dolphinscheduler-task-tis/pom.xml              |  97 ++++++
 .../dolphinscheduler-task-tis/readme.md            |   0
 .../plugin/task/tis/TISParameters.java             |  62 ++++
 .../plugin/task/tis/TISParamsConstants.java        |  27 ++
 .../dolphinscheduler/plugin/task/tis/TISTask.java  | 345 +++++++++++++++++++++
 .../plugin/task/tis/TISTaskChannel.java            |  34 ++
 .../plugin/task/tis/TISTaskChannelFactory.java     |  51 +++
 .../plugin/task/tis/TISTaskTest.java               | 142 +++++++++
 .../plugin/task/tis/TISTaskTest.json               |  60 ++++
 dolphinscheduler-task-plugin/pom.xml               |   1 +
 .../src/js/conf/home/pages/dag/_source/config.js   |   4 +
 .../src/js/conf/home/pages/dag/_source/dag.scss    |   3 +
 .../home/pages/dag/_source/formModel/formModel.vue |   9 +
 .../home/pages/dag/_source/formModel/tasks/tis.vue | 225 ++++++++++++++
 .../src/js/conf/home/pages/dag/img/toolbar_TIS.svg |  46 +++
 .../src/js/module/i18n/locale/zh_CN.js             |   2 +
 pom.xml                                            |   1 +
 tools/dependencies/known-dependencies.txt          |   4 +-
 19 files changed, 1112 insertions(+), 3 deletions(-)

diff --git a/dolphinscheduler-dist/release-docs/LICENSE b/dolphinscheduler-dist/release-docs/LICENSE
index 2308359..ef26c32 100644
--- a/dolphinscheduler-dist/release-docs/LICENSE
+++ b/dolphinscheduler-dist/release-docs/LICENSE
@@ -416,7 +416,7 @@ The text of each license is also included at licenses/LICENSE-[project].txt.
     protostuff-runtime 1.7.2: https://github.com/protostuff/protostuff/protostuff-core Apache-2.0
     protostuff-api 1.7.2: https://github.com/protostuff/protostuff/protostuff-api Apache-2.0
     protostuff-collectionschema 1.7.2: https://github.com/protostuff/protostuff/protostuff-collectionschema Apache-2.0
-
+    async-http-client 2.12.3: https://mvnrepository.com/artifact/org.asynchttpclient/async-http-client Apache-2.0
 ========================================================================
 BSD licenses
 ========================================================================
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/pom.xml b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/pom.xml
new file mode 100644
index 0000000..5abc8d0
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>dolphinscheduler-task-plugin</artifactId>
+        <groupId>org.apache.dolphinscheduler</groupId>
+        <version>1.3.6-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dolphinscheduler-task-tis</artifactId>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.dolphinscheduler</groupId>
+            <artifactId>dolphinscheduler-task-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <!--https://github.com/dreamhead/moco/blob/master/moco-doc/usage.md#socket-->
+        <dependency>
+            <groupId>com.github.dreamhead</groupId>
+            <artifactId>moco-core</artifactId>
+            <version>1.2.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.github.dreamhead</groupId>
+            <artifactId>moco-runner</artifactId>
+            <version>1.2.0</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-cli</groupId>
+                    <artifactId>commons-cli</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.asynchttpclient</groupId>
+            <artifactId>async-http-client</artifactId>
+            <version>2.12.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dolphinscheduler</groupId>
+            <artifactId>dolphinscheduler-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dolphinscheduler</groupId>
+            <artifactId>dolphinscheduler-server</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/readme.md b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/readme.md
new file mode 100644
index 0000000..e69de29
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISParameters.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISParameters.java
new file mode 100644
index 0000000..f19c33e
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISParameters.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.plugin.task.tis;
+
+import org.apache.dolphinscheduler.spi.task.AbstractParameters;
+import org.apache.dolphinscheduler.spi.task.ResourceInfo;
+import org.apache.dolphinscheduler.spi.utils.StringUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TIS parameter
+ */
+public class TISParameters extends AbstractParameters {
+
+    private static final Logger logger = LoggerFactory.getLogger(TISParameters.class);
+    /**
+     * TIS target job name
+     */
+    private String targetJobName;
+
+    public String getTargetJobName() {
+        return targetJobName;
+    }
+
+    public void setTargetJobName(String targetJobName) {
+        this.targetJobName = targetJobName;
+    }
+
+    @Override
+    public boolean checkParameters() {
+        if (StringUtils.isBlank(this.targetJobName)) {
+            logger.error("checkParameters faild targetJobName can not be null");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public List<ResourceInfo> getResourceFilesList() {
+        return Collections.emptyList();
+    }
+}
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISParamsConstants.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISParamsConstants.java
new file mode 100644
index 0000000..54dfceb
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISParamsConstants.java
@@ -0,0 +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.
+ */
+
+package org.apache.dolphinscheduler.plugin.task.tis;
+
+public class TISParamsConstants {
+
+    public static String NAME_TARGET_JOB_NAME = "targetJobName";
+    public static String TARGET_JOB_NAME = NAME_TARGET_JOB_NAME;
+
+    private TISParamsConstants() {
+    }
+}
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTask.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTask.java
new file mode 100644
index 0000000..4a70b6c
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTask.java
@@ -0,0 +1,345 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.plugin.task.tis;
+
+import org.apache.dolphinscheduler.common.utils.CollectionUtils;
+import org.apache.dolphinscheduler.spi.task.AbstractParameters;
+import org.apache.dolphinscheduler.spi.task.AbstractTask;
+import org.apache.dolphinscheduler.spi.task.TaskConstants;
+import org.apache.dolphinscheduler.spi.task.TaskRequest;
+import org.apache.dolphinscheduler.spi.utils.JSONUtils;
+import org.apache.dolphinscheduler.spi.utils.StringUtils;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+import java.net.HttpURLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.asynchttpclient.Dsl;
+import org.asynchttpclient.ws.WebSocket;
+import org.asynchttpclient.ws.WebSocketListener;
+import org.asynchttpclient.ws.WebSocketUpgradeHandler;
+import org.slf4j.Logger;
+
+/**
+ * TIS DataX Task
+ **/
+public class TISTask extends AbstractTask {
+
+    public static final String WS_REQUEST_PATH = "/tjs/download/logfeedback";
+    public static final String KEY_POOL_VAR_TIS_HOST = "tisHost";
+    private final TaskRequest taskExecutionContext;
+
+    private TISParameters tisParameters;
+
+    public TISTask(TaskRequest taskExecutionContext, Logger logger) {
+        super(taskExecutionContext, logger);
+        this.taskExecutionContext = taskExecutionContext;
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        logger.info("tis task params {}", taskExecutionContext.getTaskParams());
+        tisParameters = JSONUtils.parseObject(taskExecutionContext.getTaskParams(), TISParameters.class);
+        if (!tisParameters.checkParameters()) {
+            throw new RuntimeException("datax task params is not valid");
+        }
+    }
+
+    @Override
+    public void handle() throws Exception {
+        // Trigger TIS DataX pipeline
+        logger.info("start execute TIS task");
+        long startTime = System.currentTimeMillis();
+        String targetJobName = this.tisParameters.getTargetJobName();
+        final String tisHost = taskExecutionContext.getDefinedParams().get(KEY_POOL_VAR_TIS_HOST);
+        if (StringUtils.isEmpty(tisHost)) {
+            throw new IllegalStateException("global var '" + KEY_POOL_VAR_TIS_HOST + "' can not be empty");
+        }
+        try {
+            final String triggerUrl = String.format("http://%s/tjs/coredefine/coredefine.ajax", tisHost);
+            final String getStatusUrl = String.format("http://%s/tjs/config/config.ajax?action=collection_action&emethod=get_task_status", tisHost);
+            HttpPost post = new HttpPost(triggerUrl);
+            post.addHeader("appname", targetJobName);
+            addFormUrlencoded(post);
+            StringEntity entity = new StringEntity("action=datax_action&emethod=trigger_fullbuild_task", StandardCharsets.UTF_8);
+            post.setEntity(entity);
+            BizResult ajaxResult = null;
+            ExecResult execState = null;
+            int taskId;
+            WebSocket webSocket = null;
+            try (CloseableHttpClient client = HttpClients.createDefault();
+                 // trigger to start TIS dataX task
+                 CloseableHttpResponse response = client.execute(post)) {
+                ajaxResult = processResponse(triggerUrl, response, BizResult.class);
+                if (!ajaxResult.isSuccess()) {
+                    List<String> errormsg = ajaxResult.getErrormsg();
+                    StringBuffer errs = new StringBuffer();
+                    if (CollectionUtils.isNotEmpty(errormsg)) {
+                        errs.append(",errs:").append(errormsg.stream().collect(Collectors.joining(",")));
+                    }
+                    throw new Exception("trigger TIS job faild taskName:" + targetJobName + errs.toString());
+                }
+                taskId = ajaxResult.getBizresult().getTaskid();
+
+                webSocket = receiveRealtimeLog(tisHost, targetJobName, taskId);
+
+                setAppIds(String.valueOf(taskId));
+
+                CloseableHttpResponse status = null;
+
+                while (true) {
+                    try {
+                        post = new HttpPost(getStatusUrl);
+                        entity = new StringEntity("{\n taskid: " + taskId + "\n, log: false }", StandardCharsets.UTF_8);
+                        post.setEntity(entity);
+                        status = client.execute(post);
+                        StatusResult execStatus = processResponse(getStatusUrl, status, StatusResult.class);
+                        Map bizresult = execStatus.getBizresult();
+                        Map s = (Map) bizresult.get("status");
+                        execState = ExecResult.parse((Integer) s.get("state"));
+                        if (execState == ExecResult.SUCCESS || execState == ExecResult.FAILD) {
+                            break;
+                        }
+                        Thread.sleep(3000);
+                    } finally {
+                        status.close();
+                    }
+                }
+            } finally {
+                try {
+                    webSocket.sendCloseFrame();
+                } catch (Throwable e) {
+                    logger.warn(e.getMessage(), e);
+                }
+            }
+
+            long costTime = System.currentTimeMillis() - startTime;
+            logger.info("TIS task: {},taskId:{} costTime : {} milliseconds, statusCode : {}",
+                    targetJobName, taskId, costTime, (execState == ExecResult.SUCCESS) ? "'success'" : "'failure'");
+            setExitStatusCode((execState == ExecResult.SUCCESS) ? TaskConstants.EXIT_CODE_SUCCESS : TaskConstants.EXIT_CODE_FAILURE);
+        } catch (Exception e) {
+            logger.error("execute TIS dataX faild,TIS task name:" + targetJobName, e);
+            setExitStatusCode(TaskConstants.EXIT_CODE_FAILURE);
+        }
+    }
+
+    private void addFormUrlencoded(HttpPost post) {
+        post.addHeader("content-type", "application/x-www-form-urlencoded");
+    }
+
+    @Override
+    public void cancelApplication(boolean status) throws Exception {
+        super.cancelApplication(status);
+    }
+
+    private WebSocket receiveRealtimeLog(final String tisHost, String dataXName, int taskId) throws InterruptedException, java.util.concurrent.ExecutionException {
+
+        WebSocketUpgradeHandler.Builder upgradeHandlerBuilder
+                = new WebSocketUpgradeHandler.Builder();
+        WebSocketUpgradeHandler wsHandler = upgradeHandlerBuilder
+                .addWebSocketListener(new WebSocketListener() {
+                    @Override
+                    public void onOpen(WebSocket websocket) {
+                        // WebSocket connection opened
+                    }
+
+                    @Override
+                    public void onClose(WebSocket websocket, int code, String reason) {
+                        // WebSocket connection closed
+                    }
+
+                    public void onTextFrame(String payload, boolean finalFragment, int rsv) {
+                        ExecLog execLog = JSONUtils.parseObject(payload, ExecLog.class);
+                        logger.info(execLog.getMsg());
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        // WebSocket connection error
+                        logger.error(t.getMessage(), t);
+                    }
+                }).build();
+        WebSocket webSocketClient = Dsl.asyncHttpClient()
+                .prepareGet(String.format("ws://%s" + WS_REQUEST_PATH, tisHost))
+                // .addHeader("header_name", "header_value")
+                .addQueryParam("logtype", "full")
+                .addQueryParam("collection", dataXName)
+                .addQueryParam("taskid", String.valueOf(taskId))
+                .setRequestTimeout(5000)
+                .execute(wsHandler)
+                .get();
+
+        return webSocketClient;
+    }
+
+    private <T extends AjaxResult> T processResponse(String applyUrl, CloseableHttpResponse response, Class<T> clazz) throws Exception {
+        StatusLine resStatus = response.getStatusLine();
+        if (HttpURLConnection.HTTP_OK != resStatus.getStatusCode()) {
+            throw new IllegalStateException("request server " + applyUrl + " faild:" + resStatus.getReasonPhrase());
+        }
+        HttpEntity entity = response.getEntity();
+        String resp = EntityUtils.toString(entity, StandardCharsets.UTF_8);
+        T result = JSONUtils.parseObject(resp, clazz);
+        return result;
+    }
+
+    @Override
+    public AbstractParameters getParameters() {
+        Objects.requireNonNull(this.tisParameters, "tisParameters can not be null");
+        return this.tisParameters;
+    }
+
+    private static class BizResult extends AjaxResult<TriggerBuildResult> {
+        private TriggerBuildResult bizresult;
+
+        public TriggerBuildResult getBizresult() {
+            return this.bizresult;
+        }
+
+        public void setBizresult(TriggerBuildResult bizresult) {
+            this.bizresult = bizresult;
+        }
+    }
+
+    private static class StatusResult extends AjaxResult<Map> {
+        private Map bizresult;
+
+        public Map getBizresult() {
+            return this.bizresult;
+        }
+
+        public void setBizresult(Map bizresult) {
+            this.bizresult = bizresult;
+        }
+    }
+
+    private abstract static class AjaxResult<T> {
+
+        private boolean success;
+
+        private List<String> errormsg;
+
+        private List<String> msg;
+
+        public abstract T getBizresult();
+
+        public boolean isSuccess() {
+            return success;
+        }
+
+        public void setSuccess(boolean success) {
+            this.success = success;
+        }
+
+        public List<String> getErrormsg() {
+            return this.errormsg;
+        }
+
+        public void setErrormsg(List<String> errormsg) {
+            this.errormsg = errormsg;
+        }
+
+        public List<String> getMsg() {
+            return this.msg;
+        }
+
+        public void setMsg(List<String> msg) {
+            this.msg = msg;
+        }
+
+    }
+
+    private static class TriggerBuildResult {
+        private int taskid;
+
+        public int getTaskid() {
+            return taskid;
+        }
+
+        public void setTaskid(int taskid) {
+            this.taskid = taskid;
+        }
+    }
+
+    private enum ExecResult {
+
+        SUCCESS(1), FAILD(-1), DOING(2), ASYN_DOING(22), CANCEL(3);
+
+        private final int value;
+
+        public static ExecResult parse(int value) {
+            for (ExecResult r : values()) {
+                if (r.value == value) {
+                    return r;
+                }
+            }
+            throw new IllegalStateException("vale:" + value + " is illegal");
+        }
+
+        private ExecResult(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return this.value;
+        }
+    }
+
+    private static class ExecLog {
+        private String logType;
+        private String msg;
+        private int taskId;
+
+        public String getLogType() {
+            return logType;
+        }
+
+        public void setLogType(String logType) {
+            this.logType = logType;
+        }
+
+        public String getMsg() {
+            return msg;
+        }
+
+        public void setMsg(String msg) {
+            this.msg = msg;
+        }
+
+        public int getTaskId() {
+            return taskId;
+        }
+
+        public void setTaskId(int taskId) {
+            this.taskId = taskId;
+        }
+    }
+}
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskChannel.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskChannel.java
new file mode 100644
index 0000000..467ad7c
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskChannel.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.plugin.task.tis;
+
+import org.apache.dolphinscheduler.spi.task.AbstractTask;
+import org.apache.dolphinscheduler.spi.task.TaskChannel;
+import org.apache.dolphinscheduler.spi.task.TaskRequest;
+
+public class TISTaskChannel implements TaskChannel {
+    @Override
+    public void cancelApplication(boolean status) {
+
+    }
+
+    @Override
+    public AbstractTask createTask(TaskRequest taskRequest, org.slf4j.Logger logger) {
+        return new TISTask(taskRequest, logger);
+    }
+}
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskChannelFactory.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskChannelFactory.java
new file mode 100644
index 0000000..c00f5ee
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/main/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskChannelFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.plugin.task.tis;
+
+import org.apache.dolphinscheduler.spi.params.InputParam;
+import org.apache.dolphinscheduler.spi.params.base.PluginParams;
+import org.apache.dolphinscheduler.spi.params.base.Validate;
+import org.apache.dolphinscheduler.spi.task.TaskChannel;
+import org.apache.dolphinscheduler.spi.task.TaskChannelFactory;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * TIS endpoint
+ **/
+public class TISTaskChannelFactory implements TaskChannelFactory {
+
+    @Override
+    public TaskChannel create() {
+        return new TISTaskChannel();
+    }
+
+    @Override
+    public String getName() {
+        return "TIS";
+    }
+
+    @Override
+    public List<PluginParams> getParams() {
+        InputParam webHookParam = InputParam.newBuilder(TISParamsConstants.NAME_TARGET_JOB_NAME, TISParamsConstants.TARGET_JOB_NAME)
+                .addValidate(Validate.newBuilder().setRequired(true).build())
+                .build();
+        return Arrays.asList(webHookParam);
+    }
+}
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/test/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.java b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/test/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.java
new file mode 100644
index 0000000..a118edb
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/test/java/org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+package org.apache.dolphinscheduler.plugin.task.tis;
+
+import static com.github.dreamhead.moco.Moco.pathResource;
+import static com.github.dreamhead.moco.MocoJsonRunner.jsonHttpServer;
+import static com.github.dreamhead.moco.Runner.running;
+
+import org.apache.dolphinscheduler.server.worker.task.TaskProps;
+import org.apache.dolphinscheduler.spi.task.ExecutionStatus;
+import org.apache.dolphinscheduler.spi.task.TaskRequest;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.dreamhead.moco.HttpServer;
+
+public class TISTaskTest {
+    private static final Logger logger = LoggerFactory.getLogger(TISTaskTest.class);
+    private TISTask tisTask;
+
+    private TaskRequest taskExecutionContext;
+
+    @Before
+    public void before() throws Exception {
+
+        TaskProps props = new TaskProps();
+        props.setExecutePath("/tmp");
+        props.setTaskAppId(String.valueOf(System.currentTimeMillis()));
+        props.setTaskInstanceId(1);
+        props.setTenantCode("1");
+        props.setEnvFile(".dolphinscheduler_env.sh");
+        props.setTaskStartTime(new Date());
+        props.setTaskTimeout(0);
+        props.setTaskParams("{\"targetJobName\":\"mysql_elastic\"}");
+
+        taskExecutionContext = Mockito.mock(TaskRequest.class);
+        Mockito.when(taskExecutionContext.getTaskParams()).thenReturn(props.getTaskParams());
+        Mockito.when(taskExecutionContext.getExecutePath()).thenReturn("/tmp");
+        Mockito.when(taskExecutionContext.getTaskAppId()).thenReturn(UUID.randomUUID().toString());
+        Mockito.when(taskExecutionContext.getTenantCode()).thenReturn("root");
+        Mockito.when(taskExecutionContext.getStartTime()).thenReturn(new Date());
+        Mockito.when(taskExecutionContext.getTaskTimeout()).thenReturn(10000);
+        Mockito.when(taskExecutionContext.getLogPath()).thenReturn("/tmp/dx");
+        //        Mockito.when(taskExecutionContext.getVarPool())
+        //                .thenReturn("[{\"direct\":\"IN\",\"prop\":\"" + TISTask.KEY_POOL_VAR_TIS_HOST + "\",\"type\":\"VARCHAR\",\"value\":\"127.0.0.1:8080\"}]");
+        Map<String, String> gloabParams = Collections.singletonMap(TISTask.KEY_POOL_VAR_TIS_HOST, "127.0.0.1:8080");
+        Mockito.when(taskExecutionContext.getDefinedParams()).thenReturn(gloabParams);
+
+        tisTask = PowerMockito.spy(new TISTask(taskExecutionContext, logger));
+        tisTask.init();
+
+    }
+
+    /**
+     * Method: DataxTask()
+     */
+    @Test
+    public void testDataxTask()
+            throws Exception {
+        TaskProps props = new TaskProps();
+        props.setExecutePath("/tmp");
+        props.setTaskAppId(String.valueOf(System.currentTimeMillis()));
+        props.setTaskInstanceId(1);
+        props.setTenantCode("1");
+        Assert.assertNotNull(new TISTask(null, logger));
+    }
+
+    @Test
+    public void testInit()
+            throws Exception {
+        try {
+            tisTask.init();
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test
+    public void testHandle()
+            throws Exception {
+        HttpServer server = jsonHttpServer(8080, pathResource("org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.json"));
+
+        running(server, () -> {
+            tisTask.handle();
+
+            Assert.assertEquals("TIS execute be success", ExecutionStatus.SUCCESS, tisTask.getExitStatus());
+        });
+    }
+
+    private String loadResContent(String resName) {
+        try (InputStream i = this.getClass().getResourceAsStream(resName)) {
+            Objects.requireNonNull(i, "resource " + resName + " relevant stream content can not be null");
+            String content = IOUtils.toString(i, StandardCharsets.UTF_8);
+
+            return content;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void testCancelApplication()
+            throws Exception {
+        try {
+            tisTask.cancelApplication(true);
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+}
diff --git a/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/test/resources/org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.json b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/test/resources/org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.json
new file mode 100644
index 0000000..fe11b39
--- /dev/null
+++ b/dolphinscheduler-task-plugin/dolphinscheduler-task-tis/src/test/resources/org/apache/dolphinscheduler/plugin/task/tis/TISTaskTest.json
@@ -0,0 +1,60 @@
+[
+  {
+    "description": "trigger TIS task execute",
+    "request": {
+      "uri": "/tjs/coredefine/coredefine.ajax",
+      "method": "post",
+      "headers": {
+        "Content-Type": "application/x-www-form-urlencoded",
+        "appname": "mysql_elastic"
+      },
+      "text": "action=datax_action&emethod=trigger_fullbuild_task"
+    },
+    "response": {
+      "text": "{\n \"success\":true,\n \"errormsg\":[],\n \"msg\":[],\n \"bizresult\":{\"taskid\": \"1087\"}\n}"
+    }
+  },
+  {
+    "description": "Get TIS task execute status",
+    "request": {
+      "uri": "/tjs/config/config.ajax",
+      "method": "post",
+      "headers": {
+        "Content-Type": "text/plain; charset=UTF-8"
+      },
+      "queries": {
+        "action": "collection_action",
+        "emethod": "get_task_status"
+      },
+      "text": "{\n taskid: 1087\n, log: false }"
+    },
+    "response": {
+      "seq": [
+        {
+          "text": "{\n  \"success\": true,\n  \"errormsg\": [\n    \"err1\"\n  ],\n  \"bizresult\": {\n    \"status\": {\n      \"state\": 2\n    }\n  }\n}"
+        },
+        {
+          "text": "{\n  \"success\": true,\n  \"errormsg\": [\n    \"err1\"\n  ],\n  \"bizresult\": {\n    \"status\": {\n      \"state\": 1\n    }\n  }\n}"
+        }
+      ]
+    }
+  },
+  {
+    "websocket": {
+      "uri": "/tjs/download/logfeedback",
+      "connected": "connected",
+      "sessions": [
+        {
+          "request": {
+            "text": "logtype=full&collection=mysql_elastic&taskid=1087"
+          },
+          "response": {
+            "broadcast": {
+              "content": "{\n  \"logType\": \"FULL\",\n  \"msg\": \"message 1\",\n  \"taskId\": \"1087\"\n}"
+            }
+          }
+        }
+      ]
+    }
+  }
+]
\ No newline at end of file
diff --git a/dolphinscheduler-task-plugin/pom.xml b/dolphinscheduler-task-plugin/pom.xml
index 33ffb9d..d6459fe 100644
--- a/dolphinscheduler-task-plugin/pom.xml
+++ b/dolphinscheduler-task-plugin/pom.xml
@@ -34,6 +34,7 @@
         <module>dolphinscheduler-task-flink</module>
         <module>dolphinscheduler-task-python</module>
         <module>dolphinscheduler-task-spark</module>
+        <module>dolphinscheduler-task-tis</module>
     </modules>
 
 
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
index fb6c123..b63c899 100755
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/config.js
@@ -301,6 +301,10 @@ const tasksType = {
     desc: 'DataX',
     color: '#1fc747'
   },
+  TIS: {
+    desc: 'TIS',
+    color: '#1fc747'
+  },
   SQOOP: {
     desc: 'SQOOP',
     color: '#E46F13'
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.scss b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.scss
index 08f2fec..5627b90 100755
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.scss
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/dag.scss
@@ -107,6 +107,9 @@
   .icos-DATAX {
     background: url("../img/toolbar_DATAX.png") no-repeat 50% 50%;
   }
+  .icos-TIS {
+    background: url("../img/toolbar_TIS.svg") no-repeat 50% 50%;
+  }
   .icos-SQOOP {
     background: url("../img/toolbar_SQOOP.png") no-repeat 50% 50%;
   }
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
index 7dfcbc6..08223a2 100644
--- a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/formModel.vue
@@ -243,6 +243,13 @@
           ref="DATAX"
           :backfill-item="backfillItem">
         </m-datax>
+        <m-tis
+          v-if="nodeData.taskType === 'TIS'"
+          @on-params="_onParams"
+          @on-cache-params="_onCacheParams"
+          :backfill-item="backfillItem"
+          ref="TIS">
+        </m-tis>
         <m-sqoop
           v-if="nodeData.taskType === 'SQOOP'"
           @on-params="_onParams"
@@ -292,6 +299,7 @@
   import mDependent from './tasks/dependent'
   import mHttp from './tasks/http'
   import mDatax from './tasks/datax'
+  import mTis from './tasks/tis'
   import mConditions from './tasks/conditions'
   import mSqoop from './tasks/sqoop'
   import mSubProcess from './tasks/sub_process'
@@ -799,6 +807,7 @@
       mDependent,
       mHttp,
       mDatax,
+      mTis,
       mSqoop,
       mConditions,
       mSelectInput,
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/tis.vue b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/tis.vue
new file mode 100755
index 0000000..96f3a29
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/_source/formModel/tasks/tis.vue
@@ -0,0 +1,225 @@
+/*
+* 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>
+  <div class="datax-model">
+    <m-list-box>
+      <div slot="text">{{$t('TargetJobName')}}</div>
+      <div slot="content">
+        <el-input
+          type="input"
+          size="small"
+          v-model="targetJobName"
+          :placeholder="$t('Please enter TIS DataX job name')">
+        </el-input>
+      </div>
+    </m-list-box>
+  </div>
+</template>
+<script>
+  import _ from 'lodash'
+  import mListBox from './_source/listBox'
+  import disabledState from '@/module/mixin/disabledState'
+
+  export default {
+    name: 'tis',
+
+    data () {
+      return {
+        // target table
+        targetJobName: ''
+      }
+    },
+    mixins: [disabledState],
+    props: {
+      backfillItem: Object,
+      createNodeId: Number
+    },
+    methods: {
+      setEditorVal () {
+        // this.item = editor.getValue()
+        // this.scriptBoxDialog = true
+      },
+      getSriptBoxValue (val) {
+      },
+      /**
+       * return pre statements
+       */
+      _onPreStatements (a) {
+        this.preStatements = a
+      },
+      /**
+       * return post statements
+       */
+      _onPostStatements (a) {
+        this.postStatements = a
+      },
+      /**
+       * return localParams
+       */
+      _onLocalParams (a) {
+        this.localParams = a
+      },
+      /**
+       * verification
+       */
+      _verification () {
+        // storage
+        this.$emit('on-params', {
+          targetJobName: this.targetJobName
+        })
+
+        return true
+      },
+      /**
+       * Processing code highlighting
+       */
+      _handlerEditor () {
+        // this._destroyEditor()
+
+        // editor
+        // editor = codemirror('code-sql-mirror', {
+        //   mode: 'sql',
+        //   readOnly: this.isDetails
+        // })
+        //
+        // this.keypress = () => {
+        //   if (!editor.getOption('readOnly')) {
+        //     editor.showHint({
+        //       completeSingle: false
+        //     })
+        //   }
+        // }
+        //
+        // // Monitor keyboard
+        // editor.on('keypress', this.keypress)
+        //
+        // editor.on('changes', () => {
+        //   this._cacheParams()
+        // })
+        //
+        // editor.setValue(this.sql)
+        //
+        // return editor
+      },
+      // _handlerJsonEditor () {
+      //   this._destroyJsonEditor()
+      //
+      //   // jsonEditor
+      //   jsonEditor = codemirror('code-json-mirror', {
+      //     mode: 'json',
+      //     readOnly: this.isDetails
+      //   })
+      //
+      //   this.keypress = () => {
+      //     if (!jsonEditor.getOption('readOnly')) {
+      //       jsonEditor.showHint({
+      //         completeSingle: false
+      //       })
+      //     }
+      //   }
+      //
+      //   // Monitor keyboard
+      //   jsonEditor.on('keypress', this.keypress)
+      //
+      //   jsonEditor.on('changes', () => {
+      //     // this._cacheParams()
+      //   })
+      //
+      //   jsonEditor.setValue(this.json)
+      //
+      //   return jsonEditor
+      // },
+      _cacheParams () {
+        this.$emit('on-cache-params', {
+          // dsType: this.dsType,
+          // dataSource: this.rtDatasource,
+          // dtType: this.dtType,
+          // dataTarget: this.rtDatatarget,
+          // sql: editor ? editor.getValue() : '',
+          targetJobName: this.targetJobName
+          // jobSpeedByte: this.jobSpeedByte * 1024,
+          // jobSpeedRecord: this.jobSpeedRecord,
+          // preStatements: this.preStatements,
+          // postStatements: this.postStatements,
+          // xms: +this.xms,
+          // xmx: +this.xmx
+        })
+      }
+      // _destroyEditor () {
+      //   if (editor) {
+      //     editor.toTextArea() // Uninstall
+      //     editor.off($('.code-sql-mirror'), 'keypress', this.keypress)
+      //     editor.off($('.code-sql-mirror'), 'changes', this.changes)
+      //   }
+      // },
+      // _destroyJsonEditor () {
+      //   if (jsonEditor) {
+      //     jsonEditor.toTextArea() // Uninstall
+      //     jsonEditor.off($('.code-json-mirror'), 'keypress', this.keypress)
+      //     jsonEditor.off($('.code-json-mirror'), 'changes', this.changes)
+      //   }
+      // }
+    },
+    created () {
+      let o = this.backfillItem
+
+      // Non-null objects represent backfill
+      if (!_.isEmpty(o)) {
+        // backfill
+        this.targetJobName = o.params.targetJobName || ''
+      }
+    },
+    mounted () {
+      // if (this.customConfig) {
+      //   setTimeout(() => {
+      //     this._handlerJsonEditor()
+      //   }, 200)
+      // } else {
+      //   setTimeout(() => {
+      //     this._handlerEditor()
+      //   }, 200)
+      // }
+    },
+    destroyed () {
+      // /**
+      //  * Destroy the editor instance
+      //  */
+      // if (editor) {
+      //   editor.toTextArea() // Uninstall
+      //   editor.off($('.code-sql-mirror'), 'keypress', this.keypress)
+      // }
+      // if (jsonEditor) {
+      //   jsonEditor.toTextArea() // Uninstall
+      //   jsonEditor.off($('.code-json-mirror'), 'keypress', this.keypress)
+      // }
+    },
+    watch: {
+      // Watch the cacheParams
+      cacheParams (val) {
+        this._cacheParams()
+      }
+    },
+    computed: {
+      cacheParams () {
+        return {
+          targetJobName: this.targetJobName
+        }
+      }
+    },
+    components: { mListBox }
+  }
+</script>
diff --git a/dolphinscheduler-ui/src/js/conf/home/pages/dag/img/toolbar_TIS.svg b/dolphinscheduler-ui/src/js/conf/home/pages/dag/img/toolbar_TIS.svg
new file mode 100644
index 0000000..3c1b452
--- /dev/null
+++ b/dolphinscheduler-ui/src/js/conf/home/pages/dag/img/toolbar_TIS.svg
@@ -0,0 +1,46 @@
+<!--
+  ~ 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.
+  ~
+-->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 204.85 127.79">
+  <defs>
+    <style>
+      .cls-1{fill:#f11d07;}.cls-2{fill:#ffde00;}.cls-2,.cls-3{stroke:#fff;stroke-miterlimit:10;stroke-width:2px;}.cls-3{fill:url(#xxxxxx);}
+    </style>
+    <linearGradient id="xxxxxx" x1="103.68" y1="127.03" x2="103.68" y2="1" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#fff33b"/>
+      <stop offset="0.1" stop-color="#fee72e"/>
+      <stop offset="0.28" stop-color="#fed51b"/>
+      <stop offset="0.47" stop-color="#fdca10"/>
+      <stop offset="0.67" stop-color="#fdc70c"/>
+      <stop offset="0.95" stop-color="#f3903f"/>
+    </linearGradient>
+  </defs>
+  <g >
+    <g >
+      <path class="cls-1"
+            d="M94.32,10.92a5.7,5.7,0,0,1,7.26,0L152,52.46,193.8,86.88A5.71,5.71,0,0,1,190.17,97H5.72A5.71,5.71,0,0,1,2.09,86.88L43.88,52.46Z"/>
+      <polygon class="cls-2"
+               points="34 36.23 87.53 32.38 76.92 50.38 64 50.38 34 97.45 13.7 97.45 40.46 51.3 26.16 51.3 34 36.23"/>
+      <polygon class="cls-3"
+               points="122.6 1 154.91 1 106.28 55.45 134.14 55.45 52.46 127.03 95.38 72.06 75.53 72.99 122.6 1"/>
+      <polygon class="cls-2"
+               points="202.75 31.93 157.21 36.99 134.6 75.3 160.44 75.3 155.91 83.14 129.52 83.14 121.22 97.45 165.13 97.45 183.59 61.09 158.13 61.09 163.67 51.3 189.05 51.3 202.75 31.93"/>
+    </g>
+  </g>
+</svg>
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 07803e8..51a46eb 100755
--- a/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
+++ b/dolphinscheduler-ui/src/js/module/i18n/locale/zh_CN.js
@@ -586,6 +586,8 @@ export default {
   'Spark Version': 'Spark版本',
   TargetDataBase: '目标库',
   TargetTable: '目标表',
+  TargetJobName: 'TIS目标任务名',
+  'Please enter TIS DataX job name': '请输入TIS DataX任务名',
   'Please enter the table of target': '请输入目标表名',
   'Please enter a Target Table(required)': '请输入目标表(必填)',
   SpeedByte: '限流(字节数)',
diff --git a/pom.xml b/pom.xml
index 522d9b1..c97650d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1093,6 +1093,7 @@
                         <include>**/alert/processor/AlertRequestProcessorTest.java</include>
                         <include>**/alert/runner/AlertSenderTest.java</include>
                         <include>**/alert/AlertServerTest.java</include>
+                        <include>**/plugin/task/tis/TISTaskTest.java</include>
                     </includes>
                     <!-- <skip>true</skip> -->
                 </configuration>
diff --git a/tools/dependencies/known-dependencies.txt b/tools/dependencies/known-dependencies.txt
index 8ff0c29..17dfe9d 100755
--- a/tools/dependencies/known-dependencies.txt
+++ b/tools/dependencies/known-dependencies.txt
@@ -16,7 +16,7 @@ api-util-1.0.0-M20.jar
 asm-3.1.jar
 asm-6.2.1.jar
 aspectjweaver-1.9.6.jar
-async-http-client-1.6.5.jar
+async-http-client-2.12.3.jar
 audience-annotations-0.5.0.jar
 avro-1.7.4.jar
 aws-java-sdk-1.7.4.jar
@@ -252,4 +252,4 @@ xml-apis-1.4.01.jar
 xmlbeans-3.1.0.jar
 xmlenc-0.52.jar
 xz-1.0.jar
-zookeeper-3.4.14.jar
+zookeeper-3.4.14.jar
\ No newline at end of file