Skip to content

Commit 740425b

Browse files
committed
feat(cursor): add plugin for team usage and cost collection
Signed-off-by: Joshua Smith <jbsmith7741@gmail.com>
1 parent 7f7cf09 commit 740425b

41 files changed

Lines changed: 2810 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"github.com/apache/incubator-devlake/core/errors"
22+
coreModels "github.com/apache/incubator-devlake/core/models"
23+
"github.com/apache/incubator-devlake/core/plugin"
24+
helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
25+
"github.com/apache/incubator-devlake/helpers/srvhelper"
26+
"github.com/apache/incubator-devlake/plugins/cursor/models"
27+
"github.com/apache/incubator-devlake/plugins/cursor/tasks"
28+
)
29+
30+
// MakeDataSourcePipelinePlanV200 generates the pipeline plan for blueprint v2.0.0.
31+
func MakeDataSourcePipelinePlanV200(
32+
subtaskMetas []plugin.SubTaskMeta,
33+
connectionId uint64,
34+
bpScopes []*coreModels.BlueprintScope,
35+
) (coreModels.PipelinePlan, []plugin.Scope, errors.Error) {
36+
_, err := dsHelper.ConnSrv.FindByPk(connectionId)
37+
if err != nil {
38+
return nil, nil, err
39+
}
40+
scopeDetails, err := dsHelper.ScopeSrv.MapScopeDetails(connectionId, bpScopes)
41+
if err != nil {
42+
return nil, nil, err
43+
}
44+
45+
plan, err := makeDataSourcePipelinePlanV200(subtaskMetas, scopeDetails)
46+
if err != nil {
47+
return nil, nil, err
48+
}
49+
50+
return plan, nil, nil
51+
}
52+
53+
func makeDataSourcePipelinePlanV200(
54+
subtaskMetas []plugin.SubTaskMeta,
55+
scopeDetails []*srvhelper.ScopeDetail[models.CursorScope, models.CursorScopeConfig],
56+
) (coreModels.PipelinePlan, errors.Error) {
57+
plan := make(coreModels.PipelinePlan, len(scopeDetails))
58+
for i, scopeDetail := range scopeDetails {
59+
stage := plan[i]
60+
if stage == nil {
61+
stage = coreModels.PipelineStage{}
62+
}
63+
64+
scope := scopeDetail.Scope
65+
task, err := helper.MakePipelinePlanTask(
66+
"cursor",
67+
subtaskMetas,
68+
nil,
69+
tasks.CursorOptions{
70+
ConnectionId: scope.ConnectionId,
71+
ScopeId: scope.Id,
72+
},
73+
)
74+
if err != nil {
75+
return nil, err
76+
}
77+
stage = append(stage, task)
78+
plan[i] = stage
79+
}
80+
return plan, nil
81+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"strings"
22+
23+
"github.com/apache/incubator-devlake/core/errors"
24+
"github.com/apache/incubator-devlake/core/plugin"
25+
helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
26+
"github.com/apache/incubator-devlake/plugins/cursor/models"
27+
)
28+
29+
// PostConnections creates a new Cursor connection.
30+
func PostConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
31+
connection := &models.CursorConnection{}
32+
if err := helper.Decode(input.Body, connection, vld); err != nil {
33+
return nil, err
34+
}
35+
36+
connection.Normalize()
37+
if err := validateConnection(connection); err != nil {
38+
return nil, err
39+
}
40+
41+
if err := connectionHelper.Create(connection, input); err != nil {
42+
return nil, err
43+
}
44+
return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, nil
45+
}
46+
47+
func PatchConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
48+
connection := &models.CursorConnection{}
49+
if err := connectionHelper.First(connection, input.Params); err != nil {
50+
return nil, err
51+
}
52+
if err := (&models.CursorConnection{}).MergeFromRequest(connection, input.Body); err != nil {
53+
return nil, errors.Convert(err)
54+
}
55+
connection.Normalize()
56+
if err := validateConnection(connection); err != nil {
57+
return nil, err
58+
}
59+
if err := connectionHelper.SaveWithCreateOrUpdate(connection); err != nil {
60+
return nil, err
61+
}
62+
return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, nil
63+
}
64+
65+
func DeleteConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
66+
conn := &models.CursorConnection{}
67+
output, err := connectionHelper.Delete(conn, input)
68+
if err != nil {
69+
return output, err
70+
}
71+
output.Body = conn.Sanitize()
72+
return output, nil
73+
}
74+
75+
func ListConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
76+
var connections []models.CursorConnection
77+
if err := connectionHelper.List(&connections); err != nil {
78+
return nil, err
79+
}
80+
for i := range connections {
81+
connections[i] = connections[i].Sanitize()
82+
}
83+
return &plugin.ApiResourceOutput{Body: connections}, nil
84+
}
85+
86+
func GetConnection(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
87+
connection := &models.CursorConnection{}
88+
if err := connectionHelper.First(connection, input.Params); err != nil {
89+
return nil, err
90+
}
91+
return &plugin.ApiResourceOutput{Body: connection.Sanitize()}, nil
92+
}
93+
94+
func validateConnection(connection *models.CursorConnection) errors.Error {
95+
if connection == nil {
96+
return errors.BadInput.New("connection is required")
97+
}
98+
if strings.TrimSpace(connection.Token) == "" {
99+
return errors.BadInput.New("token is required")
100+
}
101+
if connection.RateLimitPerHour < 0 {
102+
return errors.BadInput.New("rateLimitPerHour must be non-negative")
103+
}
104+
return nil
105+
}

backend/plugins/cursor/api/init.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"github.com/go-playground/validator/v10"
22+
23+
"github.com/apache/incubator-devlake/core/context"
24+
"github.com/apache/incubator-devlake/core/plugin"
25+
helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
26+
"github.com/apache/incubator-devlake/plugins/cursor/models"
27+
)
28+
29+
var (
30+
basicRes context.BasicRes
31+
vld *validator.Validate
32+
connectionHelper *helper.ConnectionApiHelper
33+
dsHelper *helper.DsHelper[models.CursorConnection, models.CursorScope, models.CursorScopeConfig]
34+
raProxy *helper.DsRemoteApiProxyHelper[models.CursorConnection]
35+
raScopeList *helper.DsRemoteApiScopeListHelper[models.CursorConnection, models.CursorScope, CursorRemotePagination]
36+
)
37+
38+
// Init stores basic resources and configures shared helpers for API handlers.
39+
func Init(br context.BasicRes, meta plugin.PluginMeta) {
40+
basicRes = br
41+
vld = validator.New()
42+
connectionHelper = helper.NewConnectionHelper(basicRes, vld, meta.Name())
43+
dsHelper = helper.NewDataSourceHelper[
44+
models.CursorConnection, models.CursorScope, models.CursorScopeConfig,
45+
](
46+
basicRes,
47+
meta.Name(),
48+
[]string{"id", "teamId"},
49+
func(c models.CursorConnection) models.CursorConnection {
50+
c.Normalize()
51+
return c.Sanitize()
52+
},
53+
func(s models.CursorScope) models.CursorScope { return s },
54+
nil,
55+
)
56+
raProxy = helper.NewDsRemoteApiProxyHelper[models.CursorConnection](dsHelper.ConnApi.ModelApiHelper)
57+
raScopeList = helper.NewDsRemoteApiScopeListHelper[models.CursorConnection, models.CursorScope, CursorRemotePagination](raProxy, listCursorRemoteScopes)
58+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"strings"
22+
23+
"github.com/apache/incubator-devlake/core/errors"
24+
"github.com/apache/incubator-devlake/core/plugin"
25+
helperapi "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
26+
dsmodels "github.com/apache/incubator-devlake/helpers/pluginhelper/api/models"
27+
"github.com/apache/incubator-devlake/helpers/utils"
28+
"github.com/apache/incubator-devlake/plugins/cursor/models"
29+
)
30+
31+
// CursorRemotePagination is a placeholder for remote scope pagination.
32+
type CursorRemotePagination struct {
33+
Page int `json:"page"`
34+
}
35+
36+
func listCursorRemoteScopes(
37+
_ *models.CursorConnection,
38+
_ plugin.ApiClient,
39+
_ string,
40+
_ CursorRemotePagination,
41+
) (
42+
children []dsmodels.DsRemoteApiScopeListEntry[models.CursorScope],
43+
nextPage *CursorRemotePagination,
44+
err errors.Error,
45+
) {
46+
children = append(children, makeCursorRemoteScopeEntry())
47+
return children, nil, nil
48+
}
49+
50+
func makeCursorRemoteScopeEntry() dsmodels.DsRemoteApiScopeListEntry[models.CursorScope] {
51+
return dsmodels.DsRemoteApiScopeListEntry[models.CursorScope]{
52+
Type: helperapi.RAS_ENTRY_TYPE_SCOPE,
53+
Id: models.DefaultScopeID,
54+
Name: "Team",
55+
FullName: "Cursor Team",
56+
Data: &models.CursorScope{
57+
Id: models.DefaultScopeID,
58+
Name: "Team",
59+
FullName: "Cursor Team",
60+
},
61+
}
62+
}
63+
64+
// RemoteScopes lists all available scopes for this connection.
65+
func RemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
66+
return raScopeList.Get(input)
67+
}
68+
69+
// SearchRemoteScopes searches scopes for this connection.
70+
func SearchRemoteScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
71+
params := &dsmodels.DsRemoteApiScopeSearchParams{
72+
Page: 1,
73+
PageSize: 50,
74+
}
75+
if err := utils.DecodeMapStruct(input.Query, params, true); err != nil {
76+
return nil, err
77+
}
78+
if err := errors.Convert(vld.Struct(params)); err != nil {
79+
return nil, errors.BadInput.Wrap(err, "invalid params")
80+
}
81+
82+
children := []dsmodels.DsRemoteApiScopeListEntry[models.CursorScope]{}
83+
searchLower := strings.ToLower(strings.TrimSpace(params.Search))
84+
if searchLower == "" || searchLower == "team" || searchLower == "cursor" {
85+
children = append(children, makeCursorRemoteScopeEntry())
86+
}
87+
88+
return &plugin.ApiResourceOutput{
89+
Body: map[string]interface{}{
90+
"children": children,
91+
"page": params.Page,
92+
"pageSize": params.PageSize,
93+
},
94+
}, nil
95+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"github.com/apache/incubator-devlake/core/errors"
22+
"github.com/apache/incubator-devlake/core/plugin"
23+
)
24+
25+
// GetScopeList returns all scopes for a connection.
26+
func GetScopeList(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
27+
return dsHelper.ScopeApi.GetPage(input)
28+
}
29+
30+
// PutScopes creates or updates scopes for a connection.
31+
func PutScopes(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
32+
return dsHelper.ScopeApi.PutMultiple(input)
33+
}
34+
35+
// GetScope returns a single scope by id.
36+
func GetScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
37+
return dsHelper.ScopeApi.GetScopeDetail(input)
38+
}
39+
40+
// PatchScope partially updates a scope.
41+
func PatchScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
42+
return dsHelper.ScopeApi.Patch(input)
43+
}
44+
45+
// DeleteScope removes a scope.
46+
func DeleteScope(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
47+
return dsHelper.ScopeApi.Delete(input)
48+
}
49+
50+
// GetScopeLatestSyncState returns the latest sync state for a scope.
51+
func GetScopeLatestSyncState(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
52+
return dsHelper.ScopeApi.GetScopeLatestSyncState(input)
53+
}

0 commit comments

Comments
 (0)