feature: Add claude code metrics data integration#8808
feature: Add claude code metrics data integration#8808la-tamas wants to merge 1 commit intoapache:mainfrom
Conversation
(cherry picked from commit 78fd47d9e7647ff3ac8b886b2ddb45f1df55e2b4) (cherry picked from commit fa69cf9ec0fa0db6aa5fb0aec2e5ca2796355ffd) (upstream) story: XPL-551: Add license headers (cherry picked from commit a12ece8c1ca65ff27acc14d4630ac3d404d51ce8)
There was a problem hiding this comment.
Pull request overview
Adds a new Claude Code data source integration to DevLake, including backend collectors/extractors for Anthropic analytics metrics, Config UI connection/scope support, and Grafana dashboards to visualize adoption.
Changes:
- Introduces a new
claude_codebackend plugin (models, migrations, API endpoints, pipeline plan, and analytics collectors/extractors). - Extends Config UI to register/configure the Claude Code plugin, including token/org/custom-header connection fields.
- Adds two Grafana dashboards for org/user adoption views based on the new tool-layer tables.
Reviewed changes
Copilot reviewed 61 out of 62 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| grafana/dashboards/ClaudeCodeAdoption.json | New org-level Claude Code adoption dashboard querying _tool_claude_code_* tables. |
| grafana/dashboards/ClaudeCodeAdoptionByUser.json | New user-focused dashboard with row-level access filtering patterns in SQL. |
| config-ui/src/types/connection.ts | Extends connection typings to include custom headers and org/admin key fields. |
| config-ui/src/plugins/utils.ts | Adds claude_code scope naming logic and plugin alias mapping. |
| config-ui/src/plugins/register/index.ts | Registers the Claude Code plugin in the UI plugin list. |
| config-ui/src/plugins/register/claude-code/index.ts | Exports Claude Code plugin UI config module. |
| config-ui/src/plugins/register/claude-code/config.tsx | Defines Claude Code UI config: connection defaults, fields, entities. |
| config-ui/src/plugins/register/claude-code/assets/icon.svg | Adds Claude Code plugin icon. |
| config-ui/src/plugins/register/claude-code/connection-fields/index.ts | Exposes Claude Code connection field components. |
| config-ui/src/plugins/register/claude-code/connection-fields/organization.tsx | UI field to collect Anthropic organization identifier. |
| config-ui/src/plugins/register/claude-code/connection-fields/token.tsx | UI field to collect Anthropic API key (token) with conditional requirement. |
| config-ui/src/plugins/register/claude-code/connection-fields/custom-headers.tsx | UI to configure custom auth headers (middleware/proxy scenarios). |
| config-ui/src/plugins/register/claude-code/connection-fields/styled.ts | Shared styling for Claude Code connection field validation text. |
| config-ui/src/plugins/components/connection-form/index.tsx | Ensures connection test payload includes rateLimitPerHour and customHeaders. |
| config-ui/src/features/connections/utils.ts | Simplifies connection transformation by spreading API payload into UI model. |
| config-ui/src/config/entities.ts | Adds CLAUDE_CODE entity label for UI display. |
| config-ui/src/api/connection/index.ts | Extends connection test payload typing to include new connection fields. |
| backend/plugins/claude_code/claude_code.go | Adds standalone runner entry for debugging the plugin. |
| backend/plugins/claude_code/impl/impl.go | Implements plugin interfaces: init, API resources, models, subtasks, pipeline plan, migrations. |
| backend/plugins/claude_code/impl/connection_helper.go | Normalization helper for Claude Code connections. |
| backend/plugins/claude_code/api/init.go | Initializes API helpers for connections/scopes/scope-configs and remote scope listing. |
| backend/plugins/claude_code/api/connection.go | Connection CRUD + validation for Claude Code connections. |
| backend/plugins/claude_code/api/test_connection.go | Test connection endpoints (new + existing connection override). |
| backend/plugins/claude_code/api/connection_test.go | Unit tests for connection validation rules. |
| backend/plugins/claude_code/api/remote_api.go | Remote scope listing/search implementation (organization as scope). |
| backend/plugins/claude_code/api/scope.go | Scope CRUD endpoints via shared DS helper. |
| backend/plugins/claude_code/api/scope_config.go | Scope config CRUD endpoints via shared DS helper. |
| backend/plugins/claude_code/api/blueprint_v200.go | Generates blueprint v200 pipeline plan for Claude Code scopes. |
| backend/plugins/claude_code/service/connection_test_helper.go | Implements HTTP-level connection test logic against Anthropic endpoints. |
| backend/plugins/claude_code/models/models.go | Registers tool-layer tables for the plugin. |
| backend/plugins/claude_code/models/connection.go | Defines connection model, auth header setup, sanitization, and PATCH merge behavior. |
| backend/plugins/claude_code/models/connection_test.go | Tests secret-preserving merge behavior for token/custom headers. |
| backend/plugins/claude_code/models/scope.go | Defines organization scope model and normalization in BeforeSave. |
| backend/plugins/claude_code/models/scope_config.go | Defines tool-layer scope config model. |
| backend/plugins/claude_code/models/activity_summary.go | Tool-layer model for org activity summary metrics. |
| backend/plugins/claude_code/models/user_activity.go | Tool-layer model for per-user daily analytics metrics. |
| backend/plugins/claude_code/models/chat_project.go | Tool-layer model for chat project usage metrics. |
| backend/plugins/claude_code/models/skill_usage.go | Tool-layer model for skill usage metrics. |
| backend/plugins/claude_code/models/connector_usage.go | Tool-layer model for connector usage metrics. |
| backend/plugins/claude_code/models/migrationscripts/register.go | Registers Claude Code plugin migration scripts. |
| backend/plugins/claude_code/models/migrationscripts/20260309_initialize.go | Creates initial Claude Code connection/scope/scope-config tables. |
| backend/plugins/claude_code/models/migrationscripts/20260319_add_custom_headers.go | Adds custom headers support to connection table schema. |
| backend/plugins/claude_code/models/migrationscripts/20260319_replace_analytics_tables.go | Adds new analytics tool-layer + raw tables for the updated endpoints. |
| backend/plugins/claude_code/tasks/options.go | Defines task options (connectionId, scopeId). |
| backend/plugins/claude_code/tasks/task_data.go | Defines runtime task data container passed through subtasks. |
| backend/plugins/claude_code/tasks/subtasks.go | Declares subtask metas for collecting/extracting analytics datasets. |
| backend/plugins/claude_code/tasks/register.go | Returns the ordered list of Claude Code subtask metas. |
| backend/plugins/claude_code/tasks/api_client.go | Creates API client with rate limiting and Retry-After handling. |
| backend/plugins/claude_code/tasks/retry_after.go | Parses HTTP Retry-After header into a duration. |
| backend/plugins/claude_code/tasks/collector_utils.go | Shared collectors utilities: date windows, iterators, pagination parsing. |
| backend/plugins/claude_code/tasks/collector_utils_test.go | Unit tests for date-range computation logic. |
| backend/plugins/claude_code/tasks/activity_summary_collector.go | Collector for org summaries endpoint (chunked date ranges). |
| backend/plugins/claude_code/tasks/activity_summary_extractor.go | Extractor for org summary records into tool-layer table. |
| backend/plugins/claude_code/tasks/user_activity_collector.go | Collector for per-user daily analytics endpoint. |
| backend/plugins/claude_code/tasks/user_activity_extractor.go | Extractor for per-user activity records into tool-layer table. |
| backend/plugins/claude_code/tasks/user_activity_extractor_test.go | Unit tests around user activity email normalization/filter helpers. |
| backend/plugins/claude_code/tasks/chat_project_collector.go | Collector for chat project usage endpoint. |
| backend/plugins/claude_code/tasks/chat_project_extractor.go | Extractor for chat project usage records into tool-layer table. |
| backend/plugins/claude_code/tasks/skill_usage_collector.go | Collector for skill usage endpoint. |
| backend/plugins/claude_code/tasks/skill_usage_extractor.go | Extractor for skill usage records into tool-layer table. |
| backend/plugins/claude_code/tasks/connector_usage_collector.go | Collector for connector usage endpoint. |
| backend/plugins/claude_code/tasks/connector_usage_extractor.go | Extractor for connector usage records into tool-layer table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const hasCustomHeaders = (values.customHeaders ?? []).length > 0; | ||
|
|
||
| const error = useMemo(() => { | ||
| if (type === 'update') return ''; | ||
| if (hasCustomHeaders) return ''; | ||
| return values.token?.trim() ? '' : 'Anthropic API Key is required (unless custom headers are configured)'; | ||
| }, [type, values.token, hasCustomHeaders]); |
There was a problem hiding this comment.
hasCustomHeaders is currently based only on customHeaders.length > 0, so users can bypass the required API key by adding an empty custom header row. This matches the backend behavior (it only checks slice length) and can lead to saving an unauthenticated connection. Consider computing hasCustomHeaders based on at least one header with a non-empty key/value (and setting an error if headers are present but invalid).
| const headers: Array<{ key: string; value: string }> = values.customHeaders ?? []; | ||
|
|
||
| useEffect(() => { | ||
| setValues({ customHeaders: initialValues.customHeaders ?? [] }); | ||
| }, [type, initialValues.customHeaders]); | ||
|
|
||
| const addHeader = () => { | ||
| setValues({ customHeaders: [...headers, { key: '', value: '' }] }); | ||
| }; |
There was a problem hiding this comment.
The UI allows adding blank custom header entries ({ key: '', value: '' }). Because token-required logic and backend validation currently treat any non-empty header array as sufficient, this can accidentally create connections that have neither a valid token nor any effective auth headers. Consider preventing add/save when header key/value are empty, and/or filtering out empty entries before persisting.
| // replaceClaudeCodeAnalyticsTables drops the old deprecated endpoint tables and | ||
| // creates the five new analytics endpoint tables. | ||
| type replaceClaudeCodeAnalyticsTables struct{} | ||
|
|
||
| func (*replaceClaudeCodeAnalyticsTables) Up(basicRes context.BasicRes) errors.Error { | ||
| return migrationhelper.AutoMigrateTables( | ||
| basicRes, | ||
| &ccUserActivity20260319{}, |
There was a problem hiding this comment.
The comment says this migration "drops the old deprecated endpoint tables", but Up only calls AutoMigrateTables (which typically creates/updates tables but does not drop old ones). If dropping is required to avoid stale tables/data, add explicit drop/cleanup logic; otherwise update the comment to reflect what the migration actually does.
| hasToken := strings.TrimSpace(connection.Token) != "" | ||
| hasCustomHeaders := len(connection.CustomHeaders) > 0 | ||
| if !hasToken && !hasCustomHeaders { | ||
| return errors.BadInput.New("either token or at least one custom header is required") | ||
| } | ||
| if strings.TrimSpace(connection.Organization) == "" { | ||
| return errors.BadInput.New("organization is required") | ||
| } |
There was a problem hiding this comment.
validateConnection treats any non-empty CustomHeaders slice as valid authentication. This allows saving/testing a connection with only blank header entries (e.g., {key:'', value:''}), which will not set any headers in SetupAuthentication and will lead to auth failures at runtime. Consider validating that at least one custom header has a non-empty key (and likely a non-empty value) before accepting it as an auth alternative to Token.
| // Use today's date for the test request. | ||
| today := time.Now().UTC().Format("2006-01-02") | ||
| endpoint := fmt.Sprintf("v1/organizations/usage_report/claude_code?starting_at=%s&limit=1", today) |
There was a problem hiding this comment.
The connection test uses today's date (time.Now().UTC()) even though collectors explicitly assume the Analytics API requires data to be at least a few days old (see claudeCodeAvailabilityLagDays usage in tasks). This can make the test endpoint fail for otherwise-correct credentials. Use a date that is guaranteed to be outside the availability lag window (e.g., now minus the lag days) for the test request.
| // Use today's date for the test request. | |
| today := time.Now().UTC().Format("2006-01-02") | |
| endpoint := fmt.Sprintf("v1/organizations/usage_report/claude_code?starting_at=%s&limit=1", today) | |
| // Use a date safely outside the analytics availability lag window for the test request. | |
| testDate := time.Now().UTC().AddDate(0, 0, -7).Format("2006-01-02") | |
| endpoint := fmt.Sprintf("v1/organizations/usage_report/claude_code?starting_at=%s&limit=1", testDate) |
| // Use today's date for the test request. | ||
| today := time.Now().UTC().Format("2006-01-02") | ||
| endpoint := fmt.Sprintf("v1/organizations/usage_report/claude_code?starting_at=%s&limit=1", today) | ||
|
|
||
| res, err := apiClient.Get(endpoint, nil, nil) | ||
| if err != nil { |
There was a problem hiding this comment.
The test request path (v1/organizations/usage_report/claude_code) does not match the analytics endpoints used by the collectors in this PR (v1/organizations/analytics/...). If usage_report/claude_code is deprecated/removed, connection tests will fail even though collection would work (or vice versa). Align the test endpoint with the same API surface the collectors use (e.g., a minimal analytics/users request with limit=1).
| // Use today's date for the test request. | |
| today := time.Now().UTC().Format("2006-01-02") | |
| endpoint := fmt.Sprintf("v1/organizations/usage_report/claude_code?starting_at=%s&limit=1", today) | |
| res, err := apiClient.Get(endpoint, nil, nil) | |
| if err != nil { | |
| // Use a minimal analytics users request with limit=1 for the test. | |
| endpoint := "v1/organizations/analytics/users?limit=1" | |
| res, err := apiClient.Get(endpoint, nil, nil) | |
| if err != nil { | |
| if err != nil { |
| return nil, errors.BadInput.New("either token or at least one custom header is required") | ||
| } | ||
| if strings.TrimSpace(connection.Organization) == "" { | ||
| return nil, errors.BadInput.New("organizationId is required") |
There was a problem hiding this comment.
Error message says organizationId is required, but the connection field in this plugin/model is organization (and the API-side validateConnection returns "organization is required"). This inconsistency can confuse users and makes it harder to map UI fields to backend validation. Use consistent naming across validation and responses (either standardize on organization everywhere or explicitly rename the field/JSON tag to organizationId).
| return nil, errors.BadInput.New("organizationId is required") | |
| return nil, errors.BadInput.New("organization is required") |
pr-type/bug-fix,pr-type/feature-development, etc.Summary
The PR allows users to:
Does this close any open issues?
None
Screenshots
Include any relevant screenshots here.
Other Information
Any other information that is important to this PR.