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


The following commit(s) were added to refs/heads/main by this push:
     new 107a5d33 fix(framework): update code generator templates
107a5d33 is described below

commit 107a5d33c3f3dadda4c929efcbdc43cbaf311925
Author: Yingchu Chen <[email protected]>
AuthorDate: Sat Jul 30 15:32:18 2022 +0800

    fix(framework): update code generator templates
    
    closes #2640
---
 generator/cmd/create_collector.go                  |   5 +
 generator/cmd/create_extractor.go                  |   5 +
 generator/cmd/create_plugin.go                     |  35 ++---
 .../archived/connection.go-template                |   0
 .../template/plugin/api/blueprint.go-template      |  69 +++++++++
 .../template/plugin/api/connection.go-template     | 155 +++++++++++++++++++++
 .../init.go-template}                              |  28 ++--
 .../plugin/impl/impl_complete_plugin.go-template   | 123 ++++++++++++++++
 .../template/plugin/models/connection.go-template  |  51 +++++++
 ...ate => plugin_main_complete_plugin.go-template} |  32 +++--
 .../plugin/plugin_main_with_api_client.go-template | 100 -------------
 .../template/plugin/tasks/api_client.go-template   |  71 +++++-----
 ...plate => task_data_complete_plugin.go-template} |  18 +++
 13 files changed, 510 insertions(+), 182 deletions(-)

diff --git a/generator/cmd/create_collector.go 
b/generator/cmd/create_collector.go
index 6c5f3d78..bef7385f 100644
--- a/generator/cmd/create_collector.go
+++ b/generator/cmd/create_collector.go
@@ -134,6 +134,11 @@ Type in what the name of collector is, then generator will 
create a new collecto
                util.ReplaceVarInTemplates(templates, values)
                util.WriteTemplates(filepath.Join(`plugins`, pluginName, 
`tasks`), templates)
                if modifyExistCode {
+                       util.ReplaceVarInFile(
+                               filepath.Join(`plugins`, pluginName, 
`impl/impl.go`),
+                               regexp.MustCompile(`(return 
+\[]core\.SubTaskMeta ?\{ ?\n?)((\s*[\w.]+,\n)*)(\s*})`),
+                               fmt.Sprintf("$1$2\t\ttasks.Collect%sMeta,\n$4", 
collectorDataNameUpperCamel),
+                       )
                        util.ReplaceVarInFile(
                                filepath.Join(`plugins`, pluginName, 
`plugin_main.go`),
                                regexp.MustCompile(`(return 
+\[]core\.SubTaskMeta ?\{ ?\n?)((\s*[\w.]+,\n)*)(\s*})`),
diff --git a/generator/cmd/create_extractor.go 
b/generator/cmd/create_extractor.go
index b63ac6a6..d16c3d7f 100644
--- a/generator/cmd/create_extractor.go
+++ b/generator/cmd/create_extractor.go
@@ -121,6 +121,11 @@ Type in what the name of extractor is, then generator will 
create a new extracto
                                regexp.MustCompile(`(return 
+\[]core\.SubTaskMeta ?\{ ?\n?)((\s*[\w.]+,\n)*)(\s*})`),
                                fmt.Sprintf("$1$2\t\ttasks.Extract%sMeta,\n$4", 
extractorDataNameUpperCamel),
                        )
+                       util.ReplaceVarInFile(
+                               filepath.Join(`plugins`, pluginName, 
`impl/impl.go`),
+                               regexp.MustCompile(`(return 
+\[]core\.SubTaskMeta ?\{ ?\n?)((\s*[\w.]+,\n)*)(\s*})`),
+                               fmt.Sprintf("$1$2\t\ttasks.Extract%sMeta,\n$4", 
extractorDataNameUpperCamel),
+                       )
                }
        },
 }
diff --git a/generator/cmd/create_plugin.go b/generator/cmd/create_plugin.go
index 59a1ad2f..23b9d02b 100644
--- a/generator/cmd/create_plugin.go
+++ b/generator/cmd/create_plugin.go
@@ -27,6 +27,7 @@ import (
        "os"
        "regexp"
        "strings"
+       "time"
 )
 
 func init() {
@@ -103,7 +104,7 @@ Type in what the name of plugin is, then generator will 
create a new plugin in p
                }
 
                prompt := promptui.Select{
-                       Label: "with_api_client (Will this plugin request HTTP 
APIs?)",
+                       Label: "complete_plugin (Will this plugin request HTTP 
APIs?)",
                        Items: []string{"Yes", "No"},
                }
                _, withApiClient, err := prompt.Run()
@@ -112,30 +113,22 @@ Type in what the name of plugin is, then generator will 
create a new plugin in p
                values := map[string]string{}
                templates := map[string]string{}
                if withApiClient == `Yes` {
-                       prompt := promptui.Prompt{
-                               Label:   "Endpoint (which host to request)",
-                               Default: `https://open.example.cn/api/v1`,
-                               Validate: func(input string) error {
-                                       if input == `` {
-                                               return errors.New("endpoint 
require")
-                                       }
-                                       if !strings.HasPrefix(input, `http`) {
-                                               return errors.New("endpoint 
should start with http")
-                                       }
-                                       return nil
-                               },
-                       }
-                       endpoint, err := prompt.Run()
-                       cobra.CheckErr(err)
-
+                       versionTimestamp := time.Now().Format(`20060102`)
+                       values[`Date`] = versionTimestamp
                        // read template
                        templates = map[string]string{
-                               `plugin_main.go`:      
util.ReadTemplate("generator/template/plugin/plugin_main_with_api_client.go-template"),
-                               `tasks/api_client.go`: 
util.ReadTemplate("generator/template/plugin/tasks/api_client.go-template"),
-                               `tasks/task_data.go`:  
util.ReadTemplate("generator/template/plugin/tasks/task_data_with_api_client.go-template"),
+                               fmt.Sprintf(`%s.go`, pluginName): 
util.ReadTemplate("generator/template/plugin/plugin_main_complete_plugin.go-template"),
+                               `impl/impl.go`:                   
util.ReadTemplate("generator/template/plugin/impl/impl_complete_plugin.go-template"),
+                               `tasks/api_client.go`:            
util.ReadTemplate("generator/template/plugin/tasks/api_client.go-template"),
+                               `tasks/task_data.go`:             
util.ReadTemplate("generator/template/plugin/tasks/task_data_complete_plugin.go-template"),
+                               `api/connection.go`:              
util.ReadTemplate("generator/template/plugin/api/connection.go-template"),
+                               `models/connection.go`:           
util.ReadTemplate("generator/template/plugin/models/connection.go-template"),
+                               
fmt.Sprintf("models/migrationscripts/%s_add_init_tables.go", versionTimestamp): 
util.ReadTemplate("generator/template/migrationscripts/add_init_tables.go-template"),
+                               `models/migrationscripts/register.go`:          
                                
util.ReadTemplate("generator/template/migrationscripts/register.go-template"),
+                               `api/init.go`:                                  
                                
util.ReadTemplate("generator/template/plugin/api/init.go-template"),
+                               `api/blueprint.go`:                             
                                
util.ReadTemplate("generator/template/plugin/api/blueprint.go-template"),
                        }
                        util.GenerateAllFormatVar(values, `plugin_name`, 
pluginName)
-                       values[`Endpoint`] = endpoint
                } else if withApiClient == `No` {
                        // read template
                        templates = map[string]string{
diff --git 
a/generator/template/migrationscripts/archived/connection.go-template 
b/generator/template/migrationscripts/archived/connection.go-template
new file mode 100644
index 00000000..e69de29b
diff --git a/generator/template/plugin/api/blueprint.go-template 
b/generator/template/plugin/api/blueprint.go-template
new file mode 100644
index 00000000..cdf40067
--- /dev/null
+++ b/generator/template/plugin/api/blueprint.go-template
@@ -0,0 +1,69 @@
+/*
+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 api
+
+import (
+       "encoding/json"
+
+       "github.com/apache/incubator-devlake/plugins/core"
+       "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}/tasks"
+)
+
+func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, connectionId uint64, 
scope []*core.BlueprintScopeV100) (core.PipelinePlan, error) {
+       var err error
+       plan := make(core.PipelinePlan, len(scope))
+       for i, scopeElem := range scope {
+               taskOptions := make(map[string]interface{})
+               err = json.Unmarshal(scopeElem.Options, &taskOptions)
+               if err != nil {
+                       return nil, err
+               }
+               taskOptions["connectionId"] = connectionId
+
+               //TODO Add transformation rules to task options
+
+        /*
+        var transformationRules tasks.TransformationRules
+        if len(scopeElem.Transformation) > 0 {
+            err = json.Unmarshal(scopeElem.Transformation, 
&transformationRules)
+            if err != nil {
+                return nil, err
+            }
+        }
+        */
+               //taskOptions["transformationRules"] = transformationRules
+               _, err := tasks.DecodeAndValidateTaskOptions(taskOptions)
+               if err != nil {
+                       return nil, err
+               }
+               // subtasks
+               subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, 
scopeElem.Entities)
+               if err != nil {
+                       return nil, err
+               }
+               plan[i] = core.PipelineStage{
+                       {
+                               Plugin:   "{{ .plugin_name }}",
+                               Subtasks: subtasks,
+                               Options:  taskOptions,
+                       },
+               }
+       }
+       return plan, nil
+}
diff --git a/generator/template/plugin/api/connection.go-template 
b/generator/template/plugin/api/connection.go-template
new file mode 100644
index 00000000..0e781d27
--- /dev/null
+++ b/generator/template/plugin/api/connection.go-template
@@ -0,0 +1,155 @@
+/*
+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 api
+
+import (
+       "context"
+       "fmt"
+       "net/http"
+       "time"
+
+       "github.com/apache/incubator-devlake/plugins/core"
+       "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}/models"
+       "github.com/mitchellh/mapstructure"
+)
+
+//TODO Please modify the following code to fit your needs
+func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       // decode
+       var err error
+       var connection models.TestConnectionRequest
+       err = mapstructure.Decode(input.Body, &connection)
+       if err != nil {
+               return nil, err
+       }
+       // validate
+       err = vld.Struct(connection)
+       if err != nil {
+               return nil, err
+       }
+       // test connection
+       apiClient, err := helper.NewApiClient(
+               context.TODO(),
+               connection.Endpoint,
+               map[string]string{
+                       "Authorization": fmt.Sprintf("Bearer %v", 
connection.Token),
+               },
+               3*time.Second,
+               connection.Proxy,
+               basicRes,
+       )
+       if err != nil {
+               return nil, err
+       }
+
+       res, err := apiClient.Get("user", nil, nil)
+       if err != nil {
+               return nil, err
+       }
+       resBody := &models.ApiUserResponse{}
+       err = helper.UnmarshalResponse(res, resBody)
+       if err != nil {
+               return nil, err
+       }
+
+       if res.StatusCode != http.StatusOK {
+               return nil, fmt.Errorf("unexpected status code: %d", 
res.StatusCode)
+       }
+       return nil, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+POST /plugins/{{ .PluginName }}/connections
+{
+       "name": "{{ .PluginName }} data connection name",
+       "endpoint": "{{ .PluginName }} api endpoint, i.e. https://example.com";,
+       "username": "username, usually should be email address",
+       "password": "{{ .PluginName }} api access token"
+}
+*/
+func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       // update from request and save to database
+       connection := &models.{{ .PluginName }}Connection{}
+       err := connectionHelper.Create(connection, input)
+       if err != nil {
+               return nil, err
+       }
+       return &core.ApiResourceOutput{Body: connection, Status: 
http.StatusOK}, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+PATCH /plugins/{{ .PluginName }}/connections/:connectionId
+{
+       "name": "{{ .PluginName }} data connection name",
+       "endpoint": "{{ .PluginName }} api endpoint, i.e. https://example.com";,
+       "username": "username, usually should be email address",
+       "password": "{{ .PluginName }} api access token"
+}
+*/
+func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       connection := &models.{{ .PluginName }}Connection{}
+       err := connectionHelper.Patch(connection, input)
+       if err != nil {
+               return nil, err
+       }
+       return &core.ApiResourceOutput{Body: connection}, nil
+}
+
+/*
+DELETE /plugins/{{ .PluginName }}/connections/:connectionId
+*/
+func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       connection := &models.{{ .PluginName }}Connection{}
+       err := connectionHelper.First(connection, input.Params)
+       if err != nil {
+               return nil, err
+       }
+       err = connectionHelper.Delete(connection)
+       return &core.ApiResourceOutput{Body: connection}, err
+}
+
+/*
+GET /plugins/{{ .PluginName }}/connections
+*/
+func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       var connections []models.{{ .PluginName }}Connection
+       err := connectionHelper.List(&connections)
+       if err != nil {
+               return nil, err
+       }
+       return &core.ApiResourceOutput{Body: connections, Status: 
http.StatusOK}, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+GET /plugins/{{ .PluginName }}/connections/:connectionId
+{
+       "name": "{{ .PluginName }} data connection name",
+       "endpoint": "{{ .PluginName }} api endpoint, i.e. 
https://merico.atlassian.net/rest";,
+       "username": "username, usually should be email address",
+       "password": "{{ .PluginName }} api access token"
+}
+*/
+func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, 
error) {
+       connection := &models.{{ .PluginName }}Connection{}
+       err := connectionHelper.First(connection, input.Params)
+       return &core.ApiResourceOutput{Body: connection}, err
+}
\ No newline at end of file
diff --git 
a/generator/template/plugin/tasks/task_data_with_api_client.go-template 
b/generator/template/plugin/api/init.go-template
similarity index 63%
copy from generator/template/plugin/tasks/task_data_with_api_client.go-template
copy to generator/template/plugin/api/init.go-template
index 5dec7665..6774e148 100644
--- a/generator/template/plugin/tasks/task_data_with_api_client.go-template
+++ b/generator/template/plugin/api/init.go-template
@@ -15,23 +15,25 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package tasks
+package api
 
 import (
+       "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/go-playground/validator/v10"
+       "github.com/spf13/viper"
+       "gorm.io/gorm"
 )
 
-type {{ .PluginName }}ApiParams struct {
-}
-
-type {{ .PluginName }}Options struct {
-       // TODO add some custom options here if necessary
-       // options means some custom params required by plugin running.
-       // Such As How many rows do your want
-       // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
-}
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+var basicRes core.BasicRes
 
-type {{ .PluginName }}TaskData struct {
-       Options   *{{ .PluginName }}Options
-       ApiClient *helper.ApiAsyncClient
+func Init(config *viper.Viper, logger core.Logger, database *gorm.DB) {
+       basicRes = helper.NewDefaultBasicRes(config, logger, database)
+       vld = validator.New()
+       connectionHelper = helper.NewConnectionHelper(
+               basicRes,
+               vld,
+       )
 }
diff --git a/generator/template/plugin/impl/impl_complete_plugin.go-template 
b/generator/template/plugin/impl/impl_complete_plugin.go-template
new file mode 100644
index 00000000..8258b138
--- /dev/null
+++ b/generator/template/plugin/impl/impl_complete_plugin.go-template
@@ -0,0 +1,123 @@
+/*
+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 impl
+
+import (
+       "fmt"
+       "github.com/apache/incubator-devlake/migration"
+       "github.com/apache/incubator-devlake/plugins/core"
+    "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}/api"
+    "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}/models"
+    "github.com/apache/incubator-devlake/plugins/{{ .plugin_name 
}}/models/migrationscripts"
+       "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}/tasks"
+       "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/spf13/viper"
+       "gorm.io/gorm"
+)
+
+// make sure interface is implemented
+var _ core.PluginMeta = (*{{ .PluginName }})(nil)
+var _ core.PluginInit = (*{{ .PluginName }})(nil)
+var _ core.PluginTask = (*{{ .PluginName }})(nil)
+var _ core.PluginApi = (*{{ .PluginName }})(nil)
+var _ core.PluginBlueprintV100 = (*{{ .PluginName }})(nil)
+var _ core.CloseablePluginTask = (*{{ .PluginName }})(nil)
+
+
+
+type {{ .PluginName }} struct{}
+
+func (plugin {{ .PluginName }}) Description() string {
+       return "collect some {{ .PluginName }} data"
+}
+
+func (plugin {{ .PluginName }}) Init(config *viper.Viper, logger core.Logger, 
db *gorm.DB) error {
+       api.Init(config, logger, db)
+       return nil
+}
+
+func (plugin {{ .PluginName }}) SubTaskMetas() []core.SubTaskMeta {
+       // TODO add your sub task here
+       return []core.SubTaskMeta{
+       }
+}
+
+func (plugin {{ .PluginName }}) PrepareTaskData(taskCtx core.TaskContext, 
options map[string]interface{}) (interface{}, error) {
+       op, err := tasks.DecodeAndValidateTaskOptions(options)
+    if err != nil {
+        return nil, err
+    }
+    connectionHelper := helper.NewConnectionHelper(
+        taskCtx,
+        nil,
+    )
+    connection := &models.{{ .PluginName }}Connection{}
+    err = connectionHelper.FirstById(connection, op.ConnectionId)
+    if err != nil {
+        return nil, fmt.Errorf("unable to get {{ .PluginName }} connection by 
the given connection ID: %v", err)
+    }
+
+    apiClient, err := tasks.New{{ .PluginName }}ApiClient(taskCtx, connection)
+    if err != nil {
+        return nil, fmt.Errorf("unable to get {{ .PluginName }} API client 
instance: %v", err)
+    }
+
+    return &tasks.{{ .PluginName }}TaskData{
+        Options:   op,
+        ApiClient: apiClient,
+    }, nil
+}
+
+// PkgPath information lost when compiled as plugin(.so)
+func (plugin {{ .PluginName }}) RootPkgPath() string {
+       return "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}"
+}
+
+func (plugin {{ .PluginName }}) MigrationScripts() []migration.Script {
+       return migrationscripts.All()
+}
+
+func (plugin {{ .PluginName }}) ApiResources() 
map[string]map[string]core.ApiResourceHandler {
+    return map[string]map[string]core.ApiResourceHandler{
+        "test": {
+            "POST": api.TestConnection,
+        },
+        "connections": {
+            "POST": api.PostConnections,
+            "GET":  api.ListConnections,
+        },
+        "connections/:connectionId": {
+            "GET":    api.GetConnection,
+            "PATCH":  api.PatchConnection,
+            "DELETE": api.DeleteConnection,
+        },
+    }
+}
+
+func (plugin {{ .PluginName }}) MakePipelinePlan(connectionId uint64, scope 
[]*core.BlueprintScopeV100) (core.PipelinePlan, error) {
+       return api.MakePipelinePlan(plugin.SubTaskMetas(), connectionId, scope)
+}
+
+func (plugin {{ .PluginName }}) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.{{ .PluginName }}TaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/generator/template/plugin/models/connection.go-template 
b/generator/template/plugin/models/connection.go-template
new file mode 100644
index 00000000..72133f10
--- /dev/null
+++ b/generator/template/plugin/models/connection.go-template
@@ -0,0 +1,51 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import "github.com/apache/incubator-devlake/plugins/helper"
+
+//TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type {{ .PluginName }}Connection struct {
+       helper.RestConnection `mapstructure:",squash"`
+    //TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+       helper.AccessToken    `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+       Endpoint           string `json:"endpoint"`
+       Proxy              string `json:"proxy"`
+       helper.AccessToken `mapstructure:",squash"`
+}
+
+// This object conforms to what the frontend currently expects.
+type {{ .PluginName }}Response struct {
+       Name string `json:"name"`
+       ID   int    `json:"id"`
+       {{ .PluginName }}Connection
+}
+
+// Using User because it requires authentication.
+type ApiUserResponse struct {
+       Id   int
+       Name string `json:"name"`
+}
+
+func ({{ .PluginName }}Connection) TableName() string {
+       return "_tool_{{ .plugin_name }}_connections"
+}
diff --git 
a/generator/template/plugin/tasks/task_data_with_api_client.go-template 
b/generator/template/plugin/plugin_main_complete_plugin.go-template
similarity index 51%
copy from generator/template/plugin/tasks/task_data_with_api_client.go-template
copy to generator/template/plugin/plugin_main_complete_plugin.go-template
index 5dec7665..ee2d4a56 100644
--- a/generator/template/plugin/tasks/task_data_with_api_client.go-template
+++ b/generator/template/plugin/plugin_main_complete_plugin.go-template
@@ -15,23 +15,29 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package tasks
+package main
 
 import (
-       "github.com/apache/incubator-devlake/plugins/helper"
+       "github.com/apache/incubator-devlake/plugins/{{ .PluginName }}/impl"
+       "github.com/apache/incubator-devlake/runner"
+       "github.com/spf13/cobra"
 )
 
-type {{ .PluginName }}ApiParams struct {
-}
+// Export a variable named PluginEntry for Framework to search and load
+var PluginEntry impl.{{ .PluginName }} //nolint
 
-type {{ .PluginName }}Options struct {
-       // TODO add some custom options here if necessary
-       // options means some custom params required by plugin running.
-       // Such As How many rows do your want
-       // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
-}
+// standalone mode for debugging
+func main() {
+       cmd := &cobra.Command{Use: "{{ .pluginName }}"}
+
+       // TODO add your cmd flag if necessary
+       // yourFlag := cmd.Flags().IntP("yourFlag", "y", 8, "TODO add 
description here")
+       // _ = cmd.MarkFlagRequired("yourFlag")
 
-type {{ .PluginName }}TaskData struct {
-       Options   *{{ .PluginName }}Options
-       ApiClient *helper.ApiAsyncClient
+       cmd.Run = func(cmd *cobra.Command, args []string) {
+               runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
+                       // TODO add more custom params here
+               })
+       }
+       runner.RunCmd(cmd)
 }
diff --git a/generator/template/plugin/plugin_main_with_api_client.go-template 
b/generator/template/plugin/plugin_main_with_api_client.go-template
deleted file mode 100644
index 1ab8333d..00000000
--- a/generator/template/plugin/plugin_main_with_api_client.go-template
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
-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 main
-
-import (
-       "github.com/apache/incubator-devlake/plugins/core"
-       "github.com/apache/incubator-devlake/plugins/{{ .pluginName }}/tasks"
-       "github.com/apache/incubator-devlake/runner"
-       "github.com/mitchellh/mapstructure"
-       "github.com/spf13/cobra"
-       "github.com/spf13/viper"
-       "gorm.io/gorm"
-)
-
-// make sure interface is implemented
-var _ core.PluginMeta = (*{{ .PluginName }})(nil)
-var _ core.PluginInit = (*{{ .PluginName }})(nil)
-var _ core.PluginTask = (*{{ .PluginName }})(nil)
-var _ core.PluginApi = (*{{ .PluginName }})(nil)
-
-// Export a variable named PluginEntry for Framework to search and load
-var PluginEntry {{ .PluginName }} //nolint
-
-type {{ .PluginName }} struct{}
-
-func (plugin {{ .PluginName }}) Description() string {
-       return "collect some {{ .PluginName }} data"
-}
-
-func (plugin {{ .PluginName }}) Init(config *viper.Viper, logger core.Logger, 
db *gorm.DB) error {
-       // AutoSchemas is a **develop** script to auto migrate models easily.
-       // FIXME Don't submit it as a open source plugin
-       return db.Migrator().AutoMigrate(
-               // TODO add your models in here
-       )
-}
-
-func (plugin {{ .PluginName }}) SubTaskMetas() []core.SubTaskMeta {
-       // TODO add your sub task here
-       return []core.SubTaskMeta{
-       }
-}
-
-func (plugin {{ .PluginName }}) PrepareTaskData(taskCtx core.TaskContext, 
options map[string]interface{}) (interface{}, error) {
-       var op tasks.{{ .PluginName }}Options
-       err := mapstructure.Decode(options, &op)
-       if err != nil {
-               return nil, err
-       }
-
-       apiClient, err := tasks.New{{ .PluginName }}ApiClient(taskCtx)
-       if err != nil {
-               return nil, err
-       }
-
-       return &tasks.{{ .PluginName }}TaskData{
-               Options: &op,
-        ApiClient: apiClient,
-       }, nil
-}
-
-// PkgPath information lost when compiled as plugin(.so)
-func (plugin {{ .PluginName }}) RootPkgPath() string {
-       return "github.com/apache/incubator-devlake/plugins/{{ .pluginName }}"
-}
-
-func (plugin {{ .PluginName }}) ApiResources() 
map[string]map[string]core.ApiResourceHandler {
-       return nil
-}
-
-// standalone mode for debugging
-func main() {
-       cmd := &cobra.Command{Use: "{{ .pluginName }}"}
-
-       // TODO add your cmd flag if necessary
-       // yourFlag := cmd.Flags().IntP("yourFlag", "y", 8, "TODO add 
description here")
-       // _ = cmd.MarkFlagRequired("yourFlag")
-
-       cmd.Run = func(cmd *cobra.Command, args []string) {
-               runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
-                       // TODO add more custom params here
-               })
-       }
-       runner.RunCmd(cmd)
-}
diff --git a/generator/template/plugin/tasks/api_client.go-template 
b/generator/template/plugin/tasks/api_client.go-template
index a79e1312..296131fb 100644
--- a/generator/template/plugin/tasks/api_client.go-template
+++ b/generator/template/plugin/tasks/api_client.go-template
@@ -19,54 +19,55 @@ package tasks
 
 import (
        "fmt"
+       "net/http"
+       "strconv"
+       "time"
 
+       "github.com/apache/incubator-devlake/plugins/{{ .plugin_name }}/models"
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
-       "github.com/apache/incubator-devlake/utils"
 )
 
-// TODO add what host would want to requist
-const ENDPOINT = "{{ .Endpoint }}"
-
-func New{{ .PluginName }}ApiClient(taskCtx core.TaskContext) 
(*helper.ApiAsyncClient, error) {
-       // load and process configuration
-       token := taskCtx.GetConfig("{{ .PLUGIN_NAME }}_TOKEN")
-       if token == "" {
-               println("invalid {{ .PLUGIN_NAME }}_TOKEN, but ignore this 
error now")
+func New{{ .PluginName }}ApiClient(taskCtx core.TaskContext, connection 
*models.{{ .PluginName }}Connection) (*helper.ApiAsyncClient, error) {
+       // create synchronize api client so we can calculate api rate limit 
dynamically
+       headers := map[string]string{
+               "Authorization": fmt.Sprintf("Bearer %v", connection.Token),
        }
-       userRateLimit, err := utils.StrToIntOr(taskCtx.GetConfig("{{ 
.PLUGIN_NAME }}_API_REQUESTS_PER_HOUR"), 18000)
+       apiClient, err := helper.NewApiClient(taskCtx.GetContext(), 
connection.Endpoint, headers, 0, connection.Proxy, taskCtx)
        if err != nil {
                return nil, err
        }
-       proxy := taskCtx.GetConfig("{{ .PLUGIN_NAME }}_PROXY")
+       apiClient.SetAfterFunction(func(res *http.Response) error {
+               if res.StatusCode == http.StatusUnauthorized {
+                       return fmt.Errorf("authentication failed, please check 
your AccessToken")
+               }
+               return nil
+       })
 
-       // real request apiClient
-       apiClient, err := helper.NewApiClient(ENDPOINT, nil, 0, proxy, 
taskCtx.GetContext())
-       if err != nil {
-               return nil, err
-       }
-       // set token
-       if token != "" {
-               apiClient.SetHeaders(map[string]string{
-                       "Authorization": fmt.Sprintf("Bearer %v", token),
-               })
+       // create rate limit calculator
+       rateLimiter := &helper.ApiRateLimitCalculator{
+               UserRateLimitPerHour: connection.RateLimitPerHour,
+               DynamicRateLimit: func(res *http.Response) (int, time.Duration, 
error) {
+                       rateLimitHeader := res.Header.Get("RateLimit-Limit")
+                       if rateLimitHeader == "" {
+                               // use default
+                               return 0, 0, nil
+                       }
+                       rateLimit, err := strconv.Atoi(rateLimitHeader)
+                       if err != nil {
+                               return 0, 0, fmt.Errorf("failed to parse 
RateLimit-Limit header: %w", err)
+                       }
+                       // seems like {{ .plugin-ame }} rate limit is on minute 
basis
+                       return rateLimit, 1 * time.Minute, nil
+               },
        }
-
-       // TODO add some check after request if necessary
-       // apiClient.SetAfterFunction(func(res *http.Response) error {
-       //    if res.StatusCode == http.StatusUnauthorized {
-       //        return fmt.Errorf("authentication failed, please check your 
Bearer Auth Token")
-       //    }
-       //    return nil
-       // })
-
-       // create async api client
-    asyncApiClient, err := helper.CreateAsyncApiClient(taskCtx, apiClient, 
&helper.ApiRateLimitCalculator{
-               UserRateLimitPerHour: userRateLimit,
-       })
+       asyncApiClient, err := helper.CreateAsyncApiClient(
+               taskCtx,
+               apiClient,
+               rateLimiter,
+       )
        if err != nil {
                return nil, err
        }
-
        return asyncApiClient, nil
 }
diff --git 
a/generator/template/plugin/tasks/task_data_with_api_client.go-template 
b/generator/template/plugin/tasks/task_data_complete_plugin.go-template
similarity index 70%
rename from 
generator/template/plugin/tasks/task_data_with_api_client.go-template
rename to generator/template/plugin/tasks/task_data_complete_plugin.go-template
index 5dec7665..3284d249 100644
--- a/generator/template/plugin/tasks/task_data_with_api_client.go-template
+++ b/generator/template/plugin/tasks/task_data_complete_plugin.go-template
@@ -18,6 +18,8 @@ limitations under the License.
 package tasks
 
 import (
+       "fmt"
+    "github.com/mitchellh/mapstructure"
        "github.com/apache/incubator-devlake/plugins/helper"
 )
 
@@ -29,9 +31,25 @@ type {{ .PluginName }}Options struct {
        // options means some custom params required by plugin running.
        // Such As How many rows do your want
        // You can use it in sub tasks and you need pass it in main.go and 
pipelines.
+    ConnectionId               uint64   `json:"connectionId"`
+    Tasks                      []string `json:"tasks,omitempty"`
+    Since                      string
 }
 
 type {{ .PluginName }}TaskData struct {
        Options   *{{ .PluginName }}Options
        ApiClient *helper.ApiAsyncClient
 }
+
+func DecodeAndValidateTaskOptions(options map[string]interface{}) (*{{ 
.PluginName }}Options, error) {
+       var op {{ .PluginName }}Options
+       err := mapstructure.Decode(options, &op)
+       if err != nil {
+               return nil, err
+       }
+
+       if op.ConnectionId == 0 {
+               return nil, fmt.Errorf("connectionId is invalid")
+       }
+       return &op, nil
+}
\ No newline at end of file

Reply via email to