This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 63b6a9b7b fix(gitlab): use stateful extractors for users and tags 
(#9070)
63b6a9b7b is described below

commit 63b6a9b7b317e85867cb9bbccb970a869469b9bb
Author: Francisc Munteanu <[email protected]>
AuthorDate: Tue Sep 1 05:34:22 2026 +0200

    fix(gitlab): use stateful extractors for users and tags (#9070)
    
    NewApiExtractor always deleted tool-layer rows by raw params before insert,
    so a truncated Collect Users run wiped accounts. NewStatefulApiExtractor
    upserts incrementally, matching the rest of the GitLab plugin.
    
    Co-authored-by: Cursor Grok 4.6 <[email protected]>
    Co-authored-by: Cursor <[email protected]>
---
 backend/plugins/gitlab/tasks/account_extractor.go | 61 +++++------------------
 backend/plugins/gitlab/tasks/tag_extractor.go     | 22 ++------
 2 files changed, 18 insertions(+), 65 deletions(-)

diff --git a/backend/plugins/gitlab/tasks/account_extractor.go 
b/backend/plugins/gitlab/tasks/account_extractor.go
index d2d24b48c..0024c3ead 100644
--- a/backend/plugins/gitlab/tasks/account_extractor.go
+++ b/backend/plugins/gitlab/tasks/account_extractor.go
@@ -18,7 +18,6 @@ limitations under the License.
 package tasks
 
 import (
-       "encoding/json"
        "strings"
 
        "github.com/apache/incubator-devlake/core/errors"
@@ -41,52 +40,23 @@ var ExtractAccountsMeta = plugin.SubTaskMeta{
 }
 
 func ExtractAccounts(taskCtx plugin.SubTaskContext) errors.Error {
-       rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, 
RAW_USER_TABLE)
+       subtaskCommonArgs, data := CreateSubtaskCommonArgs(taskCtx, 
RAW_USER_TABLE)
 
        // Do not extract createdUserAt if we are not using /users API
-       var skipCreatedUserAt = strings.HasPrefix(data.ApiClient.GetEndpoint(), 
"https://gitlab.com";)
-
-       extractor, err := api.NewApiExtractor(api.ApiExtractorArgs{
-               RawDataSubTaskArgs: *rawDataSubTaskArgs,
-               Extract: func(row *api.RawData) ([]interface{}, errors.Error) {
-                       var userRes models.GitlabAccount
-                       err := errors.Convert(json.Unmarshal(row.Data, 
&userRes))
-                       if err != nil {
-                               return nil, err
-                       }
+       skipCreatedUserAt := strings.HasPrefix(data.ApiClient.GetEndpoint(), 
"https://gitlab.com";)
+       subtaskCommonArgs.SubtaskConfig = map[string]any{
+               "skipCreatedUserAt": skipCreatedUserAt,
+       }
 
-                       results := make([]interface{}, 0)
-                       var GitlabAccount *models.GitlabAccount
+       extractor, err := 
api.NewStatefulApiExtractor(&api.StatefulApiExtractorArgs[models.GitlabAccount]{
+               SubtaskCommonArgs: subtaskCommonArgs,
+               Extract: func(userRes *models.GitlabAccount, _ *api.RawData) 
([]interface{}, errors.Error) {
+                       account := *userRes
+                       account.ConnectionId = data.Options.ConnectionId
                        if skipCreatedUserAt {
-                               GitlabAccount = &models.GitlabAccount{
-                                       ConnectionId:    
data.Options.ConnectionId,
-                                       GitlabId:        userRes.GitlabId,
-                                       Username:        userRes.Username,
-                                       Name:            userRes.Name,
-                                       State:           userRes.State,
-                                       MembershipState: 
userRes.MembershipState,
-                                       AvatarUrl:       userRes.AvatarUrl,
-                                       WebUrl:          userRes.WebUrl,
-                                       Email:           userRes.Email,
-                               }
-                       } else {
-                               GitlabAccount = &models.GitlabAccount{
-                                       ConnectionId:    
data.Options.ConnectionId,
-                                       GitlabId:        userRes.GitlabId,
-                                       Username:        userRes.Username,
-                                       Name:            userRes.Name,
-                                       State:           userRes.State,
-                                       MembershipState: 
userRes.MembershipState,
-                                       AvatarUrl:       userRes.AvatarUrl,
-                                       WebUrl:          userRes.WebUrl,
-                                       Email:           userRes.Email,
-                                       CreatedUserAt:   userRes.CreatedUserAt,
-                               }
+                               account.CreatedUserAt = nil
                        }
-
-                       results = append(results, GitlabAccount)
-
-                       return results, nil
+                       return []interface{}{&account}, nil
                },
        })
 
@@ -94,10 +64,5 @@ func ExtractAccounts(taskCtx plugin.SubTaskContext) 
errors.Error {
                return err
        }
 
-       err = extractor.Execute()
-       if err != nil {
-               return err
-       }
-
-       return nil
+       return extractor.Execute()
 }
diff --git a/backend/plugins/gitlab/tasks/tag_extractor.go 
b/backend/plugins/gitlab/tasks/tag_extractor.go
index 60dac78a4..4f0a511de 100644
--- a/backend/plugins/gitlab/tasks/tag_extractor.go
+++ b/backend/plugins/gitlab/tasks/tag_extractor.go
@@ -18,8 +18,6 @@ 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"
@@ -51,27 +49,17 @@ var ExtractTagMeta = plugin.SubTaskMeta{
 }
 
 func ExtractApiTag(taskCtx plugin.SubTaskContext) errors.Error {
-       rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, 
RAW_TAG_TABLE)
-
-       extractor, err := api.NewApiExtractor(api.ApiExtractorArgs{
-               RawDataSubTaskArgs: *rawDataSubTaskArgs,
-               Extract: func(row *api.RawData) ([]interface{}, errors.Error) {
-                       // need to extract 1 kind of entities here
-                       results := make([]interface{}, 0, 1)
+       subtaskCommonArgs, data := CreateSubtaskCommonArgs(taskCtx, 
RAW_TAG_TABLE)
 
-                       gitlabApiTag := &GitlabApiTag{}
-                       err := errors.Convert(json.Unmarshal(row.Data, 
gitlabApiTag))
-                       if err != nil {
-                               return nil, err
-                       }
+       extractor, err := 
api.NewStatefulApiExtractor(&api.StatefulApiExtractorArgs[GitlabApiTag]{
+               SubtaskCommonArgs: subtaskCommonArgs,
+               Extract: func(gitlabApiTag *GitlabApiTag, _ *api.RawData) 
([]interface{}, errors.Error) {
                        gitlabTag, err := convertTag(gitlabApiTag)
                        if err != nil {
                                return nil, err
                        }
                        gitlabTag.ConnectionId = data.Options.ConnectionId
-                       results = append(results, gitlabTag)
-
-                       return results, nil
+                       return []interface{}{gitlabTag}, nil
                },
        })
 

Reply via email to