Repository: tapdata/tapdata (language: python)

Issue:
Tap 2892 test bug



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 34a8bd50f8..372d3582dd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -259,8 +259,6 @@ jobs:
           mkdir -p output
           rsync -r --links --progress --password-file=/tmp/rsync.passwd rsync://root@192.168.1.184:873/data/temp/${{ needs.Set-Branch.outputs.TAG_NAME }}/ output/
           bash build/build.sh -o docker -t ${{ needs.Set-Branch.outputs.TAG_NAME }}
-          echo '${{ secrets.HARBOR_PASS }}' | docker login harbor.internal.tapdata.io --username=${{ secrets.HARBOR_USER }} --password-stdin
-          docker push harbor.internal.tapdata.io/tapdata/tapdata:${{ needs.Set-Branch.outputs.TAG_NAME }}
       - name: Print Image Info
         run: |
           echo '**Google Docker Image:**' >> $GITHUB_STEP_SUMMARY
diff --git a/build/.connectors_list b/build/.connectors_list
new file mode 100644
index 0000000000..87cabf958d
--- /dev/null
+++ b/build/.connectors_list
@@ -0,0 +1,14 @@
+bigquery-connector-v1.0-SNAPSHOT.jar
+clickhouse-connector-v1.0-SNAPSHOT.jar
+doris-connector-v1.0-SNAPSHOT.jar
+dummy-connector-v1.0-SNAPSHOT.jar
+elasticsearch-connector-v1.0-SNAPSHOT.jar
+kafka-connector-v1.0-SNAPSHOT.jar
+mariadb-connector-v1.0-SNAPSHOT.jar
+mongodb-connector-v1.0-SNAPSHOT.jar
+mongodb-lower-connector-v1.0-SNAPSHOT.jar
+mongodb-atlas-connector-v1.0-SNAPSHOT.jar
+mysql-connector-v1.0-SNAPSHOT.jar
+postgres-connector-v1.0-SNAPSHOT.jar
+redis-connector-v1.0-SNAPSHOT.jar
+tdengine-connector-v1.0-SNAPSHOT.jar
\ No newline at end of file
diff --git a/build/build.sh b/build/build.sh
index b1e49232b4..18c558b10c 100755
--- a/build/build.sh
+++ b/build/build.sh
@@ -29,6 +29,8 @@ OUTPUT_DIR="$PROJECT_ROOT_DIR/output"
 PACKAGE_COMPONENTS=""
 # output type (docker or tar)
 OUTPUT_TYPE=""
+# filter connectors list
+CONNECTORS_LIST=$(cat $SCRIPT_BASE_DIR/.connectors_list)
 
 while getopts 'c:l:u:p:t:o:' OPT; do
 	case "$OPT" in
@@ -85,7 +87,6 @@ done
 
 make_package_tapdata() {
   mkdir -p $OUTPUT_DIR/etc/init/ $OUTPUT_DIR/components/ $OUTPUT_DIR/lib/
-  mkdir -p $OUTPUT_DIR/bin/manager/ $OUTPUT_DIR/bin/iengine/
   cd $OUTPUT_DIR/
   cp $TAPDATA_DIR/manager/tm/target/classes/logback.xml etc/logback.xml
   cp $TAPDATA_DIR/manager/tm/target/classes/application.yml etc/application-tm.yml
@@ -93,12 +94,16 @@ make_package_tapdata() {
   cp $TAPDATA_DIR/manager/tm/target/tm-*-exec.jar components/tm.jar
   cp $TAPDATA_DIR/iengine/ie.jar components/tapdata-agent.jar
   cp $TAPDATA_DIR/tapdata-cli/target/pdk.jar lib/pdk-deploy.jar
-  # copy script files to start manager and iengine
-  cp $TAPDATA_DIR/manager/build/start.sh $OUTPUT_DIR/bin/manager/start.sh
-  cp $TAPDATA_DIR/iengine/build/start.sh $OUTPUT_DIR/bin/iengine/start.sh
 }
 
 make_package_connectors() {
+  # filter connectors
+  mv $CONNECTOR_DIR/connectors/dist $CONNECTOR_DIR/connectors/backup
+  mkdir -p $CONNECTOR_DIR/connectors/dist/
+  for item in $CONNECTORS_LIST; do
+    find $CONNECTOR_DIR/connectors/backup/ -type f -name "${item}" | xargs -I {} mv {} $CONNECTOR_DIR/connectors/dist/
+  done
+
   mkdir -p $OUTPUT_DIR/connectors/dist/
   cd $OUTPUT_DIR/
   tar cfz connectors/dist.tar.gz -C $CONNECTOR_DIR/connectors/ dist/
@@ -127,13 +132,19 @@ make_docker() {
   cd $OUTPUT_DIR/
   cp $TAPDATA_DIR/build/image/Dockerfile .
   cp $TAPDATA_DIR/build/image/docker-entrypoint.sh .
-  docker build -t harbor.internal.tapdata.io/tapdata/tapdata:$TAG_NAME .
+  cp -r $TAPDATA_DIR/build/image/bin .
+  cp -r $TAPDATA_DIR/build/image/supervisor .
+  # docker build -t harbor.internal.tapdata.io/tapdata/tapdata:$TAG_NAME .
+  docker buildx create --use --name multi-platform --platform linux/amd64,linux/arm64
+  docker buildx build --platform linux/arm64,linux/amd64 -t harbor.internal.tapdata.io/tapdata/tapdata:$TAG_NAME . --push
 }
 
 make_tar() {
   cd $OUTPUT_DIR/
   cp $TAPDATA_DIR/build/image/docker-entrypoint.sh ./start.sh
-  chmod +x start.sh
+  rsync -a $TAPDATA_DIR/build/image/bin/ ./
+  rsync -a $TAPDATA_DIR/build/image/supervisor ./
+  chmod +x start.sh stop.sh status.sh
   tar cfz tapdata-$TAG_NAME.tar.gz *
 }
 
diff --git a/build/image/Dockerfile b/build/image/Dockerfile
index eb97ee8eb1..8863cf6642 100644
--- a/build/image/Dockerfile
+++ b/build/image/Dockerfile
@@ -1,20 +1,19 @@
-FROM ghcr.io/tapdata/tapdata/tldp/runtime-ee:0.5
+FROM ghcr.io/tapdata/base:0.1
 
 ENV JAVA_TOOL_OPTIONS -Dfile.encoding=UTF8
 COPY etc/ /tapdata/apps/etc
 COPY connectors/dist.tar.gz /tapdata/apps/connectors/dist.tar.gz
 COPY lib/ /tapdata/apps/lib
 COPY components/ /tapdata/apps/components
-COPY docker-entrypoint.sh /tapdata/docker-entrypoint.sh
-COPY bin/ /tapdata/apps/bin
+COPY docker-entrypoint.sh /tapdata/apps/start.sh
+COPY bin/ /tapdata/apps
 
-# Add tini
-ENV TINI_VERSION v0.19.0
-ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
-RUN chmod +x /tini
+RUN apt install supervisor -y
+
+COPY supervisor/ /tapdata/apps/supervisor/
 
 ENV TAPDATA_WORK_DIR /tapdata/apps
 
 ENTRYPOINT ["/tini", "--"]
 
-CMD ["bash", "-c", "/tapdata/docker-entrypoint.sh"]
+CMD ["bash", "-c", "/tapdata/apps/start.sh"]
\ No newline at end of file
diff --git a/build/image/bin/start.sh b/build/image/bin/start.sh
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/build/image/bin/status.sh b/build/image/bin/status.sh
index e69de29bb2..a42d1cb2a4 100644
--- a/build/image/bin/status.sh
+++ b/build/image/bin/status.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+SCRIPT_BASE_DIR=$(dirname "$0")
+cd $SCRIPT_BASE_DIR
+
+print_message() {
+    # Print message with color and bold
+    local message="$1"
+    local color="$2"
+    local is_bold="$3"
+
+    local reset="\e[0m"
+    local color_code=""
+    local bold_code=""
+
+    # Set color code
+    case $color in
+    "red") color_code="\e[31m" ;;
+    "green") color_code="\e[32m" ;;
+    "yellow") color_code="\e[33m" ;;
+    "blue") color_code="\e[34m" ;;
+    "magenta") color_code="\e[35m" ;;
+    "cyan") color_code="\e[36m" ;;
+    esac
+
+    # Set bold code
+    if [[ $is_bold == true ]]; then
+    bold_code="\e[1m"
+    fi
+
+    # Print message with color and bold
+    echo -e "${bold_code}${color_code}${message}${reset}"
+}
+
+service_status() {
+    local service_name="$1"
+    if [[ -f .$service_name.pid ]]; then
+        ps -p $(cat .$service_name.pid) > /dev/null && print_message "$service_name running, pid $(cat .$service_name.pid)" "green" false
+        if [[ $? -ne 0 ]]; then
+            print_message "$service_name not running." "yellow" false
+        fi
+    else
+        echo "$service_name not running, or no .$service_name.pid file found."
+    fi
+}
+
+help() {
+    print_message "Usage: status.sh" "green" true
+    print_message "Check the status of Manager, IEngine and Mongodb services." "cyan" false
+}
+
+if [[ $1 == "help" || $1 == "--help" || $1 == "-h" ]]; then
+    help
+    exit 0
+fi
+
+if [[ `cat .launch_supervisor` == 'true' ]]; then
+    supervisorctl -c supervisor/supervisord.conf status
+else
+    service_status "manager"
+    service_status "iengine"
+    service_status "mongodb"
+fi
\ No newline at end of file
diff --git a/build/image/bin/stop.sh b/build/image/bin/stop.sh
index e69de29bb2..067a51bdd2 100644
--- a/build/image/bin/stop.sh
+++ b/build/image/bin/stop.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+
+SCRIPT_BASE_DIR=$(dirname "$0")
+cd $SCRIPT_BASE_DIR
+
+components=${1:-"all"}
+
+print_message() {
+    # Print message with color and bold
+    local message="$1"
+    local color="$2"
+    local is_bold="$3"
+
+    local reset="\e[0m"
+    local color_code=""
+    local bold_code=""
+
+    # Set color code
+    case $color in
+    "red") color_code="\e[31m" ;;
+    "green") color_code="\e[32m" ;;
+    "yellow") color_code="\e[33m" ;;
+    "blue") color_code="\e[34m" ;;
+    "magenta") color_code="\e[35m" ;;
+    "cyan") color_code="\e[36m" ;;
+    esac
+
+    # Set bold code
+    if [[ $is_bold == true ]]; then
+    bold_code="\e[1m"
+    fi
+
+    # Print message with color and bold
+    echo -e "${bold_code}${color_code}${message}${reset}"
+}
+
+help() {
+    print_message "Usage: stop.sh [all|manager|iengine|mongodb]" "green" true
+    print_message "Stop Manager and IEngine services." "cyan" false
+    print_message "  [all]: stop manager and service" "cyan" false
+    print_message "  manager: stop Manager service" "cyan" false
+    print_message "  iengine: stop IEngine service" "cyan" false
+    print_message "  mongodb: stop MongoDB service" "cyan" false
+}
+
+print_message "Stopping Manager and IEngine..." "green" true
+
+if [[ $components == "help" || $components == "--help" || $components == "-h" ]]; then
+    help
+    exit 0
+fi
+
+stop_service() {
+    local service_name="$1"
+    if [[ -f .$service_name.pid ]]; then
+        ps -p $(cat .$service_name.pid) > /dev/null && kill -9 $(cat .$service_name.pid)
+        print_message "$service_name stopped." "green" false
+        rm -rf .$service_name.pid
+    else
+        print_message "$service_name not running, or no .$service_name.pid file found." "yellow" false
+    fi
+}
+
+if [[ `cat .launch_supervisor` == "false" ]]; then
+
+    if [[ $components == "all" ]]; then
+        # stop manager
+        stop_service "manager"
+        # stop iengine
+        stop_service "iengine"
+    elif [[ $components == "manager" ]]; then
+        # stop manager
+        stop_service "manager"
+    elif [[ $components == "iengine" ]]; then
+        # stop iengine
+        stop_service "iengine"
+    elif [[ $components == "mongodb" ]]; then
+        # stop mongodb
+        stop_service "mongodb"
+    fi
+else
+    if [[ $components == "all" ]]; then
+        # stop manager and iengine
+        supervisorctl -c supervisor/supervisord.conf stop iengine
+        supervisorctl -c supervisor/supervisord.conf stop manager
+    elif [[ $components == "manager" ]]; then
+        # stop manager
+        supervisorctl -c supervisor/supervisord.conf stop manager
+    elif [[ $components == "iengine" ]]; then
+        # stop iengine
+        supervisorctl -c supervisor/supervisord.conf stop iengine
+    elif [[ $components == "supervisord" ]]; then
+        # stop supervisord
+        supervisorctl -c supervisor/supervisord.conf shutdown
+    elif [[ $components == "mongodb" ]]; then
+        # stop mongodb
+        supervisorctl -c supervisor/supervisord.conf stop mongodb
+    fi
+fi
diff --git a/build/image/docker-entrypoint.sh b/build/image/docker-entrypoint.sh
index 45d040f25a..7163fa865d 100755
--- a/build/image/docker-entrypoint.sh
+++ b/build/image/docker-entrypoint.sh
@@ -1,5 +1,7 @@
 #! /bin/bash
 
+SCRIPT_BASE_DIR=$(dirname "$0")
+
 print_message() {
   # Print message with color and bold
   local message="$1"
@@ -29,21 +31,148 @@ print_message() {
   echo -e "${bold_code}${color_code}${message}${reset}"
 }
 
+install_supervisor() {
+    print_message "Supervisor Not Found, it's better to install supervisor first" "yellow" false
+    # install by pip/pip3
+    print_message "Try to installing Supervisor by pip/pip3" "yellow" false
+    which pip > /dev/null
+    if [[ $? -ne 0 ]]; then
+        which pip3 > /dev/null
+        if [[ $? -ne 0 ]]; then
+            print_message "pip or pip3 Not Found" "yellow" false
+            export LAUNCH_SUPERVISOR=false
+        else
+            pip3 install supervisor
+        fi
+    else
+        pip install supervisor
+    fi
+    if [[ $? -eq 0 ]]; then
+        which supervisord > /dev/null
+        if [[ $? -eq 0 ]]; then
+            print_message "Install Supervisor Success" "green" false
+            export LAUNCH_SUPERVISOR=true
+            return 0
+        fi
+    fi
+    print_message "Install Supervisor by pip/pip3 Failed" "yellow" false
+    print_message "Try to install by system package manager" "yellow" false
+    # Linux or Macos
+    if [[ -f /etc/redhat-release ]]; then
+        yum install -y supervisor
+    elif [[ -f /etc/lsb-release ]]; then
+        apt-get install -y supervisor
+    elif [[ $(uname) == "Darwin" ]]; then
+        brew install supervisor
+    else
+        print_message "Not Support OS" "yellow" false
+        export LAUNCH_SUPERVISOR=false
+        return 1
+    fi
+    if [[ $? -ne 0 ]]; then
+        print_message "Install Supervisor Failed" "yellow" false
+        export LAUNCH_SUPERVISOR=false
+        return 1
+    else
+        which supervisord > /dev/null
+        if [[ $? -ne 0 ]]; then
+            print_message "Install Supervisor Failed" "yellow" false
+            export LAUNCH_SUPERVISOR=false
+            return 1
+        fi
+        export LAUNCH_SUPERVISOR=true
+        print_message "Install Supervisor Success" "green" false
+        return 0
+    fi
+}
+
+start_supervisord() {
+    # install supervisor first
+    which supervisord > /dev/null
+    if [[ $? -ne 0 ]]; then
+        install_supervisor
+    fi
+    if [[ $LAUNCH_SUPERVISOR == "false" ]]; then
+        print_message "Skip Start Supervisor" "yellow" false
+        echo 'false' > .launch_supervisor
+        return 0
+    else
+        echo 'true' > .launch_supervisor
+    fi
+
+    # if supervisord is already running, skip start
+    ps -ef | grep -v grep | grep supervisord > /dev/null
+    if [[ $? -eq 0 ]]; then
+        print_message "Supervisor is Already Running" "green" false
+        return 0
+    fi
+
+    # create paths
+    mkdir -p supervisor/logs/ supervisor/run/ supervisor/conf.d/
+    touch supervisor/logs/supervisord.log # create supervisord log file
+    touch supervisor/logs/manager.log supervisor/logs/manager.err # create manager log file
+    touch supervisor/logs/iengine.log supervisor/logs/iengine.err # create iengine log file
+    touch supervisor/logs/mongodb.log supervisor/logs/mongodb.err # create mongodb log file
+    # start supervisor
+    supervisord -c supervisor/supervisord.conf
+    # check supervisor start
+    ps -ef | grep -v grep | grep supervisord > /dev/null
+    if [[ $? -ne 0 ]]; then
+        export LAUNCH_SUPERVISOR=false
+        print_message "Supervisor Start Failed" "red" false
+        return 1
+    fi
+}
+
 get_env() {
+
     # Set default env
+    export MONGO_URI=${MONGO_URI:-"mongodb://127.0.0.1:27017/tapdata"}  # mongodb uri
+    export dbMem=${dbMem:-"1"} # db memory
+    export engineMem=${engineMem:-"1G"} # engine memory
+    export managerMem=${managerMem:-"1G"} # manager memory
+    export tm_port=${tm_port:-"3030"} # manager port
+    export ACCESS_CODE=${ACCESS_CODE:-"3324cfdf-7d3e-4792-bd32-571638d4562f"}  # access code
+    export LAUNCH_SUPERVISOR=${LAUNCH_SUPERVISOR:-"false"}  # launch supervisor
+    export REPORT_DATA_OSS=true # report data oss
+
+    # Export env
+    export MONGO_URI dbMem engineMem managerMem tm_port ACCESS_CODE LAUNCH_SUPERVISOR REPORT_DATA_OSS
 
-    MONGO_URI=${MONGO_URI}  # mongodb uri
-    ACCESS_CODE=${ACCESS_CODE:-"3324cfdf-7d3e-4792-bd32-571638d4562f"}  # access code
+cat <<EOF
+MONGO_URI         : $MONGO_URI
+dbMem             : $dbMem
+engineMem         : $engineMem
+managerMem        : $managerMem
+tm_port           : $tm_port
+ACCESS_CODE       : $ACCESS_CODE
+LAUNCH_SUPERVISOR : $LAUNCH_SUPERVISOR
+REPORT_DATA_OSS   : $REPORT_DATA_OSS
+EOF
 
-    print_message "MONGO_URI  :   $MONGO_URI" "blue" false
-    print_message "ACCESS_CODE:   $ACCESS_CODE" "blue" false
+    if [[ $LAUNCH_SUPERVISOR == "true" ]]; then
+        start_supervisord
+        if [[ $? -ne 0 ]]; then
+            echo 'false' > .launch_supervisor
+        else
+            echo 'true' > .launch_supervisor
+        fi
+    else
+        echo 'false' > .launch_supervisor
+    fi
 }
 
 start_mongo() {
     # start a mongodb replSet
 
     mkdir -p /tapdata/data/logs /tapdata/data/db/
-    mongod --dbpath=/tapdata/data/db/ --replSet=rs0 --wiredTigerCacheSizeGB=1 --bind_ip_all --logpath=/tapdata/data/logs/mongod.log --fork
+    if [[ $LAUNCH_SUPERVISOR == "true" ]]; then
+        supervisorctl -c supervisor/supervisord.conf start mongodb
+    else
+        mongod --dbpath=/tapdata/data/db/ --wiredTigerCacheSizeGB=$dbMem --replSet=rs0 --bind_ip_all --logpath=/tapdata/data/logs/mongod.log --fork
+        sleep 2
+        pgrep mongod > .mongodb.pid
+    fi
     while [[ 1 ]]; do
         mongo --quiet --eval "db" &> /dev/null
         if [[ $? -eq 0 ]]; then
@@ -76,7 +205,7 @@ wait_tm_start() {
         local seconds_left=$((timeout - SECONDS))
         printf "\r* Wait Starting, Left %02d / 300 Seconds..." "$seconds_left"
         sleep 1
-        curl --fail "http://localhost:3000" &> /dev/null
+        curl --fail "http://localhost:$tm_port" &> /dev/null
         if [[ $? -ne 0 ]]; then
             continue
         else
@@ -106,7 +235,7 @@ exec_with_log() {
 
 _register_connectors() {
     print_message "* Register Connector: $i" "blue" false
-    java -jar $dir/lib/pdk-deploy.jar register -a $ACCESS_CODE -f GA -t http://localhost:3000 $dir/connectors/dist 2>&1
+    java -jar $dir/lib/pdk-deploy.jar register -a $ACCESS_CODE -t http://localhost:$tm_port $dir/connectors/dist 2>&1
     if [[ $? -ne 0 ]]; then
         print_message "* Register Connector: $i Failed" "red" false
         exit 1
@@ -124,37 +253,54 @@ register_connectors() {
     _register_connectors
 }
 
+start_tm() {
+    if [[ $LAUNCH_SUPERVISOR == "true" ]]; then
+        supervisorctl -c supervisor/supervisord.conf start manager
+    else
+        mkdir -p logs/ && touch logs/nohup.out
+        nohup java -Xmx$managerMem -jar -Dserver.port=$tm_port -server components/tm.jar --spring.config.additional-location=file:etc/ --logging.config=file:etc/logback.xml --spring.data.mongodb.default.uri=$MONGO_URI --spring.data.mongodb.obs.uri=$MONGO_URI --spring.data.mongodb.log.uri=$MONGO_URI &> logs/nohup.out &
+        echo $! > .manager.pid
+    fi
+}
+
+start_iengine() {
+    if [[ $LAUNCH_SUPERVISOR == "true" ]]; then
+        supervisorctl -c supervisor/supervisord.conf start iengine
+    else
+        mkdir -p logs/iengine/ && touch logs/iengine/tapdata-agent.jar.log
+        export app_type="DAAS"
+        export backend_url="http://127.0.0.1:$tm_port/api/"
+        export TAPDATA_MONGO_URI=$MONGO_URI
+        nohup java -Xmx$engineMem -jar components/tapdata-agent.jar &> logs/iengine/tapdata-agent.jar.log &
+        echo $! > .iengine.pid
+    fi
+}
+
 start_server() {
     # 1. start manager server
     # 2. register all connectors
     # 3. start iengine server
     #
-    # 1. start manager server
-    if [[ -d /tapdata/apps/manager ]]; then
-      mkdir -p /tapdata/apps/logs /tapdata/apps/logs/iengine
-      exec_with_log "cd /tapdata/apps/ && bash bin/manager/start.sh $MONGO_URI" "Start Manager Server" "blue" || return 1
-    else
-      mkdir -p ./logs ./logs/iengine
-      exec_with_log "bash bin/manager/start.sh $MONGO_URI" "Start Manager Server" "blue" || return 1
+    if [[ $LAUNCH_SUPERVISOR == "true" ]]; then
+        supervisorctl -c supervisor/supervisord.conf reread && supervisorctl -c supervisor/supervisord.conf update
     fi
+    # 1. start manager server
+    print_message "Start Manager Server" "blue" false
+    start_tm
     # 2. register all connectors
     # waiting for manager server start
     exec_with_log wait_tm_start "Waiting for Manager Server Start" "blue" || return 1
     # Register all connectors
     exec_with_log register_connectors "Register all connectors" "blue" || return 1
     # 3. start iengine server
-    if [[ -d /tapdata/apps/iengine ]]; then
-      exec_with_log "cd /tapdata/apps/ && bash bin/iengine/start.sh" "Start Iengine Server" "blue" || return 1
-    else
-      exec_with_log "bash bin/iengine/start.sh" "Start Iengine Server" "blue" || return 1
-    fi
+    start_iengine
 }
 
 unzip_files() {
-    if [[ -d /tapdata/apps/connectors/ ]]; then
+    if [[ -f /tapdata/apps/connectors/dist.tar.gz ]]; then
       tar xzf /tapdata/apps/connectors/dist.tar.gz -C /tapdata/apps/connectors
       rm -rf /tapdata/apps/connectors/dist.tar.gz
-    elif [[ -d ./connectors ]]; then
+    elif [[ -f ./connectors/dist.tar.gz ]]; then
       tar xzf ./connectors/dist.tar.gz -C connectors
       rm -rf ./connectors/dist.tar.gz
     fi
@@ -170,6 +316,7 @@ cat << "EOF"
 EOF
 
 _main() {
+    cd $SCRIPT_BASE_DIR
     # 1. get env settings
     # 2. unzip connectors
     # 3. start mongo if $MONGO_URI is not set
@@ -184,18 +331,18 @@ _main() {
     print_message ">>> Unzip Connectors [START]" "green" true
     unzip_files
     print_message "<<< Unzip Connectors [SUCCESS]" "green" true
-    # 3. start mongo if $MONGO_URI is not set
-    print_message ">>> Start Mongo [START]" "green" true
-    if [[ -z $MONGO_URI ]]; then
+    # 3. start mongo if $MONGO_URI is not set and in docker
+    ps -ef | grep -v grep | grep mongo > /dev/null
+    if [[ -f /.dockerenv && $? -ne 0 ]]; then
+        print_message ">>> Start Mongo [START]" "green" true
         start_mongo
         ps -ef | grep -v grep | grep mongo > /dev/null
         if [[ $? -ne 0 ]]; then
             print_message "<<< Mongodb is Not Running" "red" true
             exit 1
         fi
-        MONGO_URI="mongodb://127.0.0.1:27017/tapdata"
+        print_message "<<< Mongodb is Already Running" "green" true
     fi
-    print_message "<<< Mongodb is Already Running" "green" true
     # 4. start tm server, register connectors and start iengine
     print_message ">>> Start Server [START]" "green" true
     start_server
@@ -205,10 +352,12 @@ _main() {
     fi
     print_message "<<< Start Server [SUCCESS]" "green" true
     # Print Visit Url
-    print_message "All Done, Please Visit http://localhost:3000" "green" true
+    print_message "All Done, Please Visit http://localhost:$tm_port" "green" true
 
     # 5. hold the container
-    sleep infinity
+    if [[ -f /.dockerenv ]]; then
+        sleep infinity
+    fi
 }
 
 _main "$@"
diff --git a/build/image/supervisor/conf.d/iengine.conf b/build/image/supervisor/conf.d/iengine.conf
new file mode 100644
index 0000000000..030a42438c
--- /dev/null
+++ b/build/image/supervisor/conf.d/iengine.conf
@@ -0,0 +1,12 @@
+[program:iengine]
+command = java -Xmx%(ENV_engineMem)s -jar components/tapdata-agent.jar
+environment = app_type="DAAS", tm_port=%(ENV_tm_port)s, backend_url="http://127.0.0.1:%(ENV_tm_port)s/api/", MONGO_URI='%(ENV_MONGO_URI)s', TAPDATA_MONGO_URI='%(ENV_MONGO_URI)s', engineMem=%(ENV_engineMem)s
+directory = %(here)s/../../
+autostart = false
+autoresart = false
+stdout_logfile_maxbytes = 50MB
+stdout_logfile_backups = 10
+stdout_logfile = %(here)s/../logs/iengine.log
+stderr_logfile = %(here)s/../logs/iengine.err
+stderr_logfile_maxbytes = 50MB
+stderr_logfile_backups = 10
diff --git a/build/image/supervisor/conf.d/manager.conf b/build/image/supervisor/conf.d/manager.conf
new file mode 100644
index 0000000000..2f9944b1ab
--- /dev/null
+++ b/build/image/supervisor/conf.d/manager.conf
@@ -0,0 +1,11 @@
+[program:manager]
+command = java -Xmx%(ENV_managerMem)s -jar -Dserver.port=%(ENV_tm_port)s -server components/tm.jar --spring.config.additional-location=file:etc/ --logging.config=file:etc/logback.xml --spring.data.mongodb.default.uri=%(ENV_MONGO_URI)s --spring.data.mongodb.obs.uri=%(ENV_MONGO_URI)s --spring.data.mongodb.log.uri=%(ENV_MONGO_URI)s
+directory = %(here)s/../../
+autostart = false
+autoresart = false
+stdout_logfile_maxbytes = 50MB
+stdout_logfile_backups = 10
+stdout_logfile = %(here)s/../logs/manager.log
+stderr_logfile = %(here)s/../logs/manager.err
+stderr_logfile_maxbytes = 50MB
+stderr_logfile_backups = 10
diff --git a/build/image/supervisor/conf.d/mongodb.conf b/build/image/supervisor/conf.d/mongodb.conf
new file mode 100644
index 0000000000..2d42214791
--- /dev/null
+++ b/build/image/supervisor/conf.d/mongodb.conf
@@ -0,0 +1,10 @@
+[program:mongodb]
+command = mongod --dbpath=/tapdata/data/db/ --replSet=rs0 --wiredTigerCacheSizeGB=%(ENV_dbMem)s --bind_ip_all --logpath=/tapdata/data/logs/mongod.log
+autostart = false
+autoresart = false
+stdout_logfile_maxbytes = 50MB
+stdout_logfile_backups = 10
+stdout_logfile = %(here)s/../logs/mongodb.log
+stderr_logfile = %(here)s/../logs/mongodb.err
+stderr_logfile_maxbytes = 50MB
+stderr_logfile_backups = 10
diff --git a/build/image/supervisor/supervisord.conf b/build/image/supervisor/supervisord.conf
new file mode 100644
index 0000000000..f735b639bb
--- /dev/null
+++ b/build/image/supervisor/supervisord.conf
@@ -0,0 +1,19 @@
+[unix_http_server]
+file=%(here)s/run/supervisor.sock
+chmod=0700
+
+[supervisord]
+logfile=%(here)s/logs/supervisord.log
+pidfile=%(here)s/run/supervisord.pid
+logfile_maxbytes=50MB
+logfile_backups=10
+loglevel=info
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[supervisorctl]
+serverurl=unix://%(here)s/run/supervisor.sock
+
+[include]
+files = %(here)s/conf.d/*.conf
diff --git a/building.md b/building.md
index d9ec45f1ba..6560bacbec 100644
--- a/building.md
+++ b/building.md
@@ -94,6 +94,7 @@ If everything is ok, you will get:
 
 If you want to start the complete Tapdata service on your machine, please follow these steps:
 ### Instructions for Starting the Complete Tapdata Service on Your Machine
+
 1. **Deploy a MongoDB Replica Set and Record the Access URI**:
     - Set up a MongoDB replica set and make a note of the URI that you will use to access it.
 
diff --git a/iengine/build/start.sh b/iengine/build/start.sh
index a40fc82822..6cc4259f7c 100644
--- a/iengine/build/start.sh
+++ b/iengine/build/start.sh
@@ -2,10 +2,9 @@ ulimit -c unlimited
 
 sbin_file="tapdata-agent.jar"
 export app_type=DAAS
-export backend_url=http://127.0.0.1:3000/api/
-if [[ -z $MONGO_URI ]]; then
-  MONGO_URI="mongodb://127.0.0.1:27017/tapdata"
-fi
+export tm_port=${tm_port:-3030}
+export backend_url=http://127.0.0.1:$tm_port/api/
+export MONGO_URI=${MONGO_URI:-"mongodb://127.0.0.1:27017/tapdata"}
 export TAPDATA_MONGO_URI=$MONGO_URI
 
 mkdir -p logs/iengine && touch logs/iengine/$sbin_file.log
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
index a08c575ff0..10fbc1382c 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNode.java
@@ -59,6 +59,7 @@
 import io.tapdata.flow.engine.V2.util.SyncTypeEnum;
 import io.tapdata.flow.engine.V2.util.TapCodecUtil;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
+import io.tapdata.flow.engine.util.TaskDtoUtil;
 import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.observable.logging.ObsLoggerFactory;
 import io.tapdata.pdk.core.utils.CommonUtils;
@@ -564,6 +565,9 @@ public synchronized TapCodeException errorHandle(Throwable throwable) {
 	}
 
 	public synchronized TapCodeException errorHandle(Throwable throwable, String errorMessage) {
+		if (null == clientMongoOperator) {
+			clientMongoOperator = initClientMongoOperator();
+		}
 		TapCodeException currentEx = wrapTapCodeException(throwable);
 		TaskDto taskDto = processorBaseContext.getTaskDto();
 		handleWhenTestRun(taskDto, currentEx);
@@ -578,13 +582,21 @@ public synchronized TapCodeException errorHandle(Throwable throwable, String err
 				message = currentEx.getMessage();
 			}
 			String finalMessage = StringUtils.isBlank(message) ? errorMessage:message;
-			errorEvent.set(new ErrorEvent(finalMessage,currentEx.getCode(),Log4jUtil.getStackString(currentEx)));
+			ErrorEvent event = new ErrorEvent(finalMessage, currentEx.getCode(), Log4jUtil.getStackString(currentEx));
+			errorEvent.set(event);
 			if(whetherToSkip(taskDto.getErrorEvents(), errorEvent.get(), skipError)){
-				obsLogger.info("The current exception match the skip exception strategy, message: "+errorMessage);
+				obsLogger.info("Exception skipping - The current exception match the skip exception strategy, message: {}", finalMessage);
 				isSkip.set(true);
+			} else {
+				obsLogger.info("Exception skipping - The current exception does not match the skip exception strategy, message: {}", finalMessage);
 			}
 		},err->obsLogger.warn("Skip error failed:{}",err.getMessage()));
-		if(isSkip.get())return null;
+		if (isSkip.get()) {
+			List<ErrorEvent> errorEvents = taskDto.getErrorEvents();
+			errorEvents.remove(errorEvent.get());
+			TaskDtoUtil.updateErrorEvent(clientMongoOperator, taskDto.getErrorEvents(), taskDto.getId(), obsLogger, "Update task'error events failed: {}");
+			return null;
+		}
 		try {
 			if (globalErrorIsNull()) {
 				this.error = currentEx;
@@ -858,11 +870,7 @@ protected void saveErrorEvent(List<ErrorEvent> errorEvents,ErrorEvent event,Obje
 		if (errorEvents.size() > ERROR_EVENT_LIMIT) {
 			return;
 		}
-		try {
-			clientMongoOperator.insertOne(errorEvents,ConnectorConstant.TASK_COLLECTION + "/errorEvents/" + taskId);
-		}catch (Exception e){
-			obsLogger.warn("Save error event failed:{}",e.getMessage());
-		}
+		TaskDtoUtil.updateErrorEvent(clientMongoOperator, errorEvents, taskId, obsLogger, "Save error event failed: {}");
 	}
 
 	public static class TapValueTransform {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
index 170c8d22e0..2cfad010a3 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNode.java
@@ -69,6 +69,8 @@
 public abstract class HazelcastPdkBaseNode extends HazelcastDataBaseNode {
 	public static final int DEFAULT_READ_BATCH_SIZE = 2000;
 	public static final int DEFAULT_INCREASE_BATCH_SIZE = 1;
+	public static final String OLD_VERSION_TIMEZONE = "oldVersionTimezone";
+	public static final String OLD_VERSION_TIME_ZONE_PROP_KEY = "OLD_VERSION_TIME_ZONE";
 	private final Logger logger = LogManager.getLogger(HazelcastPdkBaseNode.class);
 	private static final String TAG = HazelcastPdkBaseNode.class.getSimpleName();
 	protected static final String COMPLETED_INITIAL_SYNC_KEY_PREFIX = "COMPLETED-INITIAL-SYNC-";
@@ -79,6 +81,8 @@ public abstract class HazelcastPdkBaseNode extends HazelcastDataBaseNode {
 
 	protected Integer readBatchSize;
 	protected Integer increaseReadSize;
+
+	public static final String FUNCTION_RETRY_STATUS = "functionRetryStatus";
 	private static final String DOUBLE_ACTIVE = "doubleActive";
 	protected TapRecordSkipDetector skipDetector;
 	private PdkStateMap pdkStateMap;
@@ -150,16 +154,20 @@ public PDKMethodInvoker createPdkMethodInvoker() {
 	public void signFunctionRetry(String taskId) {
 		CommonUtils.ignoreAnyError(() -> {
 			Update update = new Update();
-			update.set("functionRetryStatus", TaskDto.RETRY_STATUS_RUNNING);
+			update.set(FUNCTION_RETRY_STATUS, TaskDto.RETRY_STATUS_RUNNING);
 			update.set("taskRetryStartTime", System.currentTimeMillis());
-			clientMongoOperator.update(Query.query(Criteria.where("_id").is(new ObjectId(taskId))), update, ConnectorConstant.TASK_COLLECTION);
+			Criteria functionRetryStatusExists = Criteria.where(FUNCTION_RETRY_STATUS).exists(false);
+			Criteria functionRetryStatusNone = Criteria.where(FUNCTION_RETRY_STATUS).is(TaskDto.RETRY_STATUS_NONE);
+			Query query = Query.query(Criteria.where("_id").is(new ObjectId(taskId))
+					.orOperator(functionRetryStatusExists, functionRetryStatusNone));
+			clientMongoOperator.update(query, update, ConnectorConstant.TASK_COLLECTION);
 		}, "Faild to sign function retry status");
 	}
 
 	public void cleanFuctionRetry(String taskId) {
 		CommonUtils.ignoreAnyError(() -> {
 			Update update = new Update();
-			update.set("functionRetryStatus", TaskDto.RETRY_STATUS_NONE);
+			update.set(FUNCTION_RETRY_STATUS, TaskDto.RETRY_STATUS_NONE);
 			update.set("taskRetryStartTime", 0);
 			clientMongoOperator.update(Query.query(Criteria.where("_id").is(new ObjectId(taskId))), update, ConnectorConstant.TASK_COLLECTION);
 		}, "Faild to clean function retry status");
@@ -189,7 +197,7 @@ protected void createPdkConnectorNode(DataProcessorContext dataProcessorContext,
 		Node<?> node = dataProcessorContext.getNode();
 		ConnectorCapabilities connectorCapabilities = ConnectorCapabilities.create();
 		initDmlPolicy(node, connectorCapabilities);
-		Map<String, Object> nodeConfig = generateNodeConfig(node, taskDto.getDoubleActive());
+		Map<String, Object> nodeConfig = generateNodeConfig(node, taskDto);
 		this.associateId = ConnectorNodeService.getInstance().putConnectorNode(
 				PdkUtil.createNode(taskDto.getId().toHexString(),
 						databaseType,
@@ -210,7 +218,7 @@ protected void createPdkConnectorNode(DataProcessorContext dataProcessorContext,
 		AspectUtils.executeAspect(PDKNodeInitAspect.class, () -> new PDKNodeInitAspect().dataProcessorContext((DataProcessorContext) processorBaseContext));
 	}
 
-	protected Map<String, Object> generateNodeConfig(Node<?> node, Boolean doubleActive) {
+	protected Map<String, Object> generateNodeConfig(Node<?> node, TaskDto taskDto) {
 		Map<String, Object> nodeConfig = null;
 		if (node instanceof TableNode) {
 			nodeConfig = ((TableNode) node).getNodeConfig();
@@ -222,7 +230,10 @@ protected Map<String, Object> generateNodeConfig(Node<?> node, Boolean doubleAct
 		if (null == nodeConfig) {
 			nodeConfig = new HashMap<>();
 		}
-		nodeConfig.put(DOUBLE_ACTIVE, doubleActive);
+		nodeConfig.put(DOUBLE_ACTIVE, taskDto.getDoubleActive());
+		Boolean oldVersionTimezone = taskDto.getOldVersionTimezone();
+		oldVersionTimezone = CommonUtils.getPropertyBool(OLD_VERSION_TIME_ZONE_PROP_KEY, oldVersionTimezone);
+		nodeConfig.put(OLD_VERSION_TIMEZONE, oldVersionTimezone);
 		return nodeConfig;
 	}
 
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
index a6c5a42ab9..886d6d6588 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkBaseNode.java
@@ -1257,6 +1257,7 @@ protected Long doBatchCountFunction(BatchCountFunction batchCountFunction, TapTa
 
 	protected AutoCloseable doAsyncTableCount(BatchCountFunction batchCountFunction, String tableName) {
 		if (null == batchCountFunction) return () -> {
+			obsLogger.warn("Node can not support batchCountFunction");
 		};
 
 		CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
index 2b7d4b3aa8..2672a1afda 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNode.java
@@ -819,6 +819,7 @@ protected StreamReadConsumer generateStreamReadConsumer(ConnectorNode connectorN
 				if (streamReadFuncAspect != null)
 					executeAspect(streamReadFuncAspect.state(StreamReadFuncAspect.STATE_STREAM_STARTED).streamStartedTime(System.currentTimeMillis()));
 				sendCdcStartedEvent();
+				PDKInvocationMonitor.invokerRetrySetter(pdkMethodInvoker);
 				obsLogger.info("Connector {} incremental start succeed, tables: {}, data change syncing", connectorNode.getTapNodeInfo().getTapNodeSpecification().getName(), streamReadFuncAspect != null ? streamReadFuncAspect.getTables() : null);
 			}
 		});
@@ -1293,4 +1294,46 @@ protected void excludeRemoveTable(List<String> tableNames) {
 		tableNames.removeAll(removeTables);
 	}
 
+	@Override
+	protected Long doBatchCountFunction(BatchCountFunction batchCountFunction, TapTable table) {
+		AtomicReference<Long> counts = new AtomicReference<>();
+
+		executeDataFuncAspect(
+				TableCountFuncAspect.class,
+				() -> new TableCountFuncAspect().dataProcessorContext(getDataProcessorContext()).start(),
+				tableCountFuncAspect -> PDKInvocationMonitor.invoke(getConnectorNode(), PDKMethod.SOURCE_BATCH_COUNT, createPdkMethodInvoker().runnable(() -> {
+					try {
+						Node<?> node = getNode();
+						if (node instanceof TableNode) {
+							TableNode tableNode = (TableNode) dataProcessorContext.getNode();
+							if (isTableFilter(tableNode) || isPollingCDC(tableNode)) {
+								ConnectorNode connectorNode = getConnectorNode();
+								ConnectorFunctions connectorFunctions = connectorNode.getConnectorFunctions();
+								CountByPartitionFilterFunction countByPartitionFilterFunction = connectorFunctions.getCountByPartitionFilterFunction();
+								if (countByPartitionFilterFunction == null) {
+									counts.set(batchCountFunction.count(getConnectorNode().getConnectorContext(), table));
+								}else {
+									TapAdvanceFilter tapAdvanceFilter = batchFilterRead();
+									counts.set(countByPartitionFilterFunction.countByPartitionFilter(connectorNode.getConnectorContext(), table, tapAdvanceFilter));
+								}
+							} else {
+								counts.set(batchCountFunction.count(getConnectorNode().getConnectorContext(), table));
+							}
+						} else {
+							counts.set(batchCountFunction.count(getConnectorNode().getConnectorContext(), table));
+						}
+
+
+						if (null != tableCountFuncAspect) {
+							AspectUtils.accept(tableCountFuncAspect.state(TableCountFuncAspect.STATE_COUNTING).getTableCountConsumerList(), table.getName(), counts.get());
+						}
+					} catch (Throwable e) {
+						throw new NodeException("Query table '" + table.getName() + "'  count failed: " + e.getMessage(), e)
+								.context(getProcessorBaseContext());
+					}
+				}))
+		);
+		return counts.get();
+	}
+
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java
index 7e254bd3fe..a1b63481c0 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNode.java
@@ -1,77 +1,64 @@
 package io.tapdata.flow.engine.V2.node.hazelcast.processor;
 
-import com.google.common.collect.Maps;
 import com.tapdata.entity.TapdataEvent;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.process.TableRenameProcessNode;
 import com.tapdata.tm.commons.dag.vo.TableRenameTableInfo;
 import io.tapdata.entity.event.TapBaseEvent;
 import io.tapdata.entity.event.TapEvent;
-import io.tapdata.entity.event.ddl.table.TapCreateTableEvent;
 import io.tapdata.entity.event.ddl.table.TapDropTableEvent;
+import io.tapdata.entity.event.ddl.table.TapRenameTableEvent;
 import io.tapdata.flow.engine.V2.util.TapEventUtil;
-import org.apache.commons.lang3.StringUtils;
 
-import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
 
 public class HazelcastRenameTableProcessorNode extends HazelcastProcessorBaseNode {
 
-	/**
-	 * key: 源表名
-	 */
-	private Map<String, TableRenameTableInfo> tableNameMappingMap;
+    /**
+     * key: 源表名
+     */
+    private Map<String, TableRenameTableInfo> tableNameMappingMap;
+    private final Map<String, String> innerTableCacheMap = new ConcurrentHashMap<>(); // Used to speed up the name change operation
 
-	public HazelcastRenameTableProcessorNode(ProcessorBaseContext processorBaseContext) {
-		super(processorBaseContext);
-		initTableNameMapping();
-	}
+    public HazelcastRenameTableProcessorNode(ProcessorBaseContext processorBaseContext) {
+        super(processorBaseContext);
+        initTableNameMapping();
+    }
 
-	private void initTableNameMapping() {
-		TableRenameProcessNode tableRenameProcessNode = (TableRenameProcessNode) getNode();
-		LinkedHashSet<TableRenameTableInfo> tableNames = tableRenameProcessNode.getTableNames();
+    private void initTableNameMapping() {
+        TableRenameProcessNode tableRenameProcessNode = (TableRenameProcessNode) getNode();
+        this.tableNameMappingMap = tableRenameProcessNode.previousMap();
+    }
 
-		if (Objects.isNull(tableNames) || tableNames.isEmpty()) {
-			this.tableNameMappingMap = Maps.newLinkedHashMap();
-		}
+    @Override
+    protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, ProcessResult> consumer) {
+        TapEvent tapEvent = tapdataEvent.getTapEvent();
 
-		this.tableNameMappingMap = tableNames.stream()
-				.collect(Collectors.toMap(TableRenameTableInfo::getPreviousTableName, Function.identity()));
-	}
+        if (tapEvent instanceof TapBaseEvent) {
+            String newTableName = getTgtTableNameFromTapEvent(tapEvent);
+            ((TapBaseEvent) tapEvent).setTableId(newTableName);
+        }
+        consumer.accept(tapdataEvent, getProcessResult(TapEventUtil.getTableId(tapEvent)));
+    }
 
-	@Override
-	protected void tryProcess(TapdataEvent tapdataEvent, BiConsumer<TapdataEvent, ProcessResult> consumer) {
-		TapEvent tapEvent = tapdataEvent.getTapEvent();
-		String tableId = TapEventUtil.getTableId(tapEvent);
-
-		if (tapEvent instanceof TapBaseEvent) {
-			TableRenameTableInfo tableRenameTableInfo = tableNameMappingMap.get(tableId);
-			if (tableRenameTableInfo != null && StringUtils.isNotEmpty(tableRenameTableInfo.getCurrentTableName())) {
-				((TapBaseEvent) tapEvent).setTableId(tableRenameTableInfo.getCurrentTableName());
-			}
-		}
-		consumer.accept(tapdataEvent, getProcessResult(TapEventUtil.getTableId(tapEvent)));
-	}
-
-	@Override
-	public String getTgtTableNameFromTapEvent(TapEvent tapEvent) {
-		String tableId = TapEventUtil.getTableId(tapEvent);
-		TableRenameTableInfo tableRenameTableInfo = tableNameMappingMap.get(tableId);
-		if (tableRenameTableInfo != null && StringUtils.isNotEmpty(tableRenameTableInfo.getCurrentTableName())) {
-			return tableRenameTableInfo.getCurrentTableName();
-		}
-		return super.getTgtTableNameFromTapEvent(tapEvent);
-	}
-
-	protected void updateNodeConfig(TapdataEvent tapdataEvent) {
-		super.updateNodeConfig(tapdataEvent);
-		TapEvent tapEvent = tapdataEvent.getTapEvent();
-		if (tapEvent instanceof TapCreateTableEvent || tapEvent instanceof TapDropTableEvent) {
-			initTableNameMapping();
-		}
-	}
+    @Override
+    public String getTgtTableNameFromTapEvent(TapEvent tapEvent) {
+        String tableId = TapEventUtil.getTableId(tapEvent);
+        TableRenameProcessNode tableRenameProcessNode = (TableRenameProcessNode) getNode();
+        if (tapEvent instanceof TapRenameTableEvent) {
+            String newTableId = tableRenameProcessNode.convertTableName(tableNameMappingMap, tableId, true);
+            innerTableCacheMap.put(tableId, newTableId);
+            return newTableId;
+        } else if (tapEvent instanceof TapDropTableEvent) {
+            String newTableId = innerTableCacheMap.remove(tableId);
+            if (null == newTableId) {
+                newTableId = tableRenameProcessNode.convertTableName(tableNameMappingMap, tableId, false);
+            }
+            return newTableId;
+        } else {
+            return innerTableCacheMap.computeIfAbsent(tableId, id -> tableRenameProcessNode.convertTableName(tableNameMappingMap, tableId, false));
+        }
+    }
 }
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
index 9f4cedb7af..265058bf0a 100644
--- a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskService.java
@@ -1,5 +1,6 @@
 package io.tapdata.flow.engine.V2.task.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import com.hazelcast.config.Config;
 import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
@@ -45,6 +46,7 @@
 import com.tapdata.tm.commons.dag.process.ProcessorNode;
 import com.tapdata.tm.commons.dag.process.TableRenameProcessNode;
 import com.tapdata.tm.commons.dag.vo.ReadPartitionOptions;
+import com.tapdata.tm.commons.task.dto.ErrorEvent;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.aspect.TaskStartAspect;
 import io.tapdata.aspect.TaskStopAspect;
@@ -87,6 +89,7 @@
 import io.tapdata.flow.engine.V2.task.TaskClient;
 import io.tapdata.flow.engine.V2.task.TaskService;
 import io.tapdata.flow.engine.V2.util.*;
+import io.tapdata.flow.engine.util.TaskDtoUtil;
 import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.observable.logging.ObsLoggerFactory;
 import io.tapdata.pdk.core.utils.CommonUtils;
@@ -170,6 +173,10 @@ public TaskClient<TaskDto> startTask(TaskDto taskDto) {
 		try {
 			taskDto.setDag(taskDto.getDag());
 			ObsLogger obsLogger = ObsLoggerFactory.getInstance().getObsLogger(taskDto);
+
+			//Sprint 90- TAP2489: Unchecked errors will be cleared directly upon startup
+			cleanAllUnselectedError(taskDto, obsLogger);
+
 			AspectUtils.executeAspect(new TaskStartAspect().task(taskDto).log(InstanceFactory.instance(LogFactory.class).getLog(taskDto)));
 //            return threadPoolExecutorEx.submitSync(() -> {
 			JobConfig jobConfig = new JobConfig();
@@ -188,6 +195,18 @@ public TaskClient<TaskDto> startTask(TaskDto taskDto) {
 		}
 	}
 
+	protected void cleanAllUnselectedError(TaskDto taskDto, ObsLogger obsLogger) {
+		List<ErrorEvent> errorEvents = taskDto.getErrorEvents();
+		if (!CollUtil.isEmpty(errorEvents)) {
+			List<ErrorEvent> newErrorEvents = errorEvents.stream()
+					.filter(Objects::nonNull)
+					.filter(ErrorEvent::getSkip)
+					.collect(Collectors.toList());
+			taskDto.setErrorEvents(newErrorEvents);
+			TaskDtoUtil.updateErrorEvent(clientMongoOperator, newErrorEvents, taskDto.getId(), obsLogger, "Task initialization error list error, message: {}");
+		}
+	}
+
 	@Override
 	public TaskClient<TaskDto> startTestTask(TaskDto taskDto) {
 		try {
diff --git a/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/util/TaskDtoUtil.java b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/util/TaskDtoUtil.java
new file mode 100644
index 0000000000..5f31521a50
--- /dev/null
+++ b/iengine/iengine-app/src/main/java/io/tapdata/flow/engine/util/TaskDtoUtil.java
@@ -0,0 +1,20 @@
+package io.tapdata.flow.engine.util;
+
+import com.tapdata.constant.ConnectorConstant;
+import com.tapdata.mongo.ClientMongoOperator;
+import com.tapdata.tm.commons.task.dto.ErrorEvent;
+import io.tapdata.observable.logging.ObsLogger;
+import org.bson.types.ObjectId;
+
+import java.util.List;
+
+public class TaskDtoUtil {
+    private TaskDtoUtil() {}
+    public static void updateErrorEvent(ClientMongoOperator clientMongoOperator, List<ErrorEvent> errorEvents, ObjectId taskId, ObsLogger obsLogger, String errorMsg) {
+        try {
+            clientMongoOperator.insertOne(errorEvents, ConnectorConstant.TASK_COLLECTION + "/errorEvents/" + taskId);
+        } catch (Exception e){
+            obsLogger.warn(errorMsg, e.getMessage());
+        }
+    }
+}
diff --git a/iengine/iengine-app/src/main/resources/pluginKit.properties b/iengine/iengine-app/src/main/resources/pluginKit.properties
index 7479e56ed0..e728e11254 100644
--- a/iengine/iengine-app/src/main/resources/pluginKit.properties
+++ b/iengine/iengine-app/src/main/resources/pluginKit.properties
@@ -12,7 +12,7 @@ maven.compiler.target=1.8
 project.build.sourceEncoding=UTF-8
 project.reporting.outputEncoding=UTF-8
 proxy.file.name=plugin-kit-v1.0-SNAPSHOT
-tapdata.api.verison=1.3.6-SNAPSHOT
-tapdata.pdk.api.verison=1.3.6-SNAPSHOT
+tapdata.api.verison=1.3.7-SNAPSHOT
+tapdata.pdk.api.verison=1.3.7-SNAPSHOT
 tapdata.pdk.connector.core.version=1.0-SNAPSHOT
 tapdata.pdk.runner.verison=1.7-SNAPSHOT
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java
index 98e3341453..3d0f0e60dc 100644
--- a/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java
+++ b/iengine/iengine-common/src/main/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtil.java
@@ -6,10 +6,13 @@
 import io.tapdata.entity.event.ddl.entity.ValueChange;
 import io.tapdata.entity.event.ddl.table.TapRenameTableEvent;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class BatchOffsetUtil {
+    protected static final String BATCH_READ_CONNECTOR_OFFSET = "batch_read_connector_offset";
+    protected static final String BATCH_READ_CONNECTOR_STATUS = "batch_read_connector_status";
     private BatchOffsetUtil(){
 
     }
@@ -19,6 +22,9 @@ public static boolean batchIsOverOfTable(SyncProgress syncProgress, String table
         if (offsetValue instanceof BatchOffset) {
             /** 86 Iteration New Function - Full Scale Synchronization Breakpoint **/
             return TableBatchReadStatus.OVER.name().equals(((BatchOffset)offsetValue).getStatus());
+        } else if (offsetValue instanceof Map
+                && ((Map<?, ?>)offsetValue).containsKey(BATCH_READ_CONNECTOR_STATUS)) {
+            return TableBatchReadStatus.OVER.name().equals(((Map<String, Object>)offsetValue).get(BATCH_READ_CONNECTOR_STATUS));
         }
         /** history data*/
         return false;
@@ -29,6 +35,9 @@ public static Object getBatchOffsetOfTable(SyncProgress syncProgress, String tab
         if (offsetValue instanceof BatchOffset) {
             /** 86 Iteration New Function - Full Scale Synchronization Breakpoint **/
             return ((BatchOffset) offsetValue).getOffset();
+        } else if (offsetValue instanceof Map
+                && ((Map<?, ?>)offsetValue).containsKey(BATCH_READ_CONNECTOR_OFFSET)) {
+            return ((Map<String, Object>)offsetValue).get(BATCH_READ_CONNECTOR_OFFSET);
         }
 
         /** history data*/
@@ -47,18 +56,27 @@ public static void updateBatchOffset(SyncProgress syncProgress, String tableId,
         Object batchOffsetObj = syncProgress.getBatchOffsetObj();
         if (batchOffsetObj instanceof Map) {
             Map<String, Object> batchOffsetObjTemp = (Map<String, Object>) batchOffsetObj;
-            Object batchOffsetObject = batchOffsetObjTemp.computeIfAbsent(tableId, k -> new BatchOffset(offset, isOverTag));
-            if (batchOffsetObject instanceof BatchOffset) {
-                BatchOffset batchOffset = (BatchOffset) batchOffsetObject;
-                batchOffset.setOffset(offset);
-                batchOffset.setStatus(isOverTag);
+            Object batchOffsetObject = batchOffsetObjTemp.computeIfAbsent(tableId, k -> new HashMap<>());
+            if (batchOffsetObject instanceof Map
+                    && ((Map<?, ?>)batchOffsetObject).containsKey(BATCH_READ_CONNECTOR_OFFSET)
+                    && ((Map<?, ?>)batchOffsetObject).containsKey(BATCH_READ_CONNECTOR_STATUS)) {
+                updateBatchOffset((Map<String, Object>)batchOffsetObject, offset, isOverTag);
             } else {
                 /** update history offset of table which id is ${tableId} **/
-                batchOffsetObjTemp.put(tableId, new BatchOffset(offset, isOverTag));
+                batchOffsetObjTemp.put(tableId, updateBatchOffset(new HashMap<>(), offset, isOverTag));
             }
         }
     }
 
+    protected static Object updateBatchOffset(Map<String, Object> offsetMap, Object offset, String isOverTag) {
+        if (null == offsetMap) {
+            offsetMap = new HashMap<>();
+        }
+        offsetMap.put(BATCH_READ_CONNECTOR_STATUS, isOverTag);
+        offsetMap.put(BATCH_READ_CONNECTOR_OFFSET, offset);
+        return offsetMap;
+    }
+
     protected static void tableUpdateName(SyncProgress syncProgress, String oldName, String newName) {
         Object batchTableOffsetObj = syncProgress.getBatchOffsetObj();
         if (batchTableOffsetObj instanceof Map) {
diff --git a/iengine/iengine-common/src/main/java/io/tapdata/error/TaskProcessorExCode_11.java b/iengine/iengine-common/src/main/java/io/tapdata/error/TaskProcessorExCode_11.java
index 224dff206c..951270b873 100644
--- a/iengine/iengine-common/src/main/java/io/tapdata/error/TaskProcessorExCode_11.java
+++ b/iengine/iengine-common/src/main/java/io/tapdata/error/TaskProcessorExCode_11.java
@@ -153,4 +153,10 @@ public interface TaskProcessorExCode_11 {
 			describeCN = "在封装tapdata事件时，源节点克隆批量断点信息失败"
 	)
 	String SOURCE_CLONE_BATCH_OFFSET_FAILED = "11028";
+
+	@TapExCode(
+			describe = "Source node can not support countByPartitionFilterFunction",
+			describeCN = "源节点在不支持过滤count的方法"
+	)
+	String SOURCE_NOT_SUPPORT_COUNT_BY_PARTITION_FILTER_FUNCTION = "11029";
 }
diff --git a/iengine/pom.xml b/iengine/pom.xml
index 7ace683364..de383dc875 100644
--- a/iengine/pom.xml
+++ b/iengine/pom.xml
@@ -810,7 +810,7 @@
             <dependency>
                 <groupId>com.tapdata</groupId>
                 <artifactId>tapdata-api</artifactId>
-                <version>1.3.6-SNAPSHOT</version>
+                <version>1.3.7-SNAPSHOT</version>
             </dependency>
             <dependency>
                 <groupId>com.tapdata</groupId>
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/base/service/BaseService.java b/manager/tm-api/src/main/java/com/tapdata/tm/base/service/BaseService.java
index c40d142136..1e4bb89aff 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/base/service/BaseService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/base/service/BaseService.java
@@ -53,7 +53,7 @@ public abstract class BaseService<Dto extends BaseDto, Entity extends BaseEntity
     protected Class<Entity> entityClass;
 
 
-    protected boolean isAgentReq() {
+    public boolean isAgentReq() {
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
         HttpServletRequest request = attributes.getRequest();
         String userAgent = request.getHeader("user-agent");
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDto.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDto.java
index 0a424d4811..780de78f5d 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDto.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/dto/InspectDto.java
@@ -1,6 +1,7 @@
 package com.tapdata.tm.inspect.dto;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.tapdata.tm.commons.base.IDataPermissionDto;
 import com.tapdata.tm.commons.base.dto.SchedulableDto;
 import com.tapdata.tm.commons.schema.bean.PlatformInfo;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmRuleVO;
@@ -8,6 +9,7 @@
 import com.tapdata.tm.inspect.bean.Limit;
 import com.tapdata.tm.inspect.bean.Task;
 import com.tapdata.tm.inspect.bean.Timing;
+import com.tapdata.tm.inspect.vo.InspectTaskVo;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -19,7 +21,7 @@
  */
 @EqualsAndHashCode(callSuper = true)
 @Data
-public class InspectDto extends SchedulableDto {
+public class InspectDto extends SchedulableDto implements IDataPermissionDto {
     /**
      *
      */
@@ -28,6 +30,10 @@ public class InspectDto extends SchedulableDto {
      *
      */
     private String flowId;
+    /**
+     * 查询详情的时候需要返回关联的task详情
+     * */
+    private InspectTaskVo taskDto;
     /**
      *
      */
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/entity/InspectEntity.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/entity/InspectEntity.java
index 08f23b0687..3b6f469fc3 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/entity/InspectEntity.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/entity/InspectEntity.java
@@ -1,6 +1,7 @@
 package com.tapdata.tm.inspect.entity;
 
 import com.tapdata.tm.base.entity.SchedulableEntity;
+import com.tapdata.tm.commons.base.IDataPermissionEntity;
 import com.tapdata.tm.commons.schema.bean.PlatformInfo;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmRuleVO;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingVO;
@@ -20,7 +21,7 @@
 @EqualsAndHashCode(callSuper = true)
 @Data
 @Document("Inspect")
-public class InspectEntity extends SchedulableEntity {
+public class InspectEntity extends SchedulableEntity implements IDataPermissionEntity {
     private String name;        // 校验任务名称
     private String flowId;      // 任务ID
     private String mode;        // 运行方式: 手工执行, 定时调度执行, manual/cron
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java
index 3650e4d1cd..bac4e90717 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectCronJob.java
@@ -51,7 +51,7 @@ public void execute(JobExecutionContext jobExecutionContext) throws JobExecution
 
             Where where=new Where();
             where.put("id",inspectId);
-            inspectService.executeInspect(where,inspectDto,userDetail);
+            inspectService.doExecuteInspect(where,inspectDto,userDetail);
         }
     }
 }
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectService.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectService.java
index 382ea347e3..2b7d84877d 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectService.java
@@ -1,6 +1,7 @@
 package com.tapdata.tm.inspect.service;
 
 import com.mongodb.client.result.UpdateResult;
+import com.tapdata.tm.base.dto.Field;
 import com.tapdata.tm.base.dto.Filter;
 import com.tapdata.tm.base.dto.Page;
 import com.tapdata.tm.base.dto.Where;
@@ -18,11 +19,15 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 public abstract class InspectService extends BaseService<InspectDto, InspectEntity, ObjectId, InspectRepository> {
     public InspectService(@NonNull InspectRepository repository) {
         super(repository, InspectDto.class, InspectEntity.class);
     }
+
+    public abstract Supplier<InspectDto> dataPermissionFindById(ObjectId taskId, Field fields);
+
     @Transactional
     public abstract Map<String, Long> delete(String id, UserDetail user);
 
@@ -42,9 +47,7 @@ public InspectDto save(InspectDto inspectDto, UserDetail user){
 
     public abstract UpdateResult deleteByTaskId(String taskId);
 
-    public abstract InspectDto updateInspectByWhere(Where where, InspectDto updateDto, UserDetail user);
-
-    public abstract InspectDto executeInspect(Where where, InspectDto updateDto, UserDetail user);
+    public abstract InspectDto doExecuteInspect(Where where, InspectDto updateDto, UserDetail user);
 
     public abstract InspectDto updateById(ObjectId objectId, InspectDto inspectDto, UserDetail userDetail);
 
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectTaskService.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectTaskService.java
new file mode 100644
index 0000000000..16600d1753
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/service/InspectTaskService.java
@@ -0,0 +1,29 @@
+package com.tapdata.tm.inspect.service;
+
+import com.tapdata.tm.base.dto.Where;
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.dto.InspectDto;
+
+import java.util.List;
+
+public interface InspectTaskService {
+    InspectDto inspectTaskRun(Where where, InspectDto updateDto, UserDetail user);
+
+    InspectDto inspectTaskStop(String id, InspectDto updateDto, UserDetail user);
+
+    InspectDto inspectTaskError(String id, InspectDto updateDto, UserDetail user);
+
+    InspectDto inspectTaskDone(String id, InspectDto updateDto, UserDetail user);
+
+    InspectDto executeInspect(Where where, InspectDto updateDto, UserDetail user);
+
+    String startInspectTask(InspectDto inspectDto, String processId);
+
+    void stopInspectTask(InspectDto inspectDto);
+
+    List<TaskDto> findTaskList(UserDetail user);
+
+    List<DataSourceConnectionDto> findConnectionList(UserDetail user);
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/inspect/vo/InspectTaskVo.java b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/vo/InspectTaskVo.java
new file mode 100644
index 0000000000..d406e8459d
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/inspect/vo/InspectTaskVo.java
@@ -0,0 +1,26 @@
+package com.tapdata.tm.inspect.vo;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.tapdata.tm.commons.base.convert.DagDeserialize;
+import com.tapdata.tm.commons.base.convert.DagSerialize;
+import com.tapdata.tm.commons.base.convert.ObjectIdDeserialize;
+import com.tapdata.tm.commons.base.convert.ObjectIdSerialize;
+import com.tapdata.tm.commons.dag.DAG;
+import lombok.Data;
+import org.bson.types.ObjectId;
+
+@Data
+public class InspectTaskVo {
+    @JsonSerialize( using = ObjectIdSerialize.class)
+    @JsonDeserialize( using = ObjectIdDeserialize.class)
+    ObjectId id;
+
+    String name;
+
+    String syncType;
+
+    @JsonSerialize( using = DagSerialize.class)
+    @JsonDeserialize( using = DagDeserialize.class)
+    DAG dag;
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/permissions/DataPermissionHelper.java b/manager/tm-api/src/main/java/com/tapdata/tm/permissions/DataPermissionHelper.java
index 954bdbab02..3e029d7454 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/permissions/DataPermissionHelper.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/permissions/DataPermissionHelper.java
@@ -85,280 +85,3 @@ public static String signDecode(HttpServletRequest request, String id) {
         return iDataPermissionHelper.signDecode(request, id);
     }
 }
-/*
-package com.tapdata.tm.permissions;
-
-import com.tapdata.tm.base.entity.BaseEntity;
-import com.tapdata.tm.base.reporitory.BaseRepository;
-import com.tapdata.tm.commons.base.DataPermissionAction;
-import com.tapdata.tm.commons.base.IDataPermissionDto;
-import com.tapdata.tm.commons.base.IDataPermissionEntity;
-import com.tapdata.tm.commons.base.dto.BaseDto;
-import com.tapdata.tm.config.security.UserDetail;
-import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
-import com.tapdata.tm.permissions.constants.DataPermissionDataTypeEnums;
-import com.tapdata.tm.permissions.constants.DataPermissionMenuEnums;
-import com.tapdata.tm.permissions.constants.DataPermissionTypeEnums;
-import com.tapdata.tm.permissions.service.DataPermissionService;
-import com.tapdata.tm.permissions.vo.DataPermissionAuthInfoVo;
-import com.tapdata.tm.permissions.vo.DataPermissionTypeVo;
-import com.tapdata.tm.utils.AES256Util;
-import org.bson.types.ObjectId;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import static com.tapdata.tm.permissions.DataPermissionHelper.*;
-
-*//**
- * All data permissions are processed by the current class
- *
- * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
- * @version v1.0 2023/8/8 15:57 Create
- *//*
-public class DataPermissionHelper {
-
-    public static final String FIELD_NAME = "permissions";
-    public static final String AUTH_TYPE = String.join(".", FIELD_NAME, "type");
-    public static final String AUTH_TYPE_ID = String.join(".", FIELD_NAME, "typeId");
-    public static final String AUTH_ACTIONS = String.join(".", FIELD_NAME, "actions");
-    private static final ThreadLocal<DataPermissionAuthInfoVo> THREAD_LOCAL = new ThreadLocal<>();
-
-    private static boolean isAgentReq() {
-        try {
-            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
-            HttpServletRequest request = attributes.getRequest();
-            String userAgent = request.getHeader("user-agent");
-            return org.apache.commons.lang3.StringUtils.isNotBlank(userAgent) && (userAgent.contains("Java") || userAgent.contains("Node") || userAgent.contains("FlowEngine"));
-        } catch (Exception ignore) {
-            return false;
-        }
-    }
-
-    private static boolean isUnCheckAuth(UserDetail userDetail) {
-        return DataPermissionService.isCloud() || userDetail.isFreeAuth() || isAgentReq();
-    }
-
-    private static boolean isMenuAuth(DataPermissionMenuEnums menuEnums, Set<String> roleIdSet) {
-        return DataPermissionService.getInstance().isMenuAuth(
-                menuEnums.getAllDataPermissionName(), roleIdSet.stream().map(ObjectId::new).collect(Collectors.toSet())
-        );
-    }
-
-    private static <T> T executeByAuthInfo(DataPermissionAuthInfoVo vo, Supplier<T> supplier) {
-        try {
-            THREAD_LOCAL.set(vo);
-            return supplier.get();
-        } finally {
-            THREAD_LOCAL.remove();
-        }
-    }
-
-    private static DataPermissionAuthInfoVo getAuthInfo(
-            UserDetail userDetail,
-            DataPermissionMenuEnums menuEnums,
-            DataPermissionActionEnums actionEnums,
-            DataPermissionDataTypeEnums dataTypeEnums,
-            String id
-    ) {
-        if (isUnCheckAuth(userDetail)) return null;
-
-        DataPermissionAuthInfoVo authInfoVo = new DataPermissionAuthInfoVo()
-                .id(id)
-                .userId(userDetail.getUserId())
-                .menuEnums(menuEnums)
-                .dataTypeEnums(dataTypeEnums)
-                .actionEnums(actionEnums)
-                .created(System.currentTimeMillis());
-
-        // check admin auth
-        if (userDetail.isRoot()) return authInfoVo.authWithAdmin();
-
-        DataPermissionTypeVo[] typeVos = null;
-        Set<String> roleSet = DataPermissionService.getInstance().getRoleIds(userDetail.getUserId());
-        if (!(null == roleSet || roleSet.isEmpty())) {
-            authInfoVo.setRoleSet(roleSet);
-
-            // check menu auth
-            if (null != menuEnums && isMenuAuth(menuEnums, roleSet)) return authInfoVo.authWithMenu();
-
-            typeVos = new DataPermissionTypeVo[]{
-                    new DataPermissionTypeVo(DataPermissionTypeEnums.Role, roleSet)
-            };
-        }
-
-        // check data and role auth
-        if (null != id) {
-            return DataPermissionService.getInstance().setDataActions(authInfoVo, typeVos);
-        }
-        return authInfoVo;
-    }
-
-    public static Set<String> mergeActions(Set<String> actions, Set<String> roleIds, List<DataPermissionAction> permissionItems) {
-        if (null != permissionItems) {
-            for (DataPermissionAction p : permissionItems) {
-                if (DataPermissionTypeEnums.Role.name().equals(p.getType()) && roleIds.contains(p.getTypeId()) && null != p.getActions()) {
-                    actions.addAll(p.getActions());
-                }
-            }
-        }
-        return actions;
-    }
-
-    public static boolean setFilterConditions(boolean need2SetFilter, Query query, UserDetail userDetail) {
-        DataPermissionAuthInfoVo vo = THREAD_LOCAL.get();
-        if (null == vo || !need2SetFilter) return false;
-
-        if (!vo.isSetQueryFilter()) return true;
-
-        Set<String> roleIds = vo.getRoleSet();
-        if (null == roleIds) return false;
-
-        BaseRepository.removeFilter("user_id", query);
-        try {
-            if (!query.getFieldsObject().isEmpty()) {
-                query.fields().include(FIELD_NAME);
-            }
-            BaseRepository.addOrFilter(query,
-                    Criteria.where("user_id").is(userDetail.getUserId())
-                    , Criteria.where(AUTH_TYPE).is(DataPermissionTypeEnums.Role)
-                            .and(AUTH_TYPE_ID).in(roleIds)
-                            .and(AUTH_ACTIONS).in(vo.getActionEnums())
-            );
-            return true;
-        } catch (Exception e) {
-            throw new RuntimeException("Set auth condition failed: " + e.getMessage(), e);
-        }
-    }
-
-    public static <E extends BaseEntity, D extends BaseDto> void convert(E entity, D dto) {
-        if (DataPermissionService.isCloud()) return;
-
-        DataPermissionAuthInfoVo vo = THREAD_LOCAL.get();
-        if (null != vo && entity instanceof IDataPermissionEntity && dto instanceof IDataPermissionDto) {
-            // menu and admin role auth not has role ids, set all actions
-            Set<String> roleIds = vo.getRoleSet();
-            if (null == roleIds) {
-                dto.setPermissionActions(vo.getDataTypeEnums().allActions());
-                return;
-            }
-
-            // creator has all actions
-            String userId = vo.getUserId();
-            if (userId.equals(entity.getUserId())) {
-                dto.setPermissionActions(vo.getDataTypeEnums().allActions());
-                return;
-            }
-
-            Set<String> actions = new HashSet<>();
-            mergeActions(actions, roleIds, ((IDataPermissionEntity) entity).getPermissions());
-            dto.setPermissionActions(actions);
-        }
-    }
-
-    public static void cleanAuthOfRoleDelete(Set<String> roleIds) {
-        if (DataPermissionService.isCloud()) return;
-        DataPermissionService.getInstance().cleanAuthOfRoleDelete(roleIds);
-    }
-
-    *//**
-     * @param userDetail     user info
-     * @param menuEnums      menu type, check menu auth if not null
-     * @param actionEnums    action type, not null
-     * @param dataTypeEnums  data type, not null
-     * @param id             data id, query and check data auth if not null
-     * @param supplier       call in normal
-     * @param noAuthSupplier call if data un auth
-     * @param <T>            result type
-     * @return call data
-     *//*
-    public static  <T> T check(
-            UserDetail userDetail,
-            DataPermissionMenuEnums menuEnums,
-            DataPermissionActionEnums actionEnums,
-            DataPermissionDataTypeEnums dataTypeEnums,
-            String id,
-            Supplier<T> supplier,
-            Supplier<T> noAuthSupplier
-    ) {
-        DataPermissionAuthInfoVo vo = getAuthInfo(userDetail, menuEnums, actionEnums, dataTypeEnums, id);
-        if (null != vo) {
-            if (vo.isUnAuth()) return noAuthSupplier.get();
-            return executeByAuthInfo(vo, supplier);
-        }
-        return supplier.get();
-    }
-
-    public static  <T, D extends BaseDto> T checkOfQuery(
-            UserDetail userDetail,
-            DataPermissionDataTypeEnums dataTypeEnums,
-            DataPermissionActionEnums actionEnums,
-            Supplier<D> querySupplier,
-            Function<D, DataPermissionMenuEnums> menuEnumsFun,
-            Supplier<T> supplier,
-            Supplier<T> unAuthSupplier
-    ) {
-        if (isUnCheckAuth(userDetail)) return supplier.get();
-
-        DataPermissionAuthInfoVo vo = new DataPermissionAuthInfoVo()
-                .userId(userDetail.getUserId())
-                .dataTypeEnums(dataTypeEnums)
-                .actionEnums(actionEnums);
-
-        // check admin auth
-        if (userDetail.isRoot()) {
-            return executeByAuthInfo(vo.authWithAdmin(), supplier);
-        }
-
-        vo.setRoleSet(DataPermissionService.getInstance().getRoleIds(userDetail.getUserId()));
-
-        D dto = executeByAuthInfo(vo, querySupplier);
-        if (null == dto) return unAuthSupplier.get();
-
-        vo.id(dto.getId().toHexString()).menuEnums(menuEnumsFun.apply(dto));
-
-        if (userDetail.getUserId().equals(dto.getUserId())) {
-            //check user auth
-            return executeByAuthInfo(vo.authWithUserData(), supplier);
-        } else if (null != dto.getPermissionActions() && dto.getPermissionActions().contains(actionEnums.name())) {
-            //check role auth
-            return executeByAuthInfo(vo.authWithUserData(), supplier);
-        }
-
-        // check menu auth
-        if (null != vo.getMenuEnums() && isMenuAuth(vo.getMenuEnums(), vo.getRoleSet())) {
-            return executeByAuthInfo(vo.authWithMenu(), supplier);
-        }
-
-        return unAuthSupplier.get();
-    }
-
-    public static String signEncode(String currentId, String parentId) {
-        return AES256Util.Aes256Encode(String.join(",", currentId, parentId));
-    }
-
-    public static String signDecode(HttpServletRequest request, String id) {
-        if (null == id) return null;
-
-        String parentTaskSign = request.getHeader("parent_task_sign");
-        if (null != parentTaskSign) {
-            String str = AES256Util.Aes256Decode(parentTaskSign);
-            if (!str.equals(parentTaskSign)) {
-                String[] arr = str.split(",");
-                if (arr.length == 2 && id.equals(arr[0])) return arr[1];
-            }
-        }
-        return null;
-    }
-}*/
-
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnums.java b/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnums.java
index e015398560..50ee46075a 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnums.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnums.java
@@ -22,7 +22,14 @@ public enum DataPermissionDataTypeEnums implements IDataPermissionAction {
 		DataPermissionActionEnums.Reset,
 		DataPermissionActionEnums.Start,
 		DataPermissionActionEnums.Stop
-	),
+	), INSPECT(
+		"Inspect",
+		DataPermissionActionEnums.View,
+		DataPermissionActionEnums.Edit,
+		DataPermissionActionEnums.Delete,
+		DataPermissionActionEnums.Start,
+		DataPermissionActionEnums.Stop
+	)
 	;
 	private final String collection;
 	private final LinkedHashSet<String> allActions;
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnums.java b/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnums.java
index a54933749a..281d81ad28 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnums.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnums.java
@@ -19,6 +19,7 @@ public enum DataPermissionMenuEnums {
 	LogCollectorTack(DataPermissionDataTypeEnums.Task, ""),
 	ConnHeartbeatTack(DataPermissionDataTypeEnums.Task, ""),
 	MemCacheTack(DataPermissionDataTypeEnums.Task, ""),
+	INSPECT_TACK(DataPermissionDataTypeEnums.INSPECT, ""),
 	;
 
 	private final DataPermissionDataTypeEnums dataType;
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java b/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java
index 68f45f4fb4..df3528a3cc 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/task/entity/TaskEntity.java
@@ -280,6 +280,12 @@ public class TaskEntity extends BaseEntity implements IDataPermissionEntity {
     private Boolean doubleActive = false;
 
     private List<ErrorEvent> errorEvents;
+    /**
+     * Compatible with old versions of timezone processing
+     * true: The data source reads the date according to the old version logic
+     * false: The data source reads the date according to the new version logic
+     */
+    private Boolean oldVersionTimezone = true;
 
     public String getAccessNodeProcessId() {
         if (AccessNodeTypeEnum.isGroupManually(accessNodeType)) {
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/task/service/TaskService.java b/manager/tm-api/src/main/java/com/tapdata/tm/task/service/TaskService.java
index c9bb93528b..00492f38fa 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/task/service/TaskService.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/task/service/TaskService.java
@@ -258,8 +258,6 @@ public abstract TaskDto createShareCacheTask(SaveShareCacheParam saveShareCacheP
 
     public abstract void updateTaskLogSetting(String taskId, LogSettingParam logSettingParam, UserDetail userDetail);
 
-    public abstract Chart6Vo chart6(UserDetail user);
-
     public abstract void stopTaskIfNeedByAgentId(String agentId, UserDetail userDetail);
 
     public abstract List<TaskDto> getTaskStatsByTableNameOrConnectionId(String connectionId, String tableName, UserDetail userDetail);
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java b/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java
new file mode 100644
index 0000000000..d99f883646
--- /dev/null
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/utils/QueryUtil.java
@@ -0,0 +1,19 @@
+package com.tapdata.tm.utils;
+
+import com.tapdata.tm.base.dto.Where;
+import org.bson.Document;
+import org.springframework.data.mongodb.core.query.Criteria;
+
+public class QueryUtil {
+    private QueryUtil() {}
+    /**
+     * Convert filter to Criteria
+     *
+     * @param where
+     * @return
+     */
+    public static Criteria parseWhereToCriteria(Where where) {
+        Document doc = new Document(where);
+        return Criteria.matchingDocumentStructure(() -> doc);
+    }
+}
diff --git a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java
index 76b9c84c87..df95313e94 100644
--- a/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java
+++ b/manager/tm-api/src/main/java/com/tapdata/tm/webhook/enums/ConstVariable.java
@@ -1,6 +1,7 @@
 package com.tapdata.tm.webhook.enums;
 
 public class ConstVariable {
+    private ConstVariable() {}
     public static final String HOOK_ID = "hookId";
     public static final String HOOK_EVENT = "hookEvent";
     public static final String DELETED = "deleted";
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNode.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNode.java
index fcd01105f8..e06eee152a 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNode.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNode.java
@@ -4,15 +4,13 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.tapdata.tm.commons.dag.DAG;
-import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.NodeEnum;
 import com.tapdata.tm.commons.dag.NodeType;
 import com.tapdata.tm.commons.dag.vo.TableRenameTableInfo;
 import com.tapdata.tm.commons.schema.Schema;
 import com.tapdata.tm.commons.schema.SchemaUtils;
-import io.tapdata.entity.event.ddl.TapDDLEvent;
-import io.tapdata.entity.event.ddl.table.TapCreateTableEvent;
-import io.tapdata.entity.event.ddl.table.TapDropTableEvent;
+import com.tapdata.tm.error.TapDynamicTableNameExCode_35;
+import io.tapdata.exception.TapCodeException;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
@@ -20,12 +18,9 @@
 import org.apache.commons.lang3.StringUtils;
 
 import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import static com.tapdata.tm.commons.base.convert.ObjectIdDeserialize.toObjectId;
-
 @NodeType("table_rename_processor")
 @Getter
 @Setter
@@ -33,7 +28,6 @@
 public class TableRenameProcessNode extends MigrateProcessorNode {
     /**
      * 创建处理器节点
-     *
      **/
     public TableRenameProcessNode() {
         super(NodeEnum.table_rename_processor.name(), NodeCatalog.processor);
@@ -57,9 +51,8 @@ public void setTableNames(LinkedHashSet<TableRenameTableInfo> tableNames) {
     private String transferCase;
 
 
-
-    public Map<String, TableRenameTableInfo> originalMap () {
-        if (Objects.isNull(tableNames) || tableNames.isEmpty()) {
+    public Map<String, TableRenameTableInfo> originalMap() {
+        if (null == tableNames) {
             return Maps.newLinkedHashMap();
         }
 
@@ -67,124 +60,71 @@ public Map<String, TableRenameTableInfo> originalMap () {
     }
 
     public Map<String, TableRenameTableInfo> currentMap() {
-        if (Objects.isNull(tableNames) || tableNames.isEmpty()) {
+        if (null == tableNames) {
             return Maps.newLinkedHashMap();
         }
 
         return tableNames.stream().collect(Collectors.toMap(TableRenameTableInfo::getCurrentTableName, Function.identity()));
     }
 
-    @Override
-    public List<Schema> mergeSchema(List<List<Schema>> inputSchemas, List<Schema> schemas, DAG.Options options) {
-        if (CollectionUtils.isEmpty(inputSchemas)) {
-            return Lists.newArrayList();
+    public Map<String, TableRenameTableInfo> previousMap() {
+        Map<String, TableRenameTableInfo> resultMap = Maps.newLinkedHashMap();
+        if (null == tableNames) {
+            return resultMap;
         }
 
-        if (Objects.isNull(tableNames) || tableNames.isEmpty()) {
-            return inputSchemas.get(0);
+        for (TableRenameTableInfo tableRenameTableInfo : tableNames) {
+            resultMap.put(tableRenameTableInfo.getPreviousTableName(), tableRenameTableInfo);
         }
-
-        inputSchemas.get(0).forEach(schema -> {
-            String originalName = schema.getAncestorsName();
-            Map<String, TableRenameTableInfo> originalMap = originalMap();
-            if (originalMap.containsKey(originalName)) {
-                String currentTableName = originalMap.get(originalName).getCurrentTableName();
-                schema.setName(currentTableName);
-                schema.setOriginalName(currentTableName);
-                schema.setAncestorsName(originalName);
-                //schema.setDatabaseId(null);
-                //schema.setQualifiedName(MetaDataBuilderUtils.generateQualifiedName(MetaType.processor_node.name(), getId()));
-            } else {
-                String currentTableName = convertTableName(schema.getOriginalName());
-                TableRenameTableInfo tableInfo = new TableRenameTableInfo(originalName, schema.getOriginalName(), currentTableName);
-                tableNames.add(tableInfo);
-                schema.setName(currentTableName);
-                schema.setOriginalName(currentTableName);
-                schema.setAncestorsName(originalName);
-            }
-        });
-
-        Set<String> lastTable = inputSchemas.get(0).stream().map(Schema::getAncestorsName).collect(Collectors.toSet());
-
-        List<TableRenameTableInfo> removeTables = new ArrayList<>();
-        if (CollectionUtils.isNotEmpty(tableNames)) {
-            for (TableRenameTableInfo tableName : tableNames) {
-                if (!lastTable.contains(tableName.getOriginTableName())) {
-                    removeTables.add(tableName);
-                }
-            }
-
-            removeTables.forEach(tableNames::remove);
-        }
-
-        return inputSchemas.get(0);
+        return resultMap;
     }
 
-
-
-
     @Override
-    public void fieldDdlEvent(TapDDLEvent event) throws Exception {
-        if (event instanceof TapCreateTableEvent) {
-            String tableName = event.getTableId();
-
-            List<Node> sources = this.getDag().getSources();
-            Node node = sources.get(0);
-            LinkedList<TableRenameProcessNode> linkedList = new LinkedList<>();
-            while (!node.getId().equals(this.getId())) {
-                if (node instanceof TableRenameProcessNode) {
-                    linkedList.add((TableRenameProcessNode) node);
-                }
-                List successors = node.successors();
-                if (CollectionUtils.isEmpty(successors)) {
-                    break;
-                }
-                node = (Node) successors.get(0);
-            }
-            TableRenameTableInfo tableInfo = null;
-            if (CollectionUtils.isNotEmpty(linkedList)) {
-                for (TableRenameProcessNode node1 : linkedList) {
-                    Map<String, TableRenameTableInfo> tableRenameTableInfoMap = node1.originalMap();
-                    TableRenameTableInfo tableInfo1 = tableRenameTableInfoMap.get(tableName);
-                    if (tableInfo1 != null) {
-                        tableInfo = tableInfo1;
+    public List<Schema> mergeSchema(List<List<Schema>> inputSchemas, List<Schema> schemas, DAG.Options options) {
+        if (CollectionUtils.isEmpty(inputSchemas)) {
+            return Lists.newArrayList();
+        }
+
+        // 'schemas' is null because node not exists physical model
+        List<Schema> outputSchemas = SchemaUtils.cloneSchema(inputSchemas.get(0));
+
+        outputSchemas.forEach(schema -> {
+            Map<String, TableRenameTableInfo> originaledMap = originalMap();
+            String ancestorsName = schema.getAncestorsName();
+            String currentTableName = convertTableName(originaledMap, ancestorsName, false);
+            schema.setName(currentTableName);
+            schema.setOriginalName(currentTableName);
+            Optional.ofNullable(options.getIncludes()).ifPresent(includes -> {
+                for (int i = 0; i < includes.size(); i++) {
+                    if (ancestorsName.equals(includes.get(i))) {
+                        includes.set(i, currentTableName);
                     }
                 }
-            }
+            });
+        });
 
-            String lastTableName = tableName;
-            if (tableInfo != null) {
-                lastTableName = tableInfo.getCurrentTableName();
-            }
+        return outputSchemas;
+    }
 
-            if (CollectionUtils.isEmpty(tableNames))  {
-                tableNames = new LinkedHashSet<>();
-            }
 
-            boolean already = tableNames.stream().anyMatch(t -> t.getOriginTableName().equals(tableName));
-            if (!already) {
-                String currentTableName = convertTableName(lastTableName);
-                boolean exist = tableNames.stream().anyMatch(t -> t.getCurrentTableName().equals(currentTableName));
-                if (exist) {
-                    throw new IllegalArgumentException("The new table [" + currentTableName + "] already exists when it is converted as  [" + currentTableName + "]");
-                }
-                tableInfo = new TableRenameTableInfo(tableName, lastTableName, currentTableName);
-                tableNames.add(tableInfo);
-            } else {
-                log.info("The table [{}] has been added to the configuration", tableName);
+    public String convertTableName(Map<String, TableRenameTableInfo> infoMap, String tableName, boolean isRenameDDL) {
+        TableRenameTableInfo tableInfo = infoMap.get(tableName);
+        if (null != tableInfo) {
+            if (isRenameDDL) {
+                throw new TapCodeException(TapDynamicTableNameExCode_35.RENAME_DDL_CONFLICTS_WITH_CUSTOM_TABLE_NAME
+                    , String.format("Can't apply table rename DDL because it conflicts with custom-table-name from '%s' to '%s'", tableName, tableInfo.getCurrentTableName())
+                );
             }
-        } else if (event instanceof TapDropTableEvent) {
-            String tableName = event.getTableId();
-            for (TableRenameTableInfo tableInfo : tableNames) {
-                if (tableInfo.getOriginTableName().equals(tableName)) {
-                    tableNames.remove(tableInfo);
-                    break;
-                }
+            String currentTableName = tableInfo.getCurrentTableName();
+            if (StringUtils.isNotBlank(currentTableName)) {
+                return currentTableName;
             }
+            return tableName;
         }
+        return convertTableName(tableName);
     }
 
-    private String convertTableName(String tableName) {
+    public String convertTableName(String tableName) {
         if (StringUtils.isNotBlank(prefix)) {
             tableName = prefix + tableName;
         }
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/TaskDto.java b/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/TaskDto.java
index beeefbdaab..783104fb97 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/TaskDto.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/commons/task/dto/TaskDto.java
@@ -207,6 +207,7 @@ public boolean isAutoInspect() {
      * 任务导入导出需要用到
      * */
     private Map<String, Object> agentGroupInfo;
+    private Boolean oldVersionTimezone = true;
 
     public DAG getDag() {
         if (dag != null) {
diff --git a/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java b/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java
index 75467123e6..24e87a06b1 100644
--- a/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java
+++ b/manager/tm-common/src/main/java/com/tapdata/tm/error/TapDynamicTableNameExCode_35.java
@@ -53,4 +53,14 @@ public interface TapDynamicTableNameExCode_35 {
             level = TapExLevel.NORMAL
     )
     String DYNAMIC_METHOD_ACCESS_FAILED = "35005";
+
+    @TapExCode(
+        describe = "Table renamed DDL conflicts with the custom table name.\n" +
+            "Reason:\n1. The task assigns a new table name to the table, which cannot be automatically processed when the table is renamed DDL.",
+        solution = "1. The task configuration needs to be analyzed and resolved.",
+        describeCN = "表改名DDL与自定义表名冲突\n" +
+            "原因\n1. 任务为表指定新表名，这个表发生改名DDL时无法自动处理",
+        solutionCN = "1. 需要分析任务配置，并进行解决"
+    )
+    String RENAME_DDL_CONFLICTS_WITH_CUSTOM_TABLE_NAME = "35006";
 }
diff --git a/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectServiceImpl.java b/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectServiceImpl.java
index f58f93c6cf..4ffbb5a7f2 100644
--- a/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectServiceImpl.java
+++ b/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectServiceImpl.java
@@ -1,81 +1,26 @@
 package com.tapdata.tm.inspect.service;
 
-import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.bean.copier.CopyOptions;
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.lang.Assert;
-import cn.hutool.extra.cglib.CglibUtil;
-import com.google.common.collect.Maps;
 import com.mongodb.client.result.UpdateResult;
-import com.tapdata.tm.Settings.constant.CategoryEnum;
-import com.tapdata.tm.Settings.constant.KeyEnum;
-import com.tapdata.tm.Settings.entity.Settings;
-import com.tapdata.tm.Settings.service.AlarmSettingService;
-import com.tapdata.tm.Settings.service.SettingsService;
-import com.tapdata.tm.alarm.constant.AlarmComponentEnum;
-import com.tapdata.tm.alarm.constant.AlarmStatusEnum;
-import com.tapdata.tm.alarm.constant.AlarmTypeEnum;
-import com.tapdata.tm.alarm.entity.AlarmInfo;
-import com.tapdata.tm.alarm.service.AlarmService;
+import com.tapdata.tm.base.dto.Field;
 import com.tapdata.tm.base.dto.Filter;
 import com.tapdata.tm.base.dto.Page;
 import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
-import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
-import com.tapdata.tm.commons.dag.nodes.TableNode;
-import com.tapdata.tm.commons.schema.MetadataInstancesDto;
-import com.tapdata.tm.commons.schema.bean.PlatformInfo;
-import com.tapdata.tm.commons.task.constant.AlarmKeyEnum;
-import com.tapdata.tm.commons.task.constant.AlarmSettingTypeEnum;
-import com.tapdata.tm.commons.task.constant.NotifyEnum;
 import com.tapdata.tm.commons.task.dto.TaskDto;
-import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingDto;
-import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingVO;
-import com.tapdata.tm.commons.util.JsonUtil;
 import com.tapdata.tm.config.security.UserDetail;
-import com.tapdata.tm.dataflow.dto.DataFlowDto;
-import com.tapdata.tm.dataflow.service.DataFlowService;
-import com.tapdata.tm.inspect.bean.*;
-import com.tapdata.tm.inspect.constant.InspectMethod;
-import com.tapdata.tm.inspect.constant.InspectResultEnum;
+import com.tapdata.tm.inspect.bean.Task;
 import com.tapdata.tm.inspect.constant.InspectStatusEnum;
-import com.tapdata.tm.inspect.constant.Mode;
 import com.tapdata.tm.inspect.dto.InspectDto;
-import com.tapdata.tm.inspect.dto.InspectResultDto;
-import com.tapdata.tm.inspect.entity.InspectEntity;
 import com.tapdata.tm.inspect.repository.InspectRepository;
-import com.tapdata.tm.inspect.vo.InspectDetailVo;
-import com.tapdata.tm.message.constant.Level;
-import com.tapdata.tm.message.constant.MsgTypeEnum;
-import com.tapdata.tm.message.service.MessageService;
-import com.tapdata.tm.messagequeue.dto.MessageQueueDto;
-import com.tapdata.tm.messagequeue.service.MessageQueueService;
-import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
-import com.tapdata.tm.task.constant.SyncType;
-import com.tapdata.tm.userLog.constant.Modular;
-import com.tapdata.tm.userLog.constant.Operation;
-import com.tapdata.tm.userLog.service.UserLogService;
-import com.tapdata.tm.utils.*;
-import com.tapdata.tm.worker.service.WorkerService;
+import com.tapdata.tm.webhook.enums.ConstVariable;
 import lombok.NonNull;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
 import org.bson.types.ObjectId;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.mongodb.core.query.Criteria;
-import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
-import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Function;
-import java.util.stream.Collectors;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
 
 /**
  * @Author:
@@ -89,117 +34,117 @@ public InspectServiceImpl(@NonNull InspectRepository repository) {
         super(repository);
     }
 
+    @Override
+    public Supplier<InspectDto> dataPermissionFindById(ObjectId taskId, Field fields) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
     @Override
     protected void beforeSave(InspectDto dto, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public Map<String, Long> delete(String id, UserDetail user) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public Page<InspectDto> list(Filter filter, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public InspectDto findById(Filter filter, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public void saveInspect(TaskDto taskDto, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public InspectDto createCheckByTask(TaskDto taskDto, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public List<InspectDto> findByTaskIdList(List<String> taskIdList) {
-        Query query = Query.query(Criteria.where("flowId").in(taskIdList).and("is_deleted").ne(true));
-        return findAll(query);
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public UpdateResult deleteByTaskId(String taskId) {
-        return null;
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
-    public InspectDto updateInspectByWhere(Where where, InspectDto updateDto, UserDetail user) {
-        throw new BizException("TapOssNonSupportFunctionException");
-    }
-
-    @Override
-    public InspectDto executeInspect(Where where, InspectDto updateDto, UserDetail user) {
-        throw new BizException("TapOssNonSupportFunctionException");
+    public InspectDto doExecuteInspect(Where where, InspectDto updateDto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public InspectDto updateById(ObjectId objectId, InspectDto inspectDto, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public List<InspectDto> findByName(String name) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public void importData(String json, String upsert, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public void setRepeatInspectTask() {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public UpdateResult updateStatusById(String id, InspectStatusEnum inspectStatusEnum) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public UpdateResult updateStatusByIds(List<ObjectId> idList, InspectStatusEnum status) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public Map<String, Integer> inspectPreview(UserDetail user) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public List<InspectDto> findByStatus(InspectStatusEnum inspectStatusEnum) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public List<InspectDto> findByResult(boolean passed) {
-        throw new BizException("TapOssNonSupportFunctionException");
-    }
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);    }
 
     @Override
     public void cleanDeadInspect() {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public void supplementAlarm(InspectDto inspectDto, UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public List<InspectDto> findAllByIds(List<String> inspectIds) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 
     @Override
     public void fieldHandler(List<Task> tasks,UserDetail userDetail) {
-        throw new BizException("TapOssNonSupportFunctionException");
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
     }
 }
diff --git a/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectTaskServiceOOSImpl.java b/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectTaskServiceOOSImpl.java
new file mode 100644
index 0000000000..b6bdfbc187
--- /dev/null
+++ b/manager/tm-oss/src/main/java/com/tapdata/tm/inspect/service/InspectTaskServiceOOSImpl.java
@@ -0,0 +1,60 @@
+package com.tapdata.tm.inspect.service;
+
+import com.tapdata.tm.base.dto.Where;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.dto.InspectDto;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class InspectTaskServiceOOSImpl implements InspectTaskService {
+    @Override
+    public InspectDto inspectTaskRun(Where where, InspectDto updateDto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public InspectDto inspectTaskStop(String id, InspectDto updateDto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public InspectDto inspectTaskError(String id, InspectDto updateDto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public InspectDto inspectTaskDone(String id, InspectDto updateDto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public InspectDto executeInspect(Where where, InspectDto updateDto, UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public String startInspectTask(InspectDto inspectDto, String processId) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public void stopInspectTask(InspectDto inspectDto) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public List<TaskDto> findTaskList(UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+
+    @Override
+    public List<DataSourceConnectionDto> findConnectionList(UserDetail user) {
+        throw new BizException(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION);
+    }
+}
diff --git a/manager/tm-sdk/pom.xml b/manager/tm-sdk/pom.xml
index 2e849fb384..2ea6d3b14a 100644
--- a/manager/tm-sdk/pom.xml
+++ b/manager/tm-sdk/pom.xml
@@ -22,7 +22,7 @@
 		<dependency>
 			<groupId>io.tapdata</groupId>
 			<artifactId>tapdata-api</artifactId>
-			<version>1.3.6-SNAPSHOT</version>
+			<version>1.3.7-SNAPSHOT</version>
 		</dependency>
 
     <dependency>
diff --git a/manager/tm/src/main/java/com/tapdata/tm/TMApplication.java b/manager/tm/src/main/java/com/tapdata/tm/TMApplication.java
index e2e42b385a..6495102874 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/TMApplication.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/TMApplication.java
@@ -3,6 +3,8 @@
 import com.tapdata.tm.discovery.service.DefaultDataDirectoryService;
 import com.tapdata.tm.ds.service.impl.RepairCreateTimeComponent;
 import com.tapdata.tm.listener.StartupListener;
+import com.tapdata.tm.report.dto.RunsNumBatch;
+import com.tapdata.tm.report.service.UserDataReportService;
 import com.tapdata.tm.task.service.LdpService;
 import com.tapdata.tm.user.dto.UserDto;
 import com.tapdata.tm.user.service.UserService;
@@ -56,6 +58,11 @@ public static void main(String[] args) {
 				.build().run(args);
 		SpringContextHelper.applicationContext = applicationContext;
 
+		UserDataReportService userDataReportService = applicationContext.getBean(UserDataReportService.class);
+		long currentTimeMillis = System.currentTimeMillis();
+		RunsNumBatch runsNumBatch = new RunsNumBatch();
+		runsNumBatch.setTimestamp(currentTimeMillis);
+		userDataReportService.produceData(runsNumBatch);
 		new Thread(()->{
 			RepairCreateTimeComponent repairCreateTimeComponent = applicationContext.getBean("repairCreateTimeComponent", RepairCreateTimeComponent.class);
 			repairCreateTimeComponent.repair();
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java
index 47b9b4ae8c..e9fde0723f 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImpl.java
@@ -1,7 +1,6 @@
 package com.tapdata.tm.ds.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.lang.Assert;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -18,7 +17,6 @@
 import com.tapdata.tm.base.dto.Page;
 import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
-import com.tapdata.tm.base.service.BaseService;
 import com.tapdata.tm.classification.dto.ClassificationDto;
 import com.tapdata.tm.classification.service.ClassificationService;
 import com.tapdata.tm.commons.base.dto.BaseDto;
@@ -41,6 +39,7 @@
 import com.tapdata.tm.ds.dto.UpdateTagsDto;
 import com.tapdata.tm.ds.entity.DataSourceEntity;
 import com.tapdata.tm.ds.repository.DataSourceRepository;
+import com.tapdata.tm.ds.utils.DataSourceServiceUtil;
 import com.tapdata.tm.ds.utils.UriRootConvertUtils;
 import com.tapdata.tm.ds.vo.SupportListVo;
 import com.tapdata.tm.ds.vo.ValidateTableVo;
@@ -57,6 +56,8 @@
 import com.tapdata.tm.metadatainstance.vo.SourceTypeEnum;
 import com.tapdata.tm.modules.dto.ModulesDto;
 import com.tapdata.tm.modules.service.ModulesService;
+import com.tapdata.tm.report.dto.ConfigureSourceBatch;
+import com.tapdata.tm.report.service.UserDataReportService;
 import com.tapdata.tm.proxy.dto.SubscribeDto;
 import com.tapdata.tm.proxy.dto.SubscribeResponseDto;
 import com.tapdata.tm.proxy.service.impl.ProxyService;
@@ -64,7 +65,6 @@
 import com.tapdata.tm.task.service.LdpService;
 import com.tapdata.tm.task.service.LogCollectorService;
 import com.tapdata.tm.task.service.TaskService;
-import com.tapdata.tm.user.entity.User;
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.utils.*;
 import com.tapdata.tm.worker.entity.Worker;
@@ -166,6 +166,8 @@ public class DataSourceServiceImpl extends DataSourceService{
 
 	@Autowired
     private AgentGroupService agentGroupService;
+    @Autowired
+    private UserDataReportService userDataReportService;
 
     public DataSourceServiceImpl(@NonNull DataSourceRepository repository) {
         super(repository);
@@ -183,6 +185,9 @@ public DataSourceConnectionDto add(DataSourceConnectionDto connectionDto, UserDe
         sendTestConnection(connectionDto, updateSchema, submit, userDetail);
         connectionDto.setConfig(null);
         defaultDataDirectoryService.addConnection(connectionDto, userDetail);
+        ConfigureSourceBatch configureSourceBatch = new ConfigureSourceBatch();
+        configureSourceBatch.setPdkId(connectionDto.getDefinitionPdkId());
+        userDataReportService.produceData(configureSourceBatch);
         return connectionDto;
     }
 
@@ -243,6 +248,8 @@ public DataSourceConnectionDto update(UserDetail user, DataSourceConnectionDto u
                 updateDto.setLastStatus(oldConnection.getStatus());
             }
         }
+        //Fix: TAP-2686, After manually specifying the agent and saving it, the specified agent information should not be automatically cleared
+        DataSourceServiceUtil.setAccessNodeInfoFromOldConnectionDto(oldConnection, updateDto);
         Map<String, Object> config = updateDto.getConfig();
         if (oldConnection != null) {
 
@@ -266,7 +273,6 @@ public DataSourceConnectionDto update(UserDetail user, DataSourceConnectionDto u
         }
 
         DataSourceEntity entity = convertToEntity(DataSourceEntity.class, updateDto);
-        entity.setAccessNodeProcessIdList(updateDto.getAccessNodeProcessIdList());
 
         Update update = repository.buildUpdateSet(entity, user);
 
@@ -648,7 +654,7 @@ private void checkMongoUri(DataSourceConnectionDto dto) {
         }
     }
 
-    private void hiddenMqPasswd(DataSourceConnectionDto item) {
+    protected void hiddenMqPasswd(DataSourceConnectionDto item) {
         if (item != null && !isAgentReq() && !Objects.isNull(item.getConfig())
                 && !item.getConfig().isEmpty()) {
             if (item.getConfig().containsKey("password")) {
@@ -663,17 +669,13 @@ private void hiddenMqPasswd(DataSourceConnectionDto item) {
                 String uri = (String) item.getConfig().get("uri");
                 ConnectionString connectionString = null;
                 try {
-                    connectionString = new ConnectionString(uri);
-                } catch (Exception e) {
-                    if (uri.startsWith("mongodb+srv:")) {
-                        try {
-                            connectionString = new ConnectionString(uri.replace("mongodb+srv:", "mongodb:"));
-                        } catch (Exception e1) {
-                            log.error("Parse connection string failed ({}) {}", uri, e.getMessage());
-                        }
-                    } else {
-                        log.error("Parse connection string failed ({}) {}", uri, e.getMessage());
+                    if(uri.startsWith("mongodb+srv:")){
+                        connectionString = new ConnectionString(uri.replace("mongodb+srv:", "mongodb:"));
+                    }else{
+                        connectionString = new ConnectionString(uri);
                     }
+                } catch (Exception e) {
+                    log.error("Parse connection string failed ({}) {}", uri, e.getMessage());
                 }
 
                 if (connectionString != null) {
diff --git a/manager/tm/src/main/java/com/tapdata/tm/ds/utils/DataSourceServiceUtil.java b/manager/tm/src/main/java/com/tapdata/tm/ds/utils/DataSourceServiceUtil.java
new file mode 100644
index 0000000000..e437e1b4d0
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/ds/utils/DataSourceServiceUtil.java
@@ -0,0 +1,24 @@
+package com.tapdata.tm.ds.utils;
+
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+
+public class DataSourceServiceUtil {
+    private DataSourceServiceUtil() {
+
+    }
+
+    public static void setAccessNodeInfoFromOldConnectionDto(DataSourceConnectionDto oldConnection, DataSourceConnectionDto update) {
+        if (null == oldConnection){
+            return;
+        }
+        if (null == update) {
+            return;
+        }
+        if (null == update.getAccessNodeType()) {
+            update.setAccessNodeType(oldConnection.getAccessNodeType());
+        }
+        if (null == update.getAccessNodeProcessId()) {
+            update.setAccessNodeProcessId(oldConnection.getAccessNodeProcessId());
+        }
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectController.java b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectController.java
index 4933b6afd9..c9ee9a3922 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectController.java
@@ -2,25 +2,57 @@
 
 import com.alibaba.fastjson.JSON;
 import com.tapdata.tm.base.controller.BaseController;
-import com.tapdata.tm.base.dto.*;
+import com.tapdata.tm.base.dto.Field;
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.commons.schema.bean.PlatformInfo;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.util.JsonUtil;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.constant.InspectStatusEnum;
 import com.tapdata.tm.inspect.dto.InspectDto;
-import com.tapdata.tm.inspect.service.InspectResultService;
 import com.tapdata.tm.inspect.service.InspectService;
+import com.tapdata.tm.inspect.service.InspectTaskService;
+import com.tapdata.tm.inspect.vo.InspectTaskVo;
+import com.tapdata.tm.permissions.DataPermissionHelper;
+import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
+import com.tapdata.tm.permissions.constants.DataPermissionDataTypeEnums;
+import com.tapdata.tm.permissions.constants.DataPermissionMenuEnums;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.utils.Lists;
+import com.tapdata.tm.utils.MongoUtils;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.enums.ParameterIn;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+import java.util.function.Supplier;
 
 
 /**
@@ -31,13 +63,62 @@
 @Slf4j
 @RestController
 @RequestMapping("/api/Inspects")
+@Setter(onMethod_ = {@Autowired})
 public class InspectController extends BaseController {
-
-    @Autowired
     private InspectService inspectService;
+    private InspectTaskService inspectTaskService;
+    private TaskService taskService;
 
-    @Autowired
-    private InspectResultService inspectResultService;
+    private <T> T dataPermissionUnAuth(DataPermissionActionEnums action, List<DataPermissionActionEnums> need) {
+        throw new BizException("insufficient.permissions",
+                needAction(DataPermissionDataTypeEnums.INSPECT, Lists.newArrayList(action)),
+                needAction(DataPermissionDataTypeEnums.INSPECT, need)
+                );
+    }
+
+    protected String needAction(DataPermissionDataTypeEnums dataTypeEnums, List<DataPermissionActionEnums> need) {
+        StringJoiner joiner = new StringJoiner(", ");
+        need.forEach(a ->
+            joiner.add(String.format("%s.%s", dataTypeEnums.getCollection().toLowerCase(), a.name().toLowerCase()))
+        );
+        return joiner.toString();
+    }
+
+    private <T> T dataPermissionCheckOfMenu(UserDetail userDetail,
+                                            DataPermissionActionEnums actionEnums,
+                                            List<DataPermissionActionEnums> need,
+                                            Supplier<T> supplier) {
+        return DataPermissionHelper.check(userDetail,
+                DataPermissionMenuEnums.INSPECT_TACK,
+                actionEnums,
+                DataPermissionDataTypeEnums.INSPECT,
+                null,
+                supplier,
+                () -> dataPermissionUnAuth(actionEnums, need));
+    }
+
+    private <T> T dataPermissionCheckOfId(HttpServletRequest request,
+                                          UserDetail userDetail,
+                                          ObjectId id,
+                                          DataPermissionActionEnums actionEnums,
+                                          List<DataPermissionActionEnums> need,
+                                          Supplier<T> supplier) {
+        id = Optional.ofNullable(DataPermissionHelper.signDecode(request, id.toHexString())).map(MongoUtils::toObjectId).orElse(id);
+        return DataPermissionHelper.checkOfQuery(
+                userDetail,
+                DataPermissionDataTypeEnums.INSPECT,
+                actionEnums,
+                inspectService.dataPermissionFindById(id, new Field()),
+                (dto) -> DataPermissionMenuEnums.INSPECT_TACK,
+                supplier,
+                () -> dataPermissionUnAuth(actionEnums, need)
+        );
+    }
+
+    @GetMapping("/{currentId}/parent-task-sign")
+    public ResponseMessage<String> dataPermissionTaskSign(@PathVariable String currentId, @RequestParam String parentId) {
+        return success(DataPermissionHelper.signEncode(currentId, parentId));
+    }
 
     /**
      * Create a new instance of the model and persist it into the data source
@@ -75,15 +156,20 @@ public ResponseMessage<InspectDto> save(@RequestBody InspectDto inspect) {
      * @return
      */
     @PatchMapping()
-    public ResponseMessage<InspectDto> updateById(@RequestBody InspectDto inspect) {
-        InspectDto inspectDto = inspectService.updateById(inspect.getId(), inspect, getLoginUser());
-        return success(inspect);
+    public ResponseMessage<InspectDto> updateById(HttpServletRequest request, @RequestBody InspectDto inspect) {
+        InspectDto resultTask = dataPermissionCheckOfId(request,
+                getLoginUser(),
+                inspect.getId(),
+                DataPermissionActionEnums.Edit,
+                Lists.newArrayList(DataPermissionActionEnums.Edit),
+                () -> inspectService.updateById(inspect.getId(), inspect, getLoginUser())
+        );
+        return success(resultTask);
     }
 
 
     /**
-     * @param filterJson
-     * 获取校验任务列表
+     * @param filterJson Get inspect task list
      * @return
      */
     @GetMapping
@@ -92,9 +178,40 @@ public ResponseMessage<Page<InspectDto>> find(@RequestParam(value = "filter", re
         if (filter == null) {
             filter = new Filter();
         }
-        return success(inspectService.list(filter, getLoginUser()));
+        Filter finalFilter = filter;
+        Supplier<Page<InspectDto>> supplier = () -> inspectService.list(finalFilter, getLoginUser());
+        Page<InspectDto> result = Optional.ofNullable(
+                DataPermissionHelper.check(getLoginUser(),
+                        DataPermissionMenuEnums.INSPECT_TACK,
+                        DataPermissionActionEnums.View,
+                        DataPermissionDataTypeEnums.INSPECT,
+                        null,
+                        supplier,
+                        () -> dataPermissionUnAuth(DataPermissionActionEnums.View, Lists.newArrayList(DataPermissionActionEnums.View)))
+        )
+                .orElseGet(() -> inspectService.list(finalFilter, getLoginUser()));
+        return success(result);
     }
 
+    /**
+     * Get inspect task details
+     *
+     * @return
+     */
+    @Operation(summary = "Find a model instance by {{id}} from the data source")
+    @GetMapping("find/{id}")
+    public ResponseMessage<InspectDto> findOneById(HttpServletRequest request, @PathVariable("id") String id) {
+        InspectDto inspectDto = dataPermissionCheckOfId(
+                request,
+                getLoginUser(),
+                MongoUtils.toObjectId(id),
+                DataPermissionActionEnums.View,
+                Lists.newArrayList(DataPermissionActionEnums.View),
+                () -> inspectService.findOne(Query.query(Criteria.where("_id").is(MongoUtils.toObjectId(id))))
+        );
+        inspectDto.setTaskDto(findTaskDto(inspectDto.getFlowId()));
+        return success(inspectDto);
+    }
 
     /**
      * 获取校验详情
@@ -105,7 +222,13 @@ public ResponseMessage<Page<InspectDto>> find(@RequestParam(value = "filter", re
     @GetMapping("findById")
     public ResponseMessage<InspectDto> findById(@RequestParam("filter") String filterStr) {
         Filter filter = parseFilter(filterStr);
-        InspectDto inspectDto = inspectService.findById(filter, getLoginUser());
+        InspectDto inspectDto = dataPermissionCheckOfMenu(
+                getLoginUser(),
+                DataPermissionActionEnums.View,
+                Lists.newArrayList(DataPermissionActionEnums.View),
+                () -> inspectService.findById(filter, getLoginUser())
+        );
+        inspectDto.setTaskDto(findTaskDto(inspectDto.getFlowId()));
         return success(inspectDto);
     }
 
@@ -118,8 +241,16 @@ public ResponseMessage<InspectDto> findById(@RequestParam("filter") String filte
      */
     @Operation(summary = "Delete a model instance by {{id}} from the data source")
     @DeleteMapping("{id}")
-    public ResponseMessage<Map<String, Long>> delete(@PathVariable("id") String id) {
-        return success(inspectService.delete(id, getLoginUser()));
+    public ResponseMessage<Map<String, Long>> delete(HttpServletRequest request, @PathVariable("id") String id) {
+        Map<String, Long> deleteResult = dataPermissionCheckOfId(
+                request,
+                getLoginUser(),
+                MongoUtils.toObjectId(id),
+                DataPermissionActionEnums.Delete,
+                Lists.newArrayList(DataPermissionActionEnums.Delete),
+                () -> inspectService.delete(id, getLoginUser())
+        );
+        return success(deleteResult);
     }
 
 
@@ -140,28 +271,99 @@ public ResponseMessage<InspectDto> findOne(
         if (filter == null) {
             filter = new Filter();
         }
-        return success(inspectService.findOne(filter, getLoginUser()));
+        Filter finalFilter = filter;
+        InspectDto inspectDto = dataPermissionCheckOfMenu(
+                getLoginUser(),
+                DataPermissionActionEnums.View,
+                Lists.newArrayList(DataPermissionActionEnums.View),
+                () -> inspectService.findOne(finalFilter, getLoginUser())
+        );
+        inspectDto.setTaskDto(findTaskDto(inspectDto.getFlowId()));
+        return success(inspectDto);
+    }
+
+    protected InspectTaskVo findTaskDto(String taskId) {
+        ObjectId id = MongoUtils.toObjectId(taskId);
+        InspectTaskVo vo = new InspectTaskVo();
+        if (null != id) {
+            Query query = Query.query(Criteria.where("_id").is(id));
+            query.fields().include("id", "name", "syncType", "dag");
+            TaskDto dto = Optional.ofNullable(taskService.findOne(query)).orElse(new TaskDto());
+            vo.setDag(dto.getDag());
+            vo.setId(dto.getId());
+            vo.setName(dto.getName());
+            vo.setSyncType(dto.getSyncType());
+        }
+        return vo;
     }
 
 
     /**
-     * 页面点击  和engine更新inspect的时候，都会调用这个方法
+     * 页面点击 更新inspect的时候，会调用这个方法
      *
      * @param whereJson
      * @param inspect
      * @return
      */
-    @Operation(summary = "页面点击  和 engine 更新inspect的时候，都会调用这个方法")
-    @PostMapping("update")
-    public ResponseMessage updateByWhere(@RequestParam("where") String whereJson, @RequestBody InspectDto inspect) throws Exception {
-        log.info("InspectController--updateByWhere。whereJson：{}， InspectDto：{} ", whereJson, JSON.toJSONString(inspect));
+    @Operation(summary = "页面点击 更新inspect的时候，会调用这个方法")
+    @PostMapping("execute")
+    public ResponseMessage<InspectDto> updateByWhere(HttpServletRequest request, @RequestParam("where") String whereJson, @RequestBody InspectDto inspect) {
+        log.info("Inspect Controller -- do execute -  where json：{}, inspect：{} ", whereJson, JSON.toJSONString(inspect));
         whereJson = whereJson.replace("\"_id\"", "\"id\"");
         Where where = parseWhere(whereJson);
-        InspectDto inspectDto = inspectService.updateInspectByWhere(where, inspect, getLoginUser());
+        ObjectId id = Optional.ofNullable(inspect.getId()).orElse(MongoUtils.toObjectId(String.valueOf(where.get("id"))));
+        String status = inspect.getStatus();
+        DataPermissionActionEnums verifyAction = null;
+        List<DataPermissionActionEnums> needActionEnums = Lists.newArrayList(DataPermissionActionEnums.Edit);
+        //按照操作类型来确认需要校验的权限
+        if (InspectStatusEnum.SCHEDULING.getValue().equals(status)) {
+            verifyAction = DataPermissionActionEnums.Start;
+            needActionEnums.add(verifyAction);
+        } else if (InspectStatusEnum.STOPPING.getValue().equals(status)) {
+            verifyAction = DataPermissionActionEnums.Stop;
+            needActionEnums.add(verifyAction);
+        }
+        //检查是否有编辑权限
+        dataPermissionCheckOfId(
+                request,
+                getLoginUser(),
+                id,
+                DataPermissionActionEnums.Edit,
+                needActionEnums,
+                () -> inspect);
+
+        InspectDto inspectDto;
+        if (null != verifyAction) {
+            inspectDto = dataPermissionCheckOfId(
+                    request,
+                    getLoginUser(),
+                    id,
+                    verifyAction,
+                    needActionEnums,
+                    () -> inspectService.doExecuteInspect(where, inspect, getLoginUser())
+            );
+        } else {
+            inspectDto = inspectService.doExecuteInspect(where, inspect, getLoginUser());
+        }
         return success(inspectDto);
     }
 
 
+    @Operation(summary = "engine 更新inspect的时候，都会调用这个方法")
+    @PostMapping("update")
+    public ResponseMessage<InspectDto> updateByWhere(@RequestParam("where") String whereJson, @RequestBody InspectDto inspect) {
+        log.info("engine 调用了 updateByWhere.  where :{}  InspectDto:{} ", whereJson, JsonUtil.toJson(inspect));
+        whereJson = whereJson.replace("\"_id\"", "\"id\"");
+        Where where = parseWhere(whereJson);
+        return success(inspectService.doExecuteInspect(where, inspect, getLoginUser()));
+    }
+
+    @Operation(summary = "详情页查询任务列表的时候，都会调用这个方法")
+    @GetMapping("task-list")
+    public ResponseMessage<List<TaskDto>> getTaskDtoList() {
+        return success(inspectTaskService.findTaskList(getLoginUser()));
+    }
+
     /**
      * 给engin调用，更新inspect状态的
      * engin  调用该方法，result  总是返回空ss字符串，导致inspect表更新总是不正确，因此需要另作调整
diff --git a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java
index 27e58c89e8..79988d043e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/inspect/controller/InspectResultController.java
@@ -1,26 +1,49 @@
 package com.tapdata.tm.inspect.controller;
 
 import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollUtil;
 import com.alibaba.fastjson.JSON;
-import com.tapdata.tm.commons.util.JsonUtil;
 import com.tapdata.tm.base.controller.BaseController;
-import com.tapdata.tm.base.dto.*;
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.base.dto.Where;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.commons.util.JsonUtil;
 import com.tapdata.tm.inspect.bean.GroupByFilter;
 import com.tapdata.tm.inspect.dto.InspectResultDto;
 import com.tapdata.tm.inspect.param.SaveInspectResultParam;
 import com.tapdata.tm.inspect.param.UpdateInspectResultParam;
 import com.tapdata.tm.inspect.service.InspectResultService;
+import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
+import com.tapdata.tm.permissions.constants.DataPermissionDataTypeEnums;
+import com.tapdata.tm.permissions.service.DataPermissionService;
 import com.tapdata.tm.utils.MongoUtils;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.enums.ParameterIn;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.bson.types.ObjectId;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
 
 
 /**
@@ -31,12 +54,10 @@
 @RestController
 @RequestMapping("/api/InspectResults")
 @Slf4j
+@Setter(onMethod_ = {@Autowired})
 public class InspectResultController extends BaseController {
-
-    @Autowired
     private InspectResultService inspectResultService;
-
-
+    private DataPermissionService dataPermissionService;
     /**
      *  engine 通过这个接口 保存校验结果，，这时候会把inspect的内容整个传过来，保存到数据库中。
      *  因为校验历史，需要看到每一次校验的内容，所以InspectReuslt 里面还得保存Inspect的具体数据，不能去掉
@@ -67,6 +88,24 @@ public ResponseMessage<InspectResultDto> update(@RequestBody InspectResultDto in
         return success(save);
     }
 
+    protected void checkInspect(String inspectId, DataPermissionActionEnums... enums) {
+        Set<String> dataActions = dataPermissionService.findDataActions(getLoginUser(), DataPermissionDataTypeEnums.INSPECT, MongoUtils.toObjectId(inspectId));
+        if (CollUtil.isEmpty(dataActions)) {
+            throw new BizException("insufficient.permissions", "", needAction(DataPermissionDataTypeEnums.INSPECT, enums));
+        }
+        for (DataPermissionActionEnums dataAction : enums) {
+            if (!dataActions.contains(dataAction.name())) {
+                throw new BizException("insufficient.permissions", needAction(DataPermissionDataTypeEnums.INSPECT, dataAction), needAction(DataPermissionDataTypeEnums.INSPECT, enums));
+            }
+        }
+    }
+    protected String needAction(DataPermissionDataTypeEnums dataTypeEnums, DataPermissionActionEnums... need) {
+        StringJoiner joiner = new StringJoiner(", ");
+        for (DataPermissionActionEnums a : need) {
+            joiner.add(String.format("%s.%s", dataTypeEnums.getCollection().toLowerCase(), a.name().toLowerCase()));
+        }
+        return joiner.toString();
+    }
 
     /**
      * 查询校验结果列表
@@ -90,6 +129,15 @@ public ResponseMessage<Page<InspectResultDto>> find(
                 inspectGroupByFirstCheckId = filter.getInspectGroupByFirstCheckId();
             }
         }
+        String inspectId = String.valueOf(filter.getWhere().get("inspect_id"));
+        if (null == MongoUtils.toObjectId(inspectId)) {
+            InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(MongoUtils.toObjectId(String.valueOf(filter.getWhere().get("id"))))));
+            if (null == one || null == one.getInspect_id()) {
+                throw new BizException("inspect.result.not.exists", filter.getWhere().get("id"));
+            }
+            inspectId = one.getInspect_id();
+        }
+        checkInspect(inspectId, DataPermissionActionEnums.View);
         return success(inspectResultService.find(filter, getLoginUser(), inspectGroupByFirstCheckId));
     }
 
@@ -101,6 +149,12 @@ public ResponseMessage<Page<InspectResultDto>> find(
     @Operation(summary = "Replace an existing model instance or insert a new one into the data source")
     @PutMapping
     public ResponseMessage<InspectResultDto> put(@RequestBody InspectResultDto inspectResult) {
+        InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(inspectResult.getId())));
+        if (null == one || null == one.getInspect_id()) {
+            throw new BizException("inspect.result.not.exists", inspectResult.getId());
+        }
+        String inspectId = one.getInspect_id();
+        checkInspect(inspectId, DataPermissionActionEnums.Edit);
         return success(inspectResultService.replaceOrInsert(inspectResult, getLoginUser()));
     }
 
@@ -115,6 +169,12 @@ public ResponseMessage<InspectResultDto> put(@RequestBody InspectResultDto inspe
     @PatchMapping("{id}")
     public ResponseMessage<InspectResultDto> updateById(@PathVariable("id") String id, @RequestBody InspectResultDto inspectResult) {
         log.info("InspectResultController--updateById。 inspectResult：{} ",  JSON.toJSONString(inspectResult));
+        InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(inspectResult.getId())));
+        if (null == one || null == one.getInspect_id()) {
+            throw new BizException("inspect.result.not.exists", id);
+        }
+        String inspectId = one.getInspect_id();
+        checkInspect(inspectId, DataPermissionActionEnums.Edit);
         inspectResult.setId(MongoUtils.toObjectId(id));
         return success(inspectResultService.save(inspectResult, getLoginUser()));
     }
@@ -129,7 +189,13 @@ public ResponseMessage<InspectResultDto> updateById(@PathVariable("id") String i
     @GetMapping("findById")
     public ResponseMessage<InspectResultDto> findById(@RequestParam("filter") String filter) {
         Filter f = parseFilter(filter);
-        return success(inspectResultService.findById(f,getLoginUser()));
+        InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(f.getWhere().get("id"))));
+        if (null == one || null == one.getInspect_id()) {
+            throw new BizException("inspect.result.not.exists", f.getWhere().get("id"));
+        }
+        String inspectId = one.getInspect_id();
+        checkInspect(inspectId, DataPermissionActionEnums.View);
+        return success(inspectResultService.findById(f, getLoginUser()));
     }
 
     /**
@@ -140,6 +206,12 @@ public ResponseMessage<InspectResultDto> findById(@RequestParam("filter") String
     @Operation(summary = "Delete a model instance by {{id}} from the data source")
     @DeleteMapping("{id}")
     public ResponseMessage<Void> delete(@PathVariable("id") String id) {
+        InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(id)));
+        if (null == one || null == one.getInspect_id()) {
+            throw new BizException("inspect.result.not.exists", id);
+        }
+        String inspectId = one.getInspect_id();
+        checkInspect(inspectId, DataPermissionActionEnums.Delete);
         inspectResultService.deleteById(MongoUtils.toObjectId(id), getLoginUser());
         return success();
     }
@@ -161,6 +233,15 @@ public ResponseMessage<InspectResultDto> findOne(
         if (filter == null) {
             filter = new Filter();
         }
+        Object id = filter.getWhere().get("id");
+        if (null != id) {
+            InspectResultDto one = inspectResultService.findOne(Query.query(Criteria.where("_id").is(id)));
+            if (null == one || null == one.getInspect_id()) {
+                throw new BizException("inspect.result.not.exists", id);
+            }
+            String inspectId = one.getInspect_id();
+            checkInspect(inspectId, DataPermissionActionEnums.Delete);
+        }
         return success(inspectResultService.findOne(filter, getLoginUser()));
     }
 
@@ -175,6 +256,8 @@ public ResponseMessage<Map<String, Long>> updateByWhere(@RequestParam("where") S
         log.info("InspectResultController--updateByWhere 。whereJson：{}， InspectDto：{} ", whereJson, JSON.toJSONString(updateInspectResultParam));
         Where where = parseWhere(whereJson);
         InspectResultDto inspectResultDto= BeanUtil.copyProperties(updateInspectResultParam,InspectResultDto.class);
+        String inspectId = inspectResultDto.getInspect_id();
+        checkInspect(inspectId, DataPermissionActionEnums.Edit);
         long count = inspectResultService.updateByWhere(where, inspectResultDto, getLoginUser());
         HashMap<String, Long> countValue = new HashMap<>();
         countValue.put("count", count);
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/MetadataDefinitionController.java b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/MetadataDefinitionController.java
index d4c405377e..9bd9ba5ec3 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/MetadataDefinitionController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/controller/MetadataDefinitionController.java
@@ -1,5 +1,6 @@
 package com.tapdata.tm.metadatadefinition.controller;
 
+import com.tapdata.tm.Settings.service.SettingsService;
 import com.tapdata.tm.base.controller.BaseController;
 import com.tapdata.tm.base.dto.*;
 import com.tapdata.tm.metadatadefinition.dto.MetadataDefinitionDto;
@@ -11,6 +12,9 @@
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.enums.ParameterIn;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.tapdata.utils.AppType;
+import lombok.Setter;
+import org.bson.Document;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -25,12 +29,11 @@
  */
 @Tag(name = "MetadataDefinition", description = "MetadataDefinition相关接口")
 @RestController
-@RequestMapping(value = {"/api/MetadataDefinition","/api/MetadataDefinitions"})
+@RequestMapping(value = {"/api/MetadataDefinition","/api/MetadataDefinitions", "/api/metadata-definitions"})
+@Setter(onMethod_ = {@Autowired})
 public class MetadataDefinitionController extends BaseController {
-
-    @Autowired
     private MetadataDefinitionService metadataDefinitionService;
-
+    private SettingsService settingsService;
     /**
      * 创建分类
      * @param metadataDefinition
@@ -86,6 +89,7 @@ public ResponseMessage<Page<MetadataDefinitionDto>> find(
      */
     @Operation(summary = "Find all instances of the model matched by filter from the data source")
     @GetMapping("and/child_account")
+    @Deprecated
     public ResponseMessage<Page<MetadataDefinitionDto>> findAndChildAccount(
             @Parameter(in = ParameterIn.QUERY,
                     description = "Filter defining fields, where, sort, skip, and limit - must be a JSON-encoded string (`{\"where\":{\"something\":\"value\"},\"fields\":{\"something\":true|false},\"sort\": [\"name desc\"],\"page\":1,\"size\":20}`)."
@@ -98,7 +102,17 @@ public ResponseMessage<Page<MetadataDefinitionDto>> findAndChildAccount(
 
         //数据目录不需要分页
         filter.setLimit(10000);
-        return success(metadataDefinitionService.findAndChildAccount(filter, getLoginUser()));
+        return success(metadataDefinitionService.findAndChildAccount(filter, settingsService.isCloud(), getLoginUser()));
+    }
+
+    @Operation(summary = "Find all MetadataDefinition of the model matched by filter from the data source")
+    @GetMapping("all-tag/{type}")
+    public ResponseMessage<Page<MetadataDefinitionDto>> findAllMetadataDefinition(@PathVariable("type") String type) {
+        Filter filter = new Filter();
+        Where where = Where.where("or", Lists.newArrayList(new Document().append("item_type", type)));
+        filter.setWhere(where);
+        filter.setLimit(10000);
+        return success(metadataDefinitionService.findAndChildAccount(filter, !settingsService.isCloud(), getLoginUser()));
     }
 
     /**
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/repository/MetadataDefinitionRepository.java b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/repository/MetadataDefinitionRepository.java
index 2a39c3964f..b5868c89b6 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/repository/MetadataDefinitionRepository.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/repository/MetadataDefinitionRepository.java
@@ -14,7 +14,9 @@
 
 import java.util.List;
 
-import static com.tapdata.tm.utils.MongoUtils.*;
+import static com.tapdata.tm.utils.MongoUtils.applyField;
+import static com.tapdata.tm.utils.MongoUtils.applySort;
+import static com.tapdata.tm.utils.MongoUtils.buildCriteria;
 
 /**
  * @Author:
@@ -48,7 +50,7 @@ public Query applyUserDetail(Query query, UserDetail userDetail) {
         return query;
     }
 
-    public List<MetadataDefinitionEntity> findAllAndChildAccount(Filter filter, UserDetail userDetail) {
+    public List<MetadataDefinitionEntity> findAllAndChildAccount(Filter filter, boolean searchAll, UserDetail userDetail) {
 
         if (filter == null)
             filter = new Filter();
@@ -67,7 +69,9 @@ public List<MetadataDefinitionEntity> findAllAndChildAccount(Filter filter, User
         applyField(query, filter.getFields());
         applySort(query, filter.getSort());
 
-        super.applyUserDetail(query, userDetail);
+        if (!searchAll) {
+            super.applyUserDetail(query, userDetail);
+        }
 
         return mongoOperations.find(query, entityInformation.getJavaType(), entityInformation.getCollectionName());
     }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/service/MetadataDefinitionService.java b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/service/MetadataDefinitionService.java
index 27674df0d1..cc63e38e8d 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/service/MetadataDefinitionService.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/metadatadefinition/service/MetadataDefinitionService.java
@@ -11,6 +11,7 @@
 import com.tapdata.tm.commons.base.dto.BaseDto;
 import com.tapdata.tm.commons.schema.DataSourceDefinitionDto;
 import com.tapdata.tm.commons.schema.Tag;
+import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.discovery.service.DiscoveryService;
 import com.tapdata.tm.ds.entity.DataSourceEntity;
 import com.tapdata.tm.ds.service.impl.DataSourceDefinitionService;
@@ -18,7 +19,6 @@
 import com.tapdata.tm.metadatadefinition.entity.MetadataDefinitionEntity;
 import com.tapdata.tm.metadatadefinition.param.BatchUpdateParam;
 import com.tapdata.tm.metadatadefinition.repository.MetadataDefinitionRepository;
-import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
 import com.tapdata.tm.modules.entity.ModulesEntity;
 import com.tapdata.tm.task.constant.LdpDirEnum;
@@ -37,7 +37,13 @@
 import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.stereotype.Service;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -294,14 +300,14 @@ private List<MetadataDefinitionDto> findChild(List<MetadataDefinitionDto> metada
     }
 
 
-    public Page<MetadataDefinitionDto> findAndChildAccount(Filter filter, UserDetail user) {
+    public Page<MetadataDefinitionDto> findAndChildAccount(Filter filter, boolean searchAll, UserDetail user) {
         if (filter == null) {
             filter = new Filter();
         }
 
-        List<MetadataDefinitionEntity> entityList = repository.findAllAndChildAccount(filter, user);
+        List<MetadataDefinitionEntity> entityList = repository.findAllAndChildAccount(filter, searchAll, user);
 
-        long total = repository.count(filter.getWhere(), user);
+        long total = searchAll ?  repository.count(filter.getWhere()) : repository.count(filter.getWhere(), user);
 
         List<MetadataDefinitionDto> items = convertToDto(entityList, dtoClass);
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java b/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java
index 81ca77bba3..16de223506 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/monitor/service/MeasurementServiceV2Impl.java
@@ -40,7 +40,17 @@
 import org.springframework.data.mongodb.MongoExpression;
 import org.springframework.data.mongodb.core.BulkOperations;
 import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.data.mongodb.core.aggregation.*;
+import org.springframework.data.mongodb.core.aggregation.Aggregation;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpression;
+import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
+import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
+import org.springframework.data.mongodb.core.aggregation.AggregationResults;
+import org.springframework.data.mongodb.core.aggregation.Fields;
+import org.springframework.data.mongodb.core.aggregation.GroupOperation;
+import org.springframework.data.mongodb.core.aggregation.LimitOperation;
+import org.springframework.data.mongodb.core.aggregation.MatchOperation;
+import org.springframework.data.mongodb.core.aggregation.SortOperation;
+import org.springframework.data.mongodb.core.aggregation.UnwindOperation;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.data.mongodb.core.query.Update;
@@ -48,7 +58,16 @@
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/dto/ConfigureSourceBatch.java b/manager/tm/src/main/java/com/tapdata/tm/report/dto/ConfigureSourceBatch.java
new file mode 100644
index 0000000000..05aee7be7f
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/dto/ConfigureSourceBatch.java
@@ -0,0 +1,17 @@
+package com.tapdata.tm.report.dto;
+
+import lombok.Data;
+
+@Data
+public class ConfigureSourceBatch {
+    private String machineId;
+
+    private String pdkId;
+    @Override
+    public String toString() {
+        return "{" +
+                "\"machineId\": \"" + machineId + "\"," +
+                "\"pdkId\": \""+ pdkId + "\""+
+                "}";
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/dto/RunDaysBatch.java b/manager/tm/src/main/java/com/tapdata/tm/report/dto/RunDaysBatch.java
new file mode 100644
index 0000000000..3fca02bc81
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/dto/RunDaysBatch.java
@@ -0,0 +1,14 @@
+package com.tapdata.tm.report.dto;
+
+import lombok.Data;
+
+@Data
+public class RunDaysBatch {
+    private String machineId;
+    @Override
+    public String toString() {
+        return "{" +
+                "\"machineId\": \"" + machineId + "\"" +
+                "}";
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/dto/RunsNumBatch.java b/manager/tm/src/main/java/com/tapdata/tm/report/dto/RunsNumBatch.java
new file mode 100644
index 0000000000..6981fa57dc
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/dto/RunsNumBatch.java
@@ -0,0 +1,17 @@
+package com.tapdata.tm.report.dto;
+
+import lombok.Data;
+
+@Data
+public class RunsNumBatch {
+    private String machineId;
+    private long timestamp;
+
+    @Override
+    public String toString() {
+        return "{" +
+                "\"machineId\": \"" + machineId + "\"," +
+                "\"timestamp\": \""+ timestamp + "\""+
+                "}";
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/dto/TasksNumBatch.java b/manager/tm/src/main/java/com/tapdata/tm/report/dto/TasksNumBatch.java
new file mode 100644
index 0000000000..1c84f2686d
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/dto/TasksNumBatch.java
@@ -0,0 +1,16 @@
+package com.tapdata.tm.report.dto;
+
+import lombok.Data;
+
+@Data
+public class TasksNumBatch {
+    private String machineId;
+    private String taskType;
+    @Override
+    public String toString() {
+        return "{" +
+                "\"machineId\": \"" + machineId + "\"," +
+                "\"taskType\": \""+ taskType + "\""+
+                "}";
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/dto/UniqueInstallBatch.java b/manager/tm/src/main/java/com/tapdata/tm/report/dto/UniqueInstallBatch.java
new file mode 100644
index 0000000000..41a037fb35
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/dto/UniqueInstallBatch.java
@@ -0,0 +1,16 @@
+package com.tapdata.tm.report.dto;
+
+import lombok.Data;
+
+@Data
+public class UniqueInstallBatch {
+    private String machineId;
+    private long timestamp;
+    @Override
+    public String toString() {
+        return "{" +
+                "\"machineId\": \"" + machineId + "\"," +
+                "\"timestamp\": \""+ timestamp + "\""+
+                "}";
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/schedule/RunDaysSchedule.java b/manager/tm/src/main/java/com/tapdata/tm/report/schedule/RunDaysSchedule.java
new file mode 100644
index 0000000000..5349fae717
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/schedule/RunDaysSchedule.java
@@ -0,0 +1,21 @@
+package com.tapdata.tm.report.schedule;
+
+import com.tapdata.tm.report.dto.RunDaysBatch;
+import com.tapdata.tm.report.service.UserDataReportService;
+
+import lombok.Setter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+@Setter(onMethod_ = {@Autowired})
+public class RunDaysSchedule {
+    private UserDataReportService userDataReportService;
+
+    @Scheduled(cron = "0 0 * * * ?")
+    public void schedule() {
+        Thread.currentThread().setName(getClass().getSimpleName() + "-schedule");
+        userDataReportService.produceData(new RunDaysBatch());
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/service/UserDataReportService.java b/manager/tm/src/main/java/com/tapdata/tm/report/service/UserDataReportService.java
new file mode 100644
index 0000000000..6d50475506
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/service/UserDataReportService.java
@@ -0,0 +1,107 @@
+package com.tapdata.tm.report.service;
+
+import com.tapdata.tm.report.dto.*;
+import com.tapdata.tm.report.service.platform.ReportPlatform;
+import com.tapdata.tm.report.util.UserDataReportUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.config.RequestConfig;
+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.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.concurrent.*;
+
+@Service
+@Slf4j
+public class UserDataReportService {
+    public static final String CONFIGURE_DATASOURCE_EVENT = "configure_datasource_event";
+    public static final String RUNS_NUM_EVENT = "runs_num_event";
+    public static final String RUN_DAYS_EVENT = "run_days_event";
+    public static final String UNIQUE_INSTALL_EVENT = "unique_install_event";
+    public static final String TASKS_NUM_EVENT = "tasks_num_event";
+
+    private static String machineId = UserDataReportUtils.generateMachineId();
+
+    @Value("${report.data.oss}")
+    private boolean acceptReportData;
+    protected static LinkedBlockingQueue reportQueue = new LinkedBlockingQueue(100);
+    @Autowired
+    private ReportPlatform reportPlatform;
+
+    @PostConstruct
+    protected void initReportDataThread(){
+        if (!acceptReportData) return;
+        ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
+        CompletableFuture.runAsync(() -> {
+            Thread.currentThread().setName("report-user-data-thread");
+            Object reportData;
+            while (!Thread.currentThread().isInterrupted()){
+                try {
+                    reportData = reportQueue.poll(1, TimeUnit.SECONDS);
+                    if (null == reportData) continue;
+                    consumeData(reportData);
+                }catch (InterruptedException e){
+                    Thread.currentThread().interrupt();
+                } catch (Exception e){
+                    log.info("report event poll failed", e);
+                }
+            }
+        }, executorService);
+    }
+    protected void consumeData(Object reportData) {
+        if (reportData instanceof RunsNumBatch){
+            runsNum((RunsNumBatch) reportData);
+        } else if (reportData instanceof RunDaysBatch){
+            runDays((RunDaysBatch) reportData);
+        } else if (reportData instanceof UniqueInstallBatch){
+            uniqueInstall((UniqueInstallBatch) reportData);
+        } else if (reportData instanceof ConfigureSourceBatch){
+            configureDatasource((ConfigureSourceBatch) reportData);
+        } else if (reportData instanceof TasksNumBatch){
+            tasksNum((TasksNumBatch) reportData);
+        }
+    }
+
+    public void produceData(Object object){
+        if (!acceptReportData) return;
+        try {
+            reportQueue.offer(object, 100, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    protected void runsNum(RunsNumBatch runsNumBatch) {
+        if (!acceptReportData) return;
+        runsNumBatch.setMachineId(machineId);
+        reportPlatform.sendRequest(RUNS_NUM_EVENT, runsNumBatch.toString());
+    }
+    protected void runDays(RunDaysBatch runDaysBatch) {
+        if (!acceptReportData) return;
+        runDaysBatch.setMachineId(machineId);
+        reportPlatform.sendRequest(RUN_DAYS_EVENT, runDaysBatch.toString());
+    }
+    protected void uniqueInstall(UniqueInstallBatch uniqueInstallBatch) {
+        if (!acceptReportData) return;
+        uniqueInstallBatch.setMachineId(machineId);
+        reportPlatform.sendRequest(UNIQUE_INSTALL_EVENT, uniqueInstallBatch.toString());
+    }
+    protected void configureDatasource(ConfigureSourceBatch configureSourceBatch) {
+        if (!acceptReportData) return;
+        configureSourceBatch.setMachineId(machineId);
+        reportPlatform.sendRequest(CONFIGURE_DATASOURCE_EVENT, configureSourceBatch.toString());
+    }
+    protected void tasksNum(TasksNumBatch tasksNumBatch) {
+        if (!acceptReportData) return;
+        tasksNumBatch.setMachineId(machineId);
+        reportPlatform.sendRequest(TASKS_NUM_EVENT, tasksNumBatch.toString());
+    }
+
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/service/platform/GoogleAnalyticsPlatform.java b/manager/tm/src/main/java/com/tapdata/tm/report/service/platform/GoogleAnalyticsPlatform.java
new file mode 100644
index 0000000000..309ba2a7e9
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/service/platform/GoogleAnalyticsPlatform.java
@@ -0,0 +1,78 @@
+package com.tapdata.tm.report.service.platform;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.config.RequestConfig;
+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.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+public class GoogleAnalyticsPlatform implements ReportPlatform{
+    public static final String CONNECT_TIMEOUT = System.getenv().getOrDefault("GOOGLE_CLIENT_CONNECT_TIMEOUT","5000");
+    public static final String SOCKET_TIMEOUT = System.getenv().getOrDefault("GOOGLE_CLIENT_SOCKET_TIMEOUT","5000");
+    public static final String CONNECTION_REQUEST_TIMEOUT = System.getenv().getOrDefault("GOOGLE_CLIENT_CONNECTION_REQUEST_TIMEOUT","5000");
+    @Value("${report.url.measurementId}")
+    private String measurementId;
+    @Value("${report.url.apiSecret}")
+    private String apiSecret;
+    @Value("${report.url.clientId}")
+    private String clientId;
+    private final CloseableHttpClient client;
+
+    protected CloseableHttpClient getClient() {
+        return client;
+    }
+
+    public GoogleAnalyticsPlatform(){
+        RequestConfig requestConfig = RequestConfig.custom()
+                .setConnectTimeout(processParamIfNeed(CONNECT_TIMEOUT))
+                .setSocketTimeout(processParamIfNeed(SOCKET_TIMEOUT))
+                .setConnectionRequestTimeout(processParamIfNeed(CONNECTION_REQUEST_TIMEOUT))
+                .build();
+
+        HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(3, true);
+        client = HttpClientBuilder.create()
+                .setDefaultRequestConfig(requestConfig)
+                .evictExpiredConnections()
+                .setRetryHandler(retryHandler)
+                .build();
+    }
+
+    @Override
+    public void sendRequest(String eventName, String params) {
+        String url = "https://www.google-analytics.com/mp/collect?measurement_id=" + measurementId + "&api_secret=" + apiSecret;
+
+        // build JSON data
+        String jsonBody = "{\n" +
+                "    \"client_id\": \"%s\",\n" +
+                "    \"events\": [{\n" +
+                "        \"name\": \"%s\",\n" +
+                "        \"params\": %s\n" +
+                "    }]\n" +
+                "}";
+        try {
+            jsonBody = String.format(jsonBody, clientId, eventName, params);
+            HttpPost httpPost = new HttpPost(url);
+            StringEntity entity = new StringEntity(jsonBody);
+            httpPost.setEntity(entity);
+            httpPost.setHeader("Content-Type", "application/json");
+            client.execute(httpPost);
+        } catch (Exception e) {
+            log.info("report user data failed", e);
+        }
+    }
+    protected int processParamIfNeed(String value){
+        int param = 5000;
+        try {
+            return Integer.parseInt(value);
+        }catch (Exception e){
+            return param;
+        }
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/service/platform/ReportPlatform.java b/manager/tm/src/main/java/com/tapdata/tm/report/service/platform/ReportPlatform.java
new file mode 100644
index 0000000000..53413aa4d1
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/service/platform/ReportPlatform.java
@@ -0,0 +1,5 @@
+package com.tapdata.tm.report.service.platform;
+
+public interface ReportPlatform {
+    void sendRequest(String eventName, String params);
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/report/util/UserDataReportUtils.java b/manager/tm/src/main/java/com/tapdata/tm/report/util/UserDataReportUtils.java
new file mode 100644
index 0000000000..8e81b1e37b
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/report/util/UserDataReportUtils.java
@@ -0,0 +1,42 @@
+package com.tapdata.tm.report.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+@Slf4j
+public class UserDataReportUtils {
+
+    public static String PARAM_MAX_LENGTH = System.getenv().getOrDefault("PRELOAD_SCHEMA_WAIT_TIME","100");
+    private UserDataReportUtils(){
+    }
+
+    public static String generateMachineId() {
+        List<String> machineIdList = new ArrayList<>();
+        try {
+            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+            while (networkInterfaces.hasMoreElements()) {
+                NetworkInterface networkInterface = networkInterfaces.nextElement();
+                byte[] mac = networkInterface.getHardwareAddress();
+                if (mac != null) {
+                    StringBuilder macAddress = new StringBuilder();
+                    for (int i = 0; i < mac.length; i++) {
+                        macAddress.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
+                    }
+                    machineIdList.add(macAddress.toString());
+                }
+            }
+        } catch (SocketException e) {
+            log.error("get mac address failed", e);
+        }
+        String machineId = machineIdList.toString();
+        int lengthLimit = Integer.parseInt(PARAM_MAX_LENGTH);
+        if (null != machineId && machineId.length() > lengthLimit){
+            machineId = machineId.substring(0,lengthLimit);
+        }
+        return machineId;
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/main/java/com/tapdata/tm/schedule/ChartSchedule.java b/manager/tm/src/main/java/com/tapdata/tm/schedule/ChartSchedule.java
index 768f9836a2..cd7380ccef 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/schedule/ChartSchedule.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/schedule/ChartSchedule.java
@@ -2,7 +2,7 @@
 
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.task.bean.Chart6Vo;
-import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.chart.ChartViewService;
 import com.tapdata.tm.user.service.UserService;
 import lombok.Setter;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,11 +16,9 @@
 @Component
 @Setter(onMethod_ = {@Autowired})
 public class ChartSchedule {
-
-
     public static Map<String, Chart6Vo> cache = new HashMap<>();
 
-    private TaskService taskService;
+    private ChartViewService chartViewService;
 
     private UserService userService;
 
@@ -28,13 +26,13 @@ public class ChartSchedule {
     public void chart6() {
         List<UserDetail> userDetails = userService.loadAllUser();
         for (UserDetail userDetail : userDetails) {
-            Chart6Vo chart6Vo = taskService.chart6(userDetail);
+            Chart6Vo chart6Vo = chartViewService.transmissionOverviewChartData(userDetail);
             put(userDetail.getUserId(), chart6Vo);
         }
     }
 
 
-    public static void  put(String key, Chart6Vo chart6Vo) {
+    public static void put(String key, Chart6Vo chart6Vo) {
         if (chart6Vo != null && !chart6Vo.empty()) {
             cache.put(key, chart6Vo);
         }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/controller/TaskController.java b/manager/tm/src/main/java/com/tapdata/tm/task/controller/TaskController.java
index 61f76ec88e..1929ade606 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/controller/TaskController.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/controller/TaskController.java
@@ -95,6 +95,7 @@ public class TaskController extends BaseController {
     private TaskRecordService taskRecordService;
     private WorkerService workerService;
     private UserService userService;
+    private TaskErrorEventService taskErrorEventService;
 
 		private <T> T dataPermissionUnAuth() {
 			throw new RuntimeException("Un auth");
@@ -1288,11 +1289,14 @@ public ResponseMessage<Void> updateErrorEvents(@PathVariable("taskId") String ta
         return success();
     }
 
+    @GetMapping("errors/{taskId}")
+    public ResponseMessage<List<ErrorEvent>> getErrorEventByTaskId(@PathVariable("taskId") String taskId) {
+        return success(taskErrorEventService.getErrorEventByTaskId(taskId, getLoginUser()));
+    }
+
     @PostMapping("skipErrorEvents/{taskId}")
     public ResponseMessage<Void> skipErrorEvents(@PathVariable("taskId") String taskId,@RequestBody List<String> ids) {
-        Criteria criteria = Criteria.where("_id").is(taskId);
-        Update update = new Update().set("errorEvents.$[element].skip",true).filterArray(Criteria.where("element._id").in(ids.stream().map(MongoUtils::toObjectId).collect(Collectors.toList())));
-        taskService.update(new Query(criteria),update);
+        taskErrorEventService.signSkipErrorEvents(taskId, ids);
         return success();
     }
 
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskErrorEventService.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskErrorEventService.java
new file mode 100644
index 0000000000..60a42826a2
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskErrorEventService.java
@@ -0,0 +1,56 @@
+package com.tapdata.tm.task.service;
+
+import cn.hutool.core.collection.CollUtil;
+import com.tapdata.tm.commons.task.dto.ErrorEvent;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.task.repository.TaskRepository;
+import com.tapdata.tm.utils.Lists;
+import com.tapdata.tm.utils.MongoUtils;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.bson.types.ObjectId;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.BulkOperations;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+@Setter(onMethod_ = {@Autowired})
+public class TaskErrorEventService {
+    protected static final String ERROR_EVENTS_SKIP_KEY = "errorEvents.$[element].skip";
+    protected static final String ELEMENT_ID = "element._id";
+    private TaskRepository taskRepository;
+    private TaskService taskService;
+
+    public List<ErrorEvent> getErrorEventByTaskId(String taskId, UserDetail user) {
+        TaskDto taskDto = taskService.findOne(Query.query(Criteria.where("_id").is(MongoUtils.toObjectId(taskId))), user);
+        if (null == taskDto) {
+            return Lists.newArrayList();
+        }
+        return Optional.ofNullable(taskDto.getErrorEvents()).orElse(Lists.newArrayList());
+    }
+
+    public void signSkipErrorEvents(String taskId, List<String> ids) {
+        Criteria criteria = Criteria.where("_id").is(MongoUtils.toObjectId(taskId));
+        Query query = Query.query(criteria);
+        if (CollUtil.isEmpty(ids)) {
+            taskService.update(query, new Update().set(ERROR_EVENTS_SKIP_KEY, false).filterArray(Criteria.where(ELEMENT_ID).nin(Lists.newArrayList())));
+            return;
+        }
+        BulkOperations bulkOperations = taskRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED);
+        List<ObjectId> objectIds = ids.stream().map(MongoUtils::toObjectId).collect(Collectors.toList());
+        bulkOperations.updateMulti(query, new Update().set(ERROR_EVENTS_SKIP_KEY, true)
+                    .filterArray(Criteria.where(ELEMENT_ID).in(objectIds)))
+                .updateMulti(query, new Update().set(ERROR_EVENTS_SKIP_KEY, false)
+                    .filterArray(Criteria.where(ELEMENT_ID).nin(objectIds)));
+        bulkOperations.execute();
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
index f11cb0b945..782c0e99e2 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/TaskServiceImpl.java
@@ -18,25 +18,62 @@
 import com.tapdata.tm.autoinspect.service.TaskAutoInspectResultsService;
 import com.tapdata.tm.autoinspect.utils.AutoInspectUtil;
 import com.tapdata.tm.base.dto.Field;
-import com.tapdata.tm.base.dto.*;
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.MutiResponseMessage;
+import com.tapdata.tm.base.dto.Page;
+import com.tapdata.tm.base.dto.ResponseMessage;
+import com.tapdata.tm.base.dto.TmPageable;
+import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
 import com.tapdata.tm.base.handler.ExceptionHandler;
 import com.tapdata.tm.commons.base.dto.BaseDto;
-import com.tapdata.tm.commons.dag.*;
-import com.tapdata.tm.commons.dag.nodes.*;
-import com.tapdata.tm.commons.dag.process.*;
+import com.tapdata.tm.commons.dag.AccessNodeTypeEnum;
+import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.dag.DmlPolicyEnum;
+import com.tapdata.tm.commons.dag.Edge;
+import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.CacheNode;
+import com.tapdata.tm.commons.dag.nodes.DataNode;
+import com.tapdata.tm.commons.dag.nodes.DataParentNode;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
+import com.tapdata.tm.commons.dag.process.CustomProcessorNode;
+import com.tapdata.tm.commons.dag.process.JoinProcessorNode;
+import com.tapdata.tm.commons.dag.process.JsProcessorNode;
+import com.tapdata.tm.commons.dag.process.MergeTableNode;
+import com.tapdata.tm.commons.dag.process.MigrateFieldRenameProcessorNode;
+import com.tapdata.tm.commons.dag.process.MigrateJsProcessorNode;
+import com.tapdata.tm.commons.dag.process.UnwindProcessNode;
 import com.tapdata.tm.commons.dag.process.script.ScriptProcessNode;
 import com.tapdata.tm.commons.dag.process.script.py.MigratePyProcessNode;
 import com.tapdata.tm.commons.dag.process.script.py.PyProcessNode;
-import com.tapdata.tm.commons.dag.vo.*;
+import com.tapdata.tm.commons.dag.vo.SyncObjects;
+import com.tapdata.tm.commons.dag.vo.TestRunDto;
 import com.tapdata.tm.commons.externalStorage.ExternalStorageDto;
-import com.tapdata.tm.commons.schema.*;
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import com.tapdata.tm.commons.schema.DataSourceDefinitionDto;
+import com.tapdata.tm.commons.schema.MetadataInstancesDto;
+import com.tapdata.tm.commons.schema.MetadataTransformerDto;
+import com.tapdata.tm.commons.schema.MetadataTransformerItemDto;
+import com.tapdata.tm.commons.schema.Tag;
+import com.tapdata.tm.commons.schema.TransformerWsMessageDto;
 import com.tapdata.tm.commons.task.constant.NotifyEnum;
-import com.tapdata.tm.commons.task.dto.*;
+import com.tapdata.tm.commons.task.dto.Dag;
+import com.tapdata.tm.commons.task.dto.DataSyncMq;
+import com.tapdata.tm.commons.task.dto.MergeTableProperties;
+import com.tapdata.tm.commons.task.dto.Message;
+import com.tapdata.tm.commons.task.dto.Milestone;
+import com.tapdata.tm.commons.task.dto.ParentTaskDto;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.commons.task.dto.TaskHistory;
+import com.tapdata.tm.commons.task.dto.TaskRunHistoryDto;
 import com.tapdata.tm.commons.task.dto.alarm.AlarmSettingVO;
 import com.tapdata.tm.commons.task.dto.migrate.MigrateTableDto;
 import com.tapdata.tm.commons.task.dto.progress.TaskSnapshotProgress;
-import com.tapdata.tm.commons.util.*;
+import com.tapdata.tm.commons.util.ConnHeartbeatUtils;
+import com.tapdata.tm.commons.util.JsonUtil;
+import com.tapdata.tm.commons.util.MetaDataBuilderUtils;
+import com.tapdata.tm.commons.util.ThrowableUtils;
 import com.tapdata.tm.config.security.UserDetail;
 import com.tapdata.tm.customNode.dto.CustomNodeDto;
 import com.tapdata.tm.customNode.service.CustomNodeService;
@@ -50,7 +87,6 @@
 import com.tapdata.tm.inspect.bean.Source;
 import com.tapdata.tm.inspect.bean.Stats;
 import com.tapdata.tm.inspect.constant.InspectResultEnum;
-import com.tapdata.tm.inspect.constant.InspectStatusEnum;
 import com.tapdata.tm.inspect.dto.InspectDto;
 import com.tapdata.tm.inspect.dto.InspectResultDto;
 import com.tapdata.tm.inspect.service.InspectResultService;
@@ -71,14 +107,30 @@
 import com.tapdata.tm.monitoringlogs.service.MonitoringLogsService;
 import com.tapdata.tm.permissions.DataPermissionHelper;
 import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
+import com.tapdata.tm.permissions.constants.DataPermissionDataTypeEnums;
 import com.tapdata.tm.permissions.constants.DataPermissionMenuEnums;
+import com.tapdata.tm.report.dto.TasksNumBatch;
+import com.tapdata.tm.report.service.UserDataReportService;
 import com.tapdata.tm.schedule.ChartSchedule;
 import com.tapdata.tm.schedule.service.ScheduleService;
 import com.tapdata.tm.statemachine.enums.DataFlowEvent;
 import com.tapdata.tm.statemachine.model.StateMachineResult;
 import com.tapdata.tm.statemachine.service.StateMachineService;
-import com.tapdata.tm.task.bean.*;
-import com.tapdata.tm.task.constant.*;
+import com.tapdata.tm.task.bean.Chart6Vo;
+import com.tapdata.tm.task.bean.FullSyncVO;
+import com.tapdata.tm.task.bean.LogCollectorResult;
+import com.tapdata.tm.task.bean.RunTimeInfo;
+import com.tapdata.tm.task.bean.SampleTaskVo;
+import com.tapdata.tm.task.bean.SyncTaskStatusDto;
+import com.tapdata.tm.task.bean.TableStatusInfoDto;
+import com.tapdata.tm.task.bean.TaskUpAndLoadDto;
+import com.tapdata.tm.task.constant.InputNumCache;
+import com.tapdata.tm.task.constant.SyncStatus;
+import com.tapdata.tm.task.constant.SyncType;
+import com.tapdata.tm.task.constant.TableStatusEnum;
+import com.tapdata.tm.task.constant.TaskEnum;
+import com.tapdata.tm.task.constant.TaskOpStatusEnum;
+import com.tapdata.tm.task.constant.TaskStatusEnum;
 import com.tapdata.tm.task.dto.CheckEchoOneNodeParam;
 import com.tapdata.tm.task.entity.TaskEntity;
 import com.tapdata.tm.task.entity.TaskRecord;
@@ -86,9 +138,9 @@
 import com.tapdata.tm.task.param.SaveShareCacheParam;
 import com.tapdata.tm.task.repository.TaskRepository;
 import com.tapdata.tm.task.service.batchin.ParseRelMig;
-import com.tapdata.tm.task.service.batchin.ParseRelMigFile;
 import com.tapdata.tm.task.service.batchin.entity.ParseParam;
 import com.tapdata.tm.task.service.batchup.BatchUpChecker;
+import com.tapdata.tm.task.service.chart.ChartViewService;
 import com.tapdata.tm.task.service.utils.TaskServiceUtil;
 import com.tapdata.tm.task.vo.ShareCacheDetailVo;
 import com.tapdata.tm.task.vo.ShareCacheVo;
@@ -99,14 +151,27 @@
 import com.tapdata.tm.user.service.UserService;
 import com.tapdata.tm.userLog.constant.Modular;
 import com.tapdata.tm.userLog.service.UserLogService;
-import com.tapdata.tm.utils.*;
+import com.tapdata.tm.utils.FunctionUtils;
+import com.tapdata.tm.utils.GZIPUtil;
+import com.tapdata.tm.utils.Lists;
+import com.tapdata.tm.utils.MapUtils;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.utils.MongoUtils;
+import com.tapdata.tm.utils.NumberUtil;
+import com.tapdata.tm.utils.SpringContextHelper;
+import com.tapdata.tm.utils.TimeUtil;
+import com.tapdata.tm.utils.UUIDUtil;
 import com.tapdata.tm.worker.entity.Worker;
 import com.tapdata.tm.worker.service.WorkerService;
 import com.tapdata.tm.worker.vo.CalculationEngineVo;
 import com.tapdata.tm.ws.enums.MessageType;
 import io.tapdata.common.sample.request.Sample;
 import io.tapdata.exception.TapCodeException;
-import lombok.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.RandomStringUtils;
@@ -135,7 +200,20 @@
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
@@ -144,6 +222,14 @@
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.DONE;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.ERROR;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.FAILED;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.PASSED;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.RUNNING;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.SCHEDULING;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.STOPPING;
+import static com.tapdata.tm.inspect.constant.InspectStatusEnum.WAITING;
 import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
 import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
 
@@ -285,6 +371,8 @@ private static String getTableName() {
     private AgentGroupService agentGroupService;
     private DataSourceDefinitionService dataSourceDefinitionService;
     private BatchUpChecker batchUpChecker;
+    private ChartViewService chartViewService;
+    private UserDataReportService userDataReportService;
 
     public TaskServiceImpl(@NonNull TaskRepository repository) {
         super(repository);
@@ -360,6 +448,7 @@ public TaskDto create(TaskDto taskDto, UserDetail user) {
 
         //模型推演
         setDefault(taskDto);
+        taskDto.setOldVersionTimezone(false);
         taskDto = save(taskDto, user);
         if (dag != null) {
             dag.setTaskId(taskDto.getId());
@@ -2033,20 +2122,8 @@ protected TaskDto parseCacheToTaskDto(SaveShareCacheParam saveShareCacheParam, T
      */
     public Map<String, Object> chart(UserDetail user) {
         Map<String, Object> resultChart = new HashMap<>();
-        Criteria criteria = new Criteria()
-                .and(IS_DELETED).ne(true)
-                .and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_MIGRATE, TaskDto.SYNC_TYPE_SYNC)
-                .and(STATUS).nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
-                //共享缓存的任务设计的有点问题
-                .and("shareCache").ne(true);
-
-
-        Query query = Query.query(criteria);
-        query.fields().include(SYNC_TYPE, STATUS, "statuses");
         //把任务都查询出来
-        List<TaskDto> taskDtoList = DataPermissionMenuEnums.MigrateTack.checkAndSetFilter(
-                user, DataPermissionActionEnums.View, () -> findAllDto(query, user)
-        );
+        List<TaskDto> taskDtoList = chartViewService.getViewTaskDtoByUser(user);
         Map<String, List<TaskDto>> syncTypeToTaskList = taskDtoList.stream().collect(Collectors.groupingBy(TaskDto::getSyncType));
 
         List<TaskDto> migrateList =  syncTypeToTaskList.getOrDefault(SyncType.MIGRATE.getValue(), Collections.emptyList());
@@ -2058,51 +2135,62 @@ public Map<String, Object> chart(UserDetail user) {
         resultChart.put("chart5", inspectChart(user));
         Chart6Vo chart6Vo = ChartSchedule.cache.get(user.getUserId());
         if (chart6Vo == null) {
-            chart6Vo = chart6(user);
+            chart6Vo = chartViewService.transmissionOverviewChartData(taskDtoList);
             ChartSchedule.put(user.getUserId(), chart6Vo);
         }
         resultChart.put("chart6", chart6Vo);
         return resultChart;
     }
 
+    protected List<InspectDto> inspectTaskList(Filter filter, UserDetail user) {
+        return DataPermissionHelper.check(
+                user,
+                DataPermissionMenuEnums.INSPECT_TACK,
+                DataPermissionActionEnums.View,
+                DataPermissionDataTypeEnums.INSPECT,
+                null,
+                () -> inspectService.list(filter, user).getItems(),
+                ArrayList::new);
 
-    public Map<String, Integer> inspectChart(UserDetail user) {
-        Criteria criteria = Criteria.where(SYNC_TYPE).is(TaskDto.SYNC_TYPE_MIGRATE)
-                .and("isAutoInspect").is(true)
-                .and(IS_DELETED).ne(true);
-        Query query = new Query(criteria);
-        query.fields().include("_id", STATUS, "isAutoInspect", "canOpenInspect", "attrs.autoInspectProgress.tableCounts", "attrs.autoInspectProgress.tableIgnore", "attrs.autoInspectProgress.step");
-
-        int openTaskNum = 0;
-        int canTaskNum = 0;
-        int errorTaskNum = 0;
-        int diffTaskNum = 0;
-        List<TaskDto> taskDtos = findAllDto(query, user);
-
-        Set<String> taskSet = taskAutoInspectResultsService.groupByTask(user);
-        if (CollectionUtils.isNotEmpty(taskDtos)) {
-            openTaskNum = taskDtos.size();
-            for (TaskDto taskDto : taskDtos) {
-                if (taskDto.getCanOpenInspect() != null && taskDto.getCanOpenInspect()) {
-                    canTaskNum++;
-                }
-
-                if (TaskDto.STATUS_ERROR.equals(taskDto.getStatus())) {
-                    errorTaskNum++;
-                }
+    }
 
-                if (taskSet.contains(taskDto.getId().toHexString())) {
-                    diffTaskNum++;
+    public Map<String, Integer> inspectChart(UserDetail user) {
+        int error = 0;
+        int running = 0;
+        int done = 0;
+        int waiting = 0;
+        int scheduling = 0;
+        int stopping = 0;
+        int total = 0;
+        List<InspectDto> inspectDtoList = inspectTaskList(new Filter(), user);
+        if (CollectionUtils.isNotEmpty(inspectDtoList)) {
+            total = inspectDtoList.size();
+            for (InspectDto taskDto : inspectDtoList) {
+                String status = String.valueOf(taskDto.getStatus()).toLowerCase();
+                if (ERROR.getValue().equals(status)) {
+                    error++;
+                } else if(RUNNING.getValue().equals(status)) {
+                    running++;
+                } else if (DONE.getValue().equals(status)) {
+                    done++;
+                } else if (WAITING.getValue().equals(status)) {
+                    waiting++;
+                }else if (SCHEDULING.getValue().equals(status)) {
+                    scheduling++;
+                }else if (STOPPING.getValue().equals(status)) {
+                    stopping++;
                 }
-
             }
         }
 
         Map<String, Integer> chart5 = new HashMap<>();
-        chart5.put(TOTAL, openTaskNum);
-        chart5.put("error", errorTaskNum);
-        chart5.put("can", canTaskNum);
-        chart5.put("diff", diffTaskNum);
+        chart5.put(TOTAL, total);
+        chart5.put("error", error);
+        chart5.put("running", running);
+        chart5.put("done", done);
+        chart5.put("waiting", waiting);
+        chart5.put("scheduling", scheduling);
+        chart5.put("stopping", stopping);
         return chart5;
     }
 
@@ -3389,6 +3477,10 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag, boolean sy
      *                  第二位 是否开启打点任务      1 是   0 否
      */
     public void start(TaskDto taskDto, UserDetail user, String startFlag) {
+        String taskType = taskDto.getSyncType();
+        TasksNumBatch tasksNumBatch = new TasksNumBatch();
+        tasksNumBatch.setTaskType(taskType);
+        userDataReportService.produceData(tasksNumBatch);
 
         if (taskDto.getShareCdcEnable() && !TaskDto.SYNC_TYPE_LOG_COLLECTOR.equals(taskDto.getSyncType())) {
             //如果是共享挖掘任务给一个队列，避免混乱
@@ -3439,6 +3531,11 @@ public void start(TaskDto taskDto, UserDetail user, String startFlag) {
         //模型推演,如果模型已经存在，则需要推演
 //        DAG dag = taskDto.getDag();
 
+        //filter heartbeat task when status is renew failed for automatic test
+        if (TaskDto.SYNC_TYPE_CONN_HEARTBEAT.equals(taskDto.getSyncType()) && TaskDto.STATUS_RENEW_FAILED.equals(taskDto.getStatus())) {
+            log.warn("heartbeat task current status not allow to start, task = {}, status = {}, please restore the task manually.", taskDto.getName(), taskDto.getStatus());
+            return;
+        }
         //校验当前状态是否允许启动。
         if (!TaskOpStatusEnum.to_start_status.v().contains(taskDto.getStatus())) {
             log.warn("task current status not allow to start, task = {}, status = {}", taskDto.getName(), taskDto.getStatus());
@@ -4054,77 +4151,6 @@ public void updateTaskLogSetting(String taskId, LogSettingParam logSettingParam,
         updateById(taskObjectId, update, userDetail);
     }
 
-    public Chart6Vo chart6(UserDetail user) {
-        Criteria criteria = Criteria.where(IS_DELETED).ne(true).and(SYNC_TYPE).in(TaskDto.SYNC_TYPE_SYNC, TaskDto.SYNC_TYPE_MIGRATE);
-        Query query = new Query(criteria);
-        query.fields().include("_id");
-        List<TaskDto> allDto = findAllDto(query, user);
-        List<String> ids = allDto.stream().map(a->a.getId().toHexString()).collect(Collectors.toList());
-
-        List<MeasurementEntity>  allMeasurements = new ArrayList<>();
-        if (CollectionUtils.isNotEmpty(ids)) {
-            ids.stream().forEach(id -> {
-                MeasurementEntity measurement = measurementServiceV2.findLastMinuteByTaskId(id);
-                if (measurement != null) {
-                    allMeasurements.add(measurement);
-                }
-            });
-        }
-
-        BigInteger output = BigInteger.ZERO;
-        BigInteger input = BigInteger.ZERO;
-        BigInteger insert = BigInteger.ZERO;
-        BigInteger update = BigInteger.ZERO;
-        BigInteger delete = BigInteger.ZERO;
-
-        for (MeasurementEntity allMeasurement : allMeasurements) {
-            if (allMeasurement == null) {
-                continue;
-            }
-            List<Sample> samples = allMeasurement.getSamples();
-            if (CollectionUtils.isNotEmpty(samples)) {
-                Optional<Sample> max = samples.stream().max(Comparator.comparing(Sample::getDate));
-                if (max.isPresent()) {
-                    Sample sample = max.get();
-                    Map<String, Number> vs = sample.getVs();
-                    BigInteger inputInsertTotal = NumberUtil.parseDataTotal(vs.get("inputInsertTotal"));
-                    BigInteger inputOthersTotal = NumberUtil.parseDataTotal(vs.get("inputOthersTotal"));
-                    BigInteger inputDdlTotal = NumberUtil.parseDataTotal(vs.get("inputDdlTotal"));
-                    BigInteger inputUpdateTotal = NumberUtil.parseDataTotal(vs.get("inputUpdateTotal"));
-                    BigInteger inputDeleteTotal = NumberUtil.parseDataTotal(vs.get("inputDeleteTotal"));
-
-                    BigInteger outputInsertTotal = NumberUtil.parseDataTotal(vs.get("outputInsertTotal"));
-                    BigInteger outputOthersTotal = NumberUtil.parseDataTotal(vs.get("outputOthersTotal"));
-                    BigInteger outputDdlTotal = NumberUtil.parseDataTotal(vs.get("outputDdlTotal"));
-                    BigInteger outputUpdateTotal = NumberUtil.parseDataTotal(vs.get("outputUpdateTotal"));
-                    BigInteger outputDeleteTotal = NumberUtil.parseDataTotal(vs.get("outputDeleteTotal"));
-                    output = output.add(outputInsertTotal);
-                    output = output.add(outputOthersTotal);
-                    output = output.add(outputDdlTotal);
-                    output = output.add(outputUpdateTotal);
-                    output = output.add(outputDeleteTotal);
-
-                    input = input.add(inputInsertTotal);
-                    input = input.add(inputOthersTotal);
-                    input = input.add(inputDdlTotal);
-                    input = input.add(inputUpdateTotal);
-                    input = input.add(inputDeleteTotal);
-
-                    insert = insert.add(inputInsertTotal);
-                    update = update.add(inputUpdateTotal);
-                    delete = delete.add(inputDeleteTotal);
-
-                }
-            }
-        }
-
-
-        Chart6Vo chart6Vo = Chart6Vo.builder().outputTotal(output).inputTotal(input)
-                .insertedTotal(insert).updatedTotal(update).deletedTotal(delete)
-                .build();
-        return chart6Vo;
-    }
-
     public void stopTaskIfNeedByAgentId(String agentId, UserDetail userDetail) {
         Query query = Query.query(Criteria.where(AGENT_ID).is(agentId).and(STATUS).is(TaskDto.STATUS_STOPPING));
         query.fields().include("_id");
@@ -4228,11 +4254,11 @@ public boolean judgeTargetInspect(String connectionId, String tableName, UserDet
             for (Stats stats : statsList) {
                 Source target = stats.getTarget();
                 if (connectionId.equals(target.getConnectionId()) && tableName.equals(target.getTable())) {
-                    if (StringUtils.equalsAny(stats.getStatus(), InspectStatusEnum.FAILED.name(),
-                            InspectStatusEnum.ERROR.name())) {
+                    if (StringUtils.equalsAny(stats.getStatus(), FAILED.name(),
+                            ERROR.name())) {
                         return false;
-                    } else if (StringUtils.equalsAny(stats.getStatus(), InspectStatusEnum.DONE.name(),
-                            InspectStatusEnum.PASSED.name())) {
+                    } else if (StringUtils.equalsAny(stats.getStatus(), DONE.name(),
+                            PASSED.name())) {
                         return InspectResultEnum.PASSED.name().equals(stats.getResult());
                     } else {
                         return true;
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/chart/ChartViewService.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/chart/ChartViewService.java
new file mode 100644
index 0000000000..f28ed69121
--- /dev/null
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/chart/ChartViewService.java
@@ -0,0 +1,126 @@
+package com.tapdata.tm.task.service.chart;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.monitor.entity.MeasurementEntity;
+import com.tapdata.tm.monitor.service.MeasurementServiceV2;
+import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
+import com.tapdata.tm.permissions.constants.DataPermissionMenuEnums;
+import com.tapdata.tm.task.bean.Chart6Vo;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.TaskServiceImpl;
+import com.tapdata.tm.utils.NumberUtil;
+import io.tapdata.common.sample.request.Sample;
+import lombok.Setter;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Service;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+@Setter(onMethod_ = {@Autowired})
+public class ChartViewService {
+    private TaskService taskService;
+    private MeasurementServiceV2 measurementServiceV2;
+
+    public List<TaskDto> getViewTaskDtoByUser(UserDetail user) {
+        return DataPermissionMenuEnums.MigrateTack.checkAndSetFilter(user, DataPermissionActionEnums.View, () -> getViewTaskDto(user));
+    }
+
+    protected List<TaskDto> getViewTaskDto(UserDetail user) {
+        Criteria criteria = new Criteria()
+                .and(TaskServiceImpl.IS_DELETED).ne(true)
+                .and(TaskServiceImpl.SYNC_TYPE).in(TaskDto.SYNC_TYPE_MIGRATE, TaskDto.SYNC_TYPE_SYNC)
+                .and(TaskServiceImpl.STATUS).nin(TaskDto.STATUS_DELETING, TaskDto.STATUS_DELETE_FAILED)
+                //共享缓存的任务设计的有点问题
+                .and("shareCache").ne(true);
+        Query query = Query.query(criteria);
+        query.fields().include(TaskServiceImpl.SYNC_TYPE, TaskServiceImpl.STATUS, "statuses");
+        return taskService.findAllDto(query, user);
+    }
+
+    public Chart6Vo transmissionOverviewChartData(UserDetail user) {
+        List<TaskDto> allDto = getViewTaskDtoByUser(user);
+        return transmissionOverviewChartData(allDto);
+    }
+
+    public Chart6Vo transmissionOverviewChartData(List<TaskDto> allDto) {
+        List<String> ids = allDto.stream()
+                .filter(Objects::nonNull)
+                .map(a -> a.getId().toHexString())
+                .collect(Collectors.toList());
+
+        List<MeasurementEntity> allMeasurements = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(ids)) {
+            ids.forEach(id -> {
+                MeasurementEntity measurement = measurementServiceV2.findLastMinuteByTaskId(id);
+                if (measurement != null) {
+                    allMeasurements.add(measurement);
+                }
+            });
+        }
+
+        BigInteger output = BigInteger.ZERO;
+        BigInteger input = BigInteger.ZERO;
+        BigInteger insert = BigInteger.ZERO;
+        BigInteger update = BigInteger.ZERO;
+        BigInteger delete = BigInteger.ZERO;
+
+        for (MeasurementEntity allMeasurement : allMeasurements) {
+            if (allMeasurement == null) {
+                continue;
+            }
+            List<Sample> samples = allMeasurement.getSamples();
+            if (CollectionUtils.isNotEmpty(samples)) {
+                Optional<Sample> max = samples.stream().max(Comparator.comparing(Sample::getDate));
+                if (max.isPresent()) {
+                    Sample sample = max.get();
+                    Map<String, Number> vs = sample.getVs();
+                    BigInteger inputInsertTotal = NumberUtil.parseDataTotal(vs.get("inputInsertTotal"));
+                    BigInteger inputOthersTotal = NumberUtil.parseDataTotal(vs.get("inputOthersTotal"));
+                    BigInteger inputDdlTotal = NumberUtil.parseDataTotal(vs.get("inputDdlTotal"));
+                    BigInteger inputUpdateTotal = NumberUtil.parseDataTotal(vs.get("inputUpdateTotal"));
+                    BigInteger inputDeleteTotal = NumberUtil.parseDataTotal(vs.get("inputDeleteTotal"));
+
+                    BigInteger outputInsertTotal = NumberUtil.parseDataTotal(vs.get("outputInsertTotal"));
+                    BigInteger outputOthersTotal = NumberUtil.parseDataTotal(vs.get("outputOthersTotal"));
+                    BigInteger outputDdlTotal = NumberUtil.parseDataTotal(vs.get("outputDdlTotal"));
+                    BigInteger outputUpdateTotal = NumberUtil.parseDataTotal(vs.get("outputUpdateTotal"));
+                    BigInteger outputDeleteTotal = NumberUtil.parseDataTotal(vs.get("outputDeleteTotal"));
+                    output = output.add(outputInsertTotal);
+                    output = output.add(outputOthersTotal);
+                    output = output.add(outputDdlTotal);
+                    output = output.add(outputUpdateTotal);
+                    output = output.add(outputDeleteTotal);
+
+                    input = input.add(inputInsertTotal);
+                    input = input.add(inputOthersTotal);
+                    input = input.add(inputDdlTotal);
+                    input = input.add(inputUpdateTotal);
+                    input = input.add(inputDeleteTotal);
+
+                    insert = insert.add(inputInsertTotal);
+                    update = update.add(inputUpdateTotal);
+                    delete = delete.add(inputDeleteTotal);
+
+                }
+            }
+        }
+        return Chart6Vo.builder().outputTotal(output)
+                .inputTotal(input)
+                .insertedTotal(insert)
+                .updatedTotal(update)
+                .deletedTotal(delete)
+                .build();
+    }
+}
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java
index e5e20b4bca..57d8c64380 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskNodeServiceImpl.java
@@ -388,16 +388,6 @@ private Page<MetadataTransformerItemDto> getMetadataTransformerItemDtoPage(UserD
         }
         predecessors.add(currentNode);
 
-        // table rename
-        LinkedList<TableRenameProcessNode> tableRenameProcessNodes = predecessors
-                .stream()
-                .filter(node -> node instanceof TableRenameProcessNode)
-                .map(node -> (TableRenameProcessNode) node)
-                .collect(Collectors.toCollection(LinkedList::new));
-        Map<String, TableRenameTableInfo> tableNameMapping = null;
-        if (CollectionUtils.isNotEmpty(tableRenameProcessNodes)) {
-            tableNameMapping = tableRenameProcessNodes.getLast().originalMap();
-        }
         // field rename
         LinkedList<MigrateFieldRenameProcessorNode> fieldRenameProcessorNodes = predecessors
                 .stream()
@@ -455,12 +445,8 @@ private Page<MetadataTransformerItemDto> getMetadataTransformerItemDtoPage(UserD
 
             MetadataTransformerItemDto item = new MetadataTransformerItemDto();
             item.setSourceObjectName(tableName);
-            String sinkTableName = metadataInstancesDto.getOriginalName();
-            String previousTableName = metadataInstancesDto.getOriginalName();
-            if (Objects.nonNull(tableNameMapping) && !tableNameMapping.isEmpty() && Objects.nonNull(tableNameMapping.get(tableName))) {
-                sinkTableName = tableNameMapping.get(tableName).getCurrentTableName();
-                previousTableName = tableNameMapping.get(tableName).getPreviousTableName();
-            }
+            String sinkTableName = metadataInstancesDto.getName();
+            String previousTableName = metadataInstancesDto.getAncestorsName();
             item.setSinkObjectName(sinkTableName);
 
             List<FieldsMapping> fieldsMapping = new LinkedList<>();
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java
index e23aaeb9d5..69aea6485e 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskSaveServiceImpl.java
@@ -278,8 +278,6 @@ private void nodeCheckData(List<Node<List<Schema>>> nodes, List<String> tableNam
 
             if (CollectionUtils.isEmpty(tableNames)) {
                 tableNode.setTableNames(new LinkedHashSet<>());
-            } else if (CollectionUtils.isNotEmpty(tableNode.getTableNames())) {
-                tableNode.getTableNames().removeIf(t -> !tableNames.contains(t.getOriginTableName()));
             }
 
             if (CollectionUtils.isNotEmpty(tableNode.getTableNames())) {
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/TargetSettingStrategyImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/TargetSettingStrategyImpl.java
index 87bc863cb9..e60047d181 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/TargetSettingStrategyImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/dagcheckstrategy/TargetSettingStrategyImpl.java
@@ -1,7 +1,5 @@
 package com.tapdata.tm.task.service.impl.dagcheckstrategy;
 
-import cn.hutool.core.date.DateUtil;
-import com.google.common.collect.Sets;
 import com.tapdata.tm.commons.dag.DAG;
 import com.tapdata.tm.commons.dag.Node;
 import com.tapdata.tm.commons.dag.nodes.DataParentNode;
@@ -30,8 +28,11 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.text.MessageFormat;
-import java.util.*;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -53,11 +54,7 @@ public class TargetSettingStrategyImpl implements DagLogStrategy {
     @Override
     public List<TaskDagCheckLog> getLogs(TaskDto taskDto, UserDetail userDetail, Locale locale) {
         String taskId = taskDto.getId().toHexString();
-        String current = DateUtil.now();
-        Date now = new Date();
-
         List<TaskDagCheckLog> result = Lists.newArrayList();
-        Set<String> nameSet = Sets.newHashSet();
         DAG dag = taskDto.getDag();
 
         if (Objects.isNull(dag) || CollectionUtils.isEmpty(dag.getTargets())) {
@@ -82,20 +79,12 @@ public List<TaskDagCheckLog> getLogs(TaskDto taskDto, UserDetail userDetail, Loc
                 result.add(log);
             }
 
-//            boolean keepTargetSchema = false;
             AtomicReference<List<String>> tableNames = new AtomicReference<>();
-//            List<String> existDataModeList = Lists.newArrayList("keepData", "removeData");
             if (TaskDto.SYNC_TYPE_MIGRATE.equals(taskDto.getSyncType())) {
                 DatabaseNode databaseNode = (DatabaseNode) node;
                 Optional.ofNullable(databaseNode.getSyncObjects()).ifPresent(list -> tableNames.set(list.get(0).getObjectNames()));
-//                if (existDataModeList.contains(databaseNode.getExistDataProcessMode())) {
-//                    keepTargetSchema = true;
-//                }
             } else {
                 TableNode tableNode = (TableNode) node;
-//                if (existDataModeList.contains(tableNode.getExistDataProcessMode())) {
-//                    keepTargetSchema = true;
-//                }
                 tableNames.set(Lists.newArrayList(tableNode.getTableName()));
                 if ("updateOrInsert".equals(tableNode.getWriteStrategy())) {
                     List<String> updateConditionFields = tableNode.getUpdateConditionFields();
@@ -116,38 +105,6 @@ public List<TaskDagCheckLog> getLogs(TaskDto taskDto, UserDetail userDetail, Loc
                     }*/
                 }
             }
-
-//            if (keepTargetSchema) {
-//                List<MetadataInstancesDto> schemaList = metadataInstancesService.findSourceSchemaBySourceId(connectionId, tableNames.get(), userDetail);
-//                List<String> collect = schemaList.stream().map(MetadataInstancesDto::getName).collect(Collectors.toList());
-//                if (CollectionUtils.isNotEmpty(collect)) {
-//                    List<String> list = new ArrayList<>(tableNames.get());
-//                    list.removeAll(collect);
-//                    if (CollectionUtils.isNotEmpty(list)) {
-//                        TaskDagCheckLog log = TaskDagCheckLog.builder()
-//                                .taskId(taskId)
-//                                .checkType(templateEnum.name())
-//                                .log(MessageFormat.format(MessageUtil.getDagCheckMsg(locale, "TARGET_SETTING_CHECK_SCHAME"), node.getName(), JSON.toJSONString(list)))
-//                                .grade(Level.ERROR)
-//                                .nodeId(node.getId()).build();
-//                        log.setCreateAt(now);
-//                        log.setCreateUser(userId);
-//
-//                        result.add(log);
-//                    }
-//                } else {
-//                    TaskDagCheckLog log = TaskDagCheckLog.builder()
-//                            .taskId(taskId)
-//                            .checkType(templateEnum.name())
-//                            .log(MessageFormat.format(MessageUtil.getDagCheckMsg(locale, "TARGET_SETTING_CHECK_SCHAME"), node.getName(), JSON.toJSONString(tableNames.get())))
-//                            .grade(Level.ERROR)
-//                            .nodeId(node.getId()).build();
-//                    log.setCreateAt(now);
-//                    log.setCreateUser(userId);
-//
-//                    result.add(log);
-//                }
-//            }
             checkNodeExistDataMode(locale, taskId, result, userId, node, name);
             checkNodeSyncIndex(locale, taskId, result, userId, node, name);
             checkTargetUpdateField(locale, taskId ,result , userId ,node , name ,connectionId);
@@ -195,26 +152,6 @@ public List<TaskDagCheckLog> getLogs(TaskDto taskDto, UserDetail userDetail, Loc
                     }
                 }
             }
-
-            String template;
-            Level grade;
-            if (nameSet.contains(name)) {
-                template = MessageUtil.getDagCheckMsg(locale, "TARGET_NODE_ERROR");
-                grade = Level.ERROR;
-            } else {
-                template = MessageUtil.getDagCheckMsg(locale, "TARGET_NODE_INFO");
-                grade = Level.INFO;
-            }
-            nameSet.add(name);
-
-            String content = MessageFormat.format(template, current, name);
-
-            TaskDagCheckLog log = TaskDagCheckLog.builder().taskId(taskId).checkType(templateEnum.name()).log(content)
-                    .grade(grade).nodeId(nodeId).build();
-
-            log.setCreateAt(now);
-            log.setCreateUser(userId);
-            result.add(log);
         });
 
         return result;
diff --git a/manager/tm/src/main/resources/application-default.yml b/manager/tm/src/main/resources/application-default.yml
index 4cca04a79a..d5d1aece62 100644
--- a/manager/tm/src/main/resources/application-default.yml
+++ b/manager/tm/src/main/resources/application-default.yml
@@ -125,3 +125,11 @@ task:
     times: 2  #任务重置重试次数
     interval: 30000 #任务重置重试间隔，单位毫秒
     timeoutInterval: 50 #任务重置引擎没有返回时的超时时间，单位秒
+
+report:
+  url:
+    measurementId: 'G-XWF69Y4KY3'
+    apiSecret: 'noI4OfyaSuCOaFbr7AKbxA'
+    clientId: '796104786.1713955139'
+  data:
+    oss: ${REPORT_DATA_OSS:false}
\ No newline at end of file
diff --git a/manager/tm/src/main/resources/dagCheck_en_US.properties b/manager/tm/src/main/resources/dagCheck_en_US.properties
index 571028e40f..43f478352a 100644
--- a/manager/tm/src/main/resources/dagCheck_en_US.properties
+++ b/manager/tm/src/main/resources/dagCheck_en_US.properties
@@ -29,8 +29,6 @@ TARGET_SETTING_WRAN_UPDATEFIELD=$date [Target node Settings detection] : To ensu
 BATCH_READ_FUNCTION=full
 STREAM_READ_FUNCTION=cdc
 TABLE_EDIT_NODE_INFO=$date [$taskName][Table edit node setting check]: Node {1} passed the check
-TARGET_NODE_INFO=$date [$taskName][Target node setting check]: Node {1} passed the check
-TARGET_NODE_ERROR=$date [$taskName][Target node setting check]: Node {1} failed the check, abnormal item attribute: {Node name}, abnormal reason: Node name is duplicated, please reset
 SOURCE_CONNECT_INFO=$date [$taskName][Source connection check]: Connection {0} passed the check
 SOURCE_CONNECT_ERROR=$date [$taskName][Source connection check]: Connection {0} failed the check. \n {1}
 TARGET_CONNECT_INFO=$date [$taskName][Target connection check]: Connection {0} passed the check
diff --git a/manager/tm/src/main/resources/dagCheck_zh_CN.properties b/manager/tm/src/main/resources/dagCheck_zh_CN.properties
index 48be919bbf..aabbc0b92e 100644
--- a/manager/tm/src/main/resources/dagCheck_zh_CN.properties
+++ b/manager/tm/src/main/resources/dagCheck_zh_CN.properties
@@ -29,8 +29,6 @@ TARGET_SETTING_WRAN_UPDATEFIELD=$date\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\
 BATCH_READ_FUNCTION=\u5168\u91CF
 STREAM_READ_FUNCTION=\u589E\u91CF
 TABLE_EDIT_NODE_INFO=$date\u3010$taskName\u3011\u3010\u8868\u7F16\u8F91\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u8282\u70B9{1}\u68C0\u6D4B\u901A\u8FC7
-TARGET_NODE_INFO=$date\u3010$taskName\u3011\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u8282\u70B9{1}\u68C0\u6D4B\u901A\u8FC7
-TARGET_NODE_ERROR=$date\u3010$taskName\u3011\u3010\u76EE\u6807\u8282\u70B9\u8BBE\u7F6E\u68C0\u6D4B\u3011\uFF1A\u8282\u70B9{1}\u68C0\u6D4B\u672A\u901A\u8FC7\uFF0C\u5F02\u5E38\u9879\u5C5E\u6027\uFF1A{\u8282\u70B9\u540D\u79F0}\uFF0C\u5F02\u5E38\u539F\u56E0\uFF1A\u8282\u70B9\u540D\u79F0\u91CD\u590D\uFF0C\u8BF7\u91CD\u65B0\u8BBE\u7F6E
 SOURCE_CONNECT_INFO=$date\u3010$taskName\u3011\u3010\u6E90\u8FDE\u63A5\u68C0\u6D4B\u3011\uFF1A\u8FDE\u63A5{0}\u68C0\u6D4B\u901A\u8FC7
 SOURCE_CONNECT_ERROR=$date\u3010$taskName\u3011\u3010\u6E90\u8FDE\u63A5\u68C0\u6D4B\u3011\uFF1A\u8FDE\u63A5{0}\u68C0\u6D4B\u672A\u901A\u8FC7\u3002 \n {1}
 TARGET_CONNECT_INFO=$date\u3010$taskName\u3011\u3010\u76EE\u6807\u8FDE\u63A5\u68C0\u6D4B\u3011\uFF1A\u8FDE\u63A5{0}\u68C0\u6D4B\u901A\u8FC7
diff --git a/manager/tm/src/main/resources/dagCheck_zh_TW.properties b/manager/tm/src/main/resources/dagCheck_zh_TW.properties
index 2793da0d8e..e8365ec03b 100644
--- a/manager/tm/src/main/resources/dagCheck_zh_TW.properties
+++ b/manager/tm/src/main/resources/dagCheck_zh_TW.properties
@@ -29,8 +29,6 @@ TARGET_SETTING_WRAN_UPDATEFIELD=$date\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\
 BATCH_READ_FUNCTION=\u5168\u91CF
 STREAM_READ_FUNCTION=\u589E\u91CF
 TABLE_EDIT_NODE_INFO=$date\u3010$taskName\u3011\u3010\u8868\u7DE8\u8F2F\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u6E2C\u3011\uFF1A\u7BC0\u9EDE{1}\u6AA2\u6E2C\u901A\u904E
-TARGET_NODE_INFO=$date\u3010$taskName\u3011\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u6E2C\u3011\uFF1A\u7BC0\u9EDE{1}\u6AA2\u6E2C\u901A\u904E
-TARGET_NODE_ERROR=$date\u3010$taskName\u3011\u3010\u76EE\u6A19\u7BC0\u9EDE\u8A2D\u7F6E\u6AA2\u6E2C\u3011\uFF1A\u7BC0\u9EDE{1}\u6AA2\u6E2C\u672A\u901A\u904E\uFF0C\u7570\u5E38\u9805\u5C6C\u6027\uFF1A{\u7BC0\u9EDE\u540D\u7A31}\uFF0C\u7570\u5E38\u539F\u56E0\uFF1A\u7BC0\u9EDE\u540D\u7A31\u91CD\u5FA9\uFF0C\u8ACB\u91CD\u65B0\u8A2D\u7F6E
 SOURCE_CONNECT_INFO=$date\u3010$taskName\u3011\u3010\u6E90\u9023\u63A5\u6AA2\u6E2C\u3011\uFF1A\u9023\u63A5{0}\u6AA2\u6E2C\u901A\u904E
 SOURCE_CONNECT_ERROR=$date\u3010$taskName\u3011\u3010\u6E90\u9023\u63A5\u6AA2\u6E2C\u3011\uFF1A\u9023\u63A5{0}\u6AA2\u6E2C\u672A\u901A\u904E\u3002\n {1}
 TARGET_CONNECT_INFO=$date\u3010$taskName\u3011\u3010\u76EE\u6A19\u9023\u63A5\u6AA2\u6E2C\u3011\uFF1A\u9023\u63A5{0}\u6AA2\u6E2C\u901A\u904E
diff --git a/manager/tm/src/main/resources/messages.properties b/manager/tm/src/main/resources/messages.properties
index 4e7d896039..c3d0c1c690 100644
--- a/manager/tm/src/main/resources/messages.properties
+++ b/manager/tm/src/main/resources/messages.properties
@@ -253,3 +253,8 @@ webhook.reOpen.failed=WebHook re-open failed, please retry again
 webhook.history.hook.id.error=WebHook ID must not be empty
 webhook.history.page.from.error=The paging parameter [pageFrom] is invalid, the starting value should be greater than or equal to 0, actual value is: {0}
 webhook.history.page.size.error=The paging parameter [pageSize] is invalid, the page size value should be greater than to 0, actual value is: {0}
+
+
+#auth
+insufficient.permissions=Insufficient permissions: {0}, current operation requires permission: {1}
+inspect.result.not.exists=The verification record for the current verification task no longer exists: {0}
diff --git a/manager/tm/src/main/resources/messages_en_US.properties b/manager/tm/src/main/resources/messages_en_US.properties
index b4a8bed480..cfb9cb4aa8 100644
--- a/manager/tm/src/main/resources/messages_en_US.properties
+++ b/manager/tm/src/main/resources/messages_en_US.properties
@@ -184,3 +184,7 @@ webhook.reOpen.failed=WebHook re-open failed, please retry again
 webhook.history.hook.id.error=WebHook ID must not be empty
 webhook.history.page.from.error=The paging parameter [pageFrom] is invalid, the starting value should be greater than or equal to 0, actual value is: {0}
 webhook.history.page.size.error=The paging parameter [pageSize] is invalid, the page size value should be greater than to 0, actual value is: {0}
+
+#auth
+insufficient.permissions=Insufficient permissions: {0}, current operation requires permission: {1}
+inspect.result.not.exists=The verification record for the current verification task no longer exists: {0}
diff --git a/manager/tm/src/main/resources/messages_zh_CN.properties b/manager/tm/src/main/resources/messages_zh_CN.properties
index 6be09d61d8..4d2b55c2ea 100644
--- a/manager/tm/src/main/resources/messages_zh_CN.properties
+++ b/manager/tm/src/main/resources/messages_zh_CN.properties
@@ -285,3 +285,7 @@ webhook.history.hook.id.error=WebHook ID \u4E0D\u80FD\u4E3A\u7A7A
 webhook.history.page.from.error=\u5206\u9875\u53C2\u6570[pageFrom]\u65E0\u6548\uFF0C\u8D77\u59CB\u503C\u5E94\u8BE5\u5927\u4E8E\u7B49\u4E8E0\uFF0C\u5B9E\u9645\u503C\u4E3A\uFF1A{0}
 webhook.history.page.size.error=\u5206\u9875\u53C2\u6570[pageSize]\u65E0\u6548\uFF0C\u6BCF\u9875\u5927\u5C0F\u5E94\u8BE5\u5927\u4E8E0\uFF0C\u5B9E\u9645\u503C\u4E3A\uFF1A{0}
 
+#auth
+insufficient.permissions=\u6743\u9650\u4E0D\u8DB3: {0}\uFF0C\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u6743\u9650\uFF1A{1}
+inspect.result.not.exists=\u5F53\u524D\u6821\u9A8C\u4EFB\u52A1\u7684\u6821\u9A8C\u8BB0\u5F55\u5DF2\u7ECF\u4E0D\u5B58\u5728\uFF1A{0}
+
diff --git a/manager/tm/src/main/resources/messages_zh_TW.properties b/manager/tm/src/main/resources/messages_zh_TW.properties
index 677e23c916..0b14e3a24f 100644
--- a/manager/tm/src/main/resources/messages_zh_TW.properties
+++ b/manager/tm/src/main/resources/messages_zh_TW.properties
@@ -121,3 +121,7 @@ webhook.reOpen.failed=WebHook\u91CD\u65B0\u6253\u958B\u5931\u6557\uFF0C\u8ACB\u9
 webhook.history.hook.id.error=WebHook ID \u4E0D\u80FD\u70BA\u7A7A
 webhook.history.page.from.error=\u5206\u9801\u53C3\u6578[pageFrom]\u7121\u6548\uFF0C\u8D77\u59CB\u503C\u61C9\u8A72\u5927\u65BC\u7B49\u65BC0\uFF0C\u5BE6\u969B\u503C\u70BA\uFF1A{0}
 webhook.history.page.size.error=\u5206\u9801\u53C3\u6578[pageSize]\u7121\u6548\uFF0C\u6BCF\u9801\u5927\u5C0F\u61C9\u8A72\u5927\u65BC0\uFF0C\u5BE6\u969B\u503C\u70BA\uFF1A{0}
+
+#auth
+insufficient.permissions=\u8A31\u53EF\u6B0A\u4E0D\u8DB3: {0}\uFF0C\u5F53\u524D\u64CD\u4F5C\u9700\u8981\u6743\u9650\uFF1A{1}
+inspect.result.not.exists=\u7576\u524D\u6821\u9A57\u4EFB\u52D9\u7684\u6821\u9A57\u8A18\u9304\u5DF2\u7D93\u4E0D\u5B58\u5728\uFF1A{0}


Test patch:
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java
index 8ba4321bda..262ade7cb5 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/HazelcastBaseNodeTest.java
@@ -8,6 +8,7 @@
 import com.hazelcast.jet.core.JobStatus;
 import com.hazelcast.jet.core.Outbox;
 import com.tapdata.constant.BeanUtil;
+import com.tapdata.constant.ConnectorConstant;
 import com.tapdata.entity.MessageEntity;
 import com.tapdata.entity.OperationType;
 import com.tapdata.entity.TapdataEvent;
@@ -1354,11 +1355,17 @@ void testCloseWhenDoCloseError() {
 	@Nested
 	@DisplayName("ErrorHandle method test")
 	class ErrorHandleTest {
+		HttpClientMongoOperator clientMongoOperator;
+		ObsLogger obsLogger;
 		@BeforeEach
 		void beforeEach() {
+			obsLogger = mock(ObsLogger.class);
+			clientMongoOperator = mock(HttpClientMongoOperator.class);
 			hazelcastBaseNode = spy(hazelcastBaseNode);
-			ReflectionTestUtils.setField(hazelcastBaseNode,"clientMongoOperator",mock(HttpClientMongoOperator.class));
+			ReflectionTestUtils.setField(hazelcastBaseNode,"clientMongoOperator", clientMongoOperator);
 			ReflectionTestUtils.setField(hazelcastBaseNode,"obsLogger",mock(ObsLogger.class));
+			doNothing().when(clientMongoOperator).insertOne(anyList(), anyString());
+			doNothing().when(obsLogger).warn(anyString(), anyString());
 		}
 
 		@Test
@@ -1426,6 +1433,14 @@ void testErrorHandleSkipTapMessageIsNull() {
 			TapCodeException tapCodeException = new TapCodeException("1001");
 			Assertions.assertNull(hazelcastBaseNode.errorHandle(tapCodeException,"error3"));
 		}
+
+		@Test
+		void testClientMongoOperatorIsNull() {
+			ReflectionTestUtils.setField(hazelcastBaseNode,"clientMongoOperator", null);
+			when(hazelcastBaseNode.initClientMongoOperator()).thenReturn(clientMongoOperator);
+			TapCodeException tapCodeException = new TapCodeException("1001");
+			Assertions.assertNull(hazelcastBaseNode.errorHandle(tapCodeException,"error3"));
+		}
 	}
 
 	@Nested
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
index ab25b82eaa..9f14b8d9cd 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastPdkBaseNodeTest.java
@@ -19,7 +19,6 @@
 import io.tapdata.entity.simplify.TapSimplify;
 import io.tapdata.flow.engine.V2.entity.PdkStateMap;
 import io.tapdata.flow.engine.V2.filter.TapRecordSkipDetector;
-import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.pdk.apis.entity.ConnectionOptions;
 import io.tapdata.pdk.apis.entity.ConnectorCapabilities;
 import io.tapdata.pdk.core.entity.params.PDKMethodInvoker;
@@ -27,6 +26,7 @@
 import org.bson.Document;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.*;
+import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.test.util.ReflectionTestUtils;
 
@@ -358,12 +358,18 @@ void cleanFunctionRetryTest(){
 		}
 
 		@Test
-		void signFunctionRetryTest(){
+		void signFunctionRetryTest() {
 			when(mongoCollection.update(any(), any(), anyString())).thenAnswer(a -> {
-				Update update = (Update)a.getArgument(1);
-				Document updateObject = (Document)update.getUpdateObject().get("$set");
+				Query query = (Query) a.getArgument(0);
+				assertEquals(true, query.getQueryObject().containsKey("_id"));
+				Document orCriteria = (Document) query.getQueryObject().get("$or");
+				Document existDocument = (Document) orCriteria.get("functionRetryStatus");
+				assertEquals(true, existDocument.get("$exists"));
+				assertEquals(TaskDto.RETRY_STATUS_NONE, orCriteria.get("functionRetryStatus"));
+				Update update = (Update) a.getArgument(1);
+				Document updateObject = (Document) update.getUpdateObject().get("$set");
 				String functionRetryStatus = (String) updateObject.get("functionRetryStatus");
-				assertEquals("Retrying",functionRetryStatus);
+				assertEquals("Retrying", functionRetryStatus);
 				return null;
 			});
 			spyhazelcastPdkBaseNode.signFunctionRetry("65aa211475a5ac694df51c69");
@@ -446,55 +452,89 @@ void test2(){
 	}
 
 	@Nested
+	@DisplayName("Method generateNodeConfig test")
 	class GenerateNodeConfigTest {
 
 		@BeforeEach
 		void setUp(){
 			hazelcastPdkBaseNode = mock(HazelcastPdkBaseNode.class);
-			doCallRealMethod().when(hazelcastPdkBaseNode).generateNodeConfig(any(), any());
+			when(hazelcastPdkBaseNode.generateNodeConfig(any(Node.class), any(TaskDto.class))).thenCallRealMethod();
 		}
 
 		@Test
+		@DisplayName("test TableNode")
 		void testTableNode() {
 			Map<String, Object> nodeConfig = TapSimplify.map(TapSimplify.entry("key", "value"));
 			TableNode node1 = new TableNode();
 			node1.setNodeConfig(null);
 			TableNode node2 = new TableNode();
 			node2.setNodeConfig(nodeConfig);
-			Assertions.assertEquals(1, hazelcastPdkBaseNode.generateNodeConfig(node1, true).size());
-			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node1, true).containsKey("doubleActive"));
-			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node2, true).size());
+			taskDto.setDoubleActive(true);
+			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node1, taskDto).size());
+			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node1, taskDto).containsKey("doubleActive"));
+			Assertions.assertEquals(3, hazelcastPdkBaseNode.generateNodeConfig(node2, taskDto).size());
 		}
 
 		@Test
+		@DisplayName("test DatabaseNode")
 		void testDatabaseNode() {
 			Map<String, Object> nodeConfig = TapSimplify.map(TapSimplify.entry("key", "value"));
 			DatabaseNode node1 = new DatabaseNode();
 			node1.setNodeConfig(null);
 			DatabaseNode node2 = new DatabaseNode();
 			node2.setNodeConfig(nodeConfig);
-			Assertions.assertEquals(1, hazelcastPdkBaseNode.generateNodeConfig(node1, true).size());
-			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node1, true).containsKey("doubleActive"));
-			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node2, true).size());
+			taskDto.setDoubleActive(true);
+			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node1, taskDto).size());
+			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node1, taskDto).containsKey("doubleActive"));
+			Assertions.assertEquals(3, hazelcastPdkBaseNode.generateNodeConfig(node2, taskDto).size());
 		}
 
 		@Test
+		@DisplayName("test LogCollectorNode")
 		void testLogCollectorNode() {
 			Map<String, Object> nodeConfig = TapSimplify.map(TapSimplify.entry("key", "value"));
 			LogCollectorNode node1 = new LogCollectorNode();
 			node1.setNodeConfig(null);
 			LogCollectorNode node2 = new LogCollectorNode();
 			node2.setNodeConfig(nodeConfig);
-			Assertions.assertEquals(1, hazelcastPdkBaseNode.generateNodeConfig(node1, true).size());
-			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node1, true).containsKey("doubleActive"));
-			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node2, true).size());
+			taskDto.setDoubleActive(true);
+			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node1, taskDto).size());
+			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node1, taskDto).containsKey("doubleActive"));
+			Assertions.assertEquals(3, hazelcastPdkBaseNode.generateNodeConfig(node2, taskDto).size());
 		}
 
 		@Test
+		@DisplayName("test other node type")
 		void testOtherNode() {
 			CacheNode node = new CacheNode();
-			Assertions.assertEquals(1, hazelcastPdkBaseNode.generateNodeConfig(node, true).size());
-			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node, true).containsKey("doubleActive"));
+			taskDto.setDoubleActive(true);
+			Assertions.assertEquals(2, hazelcastPdkBaseNode.generateNodeConfig(node, taskDto).size());
+			Assertions.assertTrue(hazelcastPdkBaseNode.generateNodeConfig(node, taskDto).containsKey("doubleActive"));
+		}
+
+		@Test
+		@DisplayName("test oldVersionTimezone")
+		void testOldVersionTimezone() {
+			TableNode node = new TableNode();
+			taskDto.setOldVersionTimezone(true);
+			Map<String, Object> nodeConfig = hazelcastPdkBaseNode.generateNodeConfig(node, taskDto);
+			assertTrue(nodeConfig.containsKey(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
+			assertTrue((Boolean) nodeConfig.get(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
+			taskDto.setOldVersionTimezone(false);
+			nodeConfig = hazelcastPdkBaseNode.generateNodeConfig(node, taskDto);
+			assertTrue(nodeConfig.containsKey(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
+			assertFalse((Boolean) nodeConfig.get(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
+		}
+
+		@Test
+		@DisplayName("test oldVersionTimezone when set system property")
+		void testOldVersionTimezone1() {
+			TableNode node = new TableNode();
+			taskDto.setOldVersionTimezone(true);
+			System.setProperty(HazelcastPdkBaseNode.OLD_VERSION_TIME_ZONE_PROP_KEY, "false");
+			Map<String, Object> nodeConfig = hazelcastPdkBaseNode.generateNodeConfig(node, taskDto);
+			assertTrue(nodeConfig.containsKey(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
+			assertFalse((Boolean) nodeConfig.get(HazelcastPdkBaseNode.OLD_VERSION_TIMEZONE));
 		}
 	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
index a2a258b2b5..3b3940c3da 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
@@ -8,9 +8,12 @@
 import com.tapdata.entity.dataflow.SyncProgress;
 import com.tapdata.entity.dataflow.TableBatchReadStatus;
 import com.tapdata.entity.dataflow.batch.BatchOffsetUtil;
+import com.tapdata.entity.task.config.TaskConfig;
+import com.tapdata.entity.task.config.TaskRetryConfig;
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.DatabaseNode;
 import com.tapdata.tm.commons.dag.nodes.TableNode;
 import io.tapdata.aspect.BatchReadFuncAspect;
 import io.tapdata.aspect.SourceStateAspect;
@@ -27,11 +30,13 @@
 import io.tapdata.entity.schema.TapField;
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.type.TapDateTime;
+import io.tapdata.entity.schema.type.TapString;
 import io.tapdata.entity.schema.value.DateTime;
 import io.tapdata.error.TaskProcessorExCode_11;
 import io.tapdata.exception.NodeException;
 import io.tapdata.exception.TapCodeException;
 import io.tapdata.observable.logging.ObsLogger;
+import io.tapdata.pdk.apis.consumer.StreamReadConsumer;
 import io.tapdata.pdk.apis.context.TapConnectorContext;
 import io.tapdata.pdk.apis.entity.QueryOperator;
 import io.tapdata.pdk.apis.entity.TapTimeForm;
@@ -40,17 +45,21 @@
 import io.tapdata.pdk.apis.functions.PDKMethod;
 import io.tapdata.pdk.apis.functions.connector.source.BatchCountFunction;
 import io.tapdata.pdk.apis.functions.connector.source.BatchReadFunction;
+import io.tapdata.pdk.apis.functions.connector.source.CountByPartitionFilterFunction;
 import io.tapdata.pdk.apis.functions.connector.source.ExecuteCommandFunction;
 import io.tapdata.pdk.apis.functions.connector.target.QueryByAdvanceFilterFunction;
+import io.tapdata.pdk.apis.spec.TapNodeSpecification;
 import io.tapdata.pdk.core.api.ConnectorNode;
 import io.tapdata.pdk.core.entity.params.PDKMethodInvoker;
 import io.tapdata.pdk.core.monitor.PDKInvocationMonitor;
+import io.tapdata.pdk.core.tapnode.TapNodeInfo;
 import io.tapdata.pdk.core.utils.CommonUtils;
 import io.tapdata.schema.TapTableMap;
 import lombok.SneakyThrows;
 import org.junit.Assert;
 import org.junit.jupiter.api.*;
 import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.time.LocalDateTime;
@@ -1418,4 +1427,169 @@ void test1(){
 			Assertions.assertEquals(2,result.size());
 		}
 	}
+	@Nested
+	class StreamReadConsumerStateListenerTest{
+		private ConnectorNode connectorNode;
+		@BeforeEach
+		void before(){
+			connectorNode = mock(ConnectorNode.class);
+			TapNodeInfo tapNodeInfo = mock(TapNodeInfo.class);
+			when(connectorNode.getTapNodeInfo()).thenReturn(tapNodeInfo);
+			TapNodeSpecification tapNodeSpecification = mock(TapNodeSpecification.class);
+			when(tapNodeInfo.getTapNodeSpecification()).thenReturn(tapNodeSpecification);
+			when(tapNodeSpecification.getName()).thenReturn("123");
+		}
+		@DisplayName("test stateListener STATE_STREAM_READ_STARTED")
+		@Test
+		void test1(){
+			try (MockedStatic<PDKInvocationMonitor> pdkInvocationMonitorMockedStatic = mockStatic(PDKInvocationMonitor.class)) {
+				ObsLogger obsLogger = mock(ObsLogger.class);
+				ReflectionTestUtils.setField(hazelcastSourcePdkDataNode, "obsLogger", obsLogger);
+				doNothing().when(obsLogger).info(anyString(), any(), any());
+				PDKMethodInvoker pdkMethodInvoker = mock(PDKMethodInvoker.class);
+				pdkInvocationMonitorMockedStatic.when(() -> PDKInvocationMonitor.invokerRetrySetter(pdkMethodInvoker)).thenAnswer((invocationOnMock) -> {
+					PDKMethodInvoker pdkMethodArgs = (PDKMethodInvoker) invocationOnMock.getArgument(0);
+					assertEquals(pdkMethodInvoker, pdkMethodArgs);
+					return null;
+				});
+				StreamReadConsumer streamReadConsumer = hazelcastSourcePdkDataNode.generateStreamReadConsumer(connectorNode, pdkMethodInvoker);
+				streamReadConsumer.streamReadStarted();
+				verify(obsLogger, times(1)).info(anyString(), any(), any());
+			}
+		}
+
+	}
+
+	@Nested
+	class doBatchCountFunction {
+		long expectedValue = 99;
+		TapTable testTable = new TapTable("testTable");
+		BatchCountFunction mockBatchCountFunction = mock(BatchCountFunction.class);
+
+		@BeforeEach
+		void init(){
+			TaskConfig taskConfig = TaskConfig.create();
+			taskConfig.taskRetryConfig(TaskRetryConfig.create());
+			taskConfig.getTaskRetryConfig().retryIntervalSecond(1000L);
+			when(dataProcessorContext.getTaskConfig()).thenReturn(taskConfig);
+
+		}
+
+		@Test
+		@SneakyThrows
+		@DisplayName("Exception test")
+		void testException() {
+
+			when(dataProcessorContext.getNode()).thenReturn((Node) new DatabaseNode());
+
+			HazelcastSourcePdkBaseNode spyInstance = Mockito.spy(hazelcastSourcePdkDataNode);
+			doReturn(new ConnectorNode()).when(spyInstance).getConnectorNode();
+			when(mockBatchCountFunction.count(any(), any())).thenThrow(new Exception());
+
+			try {
+				spyInstance.doBatchCountFunction(mockBatchCountFunction, testTable);
+			} catch (Exception e) {
+				assertTrue(null != e.getCause() && null != e.getCause().getCause() && (e.getCause().getCause() instanceof NodeException), e.getMessage());
+			}
+		}
+
+		@Test
+		@SneakyThrows
+		@DisplayName("Normal  BatchCount test")
+		void testNormalBatchCount() {
+
+			when(dataProcessorContext.getNode()).thenReturn((Node) new TableNode());
+			HazelcastSourcePdkBaseNode spyInstance = Mockito.spy(hazelcastSourcePdkDataNode);
+			doReturn(new ConnectorNode()).when(spyInstance).getConnectorNode();
+			when(mockBatchCountFunction.count(any(), any())).thenReturn(expectedValue);
+
+
+			long actualData = spyInstance.doBatchCountFunction(mockBatchCountFunction, testTable);
+			Assertions.assertTrue(expectedValue == actualData);
+		}
+
+
+		@Test
+		@SneakyThrows
+		@DisplayName("Normal  BatchCount WithDataNode test")
+		void testNormalBatchCountWithDataNode() {
+
+			when(dataProcessorContext.getNode()).thenReturn((Node) new DatabaseNode());
+			HazelcastSourcePdkBaseNode spyInstance = Mockito.spy(hazelcastSourcePdkDataNode);
+			doReturn(new ConnectorNode()).when(spyInstance).getConnectorNode();
+			when(mockBatchCountFunction.count(any(), any())).thenReturn(expectedValue);
+
+
+			long actualData = spyInstance.doBatchCountFunction(mockBatchCountFunction, testTable);
+			Assertions.assertTrue(expectedValue == actualData);
+		}
+		@Test
+		@SneakyThrows
+		@DisplayName("Filter BatchCount test")
+		void testFilterBatchCount() {
+
+			TableNode testTableTemp = new TableNode();
+			testTableTemp.setIsFilter(true);
+			testTableTemp.setTableName("testTable");
+			List<QueryOperator> conditions = new ArrayList<>();
+			QueryOperator queryOperator = new QueryOperator();
+			queryOperator.setKey("id");
+			queryOperator.setValue("1");
+			queryOperator.setOperator(5);
+
+			conditions.add(queryOperator);
+			testTableTemp.setConditions(conditions);
+			when(dataProcessorContext.getNode()).thenReturn((Node) testTableTemp);
+			HazelcastSourcePdkBaseNode spyInstance = Mockito.spy(hazelcastSourcePdkDataNode);
+			ConnectorNode connectorNode = new ConnectorNode();
+			ConnectorFunctions connectorFunctions = new ConnectorFunctions();
+			CountByPartitionFilterFunction countByPartitionFilterFunction = mock(CountByPartitionFilterFunction.class);
+			connectorFunctions.supportCountByPartitionFilterFunction(countByPartitionFilterFunction);
+			ReflectionTestUtils.setField(connectorNode, "connectorFunctions", connectorFunctions);
+			doReturn(connectorNode).when(spyInstance).getConnectorNode();
+			LinkedHashMap<String, TapField> nameFieldMap = new LinkedHashMap<>();
+			TapField tapField = new TapField();
+			tapField.setTapType(new TapString());
+			nameFieldMap.put("id",tapField);
+			testTable.setNameFieldMap(nameFieldMap);
+			TapTableMap<String, TapTable> tapTableMap = TapTableMap.create("testTable", testTable);
+			when(dataProcessorContext.getTapTableMap()).thenReturn(tapTableMap);
+			when(countByPartitionFilterFunction.countByPartitionFilter(any(),any(),any())).thenReturn(expectedValue);
+
+			long actualData = spyInstance.doBatchCountFunction(mockBatchCountFunction, testTable);
+			Assertions.assertTrue(expectedValue == actualData);
+
+		}
+
+
+		@Test
+		@SneakyThrows
+		@DisplayName("Filter filterBatchCount  not support")
+		void testFilterBatchCountNotSupport() {
+
+			TableNode testTableTemp = new TableNode();
+			testTableTemp.setIsFilter(true);
+			testTableTemp.setTableName("testTable");
+			List<QueryOperator> conditions = new ArrayList<>();
+			QueryOperator queryOperator = new QueryOperator();
+			queryOperator.setKey("id");
+			queryOperator.setValue("1");
+			queryOperator.setOperator(5);
+
+			conditions.add(queryOperator);
+			testTableTemp.setConditions(conditions);
+			when(dataProcessorContext.getNode()).thenReturn((Node) testTableTemp);
+			HazelcastSourcePdkBaseNode spyInstance = Mockito.spy(hazelcastSourcePdkDataNode);
+			ConnectorNode connectorNode = new ConnectorNode();
+			ConnectorFunctions connectorFunctions = new ConnectorFunctions();
+			ReflectionTestUtils.setField(connectorNode, "connectorFunctions", connectorFunctions);
+			doReturn(connectorNode).when(spyInstance).getConnectorNode();
+			when(mockBatchCountFunction.count(any(), any())).thenReturn(expectedValue);
+
+			long actualData = spyInstance.doBatchCountFunction(mockBatchCountFunction, testTable);
+			Assertions.assertTrue(expectedValue == actualData);
+
+		}
+
+	}
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNodeTest.java
new file mode 100644
index 0000000000..821a5b789e
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastRenameTableProcessorNodeTest.java
@@ -0,0 +1,92 @@
+package io.tapdata.flow.engine.V2.node.hazelcast.processor;
+
+import base.hazelcast.BaseHazelcastNodeTest;
+import com.tapdata.entity.TapdataEvent;
+import com.tapdata.tm.commons.dag.process.TableRenameProcessNode;
+import io.tapdata.entity.event.TapBaseEvent;
+import io.tapdata.entity.event.control.StopEvent;
+import io.tapdata.entity.event.ddl.entity.ValueChange;
+import io.tapdata.entity.event.ddl.table.TapCreateTableEvent;
+import io.tapdata.entity.event.ddl.table.TapDropTableEvent;
+import io.tapdata.entity.event.ddl.table.TapRenameTableEvent;
+import io.tapdata.entity.event.dml.TapInsertRecordEvent;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+
+import static org.mockito.Mockito.doCallRealMethod;
+
+/**
+ * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
+ * @version v1.0 2024/5/21 15:38 Create
+ */
+class HazelcastRenameTableProcessorNodeTest extends BaseHazelcastNodeTest {
+
+    private static final String tableName = "TEST";
+    private static final String expected = tableName;
+    private HazelcastRenameTableProcessorNode instance;
+
+    @BeforeEach
+    void beforeSetUp() {
+        super.allSetup();
+
+        TableRenameProcessNode node = new TableRenameProcessNode();
+        ReflectionTestUtils.setField(processorBaseContext, "node", node);
+        doCallRealMethod().when(processorBaseContext).getNode();
+
+        instance = new HazelcastRenameTableProcessorNode(processorBaseContext);
+    }
+
+    @Test
+    void testTryProcess() {
+        TapInsertRecordEvent insertRecord = TapInsertRecordEvent.create().table(tableName);
+        TapdataEvent event = new TapdataEvent();
+        event.setTapEvent(insertRecord);
+
+        instance.tryProcess(event, (tapdataEvent, processResult) -> {
+            TapBaseEvent tapEvent = (TapBaseEvent) tapdataEvent.getTapEvent();
+            Assertions.assertEquals(expected, tapEvent.getTableId(), String.format("Case '%s' to '%s'", tableName, expected));
+        });
+
+        // Verify un-TapBaseEvent has not exceptions.
+        TapdataEvent tmpEvent = new TapdataEvent();
+        tmpEvent.setTapEvent(new StopEvent());
+        instance.tryProcess(tmpEvent, (tapdataEvent, processResult) -> {
+        });
+    }
+
+    @Nested
+    class GetTgtTableNameFromTapEventTest {
+        @Test
+        void testTableIUDAndRename() {
+
+            // create table
+            TapCreateTableEvent createTable = new TapCreateTableEvent();
+            createTable.setTableId(tableName);
+            String currentTableName = instance.getTgtTableNameFromTapEvent(createTable);
+            Assertions.assertEquals(expected, currentTableName);
+
+            // rename table
+            TapRenameTableEvent tableEvent = new TapRenameTableEvent();
+            tableEvent.setTableId(tableName);
+            tableEvent.setNameChanges(Collections.singletonList(new ValueChange<>(tableName, expected)));
+            currentTableName = instance.getTgtTableNameFromTapEvent(tableEvent);
+            Assertions.assertEquals(expected, currentTableName);
+
+            // drop table
+            TapDropTableEvent dropTable = new TapDropTableEvent();
+            dropTable.setTableId(tableName);
+            currentTableName = instance.getTgtTableNameFromTapEvent(dropTable);
+            Assertions.assertEquals(expected, currentTableName);
+
+            // second stop table test tableName not in cache
+            currentTableName = instance.getTgtTableNameFromTapEvent(dropTable);
+            Assertions.assertEquals(expected, currentTableName);
+        }
+    }
+
+}
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
index 15903bdae3..d28fc563b1 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/task/impl/HazelcastTaskServiceTest.java
@@ -16,6 +16,7 @@
 import com.tapdata.tm.commons.dag.process.*;
 import com.tapdata.tm.commons.dag.vo.ReadPartitionOptions;
 import com.tapdata.tm.commons.externalStorage.ExternalStorageDto;
+import com.tapdata.tm.commons.task.dto.ErrorEvent;
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import io.tapdata.common.SettingService;
 import io.tapdata.entity.schema.TapTable;
@@ -25,8 +26,11 @@
 import io.tapdata.flow.engine.V2.node.hazelcast.processor.*;
 import io.tapdata.flow.engine.V2.node.hazelcast.processor.join.HazelcastJoinProcessor;
 import io.tapdata.flow.engine.V2.util.ExternalStorageUtil;
+import io.tapdata.flow.engine.util.TaskDtoUtil;
+import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.schema.TapTableMap;
 import lombok.SneakyThrows;
+import org.bson.types.ObjectId;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
@@ -383,4 +387,56 @@ void testCreateNode21(){
             assertEquals(HazelcastAddDateFieldProcessNode.class, actual.getClass());
         }
     }
+
+    @Nested
+    class CleanAllUnselectedErrorTest {
+        HazelcastTaskService hazelcastTaskService;
+        TaskDto taskDto;
+        ObsLogger obsLogger;
+        ClientMongoOperator clientMongoOperator;
+
+        List<ErrorEvent> errorEvents;
+        ErrorEvent errorEvent;
+        @BeforeEach
+        void init() {
+            clientMongoOperator = mock(ClientMongoOperator.class);
+            hazelcastTaskService = mock(HazelcastTaskService.class);
+            ReflectionTestUtils.setField(hazelcastTaskService, "clientMongoOperator", clientMongoOperator);
+
+            taskDto = mock(TaskDto.class);
+            obsLogger = mock(ObsLogger.class);
+
+            errorEvents = new ArrayList<>();
+            errorEvent = new ErrorEvent();
+
+
+            when(taskDto.getErrorEvents()).thenReturn(errorEvents);
+            doNothing().when(taskDto).setErrorEvents(anyList());
+            when(taskDto.getId()).thenReturn(new ObjectId());
+
+            doCallRealMethod().when(hazelcastTaskService).cleanAllUnselectedError(taskDto, obsLogger);
+        }
+        @Test
+        void testNormal() {
+            errorEvents.add(errorEvent);
+            try(MockedStatic<TaskDtoUtil> tdu = mockStatic(TaskDtoUtil.class)) {
+                tdu.when(() -> TaskDtoUtil.updateErrorEvent(any(ClientMongoOperator.class), anyList(), any(ObjectId.class), any(ObsLogger.class), anyString())).thenAnswer(a -> null);
+                hazelcastTaskService.cleanAllUnselectedError(taskDto, obsLogger);
+                verify(taskDto).getErrorEvents();
+                verify(taskDto).setErrorEvents(anyList());
+                verify(taskDto).getId();
+            }
+        }
+
+        @Test
+        void testEmpty() {
+            try(MockedStatic<TaskDtoUtil> tdu = mockStatic(TaskDtoUtil.class)) {
+                tdu.when(() -> TaskDtoUtil.updateErrorEvent(any(ClientMongoOperator.class), anyList(), any(ObjectId.class), any(ObsLogger.class), anyString())).thenAnswer(a -> null);
+                hazelcastTaskService.cleanAllUnselectedError(taskDto, obsLogger);
+                verify(taskDto).getErrorEvents();
+                verify(taskDto, times(0)).setErrorEvents(anyList());
+                verify(taskDto, times(0)).getId();
+            }
+        }
+    }
 }
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/util/TaskDtoUtilTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/util/TaskDtoUtilTest.java
new file mode 100644
index 0000000000..3d12543533
--- /dev/null
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/util/TaskDtoUtilTest.java
@@ -0,0 +1,59 @@
+package io.tapdata.flow.engine.util;
+
+import com.tapdata.mongo.ClientMongoOperator;
+import com.tapdata.tm.commons.task.dto.ErrorEvent;
+import io.tapdata.observable.logging.ObsLogger;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+class TaskDtoUtilTest {
+
+    @Nested
+    class UpdateErrorEventTest {
+        ClientMongoOperator clientMongoOperator;
+        List<ErrorEvent> errorEvents;
+        ObsLogger obsLogger;
+        String errorMsg;
+        ObjectId taskId;
+        @BeforeEach
+        void init() {
+            clientMongoOperator = mock(ClientMongoOperator.class);
+            errorEvents = mock(List.class);
+            obsLogger = mock(ObsLogger.class);
+            errorMsg = "errorMsg";
+            taskId = mock(ObjectId.class);
+            doNothing().when(clientMongoOperator).insertOne(anyList(), anyString());
+            doNothing().when(obsLogger).warn(anyString(), anyString());
+        }
+
+        @Test
+        void testNormal() {
+            Assertions.assertDoesNotThrow(() -> TaskDtoUtil.updateErrorEvent(clientMongoOperator, errorEvents, taskId, obsLogger, errorMsg));
+            verify(clientMongoOperator).insertOne(anyList(), anyString());
+            verify(obsLogger, times(0)).warn(anyString(), anyString());
+        }
+
+        @Test
+        void testException() {
+            doAnswer(a -> {
+                throw new Exception("xxx");
+            }).when(clientMongoOperator).insertOne(anyList(), anyString());
+            Assertions.assertDoesNotThrow(() -> TaskDtoUtil.updateErrorEvent(clientMongoOperator, errorEvents, taskId, obsLogger, errorMsg));
+            verify(clientMongoOperator).insertOne(anyList(), anyString());
+            verify(obsLogger).warn(anyString(), anyString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetTest.java b/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetTest.java
index ad9e9c4eee..eb993c6157 100644
--- a/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetTest.java
+++ b/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetTest.java
@@ -8,6 +8,11 @@
 import java.util.Map;
 
 class BatchOffsetTest {
+    @Test
+    void testClass() {
+        Class<?>[] interfaces = BatchOffset.class.getInterfaces();
+        Assertions.assertEquals(2, interfaces.length);
+    }
     @Nested
     class CloneTest {
         @Test
diff --git a/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java b/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java
index 1da7a4ba86..c3b49ce762 100644
--- a/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java
+++ b/iengine/iengine-common/src/test/java/com/tapdata/entity/dataflow/batch/BatchOffsetUtilTest.java
@@ -3,9 +3,6 @@
 
 import com.tapdata.entity.dataflow.SyncProgress;
 import com.tapdata.entity.dataflow.TableBatchReadStatus;
-import com.tapdata.entity.dataflow.batch.BatchOffset;
-import com.tapdata.entity.dataflow.batch.BatchOffsetUtil;
-import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.entity.ValueChange;
 import io.tapdata.entity.event.ddl.table.TapAlterFieldNameEvent;
 import io.tapdata.entity.event.ddl.table.TapRenameTableEvent;
@@ -15,16 +12,16 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
-import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyMap;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
@@ -47,7 +44,7 @@ void init() {
         batchOffset.setStatus(TableBatchReadStatus.OVER.name());
         batchOffset.setOffset(0L);
         batchOffset = new BatchOffset(0L, TableBatchReadStatus.OVER.name());
-        batchOffsetObj = mock(Map.class);
+        batchOffsetObj = new HashMap<>();
         when(syncProgress.getBatchOffsetObj()).thenReturn(batchOffsetObj);
     }
 
@@ -78,6 +75,35 @@ void testNotIsBatchOffsetMap() {
                 Assertions.assertFalse(BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId));
             }
         }
+
+        @Test
+        void testNotContainsKey() {
+            try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId)).thenCallRealMethod();
+                bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(new HashMap<>());
+                Assertions.assertFalse(BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId));
+            }
+        }
+
+        @Test
+        void testContainsKey() {
+            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
+            objectObjectHashMap.put(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS, "0");
+            when(syncProgress.getBatchOffsetObj()).thenReturn(objectObjectHashMap);
+            try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId)).thenCallRealMethod();
+                bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(objectObjectHashMap);
+                Assertions.assertFalse(BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId));
+            }
+        }
+        @Test
+        void testNotContainsAnyKey() {
+            try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId)).thenCallRealMethod();
+                bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(0L);
+                Assertions.assertFalse(BatchOffsetUtil.batchIsOverOfTable(syncProgress, tableId));
+            }
+        }
     }
 
     @Nested
@@ -86,7 +112,7 @@ class GetTableOffsetInfoTest {
 
         @BeforeEach
         void init() {
-            when(batchOffsetObj.get(tableId)).thenReturn(0);
+            batchOffsetObj.put(tableId, 0);
         }
 
         @Test
@@ -95,7 +121,6 @@ void testNormal() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId));
-                verify(batchOffsetObj, times(1)).get(tableId);
             }
         }
 
@@ -105,7 +130,6 @@ void testBatchOffsetObjNotMap() {
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId));
-                verify(batchOffsetObj, times(0)).get(tableId);
             }
         }
     }
@@ -130,6 +154,32 @@ void testNotIsBatchOffsetMap() {
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
             }
         }
+        @Test
+        void testNotContainsKey() {
+            try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(new HashMap<>());
+                bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
+                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+            }
+        }
+        @Test
+        void testContainsKey() {
+            Map<String, Object> map = new HashMap<>();
+            map.put(BatchOffsetUtil.BATCH_READ_CONNECTOR_OFFSET, 0);
+            try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(map);
+                bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
+                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+            }
+        }
+        @Test
+        void testNotContainsAnyKey() {
+            try (MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.getTableOffsetInfo(syncProgress, tableId)).thenReturn(0L);
+                bou.when(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId)).thenCallRealMethod();
+                Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.getBatchOffsetOfTable(syncProgress, tableId));
+            }
+        }
     }
 
     @Nested
@@ -160,6 +210,7 @@ void testNormal() {
                 return tableOffsetObjTemp;
             });
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.updateBatchOffset(anyMap(), any(), anyString())).thenCallRealMethod();
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
@@ -177,6 +228,7 @@ void offsetNotMap() {
                 return tableOffsetObjTemp;
             });
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.updateBatchOffset(anyMap(), any(), anyString())).thenCallRealMethod();
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
@@ -188,15 +240,70 @@ void offsetNotMap() {
         void testTableOffsetNotBatchOffset() {
             when(syncProgress.getBatchOffsetObj()).thenReturn(batchOffsetObjTemp);
             when(batchOffsetObjTemp.computeIfAbsent(anyString(), any())).thenReturn(0);
-            when(batchOffsetObjTemp.put(anyString(), any(BatchOffset.class))).thenReturn(0);
+            when(batchOffsetObjTemp.put(anyString(), any(Map.class))).thenReturn(0);
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
+                bou.when(() -> BatchOffsetUtil.updateBatchOffset(anyMap(), any(), anyString())).thenCallRealMethod();
                 bou.when(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.updateBatchOffset(syncProgress, tableId, offset, isOverTag));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
                 verify(batchOffsetObjTemp, times(1)).computeIfAbsent(anyString(), any());
-                verify(batchOffsetObjTemp, times(1)).put(anyString(), any(BatchOffset.class));
+                verify(batchOffsetObjTemp, times(1)).put(anyString(), any(Map.class));
             }
         }
+
+        @Test
+        void testUpdateBatchOffset() {
+            Object offset = BatchOffsetUtil.updateBatchOffset(null, 1L, "OK");
+            Assertions.assertNotNull(offset);
+            Assertions.assertEquals(HashMap.class.getName(), offset.getClass().getName());
+            Assertions.assertEquals(1L, ((HashMap<String, Object>)offset).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_OFFSET));
+            Assertions.assertEquals("OK", ((HashMap<String, Object>)offset).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS));
+        }
+
+        @Test
+        void testUpdateBatchOffsetWithOffsetMap() {
+            Object offset = BatchOffsetUtil.updateBatchOffset(null, 1L, "OK");
+            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
+            objectObjectHashMap.put("tableId", offset);
+            SyncProgress syncProgress = new SyncProgress();
+            syncProgress.setBatchOffsetObj(objectObjectHashMap);
+            BatchOffsetUtil.updateBatchOffset(syncProgress, "tableId", offset, "NOT_OK");
+            Assertions.assertEquals("NOT_OK", ((Map<String, Object>)offset).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS));
+        }
+        @Test
+        void testUpdateBatchOffsetWithOffsetMapNotHaveOffset() {
+            Map<String, Object> offset = new HashMap<>();
+            offset.put(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS, "OK");
+            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
+            objectObjectHashMap.put("tableId", offset);
+            SyncProgress syncProgress = new SyncProgress();
+            syncProgress.setBatchOffsetObj(objectObjectHashMap);
+            BatchOffsetUtil.updateBatchOffset(syncProgress, "tableId", offset, "NOT_OK");
+            Assertions.assertEquals("NOT_OK", ((Map<String, Object>)objectObjectHashMap.get("tableId")).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS));
+        }
+        @Test
+        void testUpdateBatchOffsetWithOffsetMapNotHaveStatus() {
+            Map<String, Object> offset = new HashMap<>();
+            offset.put(BatchOffsetUtil.BATCH_READ_CONNECTOR_OFFSET, "OK");
+            Map<Object, Object> objectObjectHashMap = new HashMap<>();
+            objectObjectHashMap.put("tableId", offset);
+            SyncProgress syncProgress = new SyncProgress();
+            syncProgress.setBatchOffsetObj(objectObjectHashMap);
+            BatchOffsetUtil.updateBatchOffset(syncProgress, "tableId", offset, "NOT_OK");
+            Assertions.assertEquals("NOT_OK", ((Map<String, Object>)objectObjectHashMap.get("tableId")).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS));
+        }
+        @Test
+        void testUpdateBatchOffsetWithOffsetMapAllHaveStatus() {
+            Map<String, Object> offset = new HashMap<>();
+            offset.put(BatchOffsetUtil.BATCH_READ_CONNECTOR_OFFSET, "OK");
+            offset.put(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS, "OK");
+            HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
+            objectObjectHashMap.put("tableId", offset);
+            SyncProgress syncProgress = new SyncProgress();
+            syncProgress.setBatchOffsetObj(objectObjectHashMap);
+            BatchOffsetUtil.updateBatchOffset(syncProgress, "tableId", offset, "NOT_OK");
+            Assertions.assertEquals("NOT_OK", ((Map<String, Object>)objectObjectHashMap.get("tableId")).get(BatchOffsetUtil.BATCH_READ_CONNECTOR_STATUS));
+        }
     }
 
     @Nested
@@ -207,10 +314,7 @@ void init() {
             oldName = "oldName";
             newName = "newName";
 
-            when(batchOffsetObj.containsKey(oldName)).thenReturn(true);
-            when(batchOffsetObj.get(oldName)).thenReturn(0);
-            when(batchOffsetObj.remove(oldName)).thenReturn(0);
-            when(batchOffsetObj.put(newName, 0)).thenReturn(0);
+            batchOffsetObj.put(oldName, 0);
         }
         @Test
         void testNormal() {
@@ -218,10 +322,6 @@ void testNormal() {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
-                verify(batchOffsetObj, times(1)).containsKey(oldName);
-                verify(batchOffsetObj, times(1)).get(oldName);
-                verify(batchOffsetObj, times(1)).remove(oldName);
-                verify(batchOffsetObj, times(1)).put(newName, 0);
             }
         }
         @Test
@@ -231,23 +331,15 @@ void testOffsetNotMap() {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
-                verify(batchOffsetObj, times(0)).containsKey(oldName);
-                verify(batchOffsetObj, times(0)).get(oldName);
-                verify(batchOffsetObj, times(0)).remove(oldName);
-                verify(batchOffsetObj, times(0)).put(newName, 0);
             }
         }
         @Test
         void testOffsetNotContainsKey() {
-            when(batchOffsetObj.containsKey(oldName)).thenReturn(false);
+            batchOffsetObj.remove(oldName);
             try(MockedStatic<BatchOffsetUtil> bou = mockStatic(BatchOffsetUtil.class)) {
                 bou.when(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName)).thenCallRealMethod();
                 Assertions.assertDoesNotThrow(() -> BatchOffsetUtil.tableUpdateName(syncProgress, oldName, newName));
                 verify(syncProgress, times(1)).getBatchOffsetObj();
-                verify(batchOffsetObj, times(1)).containsKey(oldName);
-                verify(batchOffsetObj, times(0)).get(oldName);
-                verify(batchOffsetObj, times(0)).remove(oldName);
-                verify(batchOffsetObj, times(0)).put(newName, 0);
             }
         }
     }
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnumsTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnumsTest.java
new file mode 100644
index 0000000000..baf3a65d3a
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/permissions/constants/DataPermissionDataTypeEnumsTest.java
@@ -0,0 +1,20 @@
+package com.tapdata.tm.permissions.constants;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.LinkedHashSet;
+
+class DataPermissionDataTypeEnumsTest {
+    @Test
+    void testInspect() {
+        Assertions.assertEquals("Inspect", DataPermissionDataTypeEnums.INSPECT.getCollection());
+        LinkedHashSet<String> actions = DataPermissionDataTypeEnums.INSPECT.allActions();
+        Assertions.assertEquals(5, actions.size());
+    }
+
+    @Test
+    void testParse() {
+        Assertions.assertNotNull(DataPermissionDataTypeEnums.parse("Inspect"));
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnumsTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnumsTest.java
new file mode 100644
index 0000000000..22fc10cb7e
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/permissions/constants/DataPermissionMenuEnumsTest.java
@@ -0,0 +1,12 @@
+package com.tapdata.tm.permissions.constants;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class DataPermissionMenuEnumsTest {
+    @Test
+    void testInspect() {
+        Assertions.assertEquals("", DataPermissionMenuEnums.INSPECT_TACK.getAllDataPermissionName());
+        Assertions.assertEquals(DataPermissionDataTypeEnums.INSPECT, DataPermissionMenuEnums.INSPECT_TACK.getDataType());
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java b/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java
new file mode 100644
index 0000000000..ad4828627b
--- /dev/null
+++ b/manager/tm-api/src/test/java/com/tapdata/tm/utils/QueryUtilTest.java
@@ -0,0 +1,16 @@
+package com.tapdata.tm.utils;
+
+import com.tapdata.tm.base.dto.Where;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class QueryUtilTest {
+    @Nested
+    class ParseWhereToCriteriaTest {
+        @Test
+        void test() {
+            Assertions.assertNotNull(QueryUtil.parseWhereToCriteria(new Where()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNodeTest.java b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNodeTest.java
new file mode 100644
index 0000000000..132e825d92
--- /dev/null
+++ b/manager/tm-common/src/test/java/com/tapdata/tm/commons/dag/process/TableRenameProcessNodeTest.java
@@ -0,0 +1,175 @@
+package com.tapdata.tm.commons.dag.process;
+
+import com.tapdata.tm.commons.dag.DAG;
+import com.tapdata.tm.commons.dag.vo.TableRenameTableInfo;
+import com.tapdata.tm.commons.schema.Schema;
+import io.tapdata.exception.TapCodeException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+import java.util.function.Function;
+
+/**
+ * @author <a href="mailto:harsen_lin@163.com">Harsen</a>
+ * @version v1.0 2024/5/21 11:13 Create
+ */
+class TableRenameProcessNodeTest {
+
+    private final static String prefix = "T_";
+    private final static String suffix = "2024";
+    private final static String replaceBefore = "Before";
+    private final static String replaceAfter = "After";
+    private final static String transferCase = "toUpperCase";
+
+    private final static String customOrigin = "Custom";
+    private final static String customPrevious = "CustomPrevious";
+    private final static String customCurrent = "CustomCurrent";
+    private final static String customEmptyCurrent = "CustomEmptyCurrent";
+
+    private TableRenameProcessNode instance;
+
+    @BeforeEach
+    public void setUp() {
+        instance = new TableRenameProcessNode();
+        instance.setPrefix(prefix);
+        instance.setSuffix(suffix);
+        instance.setReplaceBefore(replaceBefore);
+        instance.setReplaceAfter(replaceAfter);
+        instance.setTransferCase(transferCase);
+        instance.setTableNames(new LinkedHashSet<>());
+        instance.getTableNames().add(new TableRenameTableInfo(customOrigin, customPrevious, customCurrent));
+        instance.getTableNames().add(new TableRenameTableInfo(customEmptyCurrent, customEmptyCurrent, ""));
+    }
+
+    private Schema initSchema(Map<String, String> expectedMap, String tableName, String expectedName) {
+        Schema schema = new Schema();
+        schema.setAncestorsName(tableName);
+        schema.setOriginalName(tableName);
+        schema.setName(tableName);
+        expectedMap.put(tableName, expectedName);
+        return schema;
+    }
+
+    private String expectedTableName(String tableName) {
+        return (prefix + tableName + suffix).toUpperCase(Locale.ROOT);
+    }
+
+
+
+    @Nested
+    class InfoMapTest {
+        String originalName = "origin";
+        String previousName = "previous";
+        String currentName = "current";
+
+        private void testTableNamesIsNull(Function<TableRenameProcessNode, Map<String, TableRenameTableInfo>> mapGetter) {
+            TableRenameProcessNode instance = new TableRenameProcessNode();
+            Map<String, TableRenameTableInfo> map = mapGetter.apply(instance);
+            Assertions.assertNotNull(map);
+            Assertions.assertTrue(map.isEmpty());
+        }
+
+        private void testKeyWithPrevious(String expectedValue, Function<TableRenameProcessNode, Map<String, TableRenameTableInfo>> mapGetter) {
+            TableRenameTableInfo info = new TableRenameTableInfo(originalName, previousName, currentName);
+
+            instance.setTableNames(new LinkedHashSet<>());
+            instance.getTableNames().add(info);
+
+            Map<String, TableRenameTableInfo> map = mapGetter.apply(instance);
+            Assertions.assertNotNull(map);
+            Assertions.assertNotNull(map.get(expectedValue));
+            Assertions.assertEquals(info, map.get(expectedValue));
+        }
+
+        @Test
+        void testOriginalMap() {
+            Function<TableRenameProcessNode, Map<String, TableRenameTableInfo>> mapGetter = TableRenameProcessNode::originalMap;
+
+            testTableNamesIsNull(mapGetter);
+            testKeyWithPrevious(originalName, mapGetter);
+        }
+
+        @Test
+        void testPreviousMap() {
+            Function<TableRenameProcessNode, Map<String, TableRenameTableInfo>> mapGetter = TableRenameProcessNode::previousMap;
+
+            testTableNamesIsNull(mapGetter);
+            testKeyWithPrevious(previousName, mapGetter);
+        }
+
+        @Test
+        void testCurrentMap() {
+            Function<TableRenameProcessNode, Map<String, TableRenameTableInfo>> mapGetter = TableRenameProcessNode::currentMap;
+
+            testTableNamesIsNull(mapGetter);
+            testKeyWithPrevious(currentName, mapGetter);
+        }
+    }
+
+    @Test
+    void testMergeSchema() {
+        // init config
+        Map<String, String> expectedMap = new HashMap<>();
+
+        DAG.Options options = new DAG.Options();
+        options.setIncludes(new ArrayList<>());
+        options.getIncludes().add("TEST");
+
+        // test empty schemas
+        List<List<Schema>> inputSchemas = new ArrayList<>();
+        List<Schema> outputSchemas = instance.mergeSchema(inputSchemas, null, options);
+        Assertions.assertTrue(outputSchemas.isEmpty());
+
+        // test rules
+        inputSchemas.add(Arrays.asList(
+            initSchema(expectedMap, "TEST", expectedTableName("TEST")), // prefix + suffix + upperCase
+            initSchema(expectedMap, "TestBefore", expectedTableName("TestAfter")), // keywords
+            initSchema(expectedMap, customOrigin, customCurrent) // custom table name
+        ));
+
+        outputSchemas = instance.mergeSchema(inputSchemas, null, options);
+        Assertions.assertNotNull(outputSchemas);
+        for (Schema schema : outputSchemas) {
+            Assertions.assertNotNull(schema);
+
+            String expectedName = expectedMap.get(schema.getAncestorsName());
+            Assertions.assertNotNull(expectedName);
+            Assertions.assertEquals(expectedName, schema.getName());
+            Assertions.assertEquals(expectedName, schema.getOriginalName());
+        }
+    }
+
+    @Test
+    void testConvertTableName() {
+        String tableName = "TEST";
+        String expectedValue;
+        Map<String, TableRenameTableInfo> infoMap = instance.originalMap();
+
+        // test normal
+        expectedValue = expectedTableName(tableName);
+        Assertions.assertEquals(expectedValue, instance.convertTableName(infoMap, tableName, false));
+
+        // test in custom-table-name
+        Assertions.assertEquals(customCurrent, instance.convertTableName(infoMap, customOrigin, false));
+
+        // test in custom-table-name, conflict with RenameDDL
+        Assertions.assertThrows(TapCodeException.class, () -> instance.convertTableName(infoMap, customOrigin, true));
+
+        // test empty currentName
+        Assertions.assertEquals(customEmptyCurrent, instance.convertTableName(infoMap, customEmptyCurrent, false));
+
+        // test empty rules
+        instance = new TableRenameProcessNode();
+        Assertions.assertEquals(tableName, instance.convertTableName(tableName));
+        // test illegal transferCase
+        instance.setTransferCase("illegalTransferCase");
+        instance.setReplaceBefore("test replaceBefore not blank & replaceAfter is null");
+        Assertions.assertEquals(tableName, instance.convertTableName(tableName));
+        // test toLowerCase
+        instance.setTransferCase("toLowerCase");
+        Assertions.assertEquals(tableName.toLowerCase(Locale.ROOT), instance.convertTableName(tableName));
+    }
+}
diff --git a/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectServiceImplTest.java b/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectServiceImplTest.java
index 84c4d03795..8dbd96a15b 100644
--- a/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectServiceImplTest.java
+++ b/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectServiceImplTest.java
@@ -1,18 +1,410 @@
 package com.tapdata.tm.inspect.service;
 
+import com.tapdata.tm.base.dto.Field;
+import com.tapdata.tm.base.dto.Filter;
+import com.tapdata.tm.base.dto.Where;
 import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.constant.InspectStatusEnum;
+import com.tapdata.tm.inspect.dto.InspectDto;
 import com.tapdata.tm.inspect.repository.InspectRepository;
 import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.bson.types.ObjectId;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 
 public class InspectServiceImplTest {
+    InspectServiceImpl impl;
+    @BeforeEach
+    void init() {
+        impl = mock(InspectServiceImpl.class);
+    }
+
+    @Test
+    void testDataPermissionFindById() {
+        when(impl.dataPermissionFindById(any(ObjectId.class), any(Field.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.dataPermissionFindById(mock(ObjectId.class), mock(Field.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void beforeSave() {
+        doCallRealMethod().when(impl).beforeSave(any(InspectDto.class), any(UserDetail.class));
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.beforeSave(mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void delete() {
+        when(impl.delete(anyString(), any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.delete("id", mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void list() {
+        when(impl.list(any(Filter.class), any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.list(mock(Filter.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findById() {
+        when(impl.findById(any(Filter.class), any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findById(mock(Filter.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void saveInspect() {
+        doCallRealMethod().when(impl).saveInspect(any(TaskDto.class), any(UserDetail.class));
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.saveInspect(mock(TaskDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void createCheckByTask() {
+        when(impl.createCheckByTask(any(TaskDto.class), any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.createCheckByTask(mock(TaskDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findByTaskIdList() {
+        when(impl.findByTaskIdList(anyList())).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findByTaskIdList(mock(List.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void deleteByTaskId() {
+        when(impl.deleteByTaskId(anyString())).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.deleteByTaskId("id");
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void doExecuteInspect() {
+        when(impl.doExecuteInspect(any(Where.class), any(InspectDto.class), any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.doExecuteInspect(mock(Where.class), mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void updateById() {
+        when(impl.updateById(any(ObjectId.class), any(InspectDto.class), any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.updateById(mock(ObjectId.class), mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findByName() {
+        when(impl.findByName(anyString())).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findByName("id");
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void importData() {
+        doCallRealMethod().when(impl).importData(anyString(), anyString(), any(UserDetail.class));
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.importData("{}", "{}", mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void setRepeatInspectTask() {
+        doCallRealMethod().when(impl).setRepeatInspectTask();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.setRepeatInspectTask();
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void updateStatusById() {
+        when(impl.updateStatusById(anyString(), any(InspectStatusEnum.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.updateStatusById("id", mock(InspectStatusEnum.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void updateStatusByIds() {
+        when(impl.updateStatusByIds(anyList(), any(InspectStatusEnum.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.updateStatusByIds(mock(List.class), mock(InspectStatusEnum.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void inspectPreview() {
+        when(impl.inspectPreview(any(UserDetail.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.inspectPreview(mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findByStatus() {
+        when(impl.findByStatus(any(InspectStatusEnum.class))).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findByStatus(mock(InspectStatusEnum.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findByResult() {
+        when(impl.findByResult(anyBoolean())).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findByResult(true);
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void cleanDeadInspect() {
+        doCallRealMethod().when(impl).cleanDeadInspect();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.cleanDeadInspect();
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void supplementAlarm() {
+        doCallRealMethod().when(impl).supplementAlarm(any(InspectDto.class), any(UserDetail.class));
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.supplementAlarm(mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findAllByIds() {
+        when(impl.findAllByIds(anyList())).thenCallRealMethod();
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findAllByIds(mock(List.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void fieldHandler() {
+        doCallRealMethod().when(impl).fieldHandler(anyList(), any(UserDetail.class));
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.fieldHandler(mock(List.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
     @Nested
     class FieldHandler{
         @Test
@@ -23,7 +415,5 @@ void test(){
                 Assertions.assertThrows(BizException.class,()->inspectService.fieldHandler(null,null));
             }
         }
-
-
     }
 }
diff --git a/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectTaskServiceOOSImplTest.java b/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectTaskServiceOOSImplTest.java
new file mode 100644
index 0000000000..e289d75354
--- /dev/null
+++ b/manager/tm-oss/src/test/java/com/tapdata/tm/inspect/service/InspectTaskServiceOOSImplTest.java
@@ -0,0 +1,159 @@
+package com.tapdata.tm.inspect.service;
+
+import com.tapdata.tm.base.dto.Where;
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.inspect.dto.InspectDto;
+import com.tapdata.tm.utils.MessageUtil;
+import com.tapdata.tm.webhook.enums.ConstVariable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+
+class InspectTaskServiceOOSImplTest {
+    InspectTaskServiceOOSImpl impl;
+    @BeforeEach
+    void init() {
+        impl = new InspectTaskServiceOOSImpl();
+    }
+
+    @Test
+    void inspectTaskRun() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.inspectTaskRun(mock(Where.class), mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void inspectTaskStop() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.inspectTaskStop("id", mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void inspectTaskError() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.inspectTaskError("id", mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void inspectTaskDone() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.inspectTaskDone("id", mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void executeInspect() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.executeInspect(mock(Where.class), mock(InspectDto.class), mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void startInspectTask() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.startInspectTask(mock(InspectDto.class), "id");
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void stopInspectTask() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.stopInspectTask(mock(InspectDto.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findTaskList() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findTaskList(mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+
+    @Test
+    void findConnectionList() {
+        try(MockedStatic<MessageUtil> messageUtilMockedStatic = Mockito.mockStatic(MessageUtil.class)) {
+            messageUtilMockedStatic.when(() -> MessageUtil.getMessage(anyString())).thenReturn("error");
+            Assertions.assertThrows(BizException.class, () -> {
+                try {
+                    impl.findConnectionList(mock(UserDetail.class));
+                } catch (BizException e) {
+                    Assertions.assertEquals(ConstVariable.TA_OSS_NON_SUPPORT_FUNCTION_EXCEPTION, e.getErrorCode());
+                    throw e;
+                }
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
index 4216fd189c..d807e2d355 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/ds/service/impl/DataSourceServiceImplTest.java
@@ -13,6 +13,7 @@
 import com.tapdata.tm.ds.repository.DataSourceRepository;
 import com.tapdata.tm.metadatainstance.service.MetadataInstancesService;
 import com.tapdata.tm.permissions.DataPermissionHelper;
+import com.tapdata.tm.report.service.UserDataReportService;
 import com.tapdata.tm.worker.service.WorkerService;
 import org.bson.types.ObjectId;
 import org.junit.jupiter.api.*;
@@ -140,6 +141,8 @@ void before(){
         }
         @Test
         void test() {
+            UserDataReportService userDataReportService = mock(UserDataReportService.class);
+            ReflectionTestUtils.setField(dataSourceService,"userDataReportService",userDataReportService);
             doNothing().when(dataSourceService).beforeSave(any(),any());
             when(dataSourceRepository.save(any(),any())).thenReturn(mock(DataSourceEntity.class));
             DataSourceConnectionDto result = dataSourceService.add(new DataSourceConnectionDto(),mock(UserDetail.class));
@@ -299,4 +302,79 @@ void testFindByIdReturnNull() {
             verify(dataSourceRepository, never()).update(any(Query.class), any(Update.class), eq(userDetail));
         }
     }
+
+    @Nested
+    @DisplayName("Method hiddenMqPasswd test")
+    class hiddenMqPasswdTest {
+        private DataSourceServiceImpl dataSourceService;
+        private DataSourceRepository dataSourceRepository;
+
+        @BeforeEach
+        void setUp() {
+            dataSourceRepository = mock(DataSourceRepository.class);
+            dataSourceService = spy(new DataSourceServiceImpl(dataSourceRepository));
+        }
+
+        @Test
+        void testHiddenMqPasswd_NullOrEmpty() {
+            DataSourceConnectionDto dto = null;
+            doReturn(false).when(dataSourceService).isAgentReq();
+            dataSourceService.hiddenMqPasswd(dto);
+
+            dto = new DataSourceConnectionDto();
+            dataSourceService.hiddenMqPasswd(dto);
+            Assertions.assertNull(dto.getConfig(), "Config should be null when DataSourceConnectionDto is empty");
+
+            dto.setConfig(new HashMap<>());
+            dataSourceService.hiddenMqPasswd(dto);
+            Assertions.assertTrue(dto.getConfig().isEmpty(), "Config should be empty when DataSourceConnectionDto is empty");
+        }
+        @Test
+        void testHiddenMqPasswd_WithMongoUri() {
+            String originalUri = "mongodb://user:password@localhost:27017/db";
+            Map<String, Object> config = new HashMap<>();
+            config.put("uri", originalUri);
+
+            DataSourceConnectionDto dto = new DataSourceConnectionDto();
+            dto.setConfig(config);
+            dto.setDatabase_type("mongo");
+            doReturn(false).when(dataSourceService).isAgentReq();
+            dataSourceService.hiddenMqPasswd(dto);
+
+            Assertions.assertEquals("mongodb://user:******@localhost:27017/db", dto.getConfig().get("uri"),
+                    "MongoDB URI password should be masked");
+        }
+
+        @Test
+        void testHiddenMqPasswd_WithMongoAtlasUri() {
+            String originalUri = "mongodb+srv://tapdata:123456@test/test";
+            Map<String, Object> config = new HashMap<>();
+            config.put("uri", originalUri);
+
+            DataSourceConnectionDto dto = new DataSourceConnectionDto();
+            dto.setConfig(config);
+            dto.setDatabase_type("mongo");
+            doReturn(false).when(dataSourceService).isAgentReq();
+            dataSourceService.hiddenMqPasswd(dto);
+
+            Assertions.assertEquals("mongodb+srv://tapdata:******@test/test", dto.getConfig().get("uri"),
+                    "MongoDB URI password should be masked");
+        }
+
+        @Test
+        void testHiddenMqPasswd_WithMongoAtlasUriError() {
+            String originalUri = "test";
+            Map<String, Object> config = new HashMap<>();
+            config.put("uri", originalUri);
+
+            DataSourceConnectionDto dto = new DataSourceConnectionDto();
+            dto.setConfig(config);
+            dto.setDatabase_type("mongo");
+            doReturn(false).when(dataSourceService).isAgentReq();
+            dataSourceService.hiddenMqPasswd(dto);
+
+            Assertions.assertEquals("test", dto.getConfig().get("uri"),
+                    "MongoDB URI password should be masked");
+        }
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/ds/utils/DataSourceServiceUtilTest.java b/manager/tm/src/test/java/com/tapdata/tm/ds/utils/DataSourceServiceUtilTest.java
new file mode 100644
index 0000000000..1a97c9be21
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/ds/utils/DataSourceServiceUtilTest.java
@@ -0,0 +1,57 @@
+package com.tapdata.tm.ds.utils;
+
+import com.tapdata.tm.commons.dag.AccessNodeTypeEnum;
+import com.tapdata.tm.commons.schema.DataSourceConnectionDto;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DataSourceServiceUtilTest {
+
+    @Nested
+    class SetAccessNodeInfoFromOldConnectionDtoTest {
+        DataSourceConnectionDto oldConnection;
+        DataSourceConnectionDto update;
+        @Test
+        void testNormal() {
+            oldConnection = new DataSourceConnectionDto();
+            update = new DataSourceConnectionDto();
+            update.setAccessNodeType(AccessNodeTypeEnum.MANUALLY_SPECIFIED_BY_THE_USER.name());
+            update.setAccessNodeProcessId("id");
+            DataSourceServiceUtil.setAccessNodeInfoFromOldConnectionDto(oldConnection, update);
+            Assertions.assertEquals(AccessNodeTypeEnum.MANUALLY_SPECIFIED_BY_THE_USER.name(), update.getAccessNodeType());
+            Assertions.assertEquals("id", update.getAccessNodeProcessId());
+        }
+        @Test
+        void testOldConnectionIsNull() {
+            oldConnection = null;
+            update = new DataSourceConnectionDto();
+            DataSourceServiceUtil.setAccessNodeInfoFromOldConnectionDto(oldConnection, update);
+            Assertions.assertNull(update.getAccessNodeType());
+            Assertions.assertNull(update.getAccessNodeProcessId());
+        }
+        @Test
+        void tesUpdateIsNull() {
+            oldConnection = new DataSourceConnectionDto();
+            Assertions.assertDoesNotThrow(() -> DataSourceServiceUtil.setAccessNodeInfoFromOldConnectionDto(oldConnection, null));
+        }
+        @Test
+        void testUpdateAccessNodeTypeIsNull() {
+            oldConnection = new DataSourceConnectionDto();
+            update = new DataSourceConnectionDto();
+            update.setAccessNodeProcessId("id");
+            DataSourceServiceUtil.setAccessNodeInfoFromOldConnectionDto(oldConnection, update);
+            Assertions.assertEquals(oldConnection.getAccessNodeType(), update.getAccessNodeType());
+            Assertions.assertEquals("id", update.getAccessNodeProcessId());
+        }
+        @Test
+        void testUpdateAccessNodeProcessIdIsNull() {
+            oldConnection = new DataSourceConnectionDto();
+            update = new DataSourceConnectionDto();
+            update.setAccessNodeType(AccessNodeTypeEnum.MANUALLY_SPECIFIED_BY_THE_USER.name());
+            DataSourceServiceUtil.setAccessNodeInfoFromOldConnectionDto(oldConnection, update);
+            Assertions.assertEquals(AccessNodeTypeEnum.MANUALLY_SPECIFIED_BY_THE_USER.name(), update.getAccessNodeType());
+            Assertions.assertEquals(oldConnection.getAccessNodeProcessId(), update.getAccessNodeProcessId());
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/inspect/controller/InspectControllerTest.java b/manager/tm/src/test/java/com/tapdata/tm/inspect/controller/InspectControllerTest.java
new file mode 100644
index 0000000000..9c3f1a4c50
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/inspect/controller/InspectControllerTest.java
@@ -0,0 +1,60 @@
+package com.tapdata.tm.inspect.controller;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.inspect.service.InspectService;
+import com.tapdata.tm.inspect.service.InspectTaskService;
+import com.tapdata.tm.task.service.TaskService;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.query.Query;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+class InspectControllerTest {
+    InspectController controller;
+    InspectService inspectService;
+    InspectTaskService inspectTaskService;
+    TaskService taskService;
+    @BeforeEach
+    void init() {
+        controller = mock(InspectController.class);
+        inspectService = mock(InspectService.class);
+        inspectTaskService = mock(InspectTaskService.class);
+        taskService = mock(TaskService.class);
+        doCallRealMethod().when(controller).setInspectService(inspectService);
+        doCallRealMethod().when(controller).setInspectTaskService(inspectTaskService);
+        doCallRealMethod().when(controller).setTaskService(taskService);
+        controller.setInspectService(inspectService);
+        controller.setInspectTaskService(inspectTaskService);
+        controller.setTaskService(taskService);
+    }
+    @Nested
+    class findTaskDtoTest {
+        @BeforeEach
+        void init() {
+            when(controller.findTaskDto(anyString())).thenCallRealMethod();
+            when(controller.findTaskDto(null)).thenCallRealMethod();
+            when(taskService.findOne(any(Query.class))).thenReturn(new TaskDto());
+        }
+        @Test
+        void testNormal() {
+            Assertions.assertNotNull(controller.findTaskDto(new ObjectId().toHexString()));
+            verify(taskService).findOne(any(Query.class));
+        }
+        @Test
+        void testNull() {
+            Assertions.assertNotNull(controller.findTaskDto("id"));
+            verify(taskService, times(0)).findOne(any(Query.class));
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
index e7e1311f3d..6487dcf863 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/metadatainstance/service/MetadataInstancesServiceImplTest.java
@@ -128,18 +128,22 @@ void testMainProcess() {
 			metadataInstancesDto1.setLastUpdate(1L);
 			metadataInstancesDto1.setSource(sourceDto1);
 			metadataInstancesDto1.setConnectionId(connectionId1);
+			metadataInstancesDto1.setQualifiedName("qualifiedName1");
 			metadataInstancesDtoList.add(metadataInstancesDto1);
 			MetadataInstancesDto metadataInstancesDto2 = new MetadataInstancesDto();
 			metadataInstancesDto2.setSource(sourceDto1);
 			metadataInstancesDto2.setLastUpdate(null);
+			metadataInstancesDto2.setQualifiedName("qualifiedName2");
 			metadataInstancesDtoList.add(metadataInstancesDto2);
 			MetadataInstancesDto metadataInstancesDto3 = new MetadataInstancesDto();
 			metadataInstancesDto3.setSource(sourceDto1);
 			metadataInstancesDto3.setLastUpdate(null);
+			metadataInstancesDto3.setQualifiedName("qualifiedName3");
 			metadataInstancesDtoList.add(metadataInstancesDto3);
 			MetadataInstancesDto metadataInstancesDto4 = new MetadataInstancesDto();
 			metadataInstancesDto4.setSource(sourceDto2);
 			metadataInstancesDto4.setLastUpdate(null);
+			metadataInstancesDto4.setQualifiedName("qualifiedName4");
 			metadataInstancesDtoList.add(metadataInstancesDto4);
 
 			doReturn(time1).when(metadataInstancesService).findDatabaseMetadataInstanceLastUpdate(connectionId1, userDetail);
diff --git a/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java
index b45e1d67b0..014574a65c 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/monitor/service/MeasurementServiceV2ImplTest.java
@@ -3,16 +3,24 @@
 
 import com.tapdata.tm.commons.task.dto.TaskDto;
 import com.tapdata.tm.monitor.constant.KeyWords;
+import com.tapdata.tm.monitor.entity.MeasurementEntity;
 import com.tapdata.tm.monitor.vo.TableSyncStaticVo;
+import org.bson.types.ObjectId;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -79,4 +87,34 @@ void testNotFINSH() {
             verify(vo, times(0)).setSyncRate(BigDecimal.ONE);
         }
     }
+
+    @Nested
+    class FindLastMinuteByTaskIdTest {
+        MongoTemplate mongoOperations;
+
+        @BeforeEach
+        void init() {
+            mongoOperations = mock(MongoTemplate.class);
+            ReflectionTestUtils.setField(measurementServiceV2, "mongoOperations", mongoOperations);
+            when(measurementServiceV2.findLastMinuteByTaskId(anyString())).thenCallRealMethod();
+            when(mongoOperations.findOne(any(Query.class), any(Class.class), anyString())).thenReturn(mock(MeasurementEntity.class));
+        }
+
+        @Test
+        void testNormal() {
+            measurementServiceV2.findLastMinuteByTaskId("");
+            verify(mongoOperations).findOne(any(Query.class), any(Class.class), anyString());
+        }
+
+        @Test
+        void testOne() {
+            measurementServiceV2.findLastMinuteByTaskId(new ObjectId().toHexString());
+            verify(mongoOperations).findOne(any(Query.class), any(Class.class), anyString());
+        }
+        @Test
+        void testOneHasResult() {
+            measurementServiceV2.findLastMinuteByTaskId(new ObjectId().toHexString());
+            verify(mongoOperations).findOne(any(Query.class), any(Class.class), anyString());
+        }
+    }
 }
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/report/schedule/RunDaysScheduleTest.java b/manager/tm/src/test/java/com/tapdata/tm/report/schedule/RunDaysScheduleTest.java
new file mode 100644
index 0000000000..7f70bce186
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/report/schedule/RunDaysScheduleTest.java
@@ -0,0 +1,32 @@
+package com.tapdata.tm.report.schedule;
+
+import com.tapdata.tm.report.dto.RunDaysBatch;
+import com.tapdata.tm.report.service.UserDataReportService;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.internal.verification.Times;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class RunDaysScheduleTest {
+    @Nested
+    class scheduleTest{
+        private RunDaysSchedule runDaysSchedule = new RunDaysSchedule();
+        @Test
+        void testNormal(){
+            UserDataReportService userDataReportService = mock(UserDataReportService.class);
+            ReflectionTestUtils.setField(runDaysSchedule,"userDataReportService",userDataReportService);
+            Thread testThread = new Thread(()->{
+                runDaysSchedule.schedule();
+                String name = Thread.currentThread().getName();
+                assertEquals("RunDaysSchedule-schedule",name);
+                verify(userDataReportService,new Times(1)).produceData(any(RunDaysBatch.class));
+            });
+            testThread.start();
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/report/service/UserDataReportServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/report/service/UserDataReportServiceTest.java
new file mode 100644
index 0000000000..d780109bbe
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/report/service/UserDataReportServiceTest.java
@@ -0,0 +1,262 @@
+package com.tapdata.tm.report.service;
+
+import com.tapdata.tm.report.dto.*;
+import com.tapdata.tm.report.service.platform.GoogleAnalyticsPlatform;
+import com.tapdata.tm.report.service.platform.ReportPlatform;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.*;
+import org.mockito.*;
+import org.mockito.internal.verification.Times;
+import org.springframework.test.util.ReflectionTestUtils;
+import java.util.concurrent.*;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class UserDataReportServiceTest {
+    private UserDataReportService userDataReportService;
+    private ReportPlatform reportPlatform;
+    @BeforeEach
+    void beforeEach(){
+        userDataReportService = mock(UserDataReportService.class);
+        reportPlatform = mock(GoogleAnalyticsPlatform.class);
+        ReflectionTestUtils.setField(userDataReportService,"reportPlatform",reportPlatform);
+        ReflectionTestUtils.setField(userDataReportService,"machineId","111");
+    }
+    @Nested
+    class InitReportDataThreadTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).initReportDataThread();
+        }
+        @Test
+        @DisplayName("test initReportDataThread method when acceptReportData is false")
+        void test1(){
+            try (MockedStatic<CompletableFuture> mb = Mockito
+                    .mockStatic(CompletableFuture.class)) {
+                mb.when(() -> CompletableFuture.runAsync(any(Runnable.class),any(Executor.class))).thenReturn(mock(CompletableFuture.class));
+                ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+                userDataReportService.initReportDataThread();
+                mb.verify(() -> CompletableFuture.runAsync(any(Runnable.class),any(Executor.class)), new Times(0));
+            }
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test initReportDataThread method when acceptReportData is true")
+        void test2(){
+            try (MockedStatic<CompletableFuture> mb = Mockito
+                    .mockStatic(CompletableFuture.class)) {
+                mb.when(() -> CompletableFuture.runAsync(any(Runnable.class),any(ExecutorService.class))).thenAnswer((invocationOnMock -> {
+                    ExecutorService actual = invocationOnMock.getArgument(1, ExecutorService.class);
+                    assertEquals(1, ((ThreadPoolExecutor)actual).getCorePoolSize());
+                    assertEquals(1, ((ThreadPoolExecutor)actual).getMaximumPoolSize());
+                    assertEquals(0, ((ThreadPoolExecutor)actual).getKeepAliveTime(TimeUnit.MILLISECONDS));
+                    return null;
+                }));
+                ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+                userDataReportService.initReportDataThread();
+                mb.verify(() -> CompletableFuture.runAsync(any(Runnable.class),any(Executor.class)), new Times(1));
+            }
+        }
+    }
+    @Nested
+    class ConsumeDataTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).consumeData(any(Object.class));
+        }
+        @Test
+        @DisplayName("test consumeData method for RunsNumBatch")
+        void test1(){
+            RunsNumBatch runsNumBatch = new RunsNumBatch();
+            userDataReportService.consumeData(runsNumBatch);
+            verify(userDataReportService,new Times(1)).runsNum(runsNumBatch);
+        }
+        @Test
+        @DisplayName("test consumeData method for RunDaysBatch")
+        void test2(){
+            RunDaysBatch runDaysBatch = new RunDaysBatch();
+            userDataReportService.consumeData(runDaysBatch);
+            verify(userDataReportService,new Times(1)).runDays(runDaysBatch);
+        }
+        @Test
+        @DisplayName("test consumeData method for UniqueInstallBatch")
+        void test3(){
+            UniqueInstallBatch uniqueInstallBatch = new UniqueInstallBatch();
+            userDataReportService.consumeData(uniqueInstallBatch);
+            verify(userDataReportService,new Times(1)).uniqueInstall(uniqueInstallBatch);
+        }
+        @Test
+        @DisplayName("test consumeData method for ConfigureSourceBatch")
+        void test4(){
+            ConfigureSourceBatch configureSourceBatch = new ConfigureSourceBatch();
+            userDataReportService.consumeData(configureSourceBatch);
+            verify(userDataReportService,new Times(1)).configureDatasource(configureSourceBatch);
+        }
+        @Test
+        @DisplayName("test consumeData method for TasksNumBatch")
+        void test5(){
+            TasksNumBatch tasksNumBatch = new TasksNumBatch();
+            userDataReportService.consumeData(tasksNumBatch);
+            verify(userDataReportService,new Times(1)).tasksNum(tasksNumBatch);
+        }
+        @Test
+        @DisplayName("test consumeData method for Object")
+        void test6(){
+            userDataReportService.consumeData(mock(Object.class));
+            verify(userDataReportService,new Times(0)).runsNum(any(RunsNumBatch.class));
+            verify(userDataReportService,new Times(0)).runDays(any(RunDaysBatch.class));
+            verify(userDataReportService,new Times(0)).uniqueInstall(any(UniqueInstallBatch.class));
+            verify(userDataReportService,new Times(0)).configureDatasource(any(ConfigureSourceBatch.class));
+            verify(userDataReportService,new Times(0)).tasksNum(any(TasksNumBatch.class));
+        }
+    }
+    @Nested
+    class ProduceDataTest{
+        private LinkedBlockingQueue reportQueue;
+        private Object obj;
+        @BeforeEach
+        void beforeEach(){
+            reportQueue = spy(LinkedBlockingQueue.class);
+            ReflectionTestUtils.setField(userDataReportService,"reportQueue",reportQueue);
+            obj = mock(Object.class);
+            doCallRealMethod().when(userDataReportService).produceData(obj);
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test produceData method when acceptReportData is false")
+        void test1(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+            userDataReportService.produceData(obj);
+            verify(reportQueue,never()).offer(obj, 100, TimeUnit.MILLISECONDS);
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test produceData method when acceptReportData is true")
+        void test2(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            userDataReportService.produceData(obj);
+            verify(reportQueue,new Times(1)).offer(obj, 100, TimeUnit.MILLISECONDS);
+            Object actual = reportQueue.poll();
+            assertEquals(obj, actual);
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test produceData method when offer occur exception")
+        void test3(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            doThrow(InterruptedException.class).when(reportQueue).offer(obj,100,TimeUnit.MILLISECONDS);
+            userDataReportService.produceData(obj);
+            verify(reportQueue,new Times(1)).offer(obj, 100, TimeUnit.MILLISECONDS);
+            Object actual = reportQueue.poll();
+            assertNull(actual);
+        }
+    }
+    @Nested
+    class RunsNumTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).runsNum(any(RunsNumBatch.class));
+        }
+        @Test
+        @DisplayName("test runsNum method when acceptReportData is false")
+        void test1(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+            userDataReportService.runsNum(mock(RunsNumBatch.class));
+            verify(reportPlatform,new Times(0)).sendRequest(anyString(),anyString());
+        }
+        @Test
+        @DisplayName("test runsNum method when acceptReportData is true")
+        void test2(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            userDataReportService.runsNum(mock(RunsNumBatch.class));
+            verify(reportPlatform,new Times(1)).sendRequest(anyString(),anyString());
+        }
+    }
+    @Nested
+    class RunDaysTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).runDays(any(RunDaysBatch.class));
+        }
+        @Test
+        @DisplayName("test runDays method when acceptReportData is false")
+        void test1(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+            userDataReportService.runDays(mock(RunDaysBatch.class));
+            verify(reportPlatform,new Times(0)).sendRequest(anyString(),anyString());
+        }
+        @Test
+        @DisplayName("test runDays method when acceptReportData is true")
+        void test2(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            userDataReportService.runDays(mock(RunDaysBatch.class));
+            verify(reportPlatform,new Times(1)).sendRequest(anyString(),anyString());
+        }
+    }
+    @Nested
+    class UniqueInstallTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).uniqueInstall(any(UniqueInstallBatch.class));
+        }
+        @Test
+        @DisplayName("test uniqueInstall method when acceptReportData is false")
+        void test1(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+            userDataReportService.uniqueInstall(mock(UniqueInstallBatch.class));
+            verify(reportPlatform,new Times(0)).sendRequest(anyString(),anyString());
+        }
+        @Test
+        @DisplayName("test uniqueInstall method when acceptReportData is true")
+        void test2(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            userDataReportService.uniqueInstall(mock(UniqueInstallBatch.class));
+            verify(reportPlatform,new Times(1)).sendRequest(anyString(),anyString());
+        }
+    }
+    @Nested
+    class ConfigureDatasourceTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).configureDatasource(any(ConfigureSourceBatch.class));
+        }
+        @Test
+        @DisplayName("test configureDatasource method when acceptReportData is false")
+        void test1(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+            userDataReportService.configureDatasource(mock(ConfigureSourceBatch.class));
+            verify(reportPlatform,new Times(0)).sendRequest(anyString(),anyString());
+        }
+        @Test
+        @DisplayName("test configureDatasource method when acceptReportData is true")
+        void test2(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            userDataReportService.configureDatasource(mock(ConfigureSourceBatch.class));
+            verify(reportPlatform,new Times(1)).sendRequest(anyString(),anyString());
+        }
+    }
+    @Nested
+    class TasksNumTest{
+        @BeforeEach
+        void beforeEach(){
+            doCallRealMethod().when(userDataReportService).tasksNum(any(TasksNumBatch.class));
+        }
+        @Test
+        @DisplayName("test tasksNum method when acceptReportData is false")
+        void test1(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",false);
+            userDataReportService.tasksNum(mock(TasksNumBatch.class));
+            verify(reportPlatform,new Times(0)).sendRequest(anyString(),anyString());
+        }
+        @Test
+        @DisplayName("test tasksNum method when acceptReportData is true")
+        void test2(){
+            ReflectionTestUtils.setField(userDataReportService,"acceptReportData",true);
+            userDataReportService.tasksNum(mock(TasksNumBatch.class));
+            verify(reportPlatform,new Times(1)).sendRequest(anyString(),anyString());
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/report/service/platform/GoogleAnalyticsPlatformTest.java b/manager/tm/src/test/java/com/tapdata/tm/report/service/platform/GoogleAnalyticsPlatformTest.java
new file mode 100644
index 0000000000..e030d14a4a
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/report/service/platform/GoogleAnalyticsPlatformTest.java
@@ -0,0 +1,88 @@
+package com.tapdata.tm.report.service.platform;
+
+import lombok.SneakyThrows;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.junit.jupiter.api.*;
+import org.mockito.internal.verification.Times;
+import org.slf4j.Logger;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+public class GoogleAnalyticsPlatformTest {
+    @Nested
+    class GoogleAnalyticsPlatformConstructMethodTest{
+        @Test
+        void testNormal(){
+            GoogleAnalyticsPlatform platform = new GoogleAnalyticsPlatform();
+            CloseableHttpClient client = platform.getClient();
+            assertNotNull(client);
+            try {
+                client.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+    @Nested
+    class SendRequestTest{
+        private GoogleAnalyticsPlatform platform;
+        private CloseableHttpClient client;
+        @BeforeEach
+        void beforeEach(){
+            platform = mock(GoogleAnalyticsPlatform.class);
+            client = mock(CloseableHttpClient.class);
+            ReflectionTestUtils.setField(platform,"client",client);
+            doCallRealMethod().when(platform).sendRequest(anyString(),anyString());
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test sendRequest method normal")
+        void test1(){
+            when(client.execute(any(HttpPost.class))).thenReturn(null);
+            platform.sendRequest("test_event","{\"test_param\":\"111\"}");
+            verify(client,new Times(1)).execute(any(HttpPost.class));
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test sendRequest method with exception")
+        void test2(){
+            Logger logger = mock(Logger.class);
+            Field log = GoogleAnalyticsPlatform.class.getDeclaredField("log");
+            log.setAccessible(true);
+            Field modifiers = Field.class.getDeclaredField("modifiers");
+            modifiers.setAccessible(true);
+            modifiers.setInt(log, log.getModifiers() & ~Modifier.FINAL);
+            log.set(platform, logger);
+            when(client.execute(any(HttpPost.class))).thenThrow(IOException.class);
+            platform.sendRequest("test_event","{\"test_param\":\"111\"}");
+            verify(logger,new Times(1)).info(anyString(),any(Exception.class));
+        }
+    }
+    @Nested
+    class ProcessParamIfNeedTest{
+        @Test
+        void testProcessParamIfNeedNormal(){
+            String value = "100";
+            GoogleAnalyticsPlatform platform = mock(GoogleAnalyticsPlatform.class);
+            doCallRealMethod().when(platform).processParamIfNeed(value);
+            int actual = platform.processParamIfNeed(value);
+            assertEquals(100, actual);
+        }
+        @Test
+        void testProcessParamIfNeedWithEx(){
+            String value = "100l";
+            GoogleAnalyticsPlatform platform = mock(GoogleAnalyticsPlatform.class);
+            doCallRealMethod().when(platform).processParamIfNeed(value);
+            int actual = platform.processParamIfNeed(value);
+            assertEquals(5000, actual);
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/report/util/UserDataReportUtilsTest.java b/manager/tm/src/test/java/com/tapdata/tm/report/util/UserDataReportUtilsTest.java
new file mode 100644
index 0000000000..4e62e48af7
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/report/util/UserDataReportUtilsTest.java
@@ -0,0 +1,114 @@
+package com.tapdata.tm.report.util;
+
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.Vector;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class UserDataReportUtilsTest {
+    @Nested
+    class GenerateMachineIdTest{
+        @Test
+        @SneakyThrows
+        @DisplayName("test generateMachineId method normal")
+        void test1(){
+            NetworkInterface networkInterface = mock(NetworkInterface.class);
+            Vector v = new Vector();
+            v.addElement(networkInterface);
+            Enumeration<NetworkInterface> interfaces = v.elements();
+            byte[] mac = new byte[1];
+            mac[0] = 1;
+            try (MockedStatic<NetworkInterface> mb = Mockito
+                    .mockStatic(NetworkInterface.class)) {
+                mb.when(NetworkInterface::getNetworkInterfaces).thenReturn(interfaces);
+                when(networkInterface.getHardwareAddress()).thenReturn(mac);
+                String actual = UserDataReportUtils.generateMachineId();
+                assertEquals("[01]", actual);
+            }
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test generateMachineId method when mac is null")
+        void test2(){
+            NetworkInterface networkInterface = mock(NetworkInterface.class);
+            Vector v = new Vector();
+            v.addElement(networkInterface);
+            Enumeration<NetworkInterface> interfaces = v.elements();
+            try (MockedStatic<NetworkInterface> mb = Mockito
+                    .mockStatic(NetworkInterface.class)) {
+                mb.when(NetworkInterface::getNetworkInterfaces).thenReturn(interfaces);
+                when(networkInterface.getHardwareAddress()).thenReturn(null);
+                String actual = UserDataReportUtils.generateMachineId();
+                assertEquals("[]", actual);
+            }
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test generateMachineId method with exception")
+        void test3(){
+            NetworkInterface networkInterface = mock(NetworkInterface.class);
+            Vector v = new Vector();
+            v.addElement(networkInterface);
+            try (MockedStatic<NetworkInterface> mb = Mockito
+                    .mockStatic(NetworkInterface.class)) {
+                mb.when(NetworkInterface::getNetworkInterfaces).thenThrow(SocketException.class);
+                when(networkInterface.getHardwareAddress()).thenReturn(null);
+                String actual = UserDataReportUtils.generateMachineId();
+                assertEquals("[]", actual);
+            }
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test generateMachineId method when mac address exceed param max length")
+        void test4(){
+            NetworkInterface networkInterface = mock(NetworkInterface.class);
+            Vector v = new Vector();
+            v.addElement(networkInterface);
+            Enumeration<NetworkInterface> interfaces = v.elements();
+            byte[] mac = new byte[50];
+            for (int i=0;i<50;i++){
+                mac[i] = 1;
+            }
+            try (MockedStatic<NetworkInterface> mb = Mockito
+                    .mockStatic(NetworkInterface.class)) {
+                mb.when(NetworkInterface::getNetworkInterfaces).thenReturn(interfaces);
+                when(networkInterface.getHardwareAddress()).thenReturn(mac);
+                String actual = UserDataReportUtils.generateMachineId();
+                assertEquals(100, actual.length());
+            }
+        }
+        @Test
+        @SneakyThrows
+        @DisplayName("test generateMachineId method when mac address less than param max length")
+        void test5(){
+            NetworkInterface networkInterface = mock(NetworkInterface.class);
+            Vector v = new Vector();
+            v.addElement(networkInterface);
+            Enumeration<NetworkInterface> interfaces = v.elements();
+            byte[] mac = new byte[20];
+            for (int i=0;i<20;i++){
+                mac[i] = 1;
+            }
+            try (MockedStatic<NetworkInterface> mb = Mockito
+                    .mockStatic(NetworkInterface.class)) {
+                mb.when(NetworkInterface::getNetworkInterfaces).thenReturn(interfaces);
+                when(networkInterface.getHardwareAddress()).thenReturn(mac);
+                String actual = UserDataReportUtils.generateMachineId();
+                assertNotEquals(100, actual.length());
+            }
+        }
+    }
+}
diff --git a/manager/tm/src/test/java/com/tapdata/tm/schedule/ChartScheduleTest.java b/manager/tm/src/test/java/com/tapdata/tm/schedule/ChartScheduleTest.java
new file mode 100644
index 0000000000..6460077595
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/schedule/ChartScheduleTest.java
@@ -0,0 +1,90 @@
+package com.tapdata.tm.schedule;
+
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.task.bean.Chart6Vo;
+import com.tapdata.tm.task.service.chart.ChartViewService;
+import com.tapdata.tm.user.service.UserService;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ChartScheduleTest {
+    ChartSchedule chartSchedule;
+    ChartViewService chartViewService;
+    UserService userService;
+    @BeforeEach
+    void init() {
+        chartSchedule = new ChartSchedule();
+        chartViewService = mock(ChartViewService.class);
+        userService = mock(UserService.class);
+        chartSchedule.setChartViewService(chartViewService);
+        chartSchedule.setUserService(userService);
+    }
+
+    @Nested
+    class Chart6Test {
+        List<UserDetail> userDetails;
+        UserDetail user;
+        Chart6Vo chart6Vo;
+        @BeforeEach
+        void init() {
+            userDetails = new ArrayList<>();
+            chart6Vo = Chart6Vo.builder().build();
+            user = mock(UserDetail.class);
+            userDetails.add(user);
+            when(userService.loadAllUser()).thenReturn(userDetails);
+            when(chartViewService.transmissionOverviewChartData(user)).thenReturn(chart6Vo);
+            when(user.getUserId()).thenReturn("gavin-id");
+        }
+
+        @Test
+        void testNormal() {
+            chart6Vo.setDeletedTotal(new BigInteger(new byte[]{100, 100}));
+            chartSchedule.chart6();
+            Assertions.assertNotNull(ChartSchedule.cache.get("gavin-id"));
+            verify(userService).loadAllUser();
+            verify(chartViewService).transmissionOverviewChartData(user);
+            verify(user).getUserId();
+        }
+
+        @Test
+        void testVoIsNull() {
+            when(chartViewService.transmissionOverviewChartData(user)).thenReturn(null);
+            chartSchedule.chart6();
+            Assertions.assertNull(ChartSchedule.cache.get("gavin-id"));
+            verify(userService).loadAllUser();
+            verify(chartViewService).transmissionOverviewChartData(user);
+            verify(user).getUserId();
+        }
+
+        @Test
+        void testVoIsEmpty() {
+            chart6Vo.setDeletedTotal(BigInteger.ZERO);
+            chart6Vo.setInputTotal(BigInteger.ZERO);
+            chart6Vo.setInsertedTotal(BigInteger.ZERO);
+            chart6Vo.setOutputTotal(BigInteger.ZERO);
+            chart6Vo.setUpdatedTotal(BigInteger.ZERO);
+            when(chartViewService.transmissionOverviewChartData(user)).thenReturn(chart6Vo);
+            chartSchedule.chart6();
+            Assertions.assertNull(ChartSchedule.cache.get("gavin-id"));
+            verify(userService).loadAllUser();
+            verify(chartViewService).transmissionOverviewChartData(user);
+            verify(user).getUserId();
+        }
+    }
+
+    @AfterEach
+    void close() {
+        ChartSchedule.cache.clear();
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskErrorEventServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskErrorEventServiceTest.java
new file mode 100644
index 0000000000..4f38013f5d
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskErrorEventServiceTest.java
@@ -0,0 +1,98 @@
+package com.tapdata.tm.task.service;
+
+import com.mongodb.bulk.BulkWriteResult;
+import com.mongodb.client.result.UpdateResult;
+import com.tapdata.tm.commons.task.dto.ErrorEvent;
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.task.repository.TaskRepository;
+import com.tapdata.tm.utils.Lists;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.BulkOperations;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+class TaskErrorEventServiceTest {
+    TaskErrorEventService taskErrorEventService;
+    TaskRepository taskRepository;
+    TaskService taskService;
+    UserDetail user;
+
+    @BeforeEach
+    void init() {
+        user = mock(UserDetail.class);
+        taskErrorEventService = new TaskErrorEventService();
+        taskRepository = mock(TaskRepository.class);
+        taskService = mock(TaskService.class);
+
+        taskErrorEventService.setTaskRepository(taskRepository);
+        taskErrorEventService.setTaskService(taskService);
+    }
+
+    @Nested
+    class GetErrorEventByTaskIdTest {
+        TaskDto taskDto;
+        @BeforeEach
+        void init() {
+            taskDto = mock(TaskDto.class);
+            when(taskDto.getErrorEvents()).thenReturn(new ArrayList<>());
+        }
+
+        @Test
+        void testNormal() {
+            when(taskService.findOne(any(Query.class), any(UserDetail.class))).thenReturn(taskDto);
+            List<ErrorEvent> errorEventByTaskId = taskErrorEventService.getErrorEventByTaskId(new ObjectId().toHexString(), user);
+            Assertions.assertNotNull(errorEventByTaskId);
+        }
+
+        @Test
+        void testTaskDtoIsNull() {
+            when(taskService.findOne(any(Query.class), any(UserDetail.class))).thenReturn(null);
+            List<ErrorEvent> errorEventByTaskId = taskErrorEventService.getErrorEventByTaskId(new ObjectId().toHexString(), user);
+            Assertions.assertNotNull(errorEventByTaskId);
+        }
+    }
+
+    @Nested
+    class SignSkipErrorEventsTest {
+        BulkOperations bulkOperations;
+        @BeforeEach
+        void init() {
+            bulkOperations = mock(BulkOperations.class);
+            when(taskService.update(any(Query.class), any(Update.class))).thenReturn(mock(UpdateResult.class));
+            when(taskRepository.bulkOperations(BulkOperations.BulkMode.UNORDERED)).thenReturn(bulkOperations);
+            when(bulkOperations.updateMulti(any(Query.class), any(Update.class))).thenReturn(bulkOperations);
+            when(bulkOperations.execute()).thenReturn(mock(BulkWriteResult.class));
+        }
+        @Test
+        void testNormal() {
+            taskErrorEventService.signSkipErrorEvents(new ObjectId().toHexString(), Lists.newArrayList(new ObjectId().toHexString()));
+            verify(taskService, times(0)).update(any(Query.class), any(Update.class));
+            verify(taskRepository, times(1)).bulkOperations(BulkOperations.BulkMode.UNORDERED);
+            verify(bulkOperations, times(2)).updateMulti(any(Query.class), any(Update.class));
+            verify(bulkOperations, times(1)).execute();
+        }
+        @Test
+        void testEmpty() {
+            taskErrorEventService.signSkipErrorEvents(new ObjectId().toHexString(), Lists.newArrayList());
+            verify(taskService, times(1)).update(any(Query.class), any(Update.class));
+            verify(taskRepository, times(0)).bulkOperations(BulkOperations.BulkMode.UNORDERED);
+            verify(bulkOperations, times(0)).updateMulti(any(Query.class), any(Update.class));
+            verify(bulkOperations, times(0)).execute();
+        }
+    }
+}
\ No newline at end of file
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
index 910505423e..fb3b5bb766 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceImplTest.java
@@ -34,6 +34,7 @@
 import com.tapdata.tm.externalStorage.service.ExternalStorageService;
 import com.tapdata.tm.inspect.bean.Source;
 import com.tapdata.tm.inspect.bean.Stats;
+import com.tapdata.tm.inspect.constant.InspectStatusEnum;
 import com.tapdata.tm.inspect.dto.InspectDto;
 import com.tapdata.tm.inspect.dto.InspectResultDto;
 import com.tapdata.tm.inspect.service.InspectResultService;
@@ -56,6 +57,7 @@
 import com.tapdata.tm.permissions.constants.DataPermissionActionEnums;
 import com.tapdata.tm.permissions.constants.DataPermissionMenuEnums;
 import com.tapdata.tm.permissions.service.DataPermissionService;
+import com.tapdata.tm.report.service.UserDataReportService;
 import com.tapdata.tm.schedule.service.ScheduleService;
 import com.tapdata.tm.statemachine.enums.DataFlowEvent;
 import com.tapdata.tm.statemachine.model.StateMachineResult;
@@ -70,6 +72,7 @@
 import com.tapdata.tm.task.repository.TaskRepository;
 import com.tapdata.tm.task.service.batchin.ParseRelMigFile;
 import com.tapdata.tm.task.service.batchin.entity.ParseParam;
+import com.tapdata.tm.task.service.chart.ChartViewService;
 import com.tapdata.tm.task.service.utils.TaskServiceUtil;
 import com.tapdata.tm.task.vo.ShareCacheDetailVo;
 import com.tapdata.tm.task.vo.ShareCacheVo;
@@ -131,6 +134,7 @@ class TaskServiceImplTest {
     WorkerService workerService;
     TaskDto taskDto;
     UserDetail user;
+    ChartViewService chartViewService;
     @BeforeEach
     void init() {
         taskService = mock(TaskServiceImpl.class);
@@ -138,6 +142,8 @@ void init() {
         ReflectionTestUtils.setField(taskService, "agentGroupService", agentGroupService);
         workerService = mock(WorkerService.class);
         ReflectionTestUtils.setField(taskService, "workerService", workerService);
+        chartViewService = mock(ChartViewService.class);
+        ReflectionTestUtils.setField(taskService, "chartViewService", chartViewService);
         taskDto = mock(TaskDto.class);
         user = mock(UserDetail.class);
     }
@@ -592,6 +598,7 @@ void test1(){
             doCallRealMethod().when(taskService).create(taskDto,user);
             TaskDto actual = taskService.create(taskDto, user);
             assertEquals(taskDto, actual);
+            assertFalse(actual.getOldVersionTimezone());
         }
         @Test
         @DisplayName("test create method when cron schedule error")
@@ -1950,30 +1957,59 @@ void test2(){
     }
     @Nested
     class InspectChartTest{
-        private TaskAutoInspectResultsService taskAutoInspectResultsService;
+        List<InspectDto> list;
         @BeforeEach
         void beforeEach(){
-            taskAutoInspectResultsService = mock(TaskAutoInspectResultsService.class);
-            ReflectionTestUtils.setField(taskService,"taskAutoInspectResultsService",taskAutoInspectResultsService);
+            list = new ArrayList<>();
+            InspectDto error = new InspectDto();
+            error.setStatus(InspectStatusEnum.ERROR.getValue());
+            list.add(error);
+            InspectDto running = new InspectDto();
+            running.setStatus(InspectStatusEnum.RUNNING.getValue());
+            list.add(running);
+            InspectDto done = new InspectDto();
+            done.setStatus(InspectStatusEnum.DONE.getValue());
+            list.add(done);
+            InspectDto waiting = new InspectDto();
+            waiting.setStatus(InspectStatusEnum.WAITING.getValue());
+            list.add(waiting);
+            InspectDto scheduling = new InspectDto();
+            scheduling.setStatus(InspectStatusEnum.SCHEDULING.getValue());
+            list.add(scheduling);
+            InspectDto stopping = new InspectDto();
+            stopping.setStatus(InspectStatusEnum.STOPPING.getValue());
+            list.add(stopping);
+            InspectDto o = new InspectDto();
+            o.setStatus(InspectStatusEnum.PASSED.getValue());
+            list.add(o);
+            when(taskService.inspectTaskList(any(Filter.class), any(UserDetail.class))).thenReturn(list);
+            when(taskService.inspectChart(user)).thenCallRealMethod();
         }
         @Test
         void testInspectChartNormal(){
-            List<TaskDto> taskDtos = new ArrayList<>();
-            TaskDto dto = mock(TaskDto.class);
-            taskDtos.add(dto);
-            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(taskDtos);
-            when(dto.getCanOpenInspect()).thenReturn(true);
-            when(dto.getStatus()).thenReturn("error");
-            ObjectId id = mock(ObjectId.class);
-            when(dto.getId()).thenReturn(id);
-            Set<String> taskSet = new HashSet<>();
-            taskSet.add(id.toHexString());
-            when(taskAutoInspectResultsService.groupByTask(user)).thenReturn(taskSet);
-            doCallRealMethod().when(taskService).inspectChart(user);
             Map<String, Integer> actual = taskService.inspectChart(user);
-            assertEquals(1,actual.get("error"));
-            assertEquals(1,actual.get("can"));
-            assertEquals(1,actual.get("diff"));
+            Assertions.assertNotNull(actual);
+            assertEquals(list.size(), actual.get("total"));
+            assertEquals(1, actual.get("error"));
+            assertEquals(1, actual.get("running"));
+            assertEquals(1, actual.get("done"));
+            assertEquals(1, actual.get("waiting"));
+            assertEquals(1, actual.get("scheduling"));
+            assertEquals(1, actual.get("stopping"));
+        }
+
+        @Test
+        void testEmpty() {
+            list.clear();
+            Map<String, Integer> actual = taskService.inspectChart(user);
+            Assertions.assertNotNull(actual);
+            assertEquals(list.size(), actual.get("total"));
+            assertEquals(0, actual.get("error"));
+            assertEquals(0, actual.get("running"));
+            assertEquals(0, actual.get("done"));
+            assertEquals(0, actual.get("waiting"));
+            assertEquals(0, actual.get("scheduling"));
+            assertEquals(0, actual.get("stopping"));
         }
     }
     @Nested
@@ -2339,6 +2375,8 @@ void beforeEach(){
             lockControlService = mock(LockControlService.class);
             disruptorService = mock(DisruptorService.class);
             logCollectorService = mock(LogCollectorService.class);
+            UserDataReportService userDataReportService = mock(UserDataReportService.class);
+            ReflectionTestUtils.setField(taskService,"userDataReportService",userDataReportService);
             ReflectionTestUtils.setField(taskService,"lockControlService",lockControlService);
             ReflectionTestUtils.setField(taskService,"disruptorService",disruptorService);
             ReflectionTestUtils.setField(taskService,"logCollectorService",logCollectorService);
@@ -2401,11 +2439,22 @@ void test5(){
             when(taskDto.getTaskRecordId()).thenReturn("");
             when(taskDto.getDag()).thenReturn(mock(DAG.class));
             when(taskDto.getStatus()).thenReturn("edit");
-            when(taskDto.getSyncType()).thenReturn("heartbeat");
+            when(taskDto.getSyncType()).thenReturn("connHeartbeat");
             doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
             taskService.start(taskDto,user,startFlag);
             verify(taskService,new Times(1)).run(taskDto,user);
         }
+        @Test
+        @DisplayName("test start method when sync type heartbeat and status is renew failed")
+        void test6(){
+            when(taskDto.getTaskRecordId()).thenReturn("");
+            when(taskDto.getDag()).thenReturn(mock(DAG.class));
+            when(taskDto.getStatus()).thenReturn("renew_failed");
+            when(taskDto.getSyncType()).thenReturn("connHeartbeat");
+            doCallRealMethod().when(taskService).start(taskDto,user,startFlag);
+            taskService.start(taskDto,user,startFlag);
+            verify(taskService,new Times(0)).run(taskDto,user);
+        }
     }
     @Nested
     class UpdateTaskRecordsStatusTest{
@@ -3348,7 +3397,7 @@ void testChartNormal(){
                 when(permission.MigrateTack.checkAndSetFilter(user, DataPermissionActionEnums.View, () -> taskService.findAllDto(any(),any()))).thenReturn(taskDtoList);
                 doReturn(new HashMap()).when(taskService).inspectChart(user);
                 Chart6Vo chart6Vo = mock(Chart6Vo.class);
-                doReturn(chart6Vo).when(taskService).chart6(user);
+                doReturn(chart6Vo).when(chartViewService).transmissionOverviewChartData(taskDtoList);
                 Map<String, Object> actual = taskService.chart(user);
                 Map chart1 = (Map) actual.get("chart1");
                 assertEquals(3,chart1.get("total"));
@@ -4333,95 +4382,7 @@ void test3(){
             verify(taskService).updateById(any(ObjectId.class),any(Update.class),any(UserDetail.class));
         }
     }
-    @Nested
-    class Chart6Test{
-        private MeasurementServiceV2 measurementServiceV2;
-        @BeforeEach
-        void beforeEach(){
-            measurementServiceV2 = mock(MeasurementServiceV2.class);
-            ReflectionTestUtils.setField(taskService,"measurementServiceV2",measurementServiceV2);
-        }
-        @Test
-        @DisplayName("test chart6 method normal")
-        void test1(){
-            List<TaskDto> allDto = new ArrayList<>();
-            TaskDto task = new TaskDto();
-            String id = "65bc933c6129fe73d7858b40";
-            task.setId(new ObjectId(id));
-            allDto.add(task);
-            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
-            MeasurementEntity measurement = new MeasurementEntity();
-            List<Sample> samples = new ArrayList<>();
-            Sample sample = new Sample();
-            sample.setDate(new Date());
-            Map<String, Number> vs = new HashMap<>();
-            vs.put("inputInsertTotal",1);
-            vs.put("outputInsertTotal",1);
-            vs.put("inputUpdateTotal",1);
-            vs.put("inputDeleteTotal",1);
-            sample.setVs(vs);
-            samples.add(sample);
-            measurement.setSamples(samples);
-            when(measurementServiceV2.findLastMinuteByTaskId(id)).thenReturn(measurement);
-            doCallRealMethod().when(taskService).chart6(user);
-            Chart6Vo actual = taskService.chart6(user);
-            assertNotEquals(3,actual.getInputTotal());
-            assertNotEquals(1,actual.getInsertedTotal());
-            assertNotEquals(1,actual.getOutputTotal());
-            assertNotEquals(1,actual.getUpdatedTotal());
-            assertNotEquals(1,actual.getDeletedTotal());
-        }
-        @Test
-        @DisplayName("test chart6 method when ids is empty")
-        void test2(){
-            List<TaskDto> allDto = new ArrayList<>();
-            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
-            doCallRealMethod().when(taskService).chart6(user);
-            Chart6Vo actual = taskService.chart6(user);
-            assertNotEquals(0,actual.getInputTotal());
-            assertNotEquals(0,actual.getInsertedTotal());
-            assertNotEquals(0,actual.getOutputTotal());
-            assertNotEquals(0,actual.getUpdatedTotal());
-            assertNotEquals(0,actual.getDeletedTotal());
-        }
-        @Test
-        @DisplayName("test chart6 method when measurement is null")
-        void test3(){
-            List<TaskDto> allDto = new ArrayList<>();
-            TaskDto task = new TaskDto();
-            String id = "65bc933c6129fe73d7858b40";
-            task.setId(new ObjectId(id));
-            allDto.add(task);
-            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
-            when(measurementServiceV2.findLastMinuteByTaskId(id)).thenReturn(null);
-            doCallRealMethod().when(taskService).chart6(user);
-            Chart6Vo actual = taskService.chart6(user);
-            assertNotEquals(0,actual.getInputTotal());
-            assertNotEquals(0,actual.getInsertedTotal());
-            assertNotEquals(0,actual.getOutputTotal());
-            assertNotEquals(0,actual.getUpdatedTotal());
-            assertNotEquals(0,actual.getDeletedTotal());
-        }
-        @Test
-        @DisplayName("test chart6 method when samples is empty")
-        void test4(){
-            List<TaskDto> allDto = new ArrayList<>();
-            TaskDto task = new TaskDto();
-            String id = "65bc933c6129fe73d7858b40";
-            task.setId(new ObjectId(id));
-            allDto.add(task);
-            when(taskService.findAllDto(any(Query.class),any(UserDetail.class))).thenReturn(allDto);
-            MeasurementEntity measurement = new MeasurementEntity();
-            when(measurementServiceV2.findLastMinuteByTaskId(id)).thenReturn(measurement);
-            doCallRealMethod().when(taskService).chart6(user);
-            Chart6Vo actual = taskService.chart6(user);
-            assertNotEquals(0,actual.getInputTotal());
-            assertNotEquals(0,actual.getInsertedTotal());
-            assertNotEquals(0,actual.getOutputTotal());
-            assertNotEquals(0,actual.getUpdatedTotal());
-            assertNotEquals(0,actual.getDeletedTotal());
-        }
-    }
+
     @Nested
     class StopTaskIfNeedByAgentIdTest{
         @Test
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
index ac9ff8dafa..4e31202b2b 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/TaskServiceTest.java
@@ -27,6 +27,7 @@
 import com.tapdata.tm.task.repository.TaskRepository;
 import com.tapdata.tm.task.service.batchin.ParseRelMigFile;
 import com.tapdata.tm.task.service.batchin.entity.ParseParam;
+import com.tapdata.tm.task.service.chart.ChartViewService;
 import com.tapdata.tm.userLog.service.UserLogService;
 import com.tapdata.tm.utils.MongoUtils;
 import com.tapdata.tm.utils.SpringContextHelper;
@@ -63,6 +64,12 @@
 @ExtendWith(MockitoExtension.class)
 public class TaskServiceTest {
     private TaskServiceImpl taskService;
+    ChartViewService chartViewService;
+
+    @BeforeEach
+    void init() {
+        chartViewService = mock(ChartViewService.class);
+    }
 
     @Test
     void testFindRunningTasksByAgentIdWithoutId() {
@@ -621,6 +628,7 @@ void testChartNormal() {
                     .mockStatic(DataPermissionService.class)) {
                 mb.when(DataPermissionService::isCloud).thenReturn(true);
                 taskService = spy(new TaskServiceImpl(taskRepository));
+                ReflectionTestUtils.setField(taskService, "chartViewService", chartViewService);
                 UserDetail user = mock(UserDetail.class);
                 DataPermissionMenuEnums permission = mock(DataPermissionMenuEnums.class);
                 List<TaskDto> taskDtoList = new ArrayList<>();
@@ -648,7 +656,7 @@ void testChartNormal() {
                 when(permission.MigrateTack.checkAndSetFilter(user, DataPermissionActionEnums.View, () -> taskService.findAllDto(any(), any()))).thenReturn(taskDtoList);
                 doReturn(new HashMap()).when(taskService).inspectChart(user);
                 Chart6Vo chart6Vo = mock(Chart6Vo.class);
-                doReturn(chart6Vo).when(taskService).chart6(user);
+                doReturn(chart6Vo).when(chartViewService).transmissionOverviewChartData(taskDtoList);
                 Map<String, Object> actual = taskService.chart(user);
                 Map chart1 = (Map) actual.get("chart1");
                 assertEquals(3, chart1.get("total"));
diff --git a/manager/tm/src/test/java/com/tapdata/tm/task/service/chart/ChartViewServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/task/service/chart/ChartViewServiceTest.java
new file mode 100644
index 0000000000..757cb2e903
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/task/service/chart/ChartViewServiceTest.java
@@ -0,0 +1,155 @@
+package com.tapdata.tm.task.service.chart;
+
+import com.tapdata.tm.commons.task.dto.TaskDto;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.monitor.entity.MeasurementEntity;
+import com.tapdata.tm.monitor.service.MeasurementServiceV2;
+import com.tapdata.tm.task.bean.Chart6Vo;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.utils.Lists;
+import io.tapdata.common.sample.request.Sample;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.mongodb.core.query.Query;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class ChartViewServiceTest {
+    UserDetail user;
+    ChartViewService chartViewService;
+    TaskService taskService;
+    MeasurementServiceV2 measurementServiceV2;
+    List<TaskDto> allDto;
+
+    @BeforeEach
+    void init() {
+        allDto = new ArrayList<>();
+        user = mock(UserDetail.class);
+        chartViewService = mock(ChartViewService.class);
+        taskService = mock(TaskService.class);
+        measurementServiceV2 = mock(MeasurementServiceV2.class);
+
+        doCallRealMethod().when(chartViewService).setMeasurementServiceV2(measurementServiceV2);
+        doCallRealMethod().when(chartViewService).setTaskService(taskService);
+        when(chartViewService.getViewTaskDtoByUser(user)).thenReturn(allDto);
+        when(chartViewService.getViewTaskDto(user)).thenCallRealMethod();
+        when(chartViewService.transmissionOverviewChartData(user)).thenCallRealMethod();
+        when(chartViewService.transmissionOverviewChartData(allDto)).thenCallRealMethod();
+
+
+        chartViewService.setMeasurementServiceV2(measurementServiceV2);
+        chartViewService.setTaskService(taskService);
+    }
+
+    @Nested
+    class Chart6Test {
+        @Test
+        @DisplayName("test chart6 method normal")
+        void test1() {
+            allDto.clear();
+            TaskDto task = new TaskDto();
+            String id = "65bc933c6129fe73d7858b40";
+            task.setId(new ObjectId(id));
+            allDto.add(task);
+            when(taskService.findAllDto(any(Query.class), any(UserDetail.class))).thenReturn(allDto);
+            MeasurementEntity measurement = new MeasurementEntity();
+
+            List<Sample> samples = new ArrayList<>();
+            Sample sample = new Sample();
+            sample.setDate(new Date());
+            Map<String, Number> vs = new HashMap<>();
+            vs.put("inputInsertTotal", 1);
+            vs.put("outputInsertTotal", 1);
+            vs.put("inputUpdateTotal", 1);
+            vs.put("inputDeleteTotal", 1);
+            sample.setVs(vs);
+            samples.add(sample);
+            measurement.setSamples(samples);
+
+            when(measurementServiceV2.findLastMinuteByTaskId(anyString())).thenReturn(measurement);
+            Chart6Vo actual = chartViewService.transmissionOverviewChartData(user);
+            assertNotEquals(3, actual.getInputTotal());
+            assertNotEquals(1, actual.getInsertedTotal());
+            assertNotEquals(1, actual.getOutputTotal());
+            assertNotEquals(1, actual.getUpdatedTotal());
+            assertNotEquals(1, actual.getDeletedTotal());
+        }
+
+        @Test
+        @DisplayName("test chart6 method when ids is empty")
+        void test2() {
+            allDto.clear();
+            when(taskService.findAllDto(any(Query.class), any(UserDetail.class))).thenReturn(allDto);
+            Chart6Vo actual = chartViewService.transmissionOverviewChartData(user);
+            assertNotEquals(0, actual.getInputTotal());
+            assertNotEquals(0, actual.getInsertedTotal());
+            assertNotEquals(0, actual.getOutputTotal());
+            assertNotEquals(0, actual.getUpdatedTotal());
+            assertNotEquals(0, actual.getDeletedTotal());
+        }
+
+        @Test
+        @DisplayName("test chart6 method when measurement is null")
+        void test3() {
+            allDto.clear();
+            TaskDto task = new TaskDto();
+            String id = "65bc933c6129fe73d7858b40";
+            task.setId(new ObjectId(id));
+            allDto.add(task);
+            when(taskService.findAllDto(any(Query.class), any(UserDetail.class))).thenReturn(allDto);
+
+            when(measurementServiceV2.findLastMinuteByTaskId(anyString())).thenReturn(null);
+
+            Chart6Vo actual = chartViewService.transmissionOverviewChartData(user);
+            assertNotEquals(0, actual.getInputTotal());
+            assertNotEquals(0, actual.getInsertedTotal());
+            assertNotEquals(0, actual.getOutputTotal());
+            assertNotEquals(0, actual.getUpdatedTotal());
+            assertNotEquals(0, actual.getDeletedTotal());
+        }
+
+        @Test
+        @DisplayName("test chart6 method when samples is empty")
+        void test4() {
+            allDto.clear();
+            TaskDto task = new TaskDto();
+            String id = "65bc933c6129fe73d7858b40";
+            task.setId(new ObjectId(id));
+            allDto.add(task);
+            when(taskService.findAllDto(any(Query.class), any(UserDetail.class))).thenReturn(allDto);
+            MeasurementEntity measurement = new MeasurementEntity();
+            when(measurementServiceV2.findLastMinuteByTaskId(anyString())).thenReturn(measurement);
+            Chart6Vo actual = chartViewService.transmissionOverviewChartData(user);
+            assertNotEquals(0, actual.getInputTotal());
+            assertNotEquals(0, actual.getInsertedTotal());
+            assertNotEquals(0, actual.getOutputTotal());
+            assertNotEquals(0, actual.getUpdatedTotal());
+            assertNotEquals(0, actual.getDeletedTotal());
+        }
+    }
+
+    @Nested
+    class GetViewTaskDtoTest {
+        @Test
+        void testNormal() {
+            when(taskService.findAllDto(any(Query.class), any(UserDetail.class))).thenReturn(allDto);
+            List<TaskDto> viewTaskDto = chartViewService.getViewTaskDto(user);
+            verify(taskService).findAllDto(any(Query.class), any(UserDetail.class));
+        }
+    }
+}
\ No newline at end of file