diff --git a/cspell.config.json b/cspell.config.json index 00b10758..01059904 100644 --- a/cspell.config.json +++ b/cspell.config.json @@ -59,6 +59,7 @@ "vals", "vladimirdotk", "Wrapf", - "confg" //this is unfortunately a typo in a file name that is not easy to fix + "confg", //this is unfortunately a typo in a file name that is not easy to fix + "neovim" ] } diff --git a/pkg/github/client/client.go b/pkg/github/client/client.go index e12742d3..2c9ac198 100644 --- a/pkg/github/client/client.go +++ b/pkg/github/client/client.go @@ -358,3 +358,45 @@ func (client *Client) getWorkflowRuns(ctx context.Context, owner, repo, workflow return workflowRuns, response.NextPage, nil } + +// GetCopilotMetrics sends a request to the GitHub REST API to get Copilot metrics for an organization or team +func (client *Client) GetCopilotMetrics(ctx context.Context, organization string, opts models.ListCopilotMetricsOptions) ([]*googlegithub.CopilotMetrics, *googlegithub.Response, error) { + var u string + if opts.TeamSlug != "" { + u = fmt.Sprintf("orgs/%s/team/%s/copilot/metrics", organization, opts.TeamSlug) + } else { + u = fmt.Sprintf("orgs/%s/copilot/metrics", organization) + } + + // Build query parameters + params := url.Values{} + if opts.Since != nil { + params.Add("since", opts.Since.Format("2006-01-02")) + } + if opts.Until != nil { + params.Add("until", opts.Until.Format("2006-01-02")) + } + if opts.Page > 0 { + params.Add("page", strconv.Itoa(opts.Page)) + } + if opts.PerPage > 0 { + params.Add("per_page", strconv.Itoa(opts.PerPage)) + } + + if len(params) > 0 { + u += "?" + params.Encode() + } + + req, err := client.restClient.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var metrics []*googlegithub.CopilotMetrics + resp, err := client.restClient.Do(ctx, req, &metrics) + if err != nil { + return nil, resp, addErrorSourceToError(err, resp) + } + + return metrics, resp, nil +} diff --git a/pkg/github/codescanning_test.go b/pkg/github/codescanning_test.go index f3cc8486..9bc99969 100644 --- a/pkg/github/codescanning_test.go +++ b/pkg/github/codescanning_test.go @@ -52,6 +52,10 @@ func (m *mockClient) ListAlertsForOrg(ctx context.Context, owner string, opts *g return m.mockAlerts, m.mockResponse, nil } +func (m *mockClient) GetCopilotMetrics(ctx context.Context, organization string, opts models.ListCopilotMetricsOptions) ([]*googlegithub.CopilotMetrics, *googlegithub.Response, error) { + return nil, nil, nil +} + func TestGetCodeScanningAlerts(t *testing.T) { var ( ctx = context.Background() diff --git a/pkg/github/copilot_metrics.go b/pkg/github/copilot_metrics.go new file mode 100644 index 00000000..e9d36039 --- /dev/null +++ b/pkg/github/copilot_metrics.go @@ -0,0 +1,181 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "time" + + googlegithub "github.com/google/go-github/v72/github" + "github.com/grafana/github-datasource/pkg/dfutil" + "github.com/grafana/github-datasource/pkg/models" + "github.com/grafana/grafana-plugin-sdk-go/data" +) + +// CopilotMetricsResponse represents the response from GitHub's Copilot metrics API +type CopilotMetricsResponse []*googlegithub.CopilotMetrics + +// GetCopilotMetrics retrieves Copilot metrics for an organization or team +func GetCopilotMetrics(ctx context.Context, client models.Client, opts models.ListCopilotMetricsOptions) (dfutil.Framer, error) { + metrics, _, err := client.GetCopilotMetrics(ctx, opts.Organization, opts) + if err != nil { + return nil, err + } + + frameName := "copilot_metrics" + if opts.TeamSlug != "" { + frameName = "copilot_metrics_team" + } + + return copilotMetricsToDataFrame(CopilotMetricsResponse(metrics), frameName) +} + +// copilotMetricsToDataFrame converts Copilot metrics to a Grafana data frame +func copilotMetricsToDataFrame(metrics CopilotMetricsResponse, name string) (dfutil.Framer, error) { + return metrics, nil +} + +// Frames converts the list of copilot metrics to a Grafana DataFrame +func (c CopilotMetricsResponse) Frames() data.Frames { + frame := data.NewFrame("copilot_metrics") + + if len(c) == 0 { + return data.Frames{frame} + } + + // Create time series for the main metrics + dates := make([]time.Time, len(c)) + totalActiveUsers := make([]int64, len(c)) + totalEngagedUsers := make([]int64, len(c)) + ideCompletionUsers := make([]int64, len(c)) + ideChatUsers := make([]int64, len(c)) + dotcomChatUsers := make([]int64, len(c)) + dotcomPRUsers := make([]int64, len(c)) + + for i, metric := range c { + date, err := time.Parse("2006-01-02", metric.Date) + if err != nil { + // If date parsing fails, use a default date + date = time.Now().AddDate(0, 0, -i) + } + + dates[i] = date + + // Handle nullable fields from go-github + if metric.TotalActiveUsers != nil { + totalActiveUsers[i] = int64(*metric.TotalActiveUsers) + } + if metric.TotalEngagedUsers != nil { + totalEngagedUsers[i] = int64(*metric.TotalEngagedUsers) + } + if metric.CopilotIDECodeCompletions != nil { + ideCompletionUsers[i] = int64(metric.CopilotIDECodeCompletions.TotalEngagedUsers) + } + if metric.CopilotIDEChat != nil { + ideChatUsers[i] = int64(metric.CopilotIDEChat.TotalEngagedUsers) + } + if metric.CopilotDotcomChat != nil { + dotcomChatUsers[i] = int64(metric.CopilotDotcomChat.TotalEngagedUsers) + } + if metric.CopilotDotcomPullRequests != nil { + dotcomPRUsers[i] = int64(metric.CopilotDotcomPullRequests.TotalEngagedUsers) + } + } + + // Add fields to the frame + frame.Fields = append(frame.Fields, data.NewField("time", nil, dates)) + frame.Fields = append(frame.Fields, data.NewField("total_active_users", nil, totalActiveUsers)) + frame.Fields = append(frame.Fields, data.NewField("total_engaged_users", nil, totalEngagedUsers)) + frame.Fields = append(frame.Fields, data.NewField("ide_completion_users", nil, ideCompletionUsers)) + frame.Fields = append(frame.Fields, data.NewField("ide_chat_users", nil, ideChatUsers)) + frame.Fields = append(frame.Fields, data.NewField("dotcom_chat_users", nil, dotcomChatUsers)) + frame.Fields = append(frame.Fields, data.NewField("dotcom_pr_users", nil, dotcomPRUsers)) + + // Add language breakdown data if available + if len(c) > 0 && c[0].CopilotIDECodeCompletions != nil && len(c[0].CopilotIDECodeCompletions.Languages) > 0 { + langData := make(map[string][]int64) + for _, metric := range c { + if metric.CopilotIDECodeCompletions != nil { + for _, lang := range metric.CopilotIDECodeCompletions.Languages { + if langData[lang.Name] == nil { + langData[lang.Name] = make([]int64, len(c)) + } + } + } + } + + for i, metric := range c { + for langName := range langData { + found := false + if metric.CopilotIDECodeCompletions != nil { + for _, lang := range metric.CopilotIDECodeCompletions.Languages { + if lang.Name == langName { + langData[langName][i] = int64(lang.TotalEngagedUsers) + found = true + break + } + } + } + if !found { + langData[langName][i] = 0 + } + } + } + + for langName, users := range langData { + fieldName := fmt.Sprintf("language_%s_users", langName) + frame.Fields = append(frame.Fields, data.NewField(fieldName, nil, users)) + } + } + + // Add editor breakdown data if available + if len(c) > 0 && c[0].CopilotIDECodeCompletions != nil && len(c[0].CopilotIDECodeCompletions.Editors) > 0 { + editorData := make(map[string][]int64) + for _, metric := range c { + if metric.CopilotIDECodeCompletions != nil { + for _, editor := range metric.CopilotIDECodeCompletions.Editors { + if editorData[editor.Name] == nil { + editorData[editor.Name] = make([]int64, len(c)) + } + } + } + } + + for i, metric := range c { + for editorName := range editorData { + found := false + if metric.CopilotIDECodeCompletions != nil { + for _, editor := range metric.CopilotIDECodeCompletions.Editors { + if editor.Name == editorName { + editorData[editorName][i] = int64(editor.TotalEngagedUsers) + found = true + break + } + } + } + if !found { + editorData[editorName][i] = 0 + } + } + } + + for editorName, users := range editorData { + fieldName := fmt.Sprintf("editor_%s_users", editorName) + frame.Fields = append(frame.Fields, data.NewField(fieldName, nil, users)) + } + } + + // Add detailed JSON for complex nested data + detailedData := make([]string, len(c)) + for i, metric := range c { + jsonData, err := json.Marshal(metric) + if err != nil { + detailedData[i] = "" + } else { + detailedData[i] = string(jsonData) + } + } + frame.Fields = append(frame.Fields, data.NewField("detailed_metrics", nil, detailedData)) + + return data.Frames{frame} +} diff --git a/pkg/github/copilot_metrics_handler.go b/pkg/github/copilot_metrics_handler.go new file mode 100644 index 00000000..b36e546a --- /dev/null +++ b/pkg/github/copilot_metrics_handler.go @@ -0,0 +1,24 @@ +package github + +import ( + "context" + + "github.com/grafana/github-datasource/pkg/dfutil" + "github.com/grafana/github-datasource/pkg/models" + "github.com/grafana/grafana-plugin-sdk-go/backend" +) + +func (s *QueryHandler) handleCopilotMetricsQuery(ctx context.Context, q backend.DataQuery) backend.DataResponse { + query := &models.CopilotMetricsQuery{} + if err := UnmarshalQuery(q.JSON, query); err != nil { + return *err + } + return dfutil.FrameResponseWithError(s.Datasource.HandleCopilotMetricsQuery(ctx, query, q)) +} + +// HandleCopilotMetrics handles the plugin query for GitHub Copilot metrics for an organization or team +func (s *QueryHandler) HandleCopilotMetrics(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + return &backend.QueryDataResponse{ + Responses: processQueries(ctx, req, s.handleCopilotMetricsQuery), + }, nil +} diff --git a/pkg/github/copilot_metrics_test.go b/pkg/github/copilot_metrics_test.go new file mode 100644 index 00000000..9dc94703 --- /dev/null +++ b/pkg/github/copilot_metrics_test.go @@ -0,0 +1,97 @@ +package github + +import ( + "testing" + + googlegithub "github.com/google/go-github/v72/github" + "github.com/stretchr/testify/assert" +) + +func TestCopilotMetricsResponse_Frames(t *testing.T) { + // Test empty response + t.Run("empty response", func(t *testing.T) { + response := CopilotMetricsResponse{} + frames := response.Frames() + assert.Len(t, frames, 1) + assert.Equal(t, "copilot_metrics", frames[0].Name) + assert.Len(t, frames[0].Fields, 0) + }) + + // Test response with data + t.Run("response with data", func(t *testing.T) { + totalActiveUsers := 100 + totalEngagedUsers := 75 + ideCompletionUsers := 50 + ideChatUsers := 30 + dotcomChatUsers := 25 + dotcomPRUsers := 15 + goLangUsers := 25 + tsLangUsers := 20 + vscodeUsers := 45 + neovimUsers := 5 + + response := CopilotMetricsResponse{ + &googlegithub.CopilotMetrics{ + Date: "2025-01-01", + TotalActiveUsers: &totalActiveUsers, + TotalEngagedUsers: &totalEngagedUsers, + CopilotIDECodeCompletions: &googlegithub.CopilotIDECodeCompletions{ + TotalEngagedUsers: ideCompletionUsers, + Languages: []*googlegithub.CopilotIDECodeCompletionsLanguage{ + {Name: "go", TotalEngagedUsers: goLangUsers}, + {Name: "typescript", TotalEngagedUsers: tsLangUsers}, + }, + Editors: []*googlegithub.CopilotIDECodeCompletionsEditor{ + {Name: "vscode", TotalEngagedUsers: vscodeUsers}, + {Name: "neovim", TotalEngagedUsers: neovimUsers}, + }, + }, + CopilotIDEChat: &googlegithub.CopilotIDEChat{ + TotalEngagedUsers: ideChatUsers, + }, + CopilotDotcomChat: &googlegithub.CopilotDotcomChat{ + TotalEngagedUsers: dotcomChatUsers, + }, + CopilotDotcomPullRequests: &googlegithub.CopilotDotcomPullRequests{ + TotalEngagedUsers: dotcomPRUsers, + }, + }, + } + + frames := response.Frames() + assert.Len(t, frames, 1) + frame := frames[0] + + assert.Equal(t, "copilot_metrics", frame.Name) + + // Check that we have the expected fields + fieldNames := make([]string, len(frame.Fields)) + for i, field := range frame.Fields { + fieldNames[i] = field.Name + } + + expectedFields := []string{ + "time", + "total_active_users", + "total_engaged_users", + "ide_completion_users", + "ide_chat_users", + "dotcom_chat_users", + "dotcom_pr_users", + "language_go_users", + "language_typescript_users", + "editor_vscode_users", + "editor_neovim_users", + "detailed_metrics", + } + + for _, expected := range expectedFields { + assert.Contains(t, fieldNames, expected, "Field %s should be present", expected) + } + + // Check that all fields have the correct length + for _, field := range frame.Fields { + assert.Equal(t, 1, field.Len(), "Field %s should have length 1", field.Name) + } + }) +} diff --git a/pkg/github/datasource.go b/pkg/github/datasource.go index bd8a4196..397c228b 100644 --- a/pkg/github/datasource.go +++ b/pkg/github/datasource.go @@ -212,6 +212,12 @@ func (d *Datasource) HandleWorkflowRunsQuery(ctx context.Context, query *models. return GetWorkflowRuns(ctx, d.client, opt, req.TimeRange) } +// HandleCopilotMetricsQuery is the query handler for listing GitHub Copilot metrics for an organization or team +func (d *Datasource) HandleCopilotMetricsQuery(ctx context.Context, query *models.CopilotMetricsQuery, req backend.DataQuery) (dfutil.Framer, error) { + opt := models.CopilotMetricsOptionsWithOrg(query.Options, query.Owner) + return GetCopilotMetrics(ctx, d.client, opt) +} + // CheckHealth is the health check for GitHub func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { _, err := GetAllRepositories(ctx, d.client, models.ListRepositoriesOptions{ diff --git a/pkg/github/query_handler.go b/pkg/github/query_handler.go index 0cf972c5..42717407 100644 --- a/pkg/github/query_handler.go +++ b/pkg/github/query_handler.go @@ -62,6 +62,7 @@ func GetQueryHandlers(s *QueryHandler) *datasource.QueryTypeMux { mux.HandleFunc(models.QueryTypeWorkflowUsage, s.HandleWorkflowUsage) mux.HandleFunc(models.QueryTypeWorkflowRuns, s.HandleWorkflowRuns) mux.HandleFunc(models.QueryTypeCodeScanning, s.HandleCodeScanning) + mux.HandleFunc(models.QueryTypeCopilotMetrics, s.HandleCopilotMetrics) return mux } diff --git a/pkg/models/client.go b/pkg/models/client.go index f7897b3e..ab45402f 100644 --- a/pkg/models/client.go +++ b/pkg/models/client.go @@ -16,4 +16,5 @@ type Client interface { GetWorkflowRuns(ctx context.Context, owner, repo, workflow string, branch string, timeRange backend.TimeRange) ([]*googlegithub.WorkflowRun, error) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) ListAlertsForOrg(ctx context.Context, owner string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) + GetCopilotMetrics(ctx context.Context, organization string, opts ListCopilotMetricsOptions) ([]*googlegithub.CopilotMetrics, *googlegithub.Response, error) } diff --git a/pkg/models/copilot_metrics.go b/pkg/models/copilot_metrics.go new file mode 100644 index 00000000..75f287a3 --- /dev/null +++ b/pkg/models/copilot_metrics.go @@ -0,0 +1,25 @@ +package models + +import "time" + +// ListCopilotMetricsOptions defines the options for listing Copilot metrics for an organization or team +type ListCopilotMetricsOptions struct { + Organization string `json:"organization"` + TeamSlug string `json:"team_slug,omitempty"` + Since *time.Time `json:"since,omitempty"` + Until *time.Time `json:"until,omitempty"` + Page int `json:"page,omitempty"` + PerPage int `json:"per_page,omitempty"` +} + +// CopilotMetricsOptionsWithOrg adds the Owner value to a ListCopilotMetricsOptions. This is a convenience function because this is a common operation +func CopilotMetricsOptionsWithOrg(opt ListCopilotMetricsOptions, owner string) ListCopilotMetricsOptions { + return ListCopilotMetricsOptions{ + Organization: owner, + TeamSlug: opt.TeamSlug, + Since: opt.Since, + Until: opt.Until, + Page: opt.Page, + PerPage: opt.PerPage, + } +} diff --git a/pkg/models/query.go b/pkg/models/query.go index 88292805..dbe5158e 100644 --- a/pkg/models/query.go +++ b/pkg/models/query.go @@ -43,6 +43,8 @@ const ( QueryTypeWorkflowRuns = "Workflow_Runs" // QueryTypeCodeScanning is used when querying code scanning alerts for a repository QueryTypeCodeScanning = "Code_Scanning" + // QueryTypeCopilotMetrics is used when querying Copilot metrics for an organization or team + QueryTypeCopilotMetrics = "Copilot_Metrics" ) // Query refers to the structure of a query built using the QueryEditor. @@ -125,6 +127,12 @@ type VulnerabilityQuery struct { Options ListVulnerabilitiesOptions `json:"options"` } +// CopilotMetricsQuery is used when querying Copilot metrics for an organization or team +type CopilotMetricsQuery struct { + Query + Options ListCopilotMetricsOptions `json:"options"` +} + // StargazersQuery is used when querying stargazers for a repository type StargazersQuery struct { Query diff --git a/pkg/plugin/datasource.go b/pkg/plugin/datasource.go index ee7ce469..e154e47f 100644 --- a/pkg/plugin/datasource.go +++ b/pkg/plugin/datasource.go @@ -28,6 +28,7 @@ type Datasource interface { HandleWorkflowsQuery(context.Context, *models.WorkflowsQuery, backend.DataQuery) (dfutil.Framer, error) HandleWorkflowUsageQuery(context.Context, *models.WorkflowUsageQuery, backend.DataQuery) (dfutil.Framer, error) HandleWorkflowRunsQuery(context.Context, *models.WorkflowRunsQuery, backend.DataQuery) (dfutil.Framer, error) + HandleCopilotMetricsQuery(context.Context, *models.CopilotMetricsQuery, backend.DataQuery) (dfutil.Framer, error) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) } diff --git a/pkg/plugin/datasource_caching.go b/pkg/plugin/datasource_caching.go index 1c19970f..ccacea71 100644 --- a/pkg/plugin/datasource_caching.go +++ b/pkg/plugin/datasource_caching.go @@ -262,6 +262,16 @@ func (c *CachedDatasource) HandleWorkflowRunsQuery(ctx context.Context, q *model return c.saveCache(req, f, err) } +// HandleCopilotMetricsQuery is the cache wrapper for the Copilot metrics query handler +func (c *CachedDatasource) HandleCopilotMetricsQuery(ctx context.Context, q *models.CopilotMetricsQuery, req backend.DataQuery) (dfutil.Framer, error) { + if value, err := c.getCache(req); err == nil { + return value, err + } + + f, err := c.datasource.HandleCopilotMetricsQuery(ctx, q, req) + return c.saveCache(req, f, err) +} + // CheckHealth forwards the request to the datasource and does not perform any caching func (c *CachedDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { return c.datasource.CheckHealth(ctx, req) diff --git a/pkg/testutil/client.go b/pkg/testutil/client.go index 13aef4fb..bb97a8d8 100644 --- a/pkg/testutil/client.go +++ b/pkg/testutil/client.go @@ -76,3 +76,8 @@ func (c *TestClient) ListAlertsForRepo(ctx context.Context, owner, repo string, func (c *TestClient) ListAlertsForOrg(ctx context.Context, owner string, opts *googlegithub.AlertListOptions) ([]*googlegithub.Alert, *googlegithub.Response, error) { panic("unimplemented") } + +// GetCopilotMetrics is not implemented because it is not being used in tests at the moment. +func (c *TestClient) GetCopilotMetrics(ctx context.Context, organization string, opts models.ListCopilotMetricsOptions) ([]*googlegithub.CopilotMetrics, *googlegithub.Response, error) { + panic("unimplemented") +} diff --git a/src/constants.ts b/src/constants.ts index a141e8cc..a190feab 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,6 +20,7 @@ export enum QueryType { Workflows = 'Workflows', Workflow_Usage = 'Workflow_Usage', Workflow_Runs = 'Workflow_Runs', + Copilot_Metrics = 'Copilot_Metrics', } export const DefaultQueryType = QueryType.Issues; diff --git a/src/types/query.ts b/src/types/query.ts index 1694020f..26a5de19 100644 --- a/src/types/query.ts +++ b/src/types/query.ts @@ -20,7 +20,8 @@ export interface GitHubQuery extends Indexable, DataQuery, RepositoryOptions { | ProjectsOptions | WorkflowsOptions | WorkflowUsageOptions - | WorkflowRunsOptions; + | WorkflowRunsOptions + | CopilotMetricsOptions; } export interface Label { @@ -108,3 +109,12 @@ export interface GitHubVariableQuery extends GitHubQuery { export interface GitHubAnnotationQuery extends GitHubVariableQuery { timeField?: string; } + +export interface CopilotMetricsOptions extends Indexable { + organization?: string; + teamSlug?: string; + since?: string; + until?: string; + page?: number; + perPage?: number; +} diff --git a/src/views/QueryEditor.tsx b/src/views/QueryEditor.tsx index f94f60cd..20ca0180 100644 --- a/src/views/QueryEditor.tsx +++ b/src/views/QueryEditor.tsx @@ -23,6 +23,7 @@ import QueryEditorWorkflows from './QueryEditorWorkflows'; import QueryEditorWorkflowUsage from './QueryEditorWorkflowUsage'; import QueryEditorWorkflowRuns from './QueryEditorWorkflowRuns'; import QueryEditorCodeScanning from './QueryEditorCodeScanning'; +import QueryEditorCopilotMetrics from './QueryEditorCopilotMetrics'; import { QueryType, DefaultQueryType } from '../constants'; import type { GitHubQuery } from '../types/query'; import type { GitHubDataSourceOptions } from '../types/config'; @@ -117,6 +118,11 @@ const queryEditors: { ), }, + [QueryType.Copilot_Metrics]: { + component: (props: Props, onChange: (val: any) => void) => ( + + ), + }, }; /* eslint-enable react/display-name */ @@ -189,7 +195,7 @@ const QueryEditor = (props: Props) => { ); }; -const nonRepoTypes = [QueryType.Projects, QueryType.ProjectItems]; +const nonRepoTypes = [QueryType.Projects, QueryType.ProjectItems, QueryType.Copilot_Metrics]; function hasRepo(qt?: string) { return !nonRepoTypes.includes(qt as QueryType); diff --git a/src/views/QueryEditorCopilotMetrics.tsx b/src/views/QueryEditorCopilotMetrics.tsx new file mode 100644 index 00000000..8d51b6db --- /dev/null +++ b/src/views/QueryEditorCopilotMetrics.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { Input, InlineField, InlineLabel } from '@grafana/ui'; +import { RightColumnWidth, LeftColumnWidth } from './QueryEditor'; +import type { CopilotMetricsOptions } from '../types/query'; +import { components } from '../components/selectors'; +import { QueryEditorRow } from '../components/Forms'; + +interface Props extends CopilotMetricsOptions { + onChange: (val: CopilotMetricsOptions) => void; +} + +const QueryEditorCopilotMetrics = (props: Props) => { + const [team, setTeam] = useState(props.teamSlug || ''); + const [owner, setOwner] = useState(props.owner || ''); + + return ( + <> + setOwner(el.currentTarget.value)} + onBlur={(el) => + props.onChange({ + ...props, + owner: el.currentTarget.value, + }) + } + /> + + setTeam(el.currentTarget.value)} + onBlur={(el) => + props.onChange({ + ...props, + teamSlug: el.currentTarget.value + }) + } + placeholder="Enter team slug (optional)" + /> + + + ); +}; + +export default QueryEditorCopilotMetrics;