You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2022/07/15 03:29:31 UTC
[incubator-devlake] branch main updated: feat: org plugin (#2461)
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/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 0322cfc0 feat: org plugin (#2461)
0322cfc0 is described below
commit 0322cfc05eea6fce6d5efe82783dd7535f1f7aa1
Author: mindlesscloud <li...@merico.dev>
AuthorDate: Fri Jul 15 11:29:27 2022 +0800
feat: org plugin (#2461)
* feat: org plugin
* fix: fix user account association
* fix: fix a typo
* fix: fix e2e for org plugin
* feat: add account_users.csv endpoint
* refactor: rename accountUser to userAccount
* fix: make slice with zero lenght
* feat: createAccount
* fix: toDomainLayer return slice of pointers
* refactor: remove user_account.csv endpoint & user as delimiter for array
* feat: add swagger support for plugin org
* refactor: rename accounts.csv to user_account_mapping.csv
* refactor: change http handler from gin.HandlerFunc to core.ApiResourceHandler
* fix: response to PR commtents
* fix: subtask ConnectUserAccountsExact only insert not modify
* fix: enable sub task by default && revise swagger doc
---
api/router.go | 18 +-
go.mod | 22 +-
go.sum | 25 +++
models/domainlayer/crossdomain/team_user.go | 3 +
models/domainlayer/crossdomain/user_account.go | 5 +-
models/migrationscripts/archived/team_user.go | 1 +
models/migrationscripts/archived/user_account.go | 3 +-
plugins/core/plugin_api.go | 19 +-
plugins/org/api/handlers.go | 60 ++++++
plugins/org/api/store.go | 113 +++++++++++
plugins/org/api/team.go | 96 +++++++++
plugins/org/api/types.go | 225 +++++++++++++++++++++
plugins/org/api/user.go | 103 ++++++++++
plugins/org/api/user_account_mapping.go | 88 ++++++++
plugins/org/e2e/raw_tables/accounts.csv | 11 +
plugins/org/e2e/raw_tables/user_accounts.csv | 5 +
plugins/org/e2e/raw_tables/users.csv | 11 +
plugins/org/e2e/snapshot_tables/user_accounts.csv | 11 +
plugins/org/e2e/user_account_test.go | 56 +++++
plugins/org/impl/impl.go | 87 ++++++++
.../archived/team_user.go => plugins/org/org.go | 11 +-
.../team_user.go => plugins/org/tasks/task_data.go | 14 +-
plugins/org/tasks/user_account.go | 116 +++++++++++
23 files changed, 1069 insertions(+), 34 deletions(-)
diff --git a/api/router.go b/api/router.go
index 3666f251..d99fba7c 100644
--- a/api/router.go
+++ b/api/router.go
@@ -20,10 +20,10 @@ package api
import (
"fmt"
"net/http"
+ "strings"
"github.com/apache/incubator-devlake/api/blueprints"
"github.com/apache/incubator-devlake/api/domainlayer"
-
"github.com/apache/incubator-devlake/api/ping"
"github.com/apache/incubator-devlake/api/pipelines"
"github.com/apache/incubator-devlake/api/push"
@@ -77,10 +77,14 @@ func RegisterRouter(r *gin.Engine) {
}
input.Query = c.Request.URL.Query()
if c.Request.Body != nil {
- err := c.ShouldBindJSON(&input.Body)
- if err != nil && err.Error() != "EOF" {
- shared.ApiOutputError(c, err, http.StatusBadRequest)
- return
+ if strings.HasPrefix(c.Request.Header.Get("Content-Type"), "multipart/form-data;") {
+ input.Request = c.Request
+ } else {
+ err = c.ShouldBindJSON(&input.Body)
+ if err != nil && err.Error() != "EOF" {
+ shared.ApiOutputError(c, err, http.StatusBadRequest)
+ return
+ }
}
}
output, err := handler(input)
@@ -91,6 +95,10 @@ func RegisterRouter(r *gin.Engine) {
if status < http.StatusContinue {
status = http.StatusOK
}
+ if output.File != nil {
+ c.Data(status, output.File.ContentType, output.File.Data)
+ return
+ }
shared.ApiOutputSuccess(c, output.Body, status)
} else {
shared.ApiOutputSuccess(c, nil, http.StatusOK)
diff --git a/go.mod b/go.mod
index 710d9139..e82cbfde 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
github.com/stoewer/go-strcase v1.2.0
github.com/stretchr/testify v1.7.0
github.com/swaggo/gin-swagger v1.4.3
- github.com/swaggo/swag v1.8.1
+ github.com/swaggo/swag v1.8.3
github.com/x-cray/logrus-prefixed-formatter v0.5.2
go.temporal.io/api v1.7.1-0.20220223032354-6e6fe738916a
go.temporal.io/sdk v1.14.0
@@ -46,16 +46,18 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
+ github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
- github.com/go-openapi/jsonreference v0.19.6 // indirect
- github.com/go-openapi/spec v0.20.4 // indirect
- github.com/go-openapi/swag v0.19.15 // indirect
+ github.com/go-openapi/jsonreference v0.20.0 // indirect
+ github.com/go-openapi/spec v0.20.6 // indirect
+ github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
+ github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gogo/status v1.1.0 // indirect
@@ -82,7 +84,7 @@ require (
github.com/json-iterator/go v1.1.11 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
- github.com/mailru/easyjson v0.7.6 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.6 // indirect
@@ -104,19 +106,21 @@ require (
github.com/stretchr/objx v0.3.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
+ github.com/urfave/cli/v2 v2.11.0 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/atomic v1.9.0 // indirect
- golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
- golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c // indirect
+ golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect
+ golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
- golang.org/x/tools v0.1.7 // indirect
+ golang.org/x/tools v0.1.11 // indirect
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect
google.golang.org/grpc v1.44.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 7fc8d5cb..0ab186a4 100644
--- a/go.sum
+++ b/go.sum
@@ -119,6 +119,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
@@ -153,11 +154,17 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
+github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
@@ -175,6 +182,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e h1:GMIV+S6grz+vlIaUsP+fedQ6L+FovyMPMY26WO8dwQE=
+github.com/gocarina/gocsv v0.0.0-20220707092902-b9da1f06c77e/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
@@ -412,6 +421,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
@@ -556,6 +567,8 @@ github.com/swaggo/gin-swagger v1.4.3 h1:mHJz+yzJne0udgYnC5qlDf4e7KuxUbVNX2dhD/cw
github.com/swaggo/gin-swagger v1.4.3/go.mod h1:hBg6tGeKJsUu/P79BH+WGUR8nq2LuGE0O160+s4iefo=
github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
+github.com/swaggo/swag v1.8.3 h1:3pZSSCQ//gAH88lfmxM3Cd1+JCsxV8Md6f36b9hrZ5s=
+github.com/swaggo/swag v1.8.3/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
@@ -563,10 +576,14 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/urfave/cli/v2 v2.11.0 h1:c6bD90aLd2iEsokxhxkY5Er0zA2V9fId2aJfwmrF+do=
+github.com/urfave/cli/v2 v2.11.0/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -706,6 +723,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
+golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -797,6 +816,8 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c h1:sSIdNI2Dd6vGv47bKc/xArpfxVmEz2+3j0E6I484xC4=
golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
+golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
@@ -875,6 +896,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
+golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1022,6 +1045,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.0.1 h1:6npnXbBtjpSb7FFVA2dG/llyTN8tvZfbUqs+WyLrYgQ=
gorm.io/datatypes v1.0.1/go.mod h1:HEHoUU3/PO5ZXfAJcVWl11+zWlE16+O0X2DgJEb4Ixs=
gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
diff --git a/models/domainlayer/crossdomain/team_user.go b/models/domainlayer/crossdomain/team_user.go
index 41417a66..c3d98f26 100644
--- a/models/domainlayer/crossdomain/team_user.go
+++ b/models/domainlayer/crossdomain/team_user.go
@@ -17,9 +17,12 @@ limitations under the License.
package crossdomain
+import "github.com/apache/incubator-devlake/models/common"
+
type TeamUser struct {
TeamId string `gorm:"primaryKey;type:varchar(255)"`
UserId string `gorm:"primaryKey;type:varchar(255)"`
+ common.NoPKModel
}
func (TeamUser) TableName() string {
diff --git a/models/domainlayer/crossdomain/user_account.go b/models/domainlayer/crossdomain/user_account.go
index 405d6f58..e6402447 100644
--- a/models/domainlayer/crossdomain/user_account.go
+++ b/models/domainlayer/crossdomain/user_account.go
@@ -17,9 +17,12 @@ limitations under the License.
package crossdomain
+import "github.com/apache/incubator-devlake/models/common"
+
type UserAccount struct {
- UserId string `gorm:"primaryKey;type:varchar(255)"`
+ UserId string `gorm:"type:varchar(255)"`
AccountId string `gorm:"primaryKey;type:varchar(255)"`
+ common.NoPKModel
}
func (UserAccount) TableName() string {
diff --git a/models/migrationscripts/archived/team_user.go b/models/migrationscripts/archived/team_user.go
index 9cd3bf27..ff143ca1 100644
--- a/models/migrationscripts/archived/team_user.go
+++ b/models/migrationscripts/archived/team_user.go
@@ -20,6 +20,7 @@ package archived
type TeamUser struct {
TeamId string `gorm:"primaryKey;type:varchar(255)"`
UserId string `gorm:"primaryKey;type:varchar(255)"`
+ NoPKModel
}
func (TeamUser) TableName() string {
diff --git a/models/migrationscripts/archived/user_account.go b/models/migrationscripts/archived/user_account.go
index 5a9acd7f..f4a9adf1 100644
--- a/models/migrationscripts/archived/user_account.go
+++ b/models/migrationscripts/archived/user_account.go
@@ -18,8 +18,9 @@ limitations under the License.
package archived
type UserAccount struct {
- UserId string `gorm:"primaryKey;type:varchar(255)"`
+ UserId string `gorm:"type:varchar(255)"`
AccountId string `gorm:"primaryKey;type:varchar(255)"`
+ NoPKModel
}
func (UserAccount) TableName() string {
diff --git a/plugins/core/plugin_api.go b/plugins/core/plugin_api.go
index a57a33d1..d7dfdd9b 100644
--- a/plugins/core/plugin_api.go
+++ b/plugins/core/plugin_api.go
@@ -17,19 +17,30 @@ limitations under the License.
package core
-import "net/url"
+import (
+ "net/http"
+ "net/url"
+)
// Contains api request information
type ApiResourceInput struct {
- Params map[string]string // path variables
- Query url.Values // query string
- Body map[string]interface{} // json body
+ Params map[string]string // path variables
+ Query url.Values // query string
+ Body map[string]interface{} // json body
+ Request *http.Request
+}
+
+// OutputFile is the file returned
+type OutputFile struct {
+ ContentType string
+ Data []byte
}
// Describe response data of a api
type ApiResourceOutput struct {
Body interface{} // response body
Status int
+ File *OutputFile
}
type ApiResourceHandler func(input *ApiResourceInput) (*ApiResourceOutput, error)
diff --git a/plugins/org/api/handlers.go b/plugins/org/api/handlers.go
new file mode 100644
index 00000000..ee5947e6
--- /dev/null
+++ b/plugins/org/api/handlers.go
@@ -0,0 +1,60 @@
+/*
+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 (
+ "encoding/csv"
+ "errors"
+ "net/http"
+
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/core/dal"
+ "github.com/gocarina/gocsv"
+)
+
+const maxMemory = 32 << 20 // 32 MB
+
+type Handlers struct {
+ store store
+}
+
+func NewHandlers(db dal.Dal, basicRes core.BasicRes) *Handlers {
+ return &Handlers{store: NewDbStore(db, basicRes)}
+}
+
+func (h *Handlers) unmarshal(r *http.Request, items interface{}) error {
+ if r == nil {
+ return errors.New("request is nil")
+ }
+ if r.MultipartForm == nil {
+ if err := r.ParseMultipartForm(maxMemory); err != nil {
+ return err
+ }
+ }
+ f, fh, err := r.FormFile("file")
+ if err != nil {
+ return err
+ }
+ f.Close()
+ file, err := fh.Open()
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ return gocsv.UnmarshalCSV(csv.NewReader(file), items)
+}
diff --git a/plugins/org/api/store.go b/plugins/org/api/store.go
new file mode 100644
index 00000000..903b45b9
--- /dev/null
+++ b/plugins/org/api/store.go
@@ -0,0 +1,113 @@
+/*
+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 (
+ "reflect"
+
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/core/dal"
+ "github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type store interface {
+ findAllUsers() ([]user, error)
+ findAllTeams() ([]team, error)
+ findAllAccounts() ([]account, error)
+ findAllUserAccounts() ([]userAccount, error)
+ deleteAll(i interface{}) error
+ save(items []interface{}) error
+}
+
+type dbStore struct {
+ db dal.Dal
+ driver *helper.BatchSaveDivider
+}
+
+func NewDbStore(db dal.Dal, basicRes core.BasicRes) *dbStore {
+ driver := helper.NewBatchSaveDivider(basicRes, 1000, "", "")
+ return &dbStore{db: db, driver: driver}
+}
+
+func (d *dbStore) findAllUsers() ([]user, error) {
+ var u *user
+ var uu []crossdomain.User
+ err := d.db.All(&uu)
+ if err != nil {
+ return nil, err
+ }
+ var tus []crossdomain.TeamUser
+ err = d.db.All(&tus)
+ if err != nil {
+ return nil, err
+ }
+ return u.fromDomainLayer(uu, tus), nil
+}
+func (d *dbStore) findAllTeams() ([]team, error) {
+ var tt []crossdomain.Team
+ err := d.db.All(&tt)
+ if err != nil {
+ return nil, err
+ }
+ var t *team
+ return t.fromDomainLayer(tt), nil
+}
+func (d *dbStore) findAllAccounts() ([]account, error) {
+ var aa []crossdomain.Account
+ err := d.db.All(&aa)
+ if err != nil {
+ return nil, err
+ }
+ var ua []crossdomain.UserAccount
+ err = d.db.All(&ua)
+ if err != nil {
+ return nil, err
+ }
+ var a *account
+ return a.fromDomainLayer(aa, ua), nil
+}
+
+func (d *dbStore) findAllUserAccounts() ([]userAccount, error) {
+ var uas []crossdomain.UserAccount
+ err := d.db.All(&uas)
+ if err != nil {
+ return nil, err
+ }
+
+ var au *userAccount
+ return au.fromDomainLayer(uas), nil
+}
+func (d *dbStore) deleteAll(i interface{}) error {
+ return d.db.Delete(i, dal.Where("1=1"))
+}
+
+func (d *dbStore) save(items []interface{}) error {
+ for _, item := range items {
+ batch, err := d.driver.ForType(reflect.TypeOf(item))
+ if err != nil {
+ return err
+ }
+ err = batch.Add(item)
+ if err != nil {
+ return err
+ }
+ }
+ d.driver.Close()
+ return nil
+}
diff --git a/plugins/org/api/team.go b/plugins/org/api/team.go
new file mode 100644
index 00000000..17a055d4
--- /dev/null
+++ b/plugins/org/api/team.go
@@ -0,0 +1,96 @@
+/*
+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/plugins/core"
+ "net/http"
+
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "github.com/gocarina/gocsv"
+)
+
+// GetTeam godoc
+// @Summary Get teams.csv file
+// @Description get teams.csv file
+// @Tags plugins/org
+// @Produce text/csv
+// @Param fake_data query bool false "return fake data or not"
+// @Success 200
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /plugins/org/teams.csv [get]
+func (h *Handlers) GetTeam(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+ input.Query.Get("fake_data")
+ var teams []team
+ var t *team
+ var err error
+ if input.Query.Get("fake_data") == "true" {
+ teams = t.fakeData()
+ } else {
+ teams, err = h.store.findAllTeams()
+ if err != nil {
+ return nil, err
+ }
+ }
+ blob, err := gocsv.MarshalBytes(teams)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{
+ Body: nil,
+ Status: http.StatusOK,
+ File: &core.OutputFile{
+ ContentType: "text/csv",
+ Data: blob,
+ },
+ }, nil
+}
+
+// CreateTeam godoc
+// @Summary Upload teams.csv file
+// @Description upload teams.csv file
+// @Tags plugins/org
+// @Accept multipart/form-data
+// @Param file formData file true "select file to upload"
+// @Produce json
+// @Success 200
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /plugins/org/teams.csv [put]
+func (h *Handlers) CreateTeam(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+ var tt []team
+ err := h.unmarshal(input.Request, &tt)
+ if err != nil {
+ return nil, err
+ }
+ var t *team
+ var items []interface{}
+ for _, tm := range t.toDomainLayer(tt) {
+ items = append(items, tm)
+ }
+ err = h.store.deleteAll(&crossdomain.Team{})
+ if err != nil {
+ return nil, err
+ }
+ err = h.store.save(items)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{Status: http.StatusOK}, nil
+}
diff --git a/plugins/org/api/types.go b/plugins/org/api/types.go
new file mode 100644
index 00000000..98d7be9a
--- /dev/null
+++ b/plugins/org/api/types.go
@@ -0,0 +1,225 @@
+/*
+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/models/domainlayer"
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "strings"
+)
+
+const TimeFormat = "2006-01-02"
+
+var fakeUsers = []user{{
+ Id: "1",
+ Name: "Tyrone K. Cummings",
+ Email: "TyroneKCummings@teleworm.us",
+ TeamIds: "1;2",
+}, {
+ Id: "2",
+ Name: "Dorothy R. Updegraff",
+ Email: "DorothyRUpdegraff@dayrep.com",
+ TeamIds: "3",
+}}
+
+var fakeTeams = []team{{
+ Id: "1",
+ Name: "Maple Leafs",
+ Alias: "ML",
+ ParentId: "2",
+ SortingIndex: 0,
+}, {
+ Id: "2",
+ Name: "Friendly Confines",
+ Alias: "FC",
+ ParentId: "",
+ SortingIndex: 1,
+}, {
+ Id: "3",
+ Name: "Blue Jays",
+ Alias: "BJ",
+ ParentId: "",
+ SortingIndex: 2,
+}}
+
+type user struct {
+ Id string
+ Name string
+ Email string
+ TeamIds string
+}
+
+func (*user) fromDomainLayer(users []crossdomain.User, teamUsers []crossdomain.TeamUser) []user {
+ var result []user
+ teamUserMap := make(map[string][]string)
+ for _, tu := range teamUsers {
+ teamUserMap[tu.UserId] = append(teamUserMap[tu.UserId], tu.TeamId)
+ }
+ for _, u := range users {
+ result = append(result, user{
+ Id: u.Id,
+ Name: u.Name,
+ Email: u.Email,
+ TeamIds: strings.Join(teamUserMap[u.Id], ";"),
+ })
+ }
+ return result
+}
+
+func (*user) toDomainLayer(uu []user) (users []*crossdomain.User, teamUsers []*crossdomain.TeamUser) {
+ for _, u := range uu {
+ users = append(users, &crossdomain.User{
+ DomainEntity: domainlayer.DomainEntity{Id: u.Id},
+ Email: u.Email,
+ Name: u.Name,
+ })
+ for _, teamId := range strings.Split(u.TeamIds, ";") {
+ if u.Id == "" || teamId == "" {
+ continue
+ }
+ teamUsers = append(teamUsers, &crossdomain.TeamUser{
+ TeamId: teamId,
+ UserId: u.Id,
+ })
+ }
+ }
+ return
+}
+
+func (*user) fakeData() []user {
+ return fakeUsers
+}
+
+type account struct {
+ Id string
+ Email string
+ FullName string
+ UserName string
+ AvatarUrl string
+ Organization string
+ CreatedDate string
+ Status int
+ UserId string
+}
+
+func (*account) fromDomainLayer(accounts []crossdomain.Account, userAccounts []crossdomain.UserAccount) []account {
+ var result []account
+ userAccountMap := make(map[string]string)
+ for _, ua := range userAccounts {
+ userAccountMap[ua.AccountId] = ua.UserId
+ }
+ for _, a := range accounts {
+ var createdDate string
+ if a.CreatedDate != nil {
+ createdDate = a.CreatedDate.Format(TimeFormat)
+ }
+ result = append(result, account{
+ Id: a.Id,
+ Email: a.Email,
+ FullName: a.FullName,
+ UserName: a.UserName,
+ AvatarUrl: a.AvatarUrl,
+ Organization: a.Organization,
+ CreatedDate: createdDate,
+ Status: a.Status,
+ UserId: userAccountMap[a.Id],
+ })
+ }
+ return result
+}
+
+func (*account) toDomainLayer(aa []account) []*crossdomain.UserAccount {
+ var userAccounts []*crossdomain.UserAccount
+ for _, a := range aa {
+ if a.UserId == "" || a.Id == "" {
+ continue
+ }
+ userAccounts = append(userAccounts, &crossdomain.UserAccount{
+ UserId: a.UserId,
+ AccountId: a.Id,
+ })
+ }
+ return userAccounts
+}
+
+type userAccount struct {
+ AccountId string
+ UserId string
+}
+
+func (au *userAccount) toDomainLayer(accountUsers []userAccount) []*crossdomain.UserAccount {
+ result := make([]*crossdomain.UserAccount, 0, len(accountUsers))
+ for _, ac := range accountUsers {
+ result = append(result, &crossdomain.UserAccount{
+ UserId: ac.UserId,
+ AccountId: ac.AccountId,
+ })
+ }
+ return result
+}
+
+func (au *userAccount) fromDomainLayer(accountUsers []crossdomain.UserAccount) []userAccount {
+ result := make([]userAccount, 0, len(accountUsers))
+ for _, ac := range accountUsers {
+ result = append(result, userAccount{
+ UserId: ac.UserId,
+ AccountId: ac.AccountId,
+ })
+ }
+ return result
+}
+
+type team struct {
+ Id string
+ Name string
+ Alias string
+ ParentId string
+ SortingIndex int
+}
+
+func (*team) fromDomainLayer(tt []crossdomain.Team) []team {
+ var result []team
+ for _, t := range tt {
+ result = append(result, team{
+ Id: t.Id,
+ Name: t.Name,
+ Alias: t.Alias,
+ ParentId: t.ParentId,
+ SortingIndex: t.SortingIndex,
+ })
+ }
+ return result
+}
+
+func (*team) toDomainLayer(tt []team) []*crossdomain.Team {
+ var result []*crossdomain.Team
+ for _, t := range tt {
+ result = append(result, &crossdomain.Team{
+ DomainEntity: domainlayer.DomainEntity{Id: t.Id},
+ Name: t.Name,
+ Alias: t.Alias,
+ ParentId: t.ParentId,
+ SortingIndex: t.SortingIndex,
+ })
+ }
+ return result
+}
+
+func (*team) fakeData() []team {
+ return fakeTeams
+}
diff --git a/plugins/org/api/user.go b/plugins/org/api/user.go
new file mode 100644
index 00000000..bc14e1ae
--- /dev/null
+++ b/plugins/org/api/user.go
@@ -0,0 +1,103 @@
+/*
+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 (
+ "net/http"
+
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/gocarina/gocsv"
+)
+
+// GetUser godoc
+// @Summary Get users.csv file
+// @Description get users.csv file
+// @Tags plugins/org
+// @Produce text/csv
+// @Param fake_data query bool false "return fake data or not"
+// @Success 200
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /plugins/org/users.csv [get]
+func (h *Handlers) GetUser(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+ var users []user
+ var u *user
+ var err error
+ if input.Query.Get("fake_data") == "true" {
+ users = u.fakeData()
+ } else {
+ users, err = h.store.findAllUsers()
+ if err != nil {
+ return nil, err
+ }
+ }
+ blob, err := gocsv.MarshalBytes(users)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{
+ Body: nil,
+ Status: http.StatusOK,
+ File: &core.OutputFile{
+ ContentType: "text/csv",
+ Data: blob,
+ },
+ }, nil
+}
+
+// CreateUser godoc
+// @Summary Upload users.csv file
+// @Description upload users.csv file
+// @Tags plugins/org
+// @Accept multipart/form-data
+// @Param file formData file true "select file to upload"
+// @Produce json
+// @Success 200
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /plugins/org/users.csv [put]
+func (h *Handlers) CreateUser(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+ var uu []user
+ err := h.unmarshal(input.Request, &uu)
+ if err != nil {
+ return nil, err
+ }
+ var u *user
+ var items []interface{}
+ users, teamUsers := u.toDomainLayer(uu)
+ for _, user := range users {
+ items = append(items, user)
+ }
+ for _, teamUser := range teamUsers {
+ items = append(items, teamUser)
+ }
+ err = h.store.deleteAll(&crossdomain.User{})
+ if err != nil {
+ return nil, err
+ }
+ err = h.store.deleteAll(&crossdomain.TeamUser{})
+ if err != nil {
+ return nil, err
+ }
+ err = h.store.save(items)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{Status: http.StatusOK}, nil
+}
diff --git a/plugins/org/api/user_account_mapping.go b/plugins/org/api/user_account_mapping.go
new file mode 100644
index 00000000..077e3144
--- /dev/null
+++ b/plugins/org/api/user_account_mapping.go
@@ -0,0 +1,88 @@
+/*
+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/models/domainlayer/crossdomain"
+ "net/http"
+
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/gocarina/gocsv"
+)
+
+// GetUserAccountMapping godoc
+// @Summary Get user_account_mapping.csv.csv file
+// @Description get user_account_mapping.csv.csv file
+// @Tags plugins/org
+// @Produce text/csv
+// @Success 200
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /plugins/org/user_account_mapping.csv [get]
+func (h *Handlers) GetUserAccountMapping(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+ accounts, err := h.store.findAllAccounts()
+ if err != nil {
+ return nil, err
+ }
+ blob, err := gocsv.MarshalBytes(accounts)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{
+ Body: nil,
+ Status: http.StatusOK,
+ File: &core.OutputFile{
+ ContentType: "text/csv",
+ Data: blob,
+ },
+ }, nil
+}
+
+// CreateUserAccountMapping godoc
+// @Summary Upload user_account_mapping.csv.csv file
+// @Description upload user_account_mapping.csv.csv file
+// @Tags plugins/org
+// @Accept multipart/form-data
+// @Param file formData file true "select file to upload"
+// @Produce json
+// @Success 200
+// @Failure 400 {object} shared.ApiBody "Bad Request"
+// @Failure 500 {object} shared.ApiBody "Internal Error"
+// @Router /plugins/org/user_account_mapping.csv [put]
+func (h *Handlers) CreateUserAccountMapping(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
+ var aa []account
+ err := h.unmarshal(input.Request, &aa)
+ if err != nil {
+ return nil, err
+ }
+ var a *account
+ var items []interface{}
+ userAccounts := a.toDomainLayer(aa)
+ for _, userAccount := range userAccounts {
+ items = append(items, userAccount)
+ }
+ err = h.store.deleteAll(&crossdomain.UserAccount{})
+ if err != nil {
+ return nil, err
+ }
+ err = h.store.save(items)
+ if err != nil {
+ return nil, err
+ }
+ return &core.ApiResourceOutput{Status: http.StatusOK}, nil
+}
diff --git a/plugins/org/e2e/raw_tables/accounts.csv b/plugins/org/e2e/raw_tables/accounts.csv
new file mode 100644
index 00000000..6bbdb2a8
--- /dev/null
+++ b/plugins/org/e2e/raw_tables/accounts.csv
@@ -0,0 +1,11 @@
+"id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","email","avatar_url","full_name","user_name","organization","created_date","status"
+"a1","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e1","","n1","n1","","2022-07-10 14:27:43.813",0
+"a10","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e15","","pq","n10","","2022-07-10 14:27:43.813",0
+"a2","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e1","","n2","n2","","2022-07-10 14:27:43.813",0
+"a3","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e2","","xyz","n3","","2022-07-10 14:27:43.813",0
+"a4","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e4","","n4","n4","","2022-07-10 14:27:43.813",0
+"a5","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e5","","abc","n5","","2022-07-10 14:27:43.813",0
+"a6","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e5","","n6","n6","","2022-07-10 14:27:43.813",0
+"a7","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","","","n5","n7","","2022-07-10 14:27:43.813",0
+"a8","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","xy","","n8","n8","","2022-07-10 14:27:43.813",0
+"a9","2022-07-10 14:27:43.813","2022-07-10 14:27:43.813","","",0,"","e11","","def","n9","","2022-07-10 14:27:43.813",0
diff --git a/plugins/org/e2e/raw_tables/user_accounts.csv b/plugins/org/e2e/raw_tables/user_accounts.csv
new file mode 100644
index 00000000..2340c876
--- /dev/null
+++ b/plugins/org/e2e/raw_tables/user_accounts.csv
@@ -0,0 +1,5 @@
+account_id,user_id
+a1,U111
+a2,U112
+a3,U113
+
diff --git a/plugins/org/e2e/raw_tables/users.csv b/plugins/org/e2e/raw_tables/users.csv
new file mode 100644
index 00000000..afab7c04
--- /dev/null
+++ b/plugins/org/e2e/raw_tables/users.csv
@@ -0,0 +1,11 @@
+"id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","email","name"
+"U001","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e1","n1"
+"U002","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e2","n2"
+"U003","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e3","n3"
+"U004","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e4","n4"
+"U005","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e5","n5"
+"U006","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e6","n6"
+"U007","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e7","n7"
+"U008","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e8","n8"
+"U009","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e9","n9"
+"U010","2022-07-10 15:29:51.239","2022-07-10 15:29:51.239","","",0,"","e10","n10"
diff --git a/plugins/org/e2e/snapshot_tables/user_accounts.csv b/plugins/org/e2e/snapshot_tables/user_accounts.csv
new file mode 100644
index 00000000..2acffef1
--- /dev/null
+++ b/plugins/org/e2e/snapshot_tables/user_accounts.csv
@@ -0,0 +1,11 @@
+account_id,user_id
+a1,U111
+a10,U010
+a2,U112
+a3,U113
+a4,U004
+a5,U005
+a6,U005
+a7,U005
+a8,U008
+a9,U009
diff --git a/plugins/org/e2e/user_account_test.go b/plugins/org/e2e/user_account_test.go
new file mode 100644
index 00000000..24f20169
--- /dev/null
+++ b/plugins/org/e2e/user_account_test.go
@@ -0,0 +1,56 @@
+/*
+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 e2e
+
+import (
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "testing"
+
+ "github.com/apache/incubator-devlake/helpers/e2ehelper"
+ "github.com/apache/incubator-devlake/plugins/org/impl"
+ "github.com/apache/incubator-devlake/plugins/org/tasks"
+)
+
+func TestUserAccountDataFlow(t *testing.T) {
+ var plugin impl.Org
+ dataflowTester := e2ehelper.NewDataFlowTester(t, "org", plugin)
+
+ taskData := &tasks.TaskData{
+ Options: &tasks.Options{
+ ConnectionId: 2,
+ },
+ }
+
+ // import raw data table
+ dataflowTester.FlushTabler(&crossdomain.User{})
+ dataflowTester.FlushTabler(&crossdomain.Account{})
+ dataflowTester.FlushTabler(&crossdomain.UserAccount{})
+ dataflowTester.ImportCsvIntoTabler("./raw_tables/users.csv", &crossdomain.User{})
+ dataflowTester.ImportCsvIntoTabler("./raw_tables/accounts.csv", &crossdomain.Account{})
+ dataflowTester.ImportCsvIntoTabler("./raw_tables/user_accounts.csv", &crossdomain.UserAccount{})
+
+ dataflowTester.Subtask(tasks.ConnectUserAccountsExactMeta, taskData)
+ dataflowTester.VerifyTable(
+ crossdomain.UserAccount{},
+ "./snapshot_tables/user_accounts.csv",
+ []string{
+ "user_id",
+ "account_id",
+ },
+ )
+}
diff --git a/plugins/org/impl/impl.go b/plugins/org/impl/impl.go
new file mode 100644
index 00000000..4faec005
--- /dev/null
+++ b/plugins/org/impl/impl.go
@@ -0,0 +1,87 @@
+/*
+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 (
+ "github.com/apache/incubator-devlake/impl/dalgorm"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/helper"
+ "github.com/apache/incubator-devlake/plugins/org/api"
+ "github.com/apache/incubator-devlake/plugins/org/tasks"
+ "github.com/mitchellh/mapstructure"
+ "github.com/spf13/viper"
+ "gorm.io/gorm"
+)
+
+var _ core.PluginMeta = (*Org)(nil)
+var _ core.PluginInit = (*Org)(nil)
+var _ core.PluginTask = (*Org)(nil)
+
+type Org struct {
+ handlers *api.Handlers
+}
+
+func (plugin *Org) Init(config *viper.Viper, logger core.Logger, db *gorm.DB) error {
+ basicRes := helper.NewDefaultBasicRes(config, logger, db)
+ plugin.handlers = api.NewHandlers(dalgorm.NewDalgorm(db), basicRes)
+ return nil
+}
+
+func (plugin Org) Description() string {
+ return "collect data related to team and organization"
+}
+
+func (plugin Org) SubTaskMetas() []core.SubTaskMeta {
+ return []core.SubTaskMeta{
+ tasks.ConnectUserAccountsExactMeta,
+ }
+}
+
+func (plugin Org) PrepareTaskData(taskCtx core.TaskContext, options map[string]interface{}) (interface{}, error) {
+ var op tasks.Options
+ err := mapstructure.Decode(options, &op)
+ if err != nil {
+ return nil, err
+ }
+ taskData := &tasks.TaskData{
+ Options: &op,
+ }
+ return taskData, nil
+}
+
+func (plugin Org) RootPkgPath() string {
+ return "github.com/apache/incubator-devlake/plugins/org"
+}
+
+func (plugin Org) ApiResources() map[string]map[string]core.ApiResourceHandler {
+ return map[string]map[string]core.ApiResourceHandler{
+ "teams.csv": {
+ "GET": plugin.handlers.GetTeam,
+ "PUT": plugin.handlers.CreateTeam,
+ },
+ "users.csv": {
+ "GET": plugin.handlers.GetUser,
+ "PUT": plugin.handlers.CreateUser,
+ },
+
+ "user_account_mapping.csv": {
+ "GET": plugin.handlers.GetUserAccountMapping,
+ "PUT": plugin.handlers.CreateUserAccountMapping,
+ },
+ }
+}
diff --git a/models/migrationscripts/archived/team_user.go b/plugins/org/org.go
similarity index 78%
copy from models/migrationscripts/archived/team_user.go
copy to plugins/org/org.go
index 9cd3bf27..5cb7a393 100644
--- a/models/migrationscripts/archived/team_user.go
+++ b/plugins/org/org.go
@@ -15,13 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package archived
+package main
-type TeamUser struct {
- TeamId string `gorm:"primaryKey;type:varchar(255)"`
- UserId string `gorm:"primaryKey;type:varchar(255)"`
-}
+import "github.com/apache/incubator-devlake/plugins/org/impl"
-func (TeamUser) TableName() string {
- return "team_users"
-}
+var PluginEntry impl.Org //nolint
diff --git a/models/migrationscripts/archived/team_user.go b/plugins/org/tasks/task_data.go
similarity index 79%
copy from models/migrationscripts/archived/team_user.go
copy to plugins/org/tasks/task_data.go
index 9cd3bf27..f78913d2 100644
--- a/models/migrationscripts/archived/team_user.go
+++ b/plugins/org/tasks/task_data.go
@@ -15,13 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package archived
+package tasks
-type TeamUser struct {
- TeamId string `gorm:"primaryKey;type:varchar(255)"`
- UserId string `gorm:"primaryKey;type:varchar(255)"`
+type Options struct {
+ ConnectionId uint64 `json:"connectionId"`
}
-func (TeamUser) TableName() string {
- return "team_users"
+type TaskData struct {
+ Options *Options
+}
+type Params struct {
+ ConnectionId uint64
}
diff --git a/plugins/org/tasks/user_account.go b/plugins/org/tasks/user_account.go
new file mode 100644
index 00000000..0d69207d
--- /dev/null
+++ b/plugins/org/tasks/user_account.go
@@ -0,0 +1,116 @@
+/*
+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/models/common"
+ "reflect"
+
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/apache/incubator-devlake/plugins/core/dal"
+ "github.com/apache/incubator-devlake/plugins/helper"
+)
+
+var ConnectUserAccountsExactMeta = core.SubTaskMeta{
+ Name: "connectUserAccountsExact",
+ EntryPoint: ConnectUserAccountsExact,
+ EnabledByDefault: true,
+ Description: "associate users and accounts",
+ DomainTypes: []string{core.DOMAIN_TYPE_CROSS},
+}
+
+func ConnectUserAccountsExact(taskCtx core.SubTaskContext) error {
+ db := taskCtx.GetDal()
+ data := taskCtx.GetData().(*TaskData)
+ type input struct {
+ UserId string
+ AccountId string
+ common.NoPKModel
+ }
+ var users []crossdomain.User
+ err := db.All(&users)
+ if err != nil {
+ return err
+ }
+ emails := make(map[string]string)
+ names := make(map[string]string)
+ for _, user := range users {
+ if user.Email != "" {
+ emails[user.Email] = user.Id
+ }
+ if user.Name != "" {
+ names[user.Name] = user.Id
+ }
+ }
+ clauses := []dal.Clause{
+ dal.Select("*"),
+ dal.From(&crossdomain.Account{}),
+ dal.Where("id NOT IN (SELECT account_id FROM user_accounts)"),
+ }
+ cursor, err := db.Cursor(clauses...)
+ if err != nil {
+ return err
+ }
+ defer cursor.Close()
+
+ converter, err := helper.NewDataConverter(helper.DataConverterArgs{
+ InputRowType: reflect.TypeOf(crossdomain.Account{}),
+ Input: cursor,
+ RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+ Ctx: taskCtx,
+ Params: Params{
+ ConnectionId: data.Options.ConnectionId,
+ },
+ Table: "users",
+ },
+
+ Convert: func(inputRow interface{}) ([]interface{}, error) {
+ account := inputRow.(*crossdomain.Account)
+ if userId, ok := emails[account.Email]; account.Email != "" && ok {
+ return []interface{}{
+ &crossdomain.UserAccount{
+ UserId: userId,
+ AccountId: account.Id,
+ },
+ }, nil
+ }
+ if userId, ok := names[account.FullName]; account.FullName != "" && ok {
+ return []interface{}{
+ &crossdomain.UserAccount{
+ UserId: userId,
+ AccountId: account.Id,
+ },
+ }, nil
+ }
+ if userId, ok := names[account.UserName]; account.UserName != "" && ok {
+ return []interface{}{
+ &crossdomain.UserAccount{
+ UserId: userId,
+ AccountId: account.Id,
+ },
+ }, nil
+ }
+ return nil, nil
+ },
+ })
+ if err != nil {
+ return err
+ }
+ return converter.Execute()
+}