From bcc6257626fb943b0ef4569fe70267ed1b99437d Mon Sep 17 00:00:00 2001 From: Richard Boisvert Date: Sun, 16 Nov 2025 10:31:07 -0500 Subject: [PATCH] fix(plugins): add missing scope-config and missing projects in grafana for argocd The scope-config was missing in the initial implementation, which prevent the DORA metrics to be collected. In Grafana, you could not select the projects, so only ALL was available as an option. I also moved the graphs under the correct sections. For the comment in the issue https://github.com/apache/incubator-devlake/issues/5207#issuecomment-3537781398 --- .../argocd/tasks/application_convertor.go | 121 ++++++++++++++++++ .../tasks/application_convertor_test.go | 52 ++++++++ backend/plugins/argocd/tasks/register.go | 1 + .../components/scope-config-form/index.tsx | 11 +- .../src/plugins/register/argocd/config.tsx | 2 +- config-ui/src/release/stable.ts | 4 + grafana/dashboards/ArgoCD.json | 44 +++++-- 7 files changed, 223 insertions(+), 12 deletions(-) create mode 100644 backend/plugins/argocd/tasks/application_convertor.go create mode 100644 backend/plugins/argocd/tasks/application_convertor_test.go diff --git a/backend/plugins/argocd/tasks/application_convertor.go b/backend/plugins/argocd/tasks/application_convertor.go new file mode 100644 index 00000000000..bc10f069f3d --- /dev/null +++ b/backend/plugins/argocd/tasks/application_convertor.go @@ -0,0 +1,121 @@ +/* +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" + "reflect" + "strings" + + "github.com/apache/incubator-devlake/core/dal" + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/models/domainlayer" + "github.com/apache/incubator-devlake/core/models/domainlayer/devops" + "github.com/apache/incubator-devlake/core/models/domainlayer/didgen" + "github.com/apache/incubator-devlake/core/plugin" + "github.com/apache/incubator-devlake/helpers/pluginhelper/api" + "github.com/apache/incubator-devlake/plugins/argocd/models" +) + +var ConvertApplicationsMeta = plugin.SubTaskMeta{ + Name: "convertApplications", + EntryPoint: ConvertApplications, + EnabledByDefault: true, + Description: "Convert ArgoCD applications into CICD scopes", + DomainTypes: []string{plugin.DOMAIN_TYPE_CICD}, + DependencyTables: []string{models.ArgocdApplication{}.TableName()}, + ProductTables: []string{devops.CicdScope{}.TableName()}, +} + +func ConvertApplications(taskCtx plugin.SubTaskContext) errors.Error { + data := taskCtx.GetData().(*ArgocdTaskData) + db := taskCtx.GetDal() + + cursor, err := db.Cursor( + dal.From(&models.ArgocdApplication{}), + dal.Where("connection_id = ?", data.Options.ConnectionId), + ) + if err != nil { + return err + } + defer cursor.Close() + + scopeIdGen := didgen.NewDomainIdGenerator(&models.ArgocdApplication{}) + + converter, err := api.NewDataConverter(api.DataConverterArgs{ + InputRowType: reflect.TypeOf(models.ArgocdApplication{}), + Input: cursor, + RawDataSubTaskArgs: api.RawDataSubTaskArgs{ + Ctx: taskCtx, + Table: RAW_APPLICATION_TABLE, + Params: models.ArgocdApiParams{ + ConnectionId: data.Options.ConnectionId, + Name: data.Options.ApplicationName, + }, + }, + Convert: func(inputRow interface{}) ([]interface{}, errors.Error) { + application := inputRow.(*models.ArgocdApplication) + scopeId := scopeIdGen.Generate(application.ConnectionId, application.Name) + scope := buildCicdScopeFromApplication(application, scopeId) + return []interface{}{scope}, nil + }, + }) + if err != nil { + return err + } + + return converter.Execute() +} + +func buildCicdScopeFromApplication(app *models.ArgocdApplication, scopeId string) *devops.CicdScope { + scope := &devops.CicdScope{ + DomainEntity: domainlayer.NewDomainEntity(scopeId), + Name: app.Name, + Description: describeApplicationScope(app), + Url: firstNonEmpty(app.RepoURL, app.DestServer), + CreatedDate: app.CreatedDate, + } + return scope +} + +func describeApplicationScope(app *models.ArgocdApplication) string { + parts := make([]string, 0, 3) + if app.Project != "" { + parts = append(parts, fmt.Sprintf("Project: %s", app.Project)) + } + if app.Namespace != "" { + parts = append(parts, fmt.Sprintf("Namespace: %s", app.Namespace)) + } + if app.DestNamespace != "" || app.DestServer != "" { + dest := strings.TrimSpace(fmt.Sprintf("%s/%s", app.DestServer, app.DestNamespace)) + dest = strings.Trim(dest, "/") + if dest != "" { + parts = append(parts, fmt.Sprintf("Destination: %s", dest)) + } + } + return strings.Join(parts, " | ") +} + +func firstNonEmpty(values ...string) string { + for _, v := range values { + if strings.TrimSpace(v) != "" { + return v + } + } + return "" +} diff --git a/backend/plugins/argocd/tasks/application_convertor_test.go b/backend/plugins/argocd/tasks/application_convertor_test.go new file mode 100644 index 00000000000..f33c749f026 --- /dev/null +++ b/backend/plugins/argocd/tasks/application_convertor_test.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 ( + "testing" + "time" + + "github.com/apache/incubator-devlake/plugins/argocd/models" + "github.com/stretchr/testify/assert" +) + +func TestDescribeApplicationScopeBuildsSummary(t *testing.T) { + desc := describeApplicationScope(&models.ArgocdApplication{ + Project: "observability", + Namespace: "argocd", + DestServer: "https://k8s.example.com", + DestNamespace: "prod", + }) + assert.Equal(t, "Project: observability | Namespace: argocd | Destination: https://k8s.example.com/prod", desc) +} + +func TestBuildCicdScopeFromApplication(t *testing.T) { + created := time.Now() + scope := buildCicdScopeFromApplication(&models.ArgocdApplication{ + Name: "test-app", + RepoURL: "https://git.example.com/app.git", + CreatedDate: &created, + }, "argocd:app:1") + + assert.Equal(t, "argocd:app:1", scope.Id) + assert.Equal(t, "test-app", scope.Name) + assert.Equal(t, "https://git.example.com/app.git", scope.Url) + if assert.NotNil(t, scope.CreatedDate) { + assert.Equal(t, created.UTC(), scope.CreatedDate.UTC()) + } +} diff --git a/backend/plugins/argocd/tasks/register.go b/backend/plugins/argocd/tasks/register.go index 56478cd1ff1..2d556691226 100644 --- a/backend/plugins/argocd/tasks/register.go +++ b/backend/plugins/argocd/tasks/register.go @@ -30,6 +30,7 @@ func CollectDataTaskMetas() []plugin.SubTaskMeta { return []plugin.SubTaskMeta{ CollectApplicationsMeta, ExtractApplicationsMeta, + ConvertApplicationsMeta, CollectSyncOperationsMeta, ExtractSyncOperationsMeta, ConvertSyncOperationsMeta, diff --git a/config-ui/src/plugins/components/scope-config-form/index.tsx b/config-ui/src/plugins/components/scope-config-form/index.tsx index 5c27d84d454..0f45a5708ff 100644 --- a/config-ui/src/plugins/components/scope-config-form/index.tsx +++ b/config-ui/src/plugins/components/scope-config-form/index.tsx @@ -34,6 +34,7 @@ import { AzureTransformation } from '@/plugins/register/azure'; import { TapdTransformation } from '@/plugins/register/tapd'; import { BambooTransformation } from '@/plugins/register/bamboo'; import { CircleCITransformation } from '@/plugins/register/circleci'; +import { ArgoCDTransformation } from '@/plugins/register/argocd'; import { DOC_URL } from '@/release'; import { operator } from '@/utils'; @@ -86,7 +87,7 @@ export const ScopeConfigForm = ({ setName(forceCreate ? `${res.name}-copy` : res.name); setEntities(res.entities ?? []); setTransformation(omit(res, ['id', 'connectionId', 'name', 'entities', 'createdAt', 'updatedAt'])); - } catch {} + } catch { } })(); }, [scopeConfigId]); @@ -193,6 +194,14 @@ export const ScopeConfigForm = ({ )}
+ {plugin === 'argocd' && ( + + )} + {plugin === 'azuredevops' && (