Skip to content

Commit d8c9677

Browse files
committed
feat: Add claude code metrics data integration
(cherry picked from commit 78fd47d9e7647ff3ac8b886b2ddb45f1df55e2b4) (cherry picked from commit fa69cf9ec0fa0db6aa5fb0aec2e5ca2796355ffd) (upstream) story: XPL-551: Add license headers (cherry picked from commit a12ece8c1ca65ff27acc14d4630ac3d404d51ce8)
1 parent 3a1cf9c commit d8c9677

62 files changed

Lines changed: 6761 additions & 13 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/claude_code/models"
27+
"github.com/apache/incubator-devlake/plugins/claude_code/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.ClaudeCodeScope, models.ClaudeCodeScopeConfig],
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+
"claude_code",
67+
subtaskMetas,
68+
nil,
69+
tasks.ClaudeCodeOptions{
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: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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/claude_code/models"
27+
)
28+
29+
// PostConnections creates a new Claude Code connection.
30+
func PostConnections(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, errors.Error) {
31+
connection := &models.ClaudeCodeConnection{}
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.ClaudeCodeConnection{}
49+
if err := connectionHelper.First(connection, input.Params); err != nil {
50+
return nil, err
51+
}
52+
if err := (&models.ClaudeCodeConnection{}).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.ClaudeCodeConnection{}
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.ClaudeCodeConnection
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.ClaudeCodeConnection{}
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.ClaudeCodeConnection) errors.Error {
95+
if connection == nil {
96+
return errors.BadInput.New("connection is required")
97+
}
98+
hasToken := strings.TrimSpace(connection.Token) != ""
99+
hasCustomHeaders := len(connection.CustomHeaders) > 0
100+
if !hasToken && !hasCustomHeaders {
101+
return errors.BadInput.New("either token or at least one custom header is required")
102+
}
103+
if strings.TrimSpace(connection.Organization) == "" {
104+
return errors.BadInput.New("organization is required")
105+
}
106+
if connection.RateLimitPerHour < 0 {
107+
return errors.BadInput.New("rateLimitPerHour must be non-negative")
108+
}
109+
return nil
110+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
25+
"github.com/apache/incubator-devlake/plugins/claude_code/models"
26+
)
27+
28+
const (
29+
testOrganization = "anthropic-labs"
30+
testToken = "sk-ant-example"
31+
)
32+
33+
func TestValidateConnectionSuccess(t *testing.T) {
34+
connection := &models.ClaudeCodeConnection{
35+
ClaudeCodeConn: models.ClaudeCodeConn{
36+
Organization: testOrganization,
37+
Token: testToken,
38+
},
39+
}
40+
connection.Normalize()
41+
42+
err := validateConnection(connection)
43+
assert.NoError(t, err)
44+
}
45+
46+
func TestValidateConnectionNil(t *testing.T) {
47+
err := validateConnection(nil)
48+
assert.Error(t, err)
49+
assert.Contains(t, err.Error(), "connection is required")
50+
}
51+
52+
func TestValidateConnectionMissingOrganization(t *testing.T) {
53+
connection := &models.ClaudeCodeConnection{
54+
ClaudeCodeConn: models.ClaudeCodeConn{
55+
Token: testToken,
56+
},
57+
}
58+
59+
err := validateConnection(connection)
60+
assert.Error(t, err)
61+
assert.Contains(t, err.Error(), "organization is required")
62+
}
63+
64+
func TestValidateConnectionMissingToken(t *testing.T) {
65+
connection := &models.ClaudeCodeConnection{
66+
ClaudeCodeConn: models.ClaudeCodeConn{
67+
Organization: testOrganization,
68+
},
69+
}
70+
71+
err := validateConnection(connection)
72+
assert.Error(t, err)
73+
assert.Contains(t, err.Error(), "either token or at least one custom header is required")
74+
}
75+
76+
func TestValidateConnectionCustomHeadersWithoutToken(t *testing.T) {
77+
connection := &models.ClaudeCodeConnection{
78+
ClaudeCodeConn: models.ClaudeCodeConn{
79+
Organization: testOrganization,
80+
CustomHeaders: []models.CustomHeader{
81+
{Key: "Ocp-Apim-Subscription-Key", Value: "secret-key"},
82+
},
83+
},
84+
}
85+
connection.Normalize()
86+
87+
err := validateConnection(connection)
88+
assert.NoError(t, err)
89+
}
90+
91+
func TestValidateConnectionInvalidRateLimit(t *testing.T) {
92+
connection := &models.ClaudeCodeConnection{
93+
ClaudeCodeConn: models.ClaudeCodeConn{
94+
Organization: testOrganization,
95+
Token: testToken,
96+
},
97+
}
98+
connection.RateLimitPerHour = -1
99+
100+
err := validateConnection(connection)
101+
assert.Error(t, err)
102+
assert.Contains(t, err.Error(), "rateLimitPerHour must be non-negative")
103+
}
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/claude_code/models"
27+
)
28+
29+
var (
30+
basicRes context.BasicRes
31+
vld *validator.Validate
32+
connectionHelper *helper.ConnectionApiHelper
33+
dsHelper *helper.DsHelper[models.ClaudeCodeConnection, models.ClaudeCodeScope, models.ClaudeCodeScopeConfig]
34+
raProxy *helper.DsRemoteApiProxyHelper[models.ClaudeCodeConnection]
35+
raScopeList *helper.DsRemoteApiScopeListHelper[models.ClaudeCodeConnection, models.ClaudeCodeScope, ClaudeCodeRemotePagination]
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.ClaudeCodeConnection, models.ClaudeCodeScope, models.ClaudeCodeScopeConfig,
45+
](
46+
basicRes,
47+
meta.Name(),
48+
[]string{"id", "organizationId"},
49+
func(c models.ClaudeCodeConnection) models.ClaudeCodeConnection {
50+
c.Normalize()
51+
return c.Sanitize()
52+
},
53+
func(s models.ClaudeCodeScope) models.ClaudeCodeScope { return s },
54+
nil,
55+
)
56+
raProxy = helper.NewDsRemoteApiProxyHelper[models.ClaudeCodeConnection](dsHelper.ConnApi.ModelApiHelper)
57+
raScopeList = helper.NewDsRemoteApiScopeListHelper[models.ClaudeCodeConnection, models.ClaudeCodeScope, ClaudeCodeRemotePagination](raProxy, listClaudeCodeRemoteScopes)
58+
}

0 commit comments

Comments
 (0)