diff --git a/backend/plugins/tapd/impl/impl.go b/backend/plugins/tapd/impl/impl.go index 9f3e932ea8e..a73130b86cd 100644 --- a/backend/plugins/tapd/impl/impl.go +++ b/backend/plugins/tapd/impl/impl.go @@ -105,6 +105,7 @@ func (p Tapd) GetTablesInfo() []dal.Tabler { &models.TapdBugCustomFieldValue{}, &models.TapdScopeConfig{}, &models.TapdWorkitemType{}, + &models.TapdLifeTime{}, } } @@ -181,6 +182,8 @@ func (p Tapd) SubTaskMetas() []plugin.SubTaskMeta { tasks.EnrichStoryCustomFieldMeta, tasks.EnrichBugCustomFieldMeta, tasks.EnrichTaskCustomFieldMeta, + tasks.CollectLifeTimesMeta, + tasks.ExtractLifeTimesMeta, } } diff --git a/backend/plugins/tapd/models/life_time.go b/backend/plugins/tapd/models/life_time.go new file mode 100644 index 00000000000..a1187f84704 --- /dev/null +++ b/backend/plugins/tapd/models/life_time.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 models + +import "github.com/apache/incubator-devlake/core/models/common" + +type TapdLifeTime struct { + ConnectionId uint64 `gorm:"primaryKey"` + Id uint64 `gorm:"primaryKey;type:BIGINT NOT NULL;autoIncrement:false" json:"id,string"` + WorkspaceId uint64 `json:"workspace_id,string"` + EntityType string `json:"entity_type" gorm:"type:varchar(255)"` + EntityId uint64 `json:"entity_id,string"` + Status string `json:"status" gorm:"type:varchar(255)"` + Owner string `json:"owner" gorm:"type:varchar(255)"` + BeginDate *common.CSTTime `json:"begin_date"` + EndDate *common.CSTTime `json:"end_date"` + TimeCost float64 `json:"time_cost,string"` + Created *common.CSTTime `json:"created"` + Operator string `json:"operator" gorm:"type:varchar(255)"` + IsRepeated int `json:"is_repeated,string"` + ChangeFrom string `json:"change_from" gorm:"type:varchar(255)"` + common.NoPKModel +} + +func (TapdLifeTime) TableName() string { + return "_tool_tapd_life_times" +} diff --git a/backend/plugins/tapd/models/migrationscripts/20250221_add_lifetime_tables.go b/backend/plugins/tapd/models/migrationscripts/20250221_add_lifetime_tables.go new file mode 100644 index 00000000000..157848fb972 --- /dev/null +++ b/backend/plugins/tapd/models/migrationscripts/20250221_add_lifetime_tables.go @@ -0,0 +1,66 @@ +/* +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 ( + "time" + + "github.com/apache/incubator-devlake/core/context" + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/models/migrationscripts/archived" + "github.com/apache/incubator-devlake/core/plugin" + "github.com/apache/incubator-devlake/helpers/migrationhelper" +) + +type TapdLifeTime struct { + ConnectionId uint64 `gorm:"primaryKey"` + Id uint64 `gorm:"primaryKey;type:BIGINT NOT NULL;autoIncrement:false" json:"id,string"` + WorkspaceId uint64 `json:"workspace_id,string"` + EntityType string `json:"entity_type" gorm:"type:varchar(255)"` + EntityId uint64 `json:"entity_id,string"` + Status string `json:"status" gorm:"type:varchar(255)"` + Owner string `json:"owner" gorm:"type:varchar(255)"` + BeginDate *time.Time `json:"begin_date"` + EndDate *time.Time `json:"end_date"` + TimeCost float64 `json:"time_cost"` + Created *time.Time `json:"created"` + Operator string `json:"operator" gorm:"type:varchar(255)"` + IsRepeated int `json:"is_repeated"` + ChangeFrom string `json:"change_from" gorm:"type:varchar(255)"` + archived.NoPKModel +} + +func (TapdLifeTime) TableName() string { + return "_tool_tapd_life_times" +} + +var _ plugin.MigrationScript = (*addLifetimeTables)(nil) + +type addLifetimeTables struct{} + +func (*addLifetimeTables) Up(basicRes context.BasicRes) errors.Error { + return migrationhelper.AutoMigrateTables(basicRes, &TapdLifeTime{}) +} + +func (*addLifetimeTables) Version() uint64 { + return 20250221000000 +} + +func (*addLifetimeTables) Name() string { + return "add tapd lifetime tables" +} diff --git a/backend/plugins/tapd/models/migrationscripts/register.go b/backend/plugins/tapd/models/migrationscripts/register.go index 206cbf297e6..18b4fe87e91 100644 --- a/backend/plugins/tapd/models/migrationscripts/register.go +++ b/backend/plugins/tapd/models/migrationscripts/register.go @@ -35,5 +35,6 @@ func All() []plugin.MigrationScript { new(addConnIdToLabels), new(addCompanyIdToConnection), new(updateScopeConfig20250305), + new(addLifetimeTables), } } diff --git a/backend/plugins/tapd/tasks/workitem_lifetime_collector.go b/backend/plugins/tapd/tasks/workitem_lifetime_collector.go new file mode 100644 index 00000000000..183b6f8683d --- /dev/null +++ b/backend/plugins/tapd/tasks/workitem_lifetime_collector.go @@ -0,0 +1,90 @@ +/* +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 ( + "fmt" + "net/url" + "reflect" + + "github.com/apache/incubator-devlake/core/dal" + "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/tapd/models" +) + +const RAW_LIFE_TIME_TABLE = "tapd_api_life_times" + +var _ plugin.SubTaskEntryPoint = CollectLifeTimes + +func CollectLifeTimes(taskCtx plugin.SubTaskContext) errors.Error { + rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, RAW_LIFE_TIME_TABLE) + db := taskCtx.GetDal() + apiCollector, err := api.NewStatefulApiCollector(*rawDataSubTaskArgs) + if err != nil { + return err + } + logger := taskCtx.GetLogger() + logger.Info("collect lifeTimes") + + clauses := []dal.Clause{ + dal.Select("id as issue_id, modified as update_time"), + dal.From(&models.TapdStory{}), + dal.Where("connection_id = ? AND workspace_id = ?", data.Options.ConnectionId, data.Options.WorkspaceId), + } + if apiCollector.IsIncremental() && apiCollector.GetSince() != nil { + clauses = append(clauses, dal.Where("modified > ?", *apiCollector.GetSince())) + } + + cursor, err := db.Cursor(clauses...) + if err != nil { + return err + } + iterator, err := api.NewDalCursorIterator(db, cursor, reflect.TypeOf(models.Input{})) + if err != nil { + return err + } + err = apiCollector.InitCollector(api.ApiCollectorArgs{ + ApiClient: data.ApiClient, + Input: iterator, + UrlTemplate: "life_times", + Query: func(reqData *api.RequestData) (url.Values, errors.Error) { + input := reqData.Input.(*models.Input) + query := url.Values{} + query.Set("workspace_id", fmt.Sprintf("%v", data.Options.WorkspaceId)) + query.Set("entity_type", "story") + query.Set("entity_id", fmt.Sprintf("%v", input.IssueId)) + return query, nil + }, + ResponseParser: GetRawMessageArrayFromResponse, + }) + if err != nil { + logger.Error(err, "collect lifeTime error") + return err + } + return apiCollector.Execute() +} + +var CollectLifeTimesMeta = plugin.SubTaskMeta{ + Name: "CollectLifeTimes", + EntryPoint: CollectLifeTimes, + EnabledByDefault: true, + Description: "convert Tapd life times", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, +} diff --git a/backend/plugins/tapd/tasks/workitem_lifetime_extractor.go b/backend/plugins/tapd/tasks/workitem_lifetime_extractor.go new file mode 100644 index 00000000000..746cd9bb077 --- /dev/null +++ b/backend/plugins/tapd/tasks/workitem_lifetime_extractor.go @@ -0,0 +1,74 @@ +/* +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 ( + "encoding/json" + + "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/tapd/models" +) + +func ExtractLifeTimes(taskCtx plugin.SubTaskContext) errors.Error { + rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, RAW_LIFE_TIME_TABLE) + rep, err := api.NewApiExtractor(api.ApiExtractorArgs{ + RawDataSubTaskArgs: *rawDataSubTaskArgs, + Extract: func(row *api.RawData) ([]interface{}, errors.Error) { + var rawData struct { + LifeTime models.TapdLifeTime `json:"LifeTime"` + } + err := json.Unmarshal([]byte(row.Data), &rawData) + if err != nil { + return nil, errors.Convert(err) + } + + toolLifetime := &models.TapdLifeTime{ + ConnectionId: data.Options.ConnectionId, + WorkspaceId: data.Options.WorkspaceId, + Id: rawData.LifeTime.Id, + EntityType: rawData.LifeTime.EntityType, + EntityId: rawData.LifeTime.EntityId, + Status: rawData.LifeTime.Status, + Owner: rawData.LifeTime.Owner, + BeginDate: rawData.LifeTime.BeginDate, + EndDate: rawData.LifeTime.EndDate, + TimeCost: rawData.LifeTime.TimeCost, + Created: rawData.LifeTime.Created, + Operator: rawData.LifeTime.Operator, + IsRepeated: rawData.LifeTime.IsRepeated, + } + return []interface{}{toolLifetime}, nil + }, + }) + + if err != nil { + return err + } + + return rep.Execute() +} + +var ExtractLifeTimesMeta = plugin.SubTaskMeta{ + Name: "extractLifeTimes", + EntryPoint: ExtractLifeTimes, + EnabledByDefault: true, + Description: "extract Tapd life times", + DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET}, +}