You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2022/06/06 15:49:52 UTC

[incubator-devlake] 04/04: minor fixes

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

warren pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 1980cb39b70c92e117b84d84e1801b3a39dc543c
Author: Yingchu Chen <yi...@merico.dev>
AuthorDate: Thu Jun 2 16:57:24 2022 +0800

    minor fixes
    
    Signed-off-by: Yingchu Chen <yi...@merico.dev>
---
 .../pages/configure/connections/ConnectionForm.jsx |   4 +-
 config-ui/webpack.config.js                        |  18 ++-
 config-ui/webpack.production.config.js             |  18 ++-
 plugins/gitlab/gitlab.go                           |   3 +
 plugins/helper/connection.go                       | 118 +++++++++-------
 plugins/helper/connection_test.go                  | 108 +++++++-------
 plugins/helper/worker_scheduler_test.go            |   2 +-
 plugins/jira/api/connection.go                     | 155 ++-------------------
 plugins/jira/api/init.go                           |   4 -
 plugins/jira/api/proxy.go                          |   2 +-
 plugins/jira/jira.go                               |   9 --
 plugins/jira/models/connection.go                  |  15 --
 .../migrationscripts/updateSchemas20220601.go      |   7 +-
 plugins/jira/tasks/api_client.go                   |   2 +-
 plugins/jira/tasks/apiv2models/issue.go            |  12 +-
 plugins/jira/tasks/issue_extractor.go              |   2 +-
 plugins/tapd/api/connection.go                     |  27 ----
 plugins/tapd/tapd.go                               |   8 +-
 runner/directrun.go                                |   3 +-
 19 files changed, 188 insertions(+), 329 deletions(-)

diff --git a/config-ui/src/pages/configure/connections/ConnectionForm.jsx b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
index e5fe71d2..2e657c13 100644
--- a/config-ui/src/pages/configure/connections/ConnectionForm.jsx
+++ b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
@@ -413,7 +413,7 @@ export default function ConnectionForm (props) {
                     ? labels.username
                     : (
                       <>Username</>
-                    )}
+                      )}
                   <span className='requiredStar'>*</span>
                 </Label>
                 <InputGroup
@@ -456,7 +456,7 @@ export default function ConnectionForm (props) {
                     ? labels.password
                     : (
                       <>Password</>
-                    )}
+                      )}
                   <span className='requiredStar'>*</span>
                 </Label>
                 <InputGroup
diff --git a/config-ui/webpack.config.js b/config-ui/webpack.config.js
index 5a253b25..51bd6a5f 100644
--- a/config-ui/webpack.config.js
+++ b/config-ui/webpack.config.js
@@ -1,4 +1,20 @@
-// DEVELOPMENT ONLY WEBPACK CONFIG
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
 const path = require('path')
 const webpack = require('webpack')
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
diff --git a/config-ui/webpack.production.config.js b/config-ui/webpack.production.config.js
index a8c34ae9..6989bd41 100644
--- a/config-ui/webpack.production.config.js
+++ b/config-ui/webpack.production.config.js
@@ -1,4 +1,20 @@
-/* eslint-disable import/no-extraneous-dependencies */
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
 const path = require('path')
 const webpack = require('webpack')
 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
diff --git a/plugins/gitlab/gitlab.go b/plugins/gitlab/gitlab.go
index 7d2be2e5..e9a9f77d 100644
--- a/plugins/gitlab/gitlab.go
+++ b/plugins/gitlab/gitlab.go
@@ -41,6 +41,9 @@ func main() {
 		}
 		wsList := make([]*models.TapdWorkspace, 0)
 		err = db.Find(&wsList, "parent_id = ?", 59169984).Error
+		if err != nil {
+			panic(err)
+		}
 		projectList := []uint64{63281714,
 			34276182,
 			46319043,
diff --git a/plugins/helper/connection.go b/plugins/helper/connection.go
index 17933a5c..30e9ab0d 100644
--- a/plugins/helper/connection.go
+++ b/plugins/helper/connection.go
@@ -1,3 +1,20 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package helper
 
 import (
@@ -21,7 +38,7 @@ type BaseConnection struct {
 
 type BasicAuth struct {
 	Username string `mapstructure:"username" validate:"required" json:"username"`
-	Password string `mapstructure:"password" validate:"required" json:"password" encryptField:"yes"`
+	Password string `mapstructure:"password" validate:"required" json:"password" encrypt:"yes"`
 }
 
 func (ba BasicAuth) GetEncodedToken() string {
@@ -29,7 +46,7 @@ func (ba BasicAuth) GetEncodedToken() string {
 }
 
 type AccessToken struct {
-	Token string `mapstructure:"token" validate:"required" json:"token" encryptField:"yes"`
+	Token string `mapstructure:"token" validate:"required" json:"token" encrypt:"yes"`
 }
 
 type RestConnection struct {
@@ -39,55 +56,50 @@ type RestConnection struct {
 	RateLimit      int    `comment:"api request rate limt per hour" json:"rateLimit"`
 }
 
-// RefreshAndSaveConnection populate from request input into connection which come from REST functions to connection struct and save to DB
+// CreateConnection populate from request input into connection which come from REST functions to connection struct and save to DB
 // and only change value which `data` has
 // mergeFieldsToConnection merges fields from data
 // `connection` is the pointer of a plugin connection
 // `data` is http request input param
-func RefreshAndSaveConnection(connection interface{}, data map[string]interface{}, db *gorm.DB) error {
+func CreateConnection(data map[string]interface{}, connection interface{}, db *gorm.DB) error {
 	var err error
 	// update fields from request body
 	err = mergeFieldsToConnection(connection, data)
 	if err != nil {
 		return err
 	}
-
 	err = saveToDb(connection, db)
-
 	if err != nil {
 		return err
 	}
 	return nil
 }
 
-func saveToDb(connection interface{}, db *gorm.DB) error {
-	dataVal := reflect.ValueOf(connection)
-	if dataVal.Kind() != reflect.Ptr {
-		panic("entityPtr is not a pointer")
-	}
-	encKey, err := getEncKey()
+func PatchConnection(input *core.ApiResourceInput, connection interface{}, db *gorm.DB) error {
+	err := GetConnection(input.Params, connection, db)
 	if err != nil {
 		return err
 	}
-	dataType := reflect.Indirect(dataVal).Type()
-	fieldName := firstFieldNameWithTag(dataType, "encryptField")
-	plainPwd := ""
-	err = encryptField(dataVal, fieldName, encKey)
+
+	err = CreateConnection(input.Body, connection, db)
 	if err != nil {
 		return err
 	}
-	err = db.Clauses(clause.OnConflict{UpdateAll: true}).Save(connection).Error
+
+	return nil
+}
+
+func saveToDb(connection interface{}, db *gorm.DB) error {
+	err := EncryptConnection(connection)
 	if err != nil {
 		return err
 	}
-
-	err = decryptField(dataVal, fieldName, encKey)
+	err = db.Clauses(clause.OnConflict{UpdateAll: true}).Save(connection).Error
 	if err != nil {
 		return err
 	}
-	dataVal.Elem().FieldByName(fieldName).Set(reflect.ValueOf(plainPwd))
 
-	return err
+	return DecryptConnection(connection)
 }
 
 // mergeFieldsToConnection will populate all value in map to connection struct and validate the struct
@@ -110,7 +122,6 @@ func mergeFieldsToConnection(specificConnection interface{}, connections ...map[
 }
 
 func getEncKey() (string, error) {
-	// encryptField
 	v := config.GetConfig()
 	encKey := v.GetString(core.EncodeKeyEnvStr)
 	if encKey == "" {
@@ -125,34 +136,44 @@ func getEncKey() (string, error) {
 	return encKey, nil
 }
 
-// FindConnectionByInput finds connection from db  by parsing request input and decrypt it
-func FindConnectionByInput(input *core.ApiResourceInput, connection interface{}, db *gorm.DB) error {
-	dataVal := reflect.ValueOf(connection)
-	if dataVal.Kind() != reflect.Ptr {
-		return fmt.Errorf("connection is not a pointer")
-	}
-
-	id, err := GetConnectionIdByInputParam(input)
+// GetConnection finds connection from db  by parsing request input and decrypt it
+func GetConnection(data map[string]string, connection interface{}, db *gorm.DB) error {
+	id, err := GetConnectionIdByInputParam(data)
 	if err != nil {
 		return fmt.Errorf("invalid connectionId")
 	}
 
 	err = db.First(connection, id).Error
 	if err != nil {
-		fmt.Printf("--- %s", err.Error())
 		return err
 	}
 
-	dataType := reflect.Indirect(dataVal).Type()
+	return DecryptConnection(connection)
 
-	fieldName := firstFieldNameWithTag(dataType, "encryptField")
-	return decryptField(dataVal, fieldName, "")
+}
 
+// ListConnections returns all connections with password/token decrypted
+func ListConnections(connections interface{}, db *gorm.DB) error {
+	err := db.Find(connections).Error
+	connPtr := reflect.ValueOf(connections)
+	connVal := reflect.Indirect(connPtr)
+	if err != nil {
+		return err
+	}
+	for i := 0; i < connVal.Len(); i++ {
+		//connVal.Index(i) returns value of ith elem in connections, .Elem() reutrns the original elem
+		tmp := connVal.Index(i).Elem()
+		err = DecryptConnection(tmp.Addr().Interface())
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
 // GetConnectionIdByInputParam gets connectionId by parsing request input
-func GetConnectionIdByInputParam(input *core.ApiResourceInput) (uint64, error) {
-	connectionId := input.Params["connectionId"]
+func GetConnectionIdByInputParam(data map[string]string) (uint64, error) {
+	connectionId := data["connectionId"]
 	if connectionId == "" {
 		return 0, fmt.Errorf("missing connectionId")
 	}
@@ -175,7 +196,7 @@ func firstFieldNameWithTag(t reflect.Type, tag string) string {
 }
 
 // DecryptConnection decrypts password/token field for connection
-func DecryptConnection(connection interface{}, fieldName string) error {
+func DecryptConnection(connection interface{}) error {
 	dataVal := reflect.ValueOf(connection)
 	if dataVal.Kind() != reflect.Ptr {
 		panic("connection is not a pointer")
@@ -184,23 +205,26 @@ func DecryptConnection(connection interface{}, fieldName string) error {
 	if err != nil {
 		return nil
 	}
-	if len(fieldName) == 0 {
-		dataType := reflect.Indirect(dataVal).Type()
-		fieldName = firstFieldNameWithTag(dataType, "encryptField")
-	}
-	return decryptField(dataVal, fieldName, encKey)
-}
-
-func decryptField(dataVal reflect.Value, fieldName string, encKey string) error {
+	dataType := reflect.Indirect(dataVal).Type()
+	fieldName := firstFieldNameWithTag(dataType, "encrypt")
 	if len(fieldName) > 0 {
 		decryptStr, _ := core.Decrypt(encKey, dataVal.Elem().FieldByName(fieldName).String())
-
 		dataVal.Elem().FieldByName(fieldName).Set(reflect.ValueOf(decryptStr))
 	}
 	return nil
 }
 
-func encryptField(dataVal reflect.Value, fieldName string, encKey string) error {
+func EncryptConnection(connection interface{}) error {
+	dataVal := reflect.ValueOf(connection)
+	if dataVal.Kind() != reflect.Ptr {
+		panic("connection is not a pointer")
+	}
+	encKey, err := getEncKey()
+	if err != nil {
+		return err
+	}
+	dataType := reflect.Indirect(dataVal).Type()
+	fieldName := firstFieldNameWithTag(dataType, "encrypt")
 	if len(fieldName) > 0 {
 		plainPwd := dataVal.Elem().FieldByName(fieldName).String()
 		encyptedStr, err := core.Encrypt(encKey, plainPwd)
diff --git a/plugins/helper/connection_test.go b/plugins/helper/connection_test.go
index 606730f3..bd1c7cc2 100644
--- a/plugins/helper/connection_test.go
+++ b/plugins/helper/connection_test.go
@@ -18,24 +18,24 @@ limitations under the License.
 package helper
 
 import (
-	"github.com/apache/incubator-devlake/config"
-	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/stretchr/testify/assert"
 	"reflect"
 	"testing"
-
-	"github.com/stretchr/testify/assert"
 )
 
-type TestConnection struct {
+type MockConnection struct {
 	RestConnection             `mapstructure:",squash"`
 	BasicAuth                  `mapstructure:",squash"`
-	EpicKeyField               string `gorm:"type:varchar(50);" json:"epicKeyField"`
 	StoryPointField            string `gorm:"type:varchar(50);" json:"storyPointField"`
 	RemotelinkCommitShaPattern string `gorm:"type:varchar(255);comment='golang regexp, the first group will be recognized as commit sha, ref https://github.com/google/re2/wiki/Syntax'" json:"remotelinkCommitShaPattern"`
 }
 
+func (MockConnection) TableName() string {
+	return "_tool_jira_connections"
+}
+
 func TestMergeFieldsToConnection(t *testing.T) {
-	v := &TestConnection{
+	v := &MockConnection{
 		RestConnection: RestConnection{
 			BaseConnection: BaseConnection{
 				Name: "1",
@@ -48,8 +48,6 @@ func TestMergeFieldsToConnection(t *testing.T) {
 			Username: "4",
 			Password: "5",
 		},
-		EpicKeyField:               "6",
-		StoryPointField:            "7",
 		RemotelinkCommitShaPattern: "8",
 	}
 	data := make(map[string]interface{})
@@ -58,9 +56,7 @@ func TestMergeFieldsToConnection(t *testing.T) {
 	data["Password"] = "5-5"
 
 	err := mergeFieldsToConnection(v, data)
-	if err != nil {
-		return
-	}
+	assert.Nil(t, err)
 
 	assert.Equal(t, "4-4", v.Username)
 	assert.Equal(t, "2-2", v.Endpoint)
@@ -68,7 +64,7 @@ func TestMergeFieldsToConnection(t *testing.T) {
 }
 
 func TestDecryptAndEncrypt(t *testing.T) {
-	v := &TestConnection{
+	v := &MockConnection{
 		RestConnection: RestConnection{
 			BaseConnection: BaseConnection{
 				Name: "1",
@@ -81,28 +77,21 @@ func TestDecryptAndEncrypt(t *testing.T) {
 			Username: "4",
 			Password: "5",
 		},
-		EpicKeyField:               "6",
-		StoryPointField:            "7",
 		RemotelinkCommitShaPattern: "8",
 	}
-	dataVal := reflect.ValueOf(v)
-	encKey := "test"
-	err := encryptField(dataVal, "Password", encKey)
-	if err != nil {
-		return
-	}
+	err := EncryptConnection(v)
+	assert.Nil(t, err)
+
 	assert.NotEqual(t, "5", v.Password)
-	err = decryptField(dataVal, "Password", encKey)
-	if err != nil {
-		return
-	}
+	err = DecryptConnection(v)
+	assert.Nil(t, err)
 
 	assert.Equal(t, "5", v.Password)
 
 }
 
 func TestDecryptConnection(t *testing.T) {
-	v := &TestConnection{
+	v := &MockConnection{
 		RestConnection: RestConnection{
 			BaseConnection: BaseConnection{
 				Name: "1",
@@ -115,46 +104,20 @@ func TestDecryptConnection(t *testing.T) {
 			Username: "4",
 			Password: "5",
 		},
-		EpicKeyField:               "6",
-		StoryPointField:            "7",
 		RemotelinkCommitShaPattern: "8",
 	}
-	encKey, err := getEncKey()
-	if err != nil {
-		return
-	}
-	dataVal := reflect.ValueOf(v)
-	err = encryptField(dataVal, "Password", encKey)
-	if err != nil {
-		return
-	}
+	err := EncryptConnection(v)
+	assert.Nil(t, err)
+
 	encryptedPwd := v.Password
-	err = DecryptConnection(v, "Password")
-	if err != nil {
-		return
-	}
+	err = DecryptConnection(v)
+	assert.Nil(t, err)
 	assert.NotEqual(t, encryptedPwd, v.Password)
 	assert.Equal(t, "5", v.Password)
 }
 
-func TestGetEncKey(t *testing.T) {
-	// encryptField
-	v := config.GetConfig()
-	encKey := v.GetString(core.EncodeKeyEnvStr)
-	str, err := getEncKey()
-	if err != nil {
-		return
-	}
-	if len(encKey) > 0 {
-		assert.Equal(t, encKey, str)
-	} else {
-		assert.NotEqual(t, 0, len(str))
-	}
-
-}
-
 func TestFirstFieldNameWithTag(t *testing.T) {
-	v := &TestConnection{
+	v := &MockConnection{
 		RestConnection: RestConnection{
 			BaseConnection: BaseConnection{
 				Name: "1",
@@ -167,12 +130,37 @@ func TestFirstFieldNameWithTag(t *testing.T) {
 			Username: "4",
 			Password: "5",
 		},
-		EpicKeyField:               "6",
 		StoryPointField:            "7",
 		RemotelinkCommitShaPattern: "8",
 	}
 	dataVal := reflect.ValueOf(v)
 	dataType := reflect.Indirect(dataVal).Type()
-	fieldName := firstFieldNameWithTag(dataType, "encryptField")
+	fieldName := firstFieldNameWithTag(dataType, "encrypt")
 	assert.Equal(t, "Password", fieldName)
 }
+
+//func TestListConnections(t *testing.T) {
+//	jiraConnections := make([]*MockConnection, 0)
+//	cfg := config.GetConfig()
+//	dbUrl := cfg.GetString("DB_URL")
+//	u, err := url.Parse(dbUrl)
+//	dbUrl = fmt.Sprintf("%s@tcp(%s)%s?%s", u.User.String(), u.Host, u.Path, u.RawQuery)
+//	dbConfig := &gorm.Config{
+//		Logger: gormLogger.New(
+//			log.Default(),
+//			gormLogger.Config{
+//				SlowThreshold:             time.Second,      // Slow SQL threshold
+//				LogLevel:                  gormLogger.Error, // Log level
+//				IgnoreRecordNotFoundError: true,             // Ignore ErrRecordNotFound error for logger
+//				Colorful:                  true,             // Disable color
+//			},
+//		),
+//		// most of our operation are in batch, this can improve performance
+//		PrepareStmt: true,
+//	}
+//	db, err := gorm.Open(mysql.Open(dbUrl), dbConfig)
+//
+//	err = ListConnections(&jiraConnections, db)
+//
+//	assert.Nil(t, err)
+//}
diff --git a/plugins/helper/worker_scheduler_test.go b/plugins/helper/worker_scheduler_test.go
index a3de7adb..4f65e7a0 100644
--- a/plugins/helper/worker_scheduler_test.go
+++ b/plugins/helper/worker_scheduler_test.go
@@ -88,7 +88,7 @@ func TestNewWorkerSchedulerWithoutSecond(t *testing.T) {
 func TestNewWorkerSchedulerWithPanic(t *testing.T) {
 	testChannel := make(chan int, 100)
 	ctx, cancel := context.WithCancel(context.Background())
-	s, _ := NewWorkerScheduler(1, 1, ctx)
+	s,_ := NewWorkerScheduler(1, 1, ctx)
 	defer s.Release()
 	_ = s.Submit(func() error {
 		testChannel <- 1
diff --git a/plugins/jira/api/connection.go b/plugins/jira/api/connection.go
index aa94537a..0c422bbe 100644
--- a/plugins/jira/api/connection.go
+++ b/plugins/jira/api/connection.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
-	"strconv"
 	"strings"
 	"time"
 
@@ -103,19 +102,8 @@ POST /plugins/jira/connections
 {
 	"name": "jira data connection name",
 	"endpoint": "jira api endpoint, i.e. https://merico.atlassian.net/rest",
-	"basicAuthEncoded": "generated by `echo -n <jira login email>:<jira token> | base64`",
-	"epicKeyField": "name of customfield of epic key",
-	"storyPointField": "name of customfield of story point",
-	"typeMappings": { // optional, send empty object to delete all typeMappings of the data connection
-		"userType": {
-			"standardType": "devlake standard type",
-			"statusMappings": {  // optional, send empt object to delete all status mapping for the user type
-				"userStatus": {
-					"standardStatus": "devlake standard status"
-				}
-			}
-		}
-	}
+	"username": "username, usually should be email address",
+	"password": "jira api access token"
 }
 */
 func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
@@ -123,7 +111,7 @@ func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, err
 	jiraConnection := &models.JiraConnection{}
 
 	// update from request and save to database
-	err := helper.RefreshAndSaveConnection(jiraConnection, input.Body, db)
+	err := helper.CreateConnection(input.Body, jiraConnection, db)
 	if err != nil {
 		return nil, err
 	}
@@ -136,31 +124,13 @@ PATCH /plugins/jira/connections/:connectionId
 {
 	"name": "jira data connection name",
 	"endpoint": "jira api endpoint, i.e. https://merico.atlassian.net/rest",
-	"basicAuthEncoded": "generated by `echo -n <jira login email>:<jira token> | base64`",
-	"epicKeyField": "name of customfield of epic key",
-	"storyPointField": "name of customfield of story point",
-	"typeMappings": { // optional, send empty object to delete all typeMappings of the data connection
-		"userType": {
-			"standardType": "devlake standard type",
-			"statusMappings": {  // optional, send empt object to delete all status mapping for the user type
-				"userStatus": {
-					"standardStatus": "devlake standard status"
-				}
-			}
-		}
-	}
+	"username": "username, usually should be email address",
+	"password": "jira api access token"
 }
 */
 func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
 	jiraConnection := &models.JiraConnection{}
-	// load from db
-	err := helper.FindConnectionByInput(input, jiraConnection, db)
-	if err != nil {
-		return nil, err
-	}
-
-	// update from request and save to database
-	err = helper.RefreshAndSaveConnection(jiraConnection, input.Body, db)
+	err := helper.PatchConnection(input, jiraConnection, db)
 	if err != nil {
 		return nil, err
 	}
@@ -173,7 +143,7 @@ DELETE /plugins/jira/connections/:connectionId
 */
 func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
 	// load from db
-	jiraConnectionID, err := helper.GetConnectionIdByInputParam(input)
+	jiraConnectionID, err := helper.GetConnectionIdByInputParam(input.Params)
 	if err != nil {
 		return nil, err
 	}
@@ -182,15 +152,6 @@ func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, er
 	if err != nil {
 		return nil, err
 	}
-	err = db.Where("connection_id = ?", jiraConnectionID).Delete(&models.JiraIssueTypeMapping{}).Error
-	if err != nil {
-		return nil, err
-	}
-	err = db.Where("connection_id = ?", jiraConnectionID).Delete(&models.JiraIssueStatusMapping{}).Error
-	if err != nil {
-		return nil, err
-	}
-
 	return &core.ApiResourceOutput{Body: jiraConnectionID}, nil
 }
 
@@ -199,17 +160,11 @@ GET /plugins/jira/connections
 */
 func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
 	jiraConnections := make([]*models.JiraConnection, 0)
-	err := db.Find(&jiraConnections).Error
+
+	err := helper.ListConnections(&jiraConnections, db)
 	if err != nil {
 		return nil, err
 	}
-	for i, _ := range jiraConnections {
-		err = helper.DecryptConnection(jiraConnections[i], "Password")
-		if err != nil {
-			return nil, err
-		}
-	}
-
 	return &core.ApiResourceOutput{Body: jiraConnections, Status: http.StatusOK}, nil
 }
 
@@ -220,100 +175,16 @@ GET /plugins/jira/connections/:connectionId
 {
 	"name": "jira data connection name",
 	"endpoint": "jira api endpoint, i.e. https://merico.atlassian.net/rest",
-	"basicAuthEncoded": "generated by `echo -n <jira login email>:<jira token> | base64`",
-	"epicKeyField": "name of customfield of epic key",
-	"storyPointField": "name of customfield of story point",
-	"typeMappings": { // optional, send empty object to delete all typeMappings of the data connection
-		"userType": {
-			"standardType": "devlake standard type",
-			"statusMappings": {  // optional, send empt object to delete all status mapping for the user type
-				"userStatus": {
-					"standardStatus": "devlake standard status"
-				}
-			}
-		}
-	}
+	"username": "username, usually should be email address",
+	"password": "jira api access token"
 }
 */
 func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
 	jiraConnection := &models.JiraConnection{}
-	err := helper.FindConnectionByInput(input, jiraConnection, db)
+	err := helper.GetConnection(input.Params, jiraConnection, db)
 	if err != nil {
 		return nil, err
 	}
 
-	detail := &models.JiraConnectionDetail{
-		JiraConnection: *jiraConnection,
-	}
-
-	if err != nil {
-		return nil, err
-	}
-
-	return &core.ApiResourceOutput{Body: detail}, nil
-}
-
-// GET /plugins/jira/connections/:connectionId/epics
-func GetEpicsByConnectionId(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
-	jiraConnection := &models.JiraConnection{}
-	err := helper.FindConnectionByInput(input, jiraConnection, db)
-	if err != nil {
-		return nil, err
-	}
-	return &core.ApiResourceOutput{Body: [1]models.EpicResponse{{
-		Id:    1,
-		Title: jiraConnection.EpicKeyField,
-		Value: jiraConnection.EpicKeyField,
-	}}}, nil
-}
-
-// GET /plugins/jira/connections/:connectionId/granularities
-type GranularitiesResponse struct {
-	Id    int
-	Title string
-	Value string
-}
-
-func GetGranularitiesByConnectionId(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
-	jiraConnection := &models.JiraConnection{}
-	err := helper.FindConnectionByInput(input, jiraConnection, db)
-	if err != nil {
-		return nil, err
-	}
-	if err != nil {
-		return nil, err
-	}
-	return &core.ApiResourceOutput{Body: [1]GranularitiesResponse{
-		{
-			Id:    1,
-			Title: "Story Point Field",
-			Value: jiraConnection.StoryPointField,
-		},
-	}}, nil
-}
-
-// GET /plugins/jira/connections/:connectionId/boards
-func GetBoardsByConnectionId(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
-	connectionId := input.Params["connectionId"]
-	if connectionId == "" {
-		return nil, fmt.Errorf("missing connectionid")
-	}
-	jiraConnectionId, err := strconv.ParseUint(connectionId, 10, 64)
-	if err != nil {
-		return nil, fmt.Errorf("invalid connectionId")
-	}
-	var jiraBoards []models.JiraBoard
-	err = db.Where("connection_Id = ?", jiraConnectionId).Find(&jiraBoards).Error
-	if err != nil {
-		return nil, err
-	}
-	var boardResponses []models.BoardResponse
-	for _, board := range jiraBoards {
-		boardResponses = append(boardResponses, models.BoardResponse{
-			Id:    int(board.BoardId),
-			Title: board.Name,
-			Value: fmt.Sprintf("%v", board.BoardId),
-		})
-	}
-	return &core.ApiResourceOutput{Body: boardResponses}, nil
+	return &core.ApiResourceOutput{Body: jiraConnection}, nil
 }
diff --git a/plugins/jira/api/init.go b/plugins/jira/api/init.go
index 663d5033..acaa495e 100644
--- a/plugins/jira/api/init.go
+++ b/plugins/jira/api/init.go
@@ -24,11 +24,7 @@ import (
 )
 
 var db *gorm.DB
-var cfg *viper.Viper
-var log core.Logger
 
 func Init(config *viper.Viper, logger core.Logger, database *gorm.DB) {
 	db = database
-	cfg = config
-	log = logger
 }
diff --git a/plugins/jira/api/proxy.go b/plugins/jira/api/proxy.go
index e4a3eb90..398f4f1f 100644
--- a/plugins/jira/api/proxy.go
+++ b/plugins/jira/api/proxy.go
@@ -35,7 +35,7 @@ const (
 
 func Proxy(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
 	jiraConnection := &models.JiraConnection{}
-	err := helper.FindConnectionByInput(input, jiraConnection, db)
+	err := helper.GetConnection(input.Params, jiraConnection, db)
 	if err != nil {
 		return nil, err
 	}
diff --git a/plugins/jira/jira.go b/plugins/jira/jira.go
index a36177da..37313972 100644
--- a/plugins/jira/jira.go
+++ b/plugins/jira/jira.go
@@ -176,15 +176,6 @@ func (plugin Jira) ApiResources() map[string]map[string]core.ApiResourceHandler
 			"DELETE": api.DeleteConnection,
 			"GET":    api.GetConnection,
 		},
-		"connections/:connectionId/epics": {
-			"GET": api.GetEpicsByConnectionId,
-		},
-		"connections/:connectionId/granularities": {
-			"GET": api.GetGranularitiesByConnectionId,
-		},
-		"connections/:connectionId/boards": {
-			"GET": api.GetBoardsByConnectionId,
-		},
 		"connections/:connectionId/proxy/rest/*path": {
 			"GET": api.Proxy,
 		},
diff --git a/plugins/jira/models/connection.go b/plugins/jira/models/connection.go
index f1ed2f8a..fa0664f6 100644
--- a/plugins/jira/models/connection.go
+++ b/plugins/jira/models/connection.go
@@ -42,8 +42,6 @@ type BoardResponse struct {
 type JiraConnection struct {
 	helper.RestConnection      `mapstructure:",squash"`
 	helper.BasicAuth           `mapstructure:",squash"`
-	EpicKeyField               string `gorm:"type:varchar(50);" json:"epicKeyField"`
-	StoryPointField            string `gorm:"type:varchar(50);" json:"storyPointField"`
 	RemotelinkCommitShaPattern string `gorm:"type:varchar(255);comment='golang regexp, the first group will be recognized as commit sha, ref https://github.com/google/re2/wiki/Syntax'" json:"remotelinkCommitShaPattern"`
 }
 
@@ -60,19 +58,6 @@ type JiraIssueStatusMapping struct {
 	StandardStatus string `gorm:"type:varchar(50)" json:"standardStatus" validate:"required"`
 }
 
-type JiraConnectionDetail struct {
-	JiraConnection
-	TypeMappings map[string]map[string]interface{} `json:"typeMappings"`
-}
-
 func (JiraConnection) TableName() string {
 	return "_tool_jira_connections"
 }
-
-func (JiraIssueTypeMapping) TableName() string {
-	return "_tool_jira_issue_type_mappings"
-}
-
-func (JiraIssueStatusMapping) TableName() string {
-	return "_tool_jira_issue_status_mappings"
-}
diff --git a/plugins/jira/models/migrationscripts/updateSchemas20220601.go b/plugins/jira/models/migrationscripts/updateSchemas20220601.go
index ec528dcb..24d3786c 100644
--- a/plugins/jira/models/migrationscripts/updateSchemas20220601.go
+++ b/plugins/jira/models/migrationscripts/updateSchemas20220601.go
@@ -58,9 +58,12 @@ func (*UpdateSchemas20220601) Up(ctx context.Context, db *gorm.DB) error {
 
 	if db.Migrator().HasColumn(&JiraConnection20220505{}, "basic_auth_encoded") {
 		connections := make([]*JiraConnection20220505, 0)
-		db.Find(&connections)
+		err = db.Find(&connections).Error
+		if err != nil {
+			return err
+		}
 		for i, _ := range connections {
-			err = helper.DecryptConnection(connections[i], "BasicAuthEncoded")
+			err = helper.DecryptConnection(connections[i])
 			if err != nil {
 				return err
 			}
diff --git a/plugins/jira/tasks/api_client.go b/plugins/jira/tasks/api_client.go
index 811c0b6a..b44b024b 100644
--- a/plugins/jira/tasks/api_client.go
+++ b/plugins/jira/tasks/api_client.go
@@ -28,7 +28,7 @@ import (
 
 func NewJiraApiClient(taskCtx core.TaskContext, connection *models.JiraConnection) (*helper.ApiAsyncClient, error) {
 	// decrypt connection first
-	err := helper.DecryptConnection(connection, "Password")
+	err := helper.DecryptConnection(connection)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to decrypt Auth AccessToken: %w", err)
 	}
diff --git a/plugins/jira/tasks/apiv2models/issue.go b/plugins/jira/tasks/apiv2models/issue.go
index 3544150e..591ff7b7 100644
--- a/plugins/jira/tasks/apiv2models/issue.go
+++ b/plugins/jira/tasks/apiv2models/issue.go
@@ -151,11 +151,8 @@ type Issue struct {
 	} `json:"changelog"`
 }
 
-func (i Issue) toToolLayer(connectionId uint64, epicField, storyPointField string) *models.JiraIssue {
+func (i Issue) toToolLayer(connectionId uint64) *models.JiraIssue {
 	var workload float64
-	if storyPointField != "" {
-		workload, _ = i.Fields.AllFields[storyPointField].(float64)
-	}
 	result := &models.JiraIssue{
 		ConnectionId:       connectionId,
 		IssueId:            i.ID,
@@ -177,9 +174,6 @@ func (i Issue) toToolLayer(connectionId uint64, epicField, storyPointField strin
 	if i.Fields.Epic != nil {
 		result.EpicKey = i.Fields.Epic.Key
 	}
-	if epicField != "" {
-		result.EpicKey, _ = i.Fields.AllFields[epicField].(string)
-	}
 	if i.Fields.Assignee != nil {
 		result.AssigneeAccountId = i.Fields.Assignee.getAccountId()
 		result.AssigneeDisplayName = i.Fields.Assignee.DisplayName
@@ -227,8 +221,8 @@ func (i *Issue) SetAllFields(raw datatypes.JSON) error {
 	return nil
 }
 
-func (i Issue) ExtractEntities(connectionId uint64, epicField, storyPointField string) ([]uint64, *models.JiraIssue, bool, []*models.JiraWorklog, []*models.JiraChangelog, []*models.JiraChangelogItem, []*models.JiraUser) {
-	issue := i.toToolLayer(connectionId, epicField, storyPointField)
+func (i Issue) ExtractEntities(connectionId uint64) ([]uint64, *models.JiraIssue, bool, []*models.JiraWorklog, []*models.JiraChangelog, []*models.JiraChangelogItem, []*models.JiraUser) {
+	issue := i.toToolLayer(connectionId)
 	var worklogs []*models.JiraWorklog
 	var changelogs []*models.JiraChangelog
 	var changelogItems []*models.JiraChangelogItem
diff --git a/plugins/jira/tasks/issue_extractor.go b/plugins/jira/tasks/issue_extractor.go
index 17882df8..266be502 100644
--- a/plugins/jira/tasks/issue_extractor.go
+++ b/plugins/jira/tasks/issue_extractor.go
@@ -106,7 +106,7 @@ func ExtractIssues(taskCtx core.SubTaskContext) error {
 				return nil, err
 			}
 			var results []interface{}
-			sprints, issue, _, worklogs, changelogs, changelogItems, users := apiIssue.ExtractEntities(data.Connection.ID, data.Connection.EpicKeyField, data.Connection.StoryPointField)
+			sprints, issue, _, worklogs, changelogs, changelogItems, users := apiIssue.ExtractEntities(data.Connection.ID)
 			for _, sprintId := range sprints {
 				sprintIssue := &models.JiraSprintIssue{
 					ConnectionId:     data.Connection.ID,
diff --git a/plugins/tapd/api/connection.go b/plugins/tapd/api/connection.go
index 5065f096..96c6bbba 100644
--- a/plugins/tapd/api/connection.go
+++ b/plugins/tapd/api/connection.go
@@ -281,30 +281,3 @@ func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error
 	}
 	return &core.ApiResourceOutput{Body: detail}, nil
 }
-
-// GET /plugins/tapd/connections/:connectionId/boards
-
-func GetBoardsByConnectionId(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
-	connectionId := input.Params["connectionId"]
-	if connectionId == "" {
-		return nil, fmt.Errorf("missing connectionId")
-	}
-	tapdConnectionId, err := strconv.ParseUint(connectionId, 10, 64)
-	if err != nil {
-		return nil, fmt.Errorf("invalid connectionId")
-	}
-	var tapdWorkspaces []models.TapdWorkspace
-	err = db.Where("connection_Id = ?", tapdConnectionId).Find(&tapdWorkspaces).Error
-	if err != nil {
-		return nil, err
-	}
-	var workSpaceResponses []models.WorkspaceResponse
-	for _, workSpace := range tapdWorkspaces {
-		workSpaceResponses = append(workSpaceResponses, models.WorkspaceResponse{
-			Id:    uint64(workSpace.ID),
-			Title: workSpace.Name,
-			Value: fmt.Sprintf("%v", workSpace.ID),
-		})
-	}
-	return &core.ApiResourceOutput{Body: workSpaceResponses}, nil
-}
diff --git a/plugins/tapd/tapd.go b/plugins/tapd/tapd.go
index ea556438..c74d278d 100644
--- a/plugins/tapd/tapd.go
+++ b/plugins/tapd/tapd.go
@@ -180,9 +180,6 @@ func (plugin Tapd) ApiResources() map[string]map[string]core.ApiResourceHandler
 			"DELETE": api.DeleteConnection,
 			"GET":    api.GetConnection,
 		},
-		"connections/:connectionId/boards": {
-			"GET": api.GetBoardsByConnectionId,
-		},
 		"connections/:connectionId/proxy/rest/*path": {
 			"GET": api.Proxy,
 		},
@@ -220,7 +217,10 @@ func main() {
 			panic(err)
 		}
 		wsList := make([]*models.TapdWorkspace, 0)
-		err = db.Find(&wsList, "parent_id = ?", 59169984).Error //nolint TODO: fix the unused err
+		err = db.Find(&wsList, "parent_id = ?", 59169984).Error
+		if err != nil {
+			panic(err)
+		}
 		for _, v := range wsList {
 			*workspaceId = v.ID
 			runner.DirectRun(c, args, PluginEntry, []string{}, map[string]interface{}{
diff --git a/runner/directrun.go b/runner/directrun.go
index 6ab5b83d..e2343cd4 100644
--- a/runner/directrun.go
+++ b/runner/directrun.go
@@ -47,7 +47,6 @@ func DirectRun(cmd *cobra.Command, args []string, pluginTask core.PluginTask, su
 	if err != nil {
 		panic(err)
 	}
-	subtasks = tasks
 	cfg := config.GetConfig()
 	log := logger.Global.Nested(cmd.Use)
 	db, err := NewGormDb(cfg, log)
@@ -102,7 +101,7 @@ func DirectRun(cmd *cobra.Command, args []string, pluginTask core.PluginTask, su
 		db,
 		ctx,
 		cmd.Use,
-		subtasks,
+		tasks,
 		options,
 		pluginTask,
 		nil,