From afc2628879b26f781777dada1b96c10a7f9fdb42 Mon Sep 17 00:00:00 2001 From: Zihan Dai Date: Wed, 4 Nov 2026 00:12:34 +1100 Subject: [PATCH] fix(azuredevops): run convertApiTimelineRecords when CICD is selected The subtask that converts Azure Pipelines timeline records into cicd_tasks was registered under the CODEREVIEW domain type, while the subtasks that collect and extract those records are registered under CICD. The blueprint picks subtasks by domain type, so the converter was left out of the plan whenever CODEREVIEW was not selected. That is always the case for repositories hosted outside Azure Repos (GitHub, Bitbucket), which only get CICD (plus CODE for public repositories), and also for Azure Repos scopes configured with CICD only. In those cases timeline records were collected and extracted, but no cicd_tasks were written, so deployments recognized by stage or job names never reached the DORA metrics. Register the converter under CICD like the other timeline subtasks. Closes #8943 Signed-off-by: Zihan Dai --- .../azuredevops_go/api/blueprint_v200_test.go | 58 +++++++++++++++++++ .../tasks/ci_cd_timeline_records_converter.go | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/backend/plugins/azuredevops_go/api/blueprint_v200_test.go b/backend/plugins/azuredevops_go/api/blueprint_v200_test.go index ce45e380881..640a296e727 100644 --- a/backend/plugins/azuredevops_go/api/blueprint_v200_test.go +++ b/backend/plugins/azuredevops_go/api/blueprint_v200_test.go @@ -241,6 +241,64 @@ func TestMakeDataSourcePipelinePlanV200(t *testing.T) { assert.Equal(t, expectPlans, actualPlans) } +func TestMakeDataSourcePipelinePlanV200ConvertsTimelineRecordsWithCicd(t *testing.T) { + data := []struct { + Name string + Type string + Private bool + Entities []string + }{ + // only CICD is planned for repositories hosted outside Azure Repos + {Name: "Private GitHub Repository", Type: models.RepositoryTypeGithub, Private: true, Entities: plugin.DOMAIN_TYPES}, + {Name: "Azure DevOps Repository with CICD only", Type: models.RepositoryTypeADO, Private: false, Entities: []string{plugin.DOMAIN_TYPE_CICD}}, + } + + for _, d := range data { + t.Run(d.Name, func(t *testing.T) { + actualPlans, err := makePipelinePlanV200( + []plugin.SubTaskMeta{ + tasks.CollectBuildsMeta, + tasks.ExtractApiBuildsMeta, + tasks.ConvertBuildsMeta, + tasks.CollectJobsMeta, + tasks.ExtractApiBuildRecordsMeta, + tasks.ConvertApiTimelineRecordsMeta, + }, + &models.AzuredevopsConnection{ + BaseConnection: api.BaseConnection{ + Model: common.Model{ + ID: connectionID, + }, + }, + }, + []*srvhelper.ScopeDetail[models.AzuredevopsRepo, models.AzuredevopsScopeConfig]{ + { + Scope: models.AzuredevopsRepo{ + Id: azuredevopsRepoId, + Type: d.Type, + IsPrivate: d.Private, + }, + ScopeConfig: &models.AzuredevopsScopeConfig{ + ScopeConfig: common.ScopeConfig{ + Entities: d.Entities, + }, + }, + }, + }, + ) + assert.Nil(t, err) + assert.Equal(t, []string{ + tasks.CollectBuildsMeta.Name, + tasks.ExtractApiBuildsMeta.Name, + tasks.ConvertBuildsMeta.Name, + tasks.CollectJobsMeta.Name, + tasks.ExtractApiBuildRecordsMeta.Name, + tasks.ConvertApiTimelineRecordsMeta.Name, + }, actualPlans[0][0].Subtasks) + }) + } +} + func TestMakeRemoteRepoScopes(t *testing.T) { mockAzuredevopsPlugin(t) diff --git a/backend/plugins/azuredevops_go/tasks/ci_cd_timeline_records_converter.go b/backend/plugins/azuredevops_go/tasks/ci_cd_timeline_records_converter.go index e5272a014b9..fb595ad6d54 100644 --- a/backend/plugins/azuredevops_go/tasks/ci_cd_timeline_records_converter.go +++ b/backend/plugins/azuredevops_go/tasks/ci_cd_timeline_records_converter.go @@ -39,7 +39,7 @@ var ConvertApiTimelineRecordsMeta = plugin.SubTaskMeta{ EntryPoint: ConvertApiTimelineRecords, EnabledByDefault: true, Description: "Convert tool layer table azuredevops_timeline_records into domain layer table cicd_tasks", - DomainTypes: []string{plugin.DOMAIN_TYPE_CODE_REVIEW}, + DomainTypes: []string{plugin.DOMAIN_TYPE_CICD}, DependencyTables: []string{ models.AzuredevopsTimelineRecord{}.TableName(), },