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 641607837 feat: bamboo connection (#4435)
641607837 is described below

commit 64160783720e08d2c5f2bea4fa795f94832775cf
Author: mappjzc <[email protected]>
AuthorDate: Fri Feb 17 13:16:03 2023 +0800

    feat: bamboo connection (#4435)
    
    * feat: bamboo connection
    
    Add connection for bamboo
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for review
    
    fix for review
    
    Nddtfjiang <[email protected]>
---
 backend/plugins/bamboo/api/connection.go           | 149 ++++++++++++++++++++
 backend/plugins/bamboo/api/init.go                 |  37 +++++
 backend/plugins/bamboo/bamboo.go                   |  42 ++++++
 backend/plugins/bamboo/impl/impl.go                | 150 +++++++++++++++++++++
 backend/plugins/bamboo/models/connection.go        | 128 ++++++++++++++++++
 .../migrationscripts/20230216_add_init_tables.go   |  42 ++++++
 .../models/migrationscripts/archived/connection.go |  77 +++++++++++
 .../bamboo/models/migrationscripts/register.go     |  29 ++++
 backend/plugins/bamboo/tasks/api_client.go         |  72 ++++++++++
 backend/plugins/bamboo/tasks/task_data.go          |  52 +++++++
 10 files changed, 778 insertions(+)

diff --git a/backend/plugins/bamboo/api/connection.go 
b/backend/plugins/bamboo/api/connection.go
new file mode 100644
index 000000000..c516c3206
--- /dev/null
+++ b/backend/plugins/bamboo/api/connection.go
@@ -0,0 +1,149 @@
+/*
+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"
+       "net/http"
+
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/bamboo/models"
+       "github.com/apache/incubator-devlake/server/api/shared"
+)
+
+type BambooTestConnResponse struct {
+       shared.ApiBody
+       Connection *models.BambooConn
+}
+
+// @Summary test bamboo connection
+// @Description Test bamboo Connection
+// @Tags plugins/bamboo
+// @Param body body models.BambooConn true "json body"
+// @Success 200  {object} BambooTestConnResponse "Success"
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internal Error"
+// @Router /plugins/Bamboo/test [POST]
+func TestConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       // decode
+       var err errors.Error
+       var connection models.BambooConn
+       if err = api.Decode(input.Body, &connection, vld); err != nil {
+               return nil, err
+       }
+
+       // test connection
+       _, err = api.NewApiClientFromConnection(
+               context.TODO(),
+               basicRes,
+               &connection,
+       )
+       if err != nil {
+               return nil, err
+       }
+
+       body := BambooTestConnResponse{}
+       body.Success = true
+       body.Message = "success"
+       body.Connection = &connection
+
+       return &plugin.ApiResourceOutput{Body: body, Status: http.StatusOK}, nil
+}
+
+// @Summary create bamboo connection
+// @Description Create bamboo connection
+// @Tags plugins/bamboo
+// @Param body body models.BambooConnection true "json body"
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections [POST]
+func PostConnections(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       // update from request and save to database
+       connection := &models.BambooConnection{}
+       err := connectionHelper.Create(connection, input)
+       if err != nil {
+               return nil, err
+       }
+       return &plugin.ApiResourceOutput{Body: connection, Status: 
http.StatusOK}, nil
+}
+
+// @Summary patch bamboo connection
+// @Description Patch bamboo connection
+// @Tags plugins/bamboo
+// @Param body body models.BambooConnection true "json body"
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections/{connectionId} [PATCH]
+func PatchConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       connection := &models.BambooConnection{}
+       err := connectionHelper.Patch(connection, input)
+       if err != nil {
+               return nil, err
+       }
+       return &plugin.ApiResourceOutput{Body: connection}, nil
+}
+
+// @Summary delete a bamboo connection
+// @Description Delete a bamboo connection
+// @Tags plugins/bamboo
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections/{connectionId} [DELETE]
+func DeleteConnection(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       connection := &models.BambooConnection{}
+       err := connectionHelper.First(connection, input.Params)
+       if err != nil {
+               return nil, err
+       }
+       err = connectionHelper.Delete(connection)
+       return &plugin.ApiResourceOutput{Body: connection}, err
+}
+
+// @Summary get all bamboo connections
+// @Description Get all bamboo connections
+// @Tags plugins/bamboo
+// @Success 200  {object} []models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections [GET]
+func ListConnections(input *plugin.ApiResourceInput) 
(*plugin.ApiResourceOutput, errors.Error) {
+       var connections []models.BambooConnection
+       err := connectionHelper.List(&connections)
+       if err != nil {
+               return nil, err
+       }
+       return &plugin.ApiResourceOutput{Body: connections, Status: 
http.StatusOK}, nil
+}
+
+// @Summary get bamboo connection detail
+// @Description Get bamboo connection detail
+// @Tags plugins/bamboo
+// @Success 200  {object} models.BambooConnection
+// @Failure 400  {string} errcode.Error "Bad Request"
+// @Failure 500  {string} errcode.Error "Internel Error"
+// @Router /plugins/bamboo/connections/{connectionId} [GET]
+func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, 
errors.Error) {
+       connection := &models.BambooConnection{}
+       err := connectionHelper.First(connection, input.Params)
+       return &plugin.ApiResourceOutput{Body: connection}, err
+}
diff --git a/backend/plugins/bamboo/api/init.go 
b/backend/plugins/bamboo/api/init.go
new file mode 100644
index 000000000..cd019a36f
--- /dev/null
+++ b/backend/plugins/bamboo/api/init.go
@@ -0,0 +1,37 @@
+/*
+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 (
+       "github.com/apache/incubator-devlake/core/context"
+       helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/go-playground/validator/v10"
+)
+
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+var basicRes context.BasicRes
+
+func Init(br context.BasicRes) {
+       basicRes = br
+       vld = validator.New()
+       connectionHelper = helper.NewConnectionHelper(
+               basicRes,
+               vld,
+       )
+}
diff --git a/backend/plugins/bamboo/bamboo.go b/backend/plugins/bamboo/bamboo.go
new file mode 100644
index 000000000..076323e69
--- /dev/null
+++ b/backend/plugins/bamboo/bamboo.go
@@ -0,0 +1,42 @@
+/*
+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/core/runner"
+       "github.com/apache/incubator-devlake/plugins/bamboo/impl"
+       "github.com/spf13/cobra"
+)
+
+// PluginEntry exports for Framework to search and load
+var PluginEntry impl.Bamboo //nolint
+
+// standalone mode for debugging
+func main() {
+       bambooCmd := &cobra.Command{Use: "bamboo"}
+       connectionId := bambooCmd.Flags().Uint64P("Connection-id", "c", 0, 
"bamboo connection id")
+       projectId := bambooCmd.Flags().IntP("project-id", "p", 0, "bamboo 
project id")
+       _ = bambooCmd.MarkFlagRequired("project-id")
+       bambooCmd.Run = func(cmd *cobra.Command, args []string) {
+               runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
+                       "connectionId": *connectionId,
+                       "projectId":    *projectId,
+               })
+       }
+       runner.RunCmd(bambooCmd)
+}
diff --git a/backend/plugins/bamboo/impl/impl.go 
b/backend/plugins/bamboo/impl/impl.go
new file mode 100644
index 000000000..a7c922d88
--- /dev/null
+++ b/backend/plugins/bamboo/impl/impl.go
@@ -0,0 +1,150 @@
+/*
+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/core/context"
+       "github.com/apache/incubator-devlake/core/dal"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+       helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/bamboo/api"
+       "github.com/apache/incubator-devlake/plugins/bamboo/models"
+       
"github.com/apache/incubator-devlake/plugins/bamboo/models/migrationscripts"
+       "github.com/apache/incubator-devlake/plugins/bamboo/tasks"
+)
+
+// make sure interface is implemented
+var _ interface {
+       plugin.PluginMeta
+       plugin.PluginInit
+       plugin.PluginTask
+       plugin.PluginModel
+       plugin.PluginMigration
+       plugin.PluginBlueprintV100
+       plugin.DataSourcePluginBlueprintV200
+       plugin.CloseablePluginTask
+       plugin.PluginSource
+} = (*Bamboo)(nil)
+
+type Bamboo struct{}
+
+func (p Bamboo) Init(br context.BasicRes) errors.Error {
+       api.Init(br)
+       return nil
+}
+
+func (p Bamboo) Connection() interface{} {
+       return &models.BambooConnection{}
+}
+
+func (p Bamboo) Scope() interface{} {
+       return nil
+}
+
+func (p Bamboo) TransformationRule() interface{} {
+       return nil
+}
+
+func (p Bamboo) MakeDataSourcePipelinePlanV200(connectionId uint64, scopes 
[]*plugin.BlueprintScopeV200, syncPolicy plugin.BlueprintSyncPolicy) 
(plugin.PipelinePlan, []plugin.Scope, errors.Error) {
+       //return api.MakePipelinePlanV200(p.SubTaskMetas(), connectionId, 
scopes, &syncPolicy)
+       return nil, nil, nil
+}
+
+func (p Bamboo) GetTablesInfo() []dal.Tabler {
+       return []dal.Tabler{
+               &models.BambooConnection{},
+       }
+}
+
+func (p Bamboo) Description() string {
+       return "collect some Bamboo data"
+}
+
+func (p Bamboo) SubTaskMetas() []plugin.SubTaskMeta {
+       // TODO add your sub task here
+       return []plugin.SubTaskMeta{}
+}
+
+func (p Bamboo) PrepareTaskData(taskCtx plugin.TaskContext, options 
map[string]interface{}) (interface{}, errors.Error) {
+       op, err := tasks.DecodeAndValidateTaskOptions(options)
+       if err != nil {
+               return nil, err
+       }
+       connectionHelper := helper.NewConnectionHelper(
+               taskCtx,
+               nil,
+       )
+       connection := &models.BambooConnection{}
+       err = connectionHelper.FirstById(connection, op.ConnectionId)
+       if err != nil {
+               return nil, errors.Default.Wrap(err, "unable to get Bamboo 
connection by the given connection ID")
+       }
+
+       apiClient, err := tasks.NewBambooApiClient(taskCtx, connection)
+       if err != nil {
+               return nil, errors.Default.Wrap(err, "unable to get Bamboo API 
client instance")
+       }
+
+       return &tasks.BambooTaskData{
+               Options:   op,
+               ApiClient: apiClient,
+       }, nil
+}
+
+// PkgPath information lost when compiled as plugin(.so)
+func (p Bamboo) RootPkgPath() string {
+       return "github.com/apache/incubator-devlake/plugins/bamboo"
+}
+
+func (p Bamboo) MigrationScripts() []plugin.MigrationScript {
+       return migrationscripts.All()
+}
+
+func (p Bamboo) ApiResources() map[string]map[string]plugin.ApiResourceHandler 
{
+       return map[string]map[string]plugin.ApiResourceHandler{
+               "test": {
+                       "POST": api.TestConnection,
+               },
+               "connections": {
+                       "POST": api.PostConnections,
+                       "GET":  api.ListConnections,
+               },
+               "connections/:connectionId": {
+                       "GET":    api.GetConnection,
+                       "PATCH":  api.PatchConnection,
+                       "DELETE": api.DeleteConnection,
+               },
+       }
+}
+
+func (p Bamboo) MakePipelinePlan(connectionId uint64, scope 
[]*plugin.BlueprintScopeV100) (plugin.PipelinePlan, errors.Error) {
+       //return api.MakePipelinePlan(p.SubTaskMetas(), connectionId, scope)
+       return nil, nil
+}
+
+func (p Bamboo) Close(taskCtx plugin.TaskContext) errors.Error {
+       data, ok := taskCtx.GetData().(*tasks.BambooTaskData)
+       if !ok {
+               return errors.Default.New(fmt.Sprintf("GetData failed when try 
to close %+v", taskCtx))
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/backend/plugins/bamboo/models/connection.go 
b/backend/plugins/bamboo/models/connection.go
new file mode 100644
index 000000000..88520d463
--- /dev/null
+++ b/backend/plugins/bamboo/models/connection.go
@@ -0,0 +1,128 @@
+/*
+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 (
+       "encoding/xml"
+       "fmt"
+       "net/http"
+
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       
"github.com/apache/incubator-devlake/helpers/pluginhelper/api/apihelperabstract"
+)
+
+type BambooConnection struct {
+       api.BaseConnection `mapstructure:",squash"`
+       BambooConn         `mapstructure:",squash"`
+}
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type BambooConn struct {
+       api.RestConnection `mapstructure:",squash"`
+       //TODO you may need to use helper.BasicAuth instead of 
helper.AccessToken
+       api.AccessToken `mapstructure:",squash"`
+}
+
+// PrepareApiClient test api and set the IsPrivateToken,version,UserId and so 
on.
+func (conn *BambooConn) PrepareApiClient(apiClient 
apihelperabstract.ApiClientAbstract) errors.Error {
+       header := http.Header{}
+       header.Set("Authorization", fmt.Sprintf("Bearer %v", conn.Token))
+
+       res, err := apiClient.Get("", nil, header)
+       if err != nil {
+               return errors.HttpStatus(400).New(fmt.Sprintf("Get failed %s", 
err.Error()))
+       }
+       resources := &ApiXMLResourcesResponse{}
+
+       if res.StatusCode != http.StatusOK {
+               return 
errors.HttpStatus(res.StatusCode).New(fmt.Sprintf("unexpected status code: %d", 
res.StatusCode))
+       }
+
+       err = api.UnmarshalResponseXML(res, resources)
+
+       if err != nil {
+               return 
errors.HttpStatus(500).New(fmt.Sprintf("UnmarshalResponse failed %s", 
err.Error()))
+       }
+
+       if resources.Link.Href != conn.Endpoint {
+               return errors.HttpStatus(400).New(fmt.Sprintf("Response Data 
error for connection endpoint: %s, it should like: 
http://{domain}/rest/api/latest/";, conn.Endpoint))
+       }
+
+       res, err = apiClient.Get("repository", nil, header)
+       if err != nil {
+               return errors.HttpStatus(400).New(fmt.Sprintf("Get failed %s", 
err.Error()))
+       }
+       repo := &ApiRepository{}
+
+       if res.StatusCode != http.StatusOK {
+               return 
errors.HttpStatus(res.StatusCode).New(fmt.Sprintf("unexpected status code: %d", 
res.StatusCode))
+       }
+
+       err = api.UnmarshalResponse(res, repo)
+
+       if err != nil {
+               return 
errors.HttpStatus(400).New(fmt.Sprintf("UnmarshalResponse repository failed 
%s", err.Error()))
+       }
+
+       return nil
+}
+
+// This object conforms to what the frontend currently expects.
+type BambooResponse struct {
+       Name string `json:"name"`
+       ID   int    `json:"id"`
+       BambooConnection
+}
+
+type ApiRepository struct {
+       Size          int         `json:"size"`
+       SearchResults interface{} `json:"searchResults"`
+       StartIndex    int         `json:"start-index"`
+       MaxResult     int         `json:"max-result"`
+}
+
+type ApiXMLLink struct {
+       XMLName xml.Name `json:"xml_name" xml:"link"`
+       Href    string   `json:"href" xml:"href,attr"`
+}
+
+type ApiXMLResource struct {
+       XMLName xml.Name `json:"xml_name" xml:"resource"`
+       Name    string   `json:"name" xml:"name,attr"`
+}
+
+type ApiXMLResources struct {
+       XMLName   xml.Name         `json:"xml_name" xml:"resources"`
+       Size      string           `json:"size" xml:"size,attr"`
+       Resources []ApiXMLResource `json:"resource" xml:"resource"`
+}
+
+// Using User because it requires authentication.
+type ApiXMLResourcesResponse struct {
+       XMLName xml.Name `json:"xml_name" xml:"resources"`
+       Expand  string   `json:"expand" xml:"expand,attr"`
+
+       Link      ApiXMLLink      `json:"link" xml:"link"`
+       Resources ApiXMLResources `json:"resources" xml:"resources"`
+}
+
+func (BambooConnection) TableName() string {
+       return "_tool_bamboo_connections"
+}
diff --git 
a/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go 
b/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
new file mode 100644
index 000000000..d43bd53b3
--- /dev/null
+++ b/backend/plugins/bamboo/models/migrationscripts/20230216_add_init_tables.go
@@ -0,0 +1,42 @@
+/*
+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 migrationscripts
+
+import (
+       "github.com/apache/incubator-devlake/core/context"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/helpers/migrationhelper"
+       
"github.com/apache/incubator-devlake/plugins/bamboo/models/migrationscripts/archived"
+)
+
+type addInitTables struct{}
+
+func (u *addInitTables) Up(baseRes context.BasicRes) errors.Error {
+       return migrationhelper.AutoMigrateTables(
+               baseRes,
+               archived.BambooConnection{},
+       )
+}
+
+func (*addInitTables) Version() uint64 {
+       return 20230216205024
+}
+
+func (*addInitTables) Name() string {
+       return "bamboo init schemas"
+}
diff --git 
a/backend/plugins/bamboo/models/migrationscripts/archived/connection.go 
b/backend/plugins/bamboo/models/migrationscripts/archived/connection.go
new file mode 100644
index 000000000..e99545ebe
--- /dev/null
+++ b/backend/plugins/bamboo/models/migrationscripts/archived/connection.go
@@ -0,0 +1,77 @@
+/*
+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 archived
+
+import "time"
+
+type Model struct {
+       ID        uint64    `gorm:"primaryKey" json:"id"`
+       CreatedAt time.Time `json:"createdAt"`
+       UpdatedAt time.Time `json:"updatedAt"`
+}
+type BaseConnection struct {
+       Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" 
validate:"required"`
+       Model
+}
+
+// RestConnection implements the ApiConnection interface
+type RestConnection struct {
+       Endpoint         string `mapstructure:"endpoint" validate:"required" 
json:"endpoint"`
+       Proxy            string `mapstructure:"proxy" json:"proxy"`
+       RateLimitPerHour int    `comment:"api request rate limit per hour" 
json:"rateLimitPerHour"`
+}
+
+// AccessToken FIXME ...
+type AccessToken struct {
+       Token string `mapstructure:"token" validate:"required" json:"token" 
encrypt:"yes"`
+}
+
+type BambooConn struct {
+       RestConnection `mapstructure:",squash"`
+       //TODO you may need to use helper.BasicAuth instead of 
helper.AccessToken
+       AccessToken `mapstructure:",squash"`
+}
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type BambooConnection struct {
+       BaseConnection `mapstructure:",squash"`
+       BambooConn     `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+       Endpoint    string `json:"endpoint"`
+       Proxy       string `json:"proxy"`
+       AccessToken `mapstructure:",squash"`
+}
+
+// This object conforms to what the frontend currently expects.
+type BambooResponse struct {
+       Name string `json:"name"`
+       ID   int    `json:"id"`
+       BambooConnection
+}
+
+// Using User because it requires authentication.
+type ApiResourcesResponse struct {
+       Resources string `json:"resources" xml:"resources"`
+}
+
+func (BambooConnection) TableName() string {
+       return "_tool_bamboo_connections"
+}
diff --git a/backend/plugins/bamboo/models/migrationscripts/register.go 
b/backend/plugins/bamboo/models/migrationscripts/register.go
new file mode 100644
index 000000000..ec054748c
--- /dev/null
+++ b/backend/plugins/bamboo/models/migrationscripts/register.go
@@ -0,0 +1,29 @@
+/*
+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 migrationscripts
+
+import (
+       "github.com/apache/incubator-devlake/core/plugin"
+)
+
+// All return all the migration scripts
+func All() []plugin.MigrationScript {
+       return []plugin.MigrationScript{
+               new(addInitTables),
+       }
+}
diff --git a/backend/plugins/bamboo/tasks/api_client.go 
b/backend/plugins/bamboo/tasks/api_client.go
new file mode 100644
index 000000000..6efbb21a5
--- /dev/null
+++ b/backend/plugins/bamboo/tasks/api_client.go
@@ -0,0 +1,72 @@
+/*
+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 tasks
+
+import (
+       "net/http"
+       "strconv"
+       "time"
+
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/plugins/bamboo/models"
+)
+
+func CreateBambooAsyncApiClient(
+       taskCtx plugin.TaskContext,
+       apiClient *api.ApiClient,
+       connection *models.BambooConnection,
+) (*api.ApiAsyncClient, errors.Error) {
+       // create rate limit calculator
+       rateLimiter := &api.ApiRateLimitCalculator{
+               UserRateLimitPerHour: connection.RateLimitPerHour,
+               DynamicRateLimit: func(res *http.Response) (int, time.Duration, 
errors.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, errors.Default.Wrap(err, "failed 
to parse RateLimit-Limit header")
+                       }
+                       // seems like {{ .plugin-ame }} rate limit is on minute 
basis
+                       return rateLimit, 1 * time.Minute, nil
+               },
+       }
+       asyncApiClient, err := api.CreateAsyncApiClient(
+               taskCtx,
+               apiClient,
+               rateLimiter,
+       )
+       if err != nil {
+               return nil, err
+       }
+       return asyncApiClient, nil
+}
+
+func NewBambooApiClient(taskCtx plugin.TaskContext, connection 
*models.BambooConnection) (*api.ApiAsyncClient, errors.Error) {
+       // create synchronize api client so we can calculate api rate limit 
dynamically
+       apiClient, err := api.NewApiClientFromConnection(taskCtx.GetContext(), 
taskCtx, connection)
+       if err != nil {
+               return nil, err
+       }
+
+       return CreateBambooAsyncApiClient(taskCtx, apiClient, connection)
+}
diff --git a/backend/plugins/bamboo/tasks/task_data.go 
b/backend/plugins/bamboo/tasks/task_data.go
new file mode 100644
index 000000000..ea5c62bce
--- /dev/null
+++ b/backend/plugins/bamboo/tasks/task_data.go
@@ -0,0 +1,52 @@
+/*
+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 tasks
+
+import (
+       "github.com/apache/incubator-devlake/core/errors"
+       helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+type BambooApiParams struct {
+}
+
+type BambooOptions 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.
+       ConnectionId uint64   `json:"connectionId"`
+       Tasks        []string `json:"tasks,omitempty"`
+       Since        string
+}
+
+type BambooTaskData struct {
+       Options   *BambooOptions
+       ApiClient *helper.ApiAsyncClient
+}
+
+func DecodeAndValidateTaskOptions(options map[string]interface{}) 
(*BambooOptions, errors.Error) {
+       var op BambooOptions
+       if err := helper.Decode(options, &op, nil); err != nil {
+               return nil, err
+       }
+       if op.ConnectionId == 0 {
+               return nil, errors.Default.New("connectionId is invalid")
+       }
+       return &op, nil
+}

Reply via email to