diff --git a/utils/usage/usage.go b/utils/usage/usage.go index 9b493637b..821f619e5 100644 --- a/utils/usage/usage.go +++ b/utils/usage/usage.go @@ -6,11 +6,14 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-client-go/artifactory/usage" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ecosysusage "github.com/jfrog/jfrog-client-go/utils/usage" + xrayusage "github.com/jfrog/jfrog-client-go/xray/usage" + "golang.org/x/sync/errgroup" ) @@ -46,6 +49,7 @@ func NewUsageReporter(productId string, serverDetails *config.ServerDetails) *Us serverDetails: serverDetails, reportWaitGroup: new(errgroup.Group), sendToEcosystem: true, + sendToXray: true, sendToArtifactory: true, } } @@ -80,11 +84,23 @@ func (ur *UsageReporter) Report(features ...ReportFeature) { log.Debug("Usage info is disabled.") return } + if len(features) == 0 { + log.Debug(ReportUsagePrefix, "Nothing to send.") + return + } log.Debug(ReportUsagePrefix, "Sending info...") if ur.sendToEcosystem { ur.reportWaitGroup.Go(func() (err error) { if err = ur.reportToEcosystem(features...); err != nil { - err = fmt.Errorf("ecosystem, %s", err.Error()) + err = fmt.Errorf("ecosystem, %w", err) + } + return + }) + } + if ur.sendToXray { + ur.reportWaitGroup.Go(func() (err error) { + if err = ur.reportToXray(features...); err != nil { + err = fmt.Errorf("xray, %w", err) } return }) @@ -92,7 +108,7 @@ func (ur *UsageReporter) Report(features ...ReportFeature) { if ur.sendToArtifactory { ur.reportWaitGroup.Go(func() (err error) { if err = ur.reportToArtifactory(features...); err != nil { - err = fmt.Errorf("artifactory, %s", err.Error()) + err = fmt.Errorf("artifactory, %w", err) } return }) @@ -122,20 +138,37 @@ func (ur *UsageReporter) reportToEcosystem(features ...ReportFeature) (err error return ecosysusage.SendEcosystemUsageReports(reports...) } -func (ur *UsageReporter) reportToArtifactory(features ...ReportFeature) (err error) { - if ur.serverDetails.ArtifactoryUrl == "" { - err = errorutils.CheckErrorf("Artifactory URL is not set") +func (ur *UsageReporter) reportToXray(features ...ReportFeature) (err error) { + events := ur.convertAttributesToXrayEvents(features...) + if len(events) == 0 { + err = errorutils.CheckErrorf("Nothing to send.") return } - serviceManager, err := utils.CreateServiceManager(ur.serverDetails, -1, 0, false) + if ur.serverDetails.XrayUrl == "" { + err = errorutils.CheckErrorf("Xray Url is not set.") + return + } + serviceManager, err := xrayutils.CreateXrayServiceManager(ur.serverDetails) if err != nil { return } + return xrayusage.SendXrayUsageEvents(*serviceManager, events...) +} + +func (ur *UsageReporter) reportToArtifactory(features ...ReportFeature) (err error) { converted := ur.convertAttributesToArtifactoryFeatures(features...) if len(converted) == 0 { err = errorutils.CheckErrorf("nothing to send") return } + if ur.serverDetails.ArtifactoryUrl == "" { + err = errorutils.CheckErrorf("Artifactory URL is not set") + return + } + serviceManager, err := utils.CreateServiceManager(ur.serverDetails, -1, 0, false) + if err != nil { + return + } return usage.ReportUsageToArtifactory(ur.ProductId, serviceManager, converted...) } @@ -164,6 +197,29 @@ func (ur *UsageReporter) convertAttributesToArtifactoryFeatures(reportFeatures . return } +func (ur *UsageReporter) convertAttributesToXrayEvents(reportFeatures ...ReportFeature) (events []xrayusage.ReportXrayEventData) { + for _, feature := range reportFeatures { + convertedAttributes := []xrayusage.ReportUsageAttribute{} + for _, attribute := range feature.Attributes { + convertedAttributes = append(convertedAttributes, xrayusage.ReportUsageAttribute{ + AttributeName: attribute.AttributeName, + AttributeValue: attribute.AttributeValue, + }) + } + if feature.ClientId != "" { + // Add clientId as attribute + convertedAttributes = append(convertedAttributes, xrayusage.ReportUsageAttribute{ + AttributeName: clientIdAttributeName, + AttributeValue: feature.ClientId, + }) + } + events = append(events, xrayusage.CreateUsageEvent( + ur.ProductId, feature.FeatureId, convertedAttributes..., + )) + } + return +} + func (ur *UsageReporter) convertAttributesToEcosystemReports(reportFeatures ...ReportFeature) (reports []ecosysusage.ReportEcosystemUsageData, err error) { accountId := ur.serverDetails.Url clientToFeaturesMap := map[string][]string{} diff --git a/utils/usage/usage_test.go b/utils/usage/usage_test.go index c3b2b08a1..4ff918642 100644 --- a/utils/usage/usage_test.go +++ b/utils/usage/usage_test.go @@ -10,6 +10,8 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/artifactory/usage" ecosysusage "github.com/jfrog/jfrog-client-go/utils/usage" + xrayusage "github.com/jfrog/jfrog-client-go/xray/usage" + "github.com/stretchr/testify/assert" ) @@ -129,11 +131,67 @@ func createArtifactoryUsageHandler(t *testing.T, productName, commandName string } } +func TestReportXrayUsage(t *testing.T) { + const productName = "test-product" + const commandName = "test-command" + const clientName = "test-client" + + server := httptest.NewServer(createXrayUsageHandler(t, productName, commandName, clientName)) + defer server.Close() + serverDetails := &config.ServerDetails{XrayUrl: server.URL + "/"} + + reporter := NewUsageReporter(productName, serverDetails).SetSendToEcosystem(false).SetSendToArtifactory(false) + + reporter.Report(ReportFeature{ + FeatureId: commandName, + ClientId: clientName, + }) + assert.NoError(t, reporter.WaitForResponses()) +} + +func TestReportXrayError(t *testing.T) { + reporter := NewUsageReporter("", &config.ServerDetails{}).SetSendToEcosystem(false).SetSendToArtifactory(false) + reporter.Report(ReportFeature{}) + assert.Error(t, reporter.WaitForResponses()) + + server := httptest.NewServer(create404UsageHandler(t)) + defer server.Close() + reporter = NewUsageReporter("", &config.ServerDetails{ArtifactoryUrl: server.URL + "/"}).SetSendToEcosystem(false).SetSendToArtifactory(false) + reporter.Report(ReportFeature{}) + assert.Error(t, reporter.WaitForResponses()) +} + +func createXrayUsageHandler(t *testing.T, productId, commandName, clientId string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/api/v1/system/version" { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"xray_version":"6.9.0"}`)) + assert.NoError(t, err) + return + } + if r.RequestURI == "/api/v1/usage/events/send" { + // Check request + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(r.Body) + assert.NoError(t, err) + featureId := xrayusage.GetExpectedXrayEventName(productId, commandName) + assert.Equal(t, fmt.Sprintf(`[{"data":{"clientId":"%s"},"product_name":"%s","event_name":"%s","origin":"API_CLI"}]`, clientId, productId, featureId), buf.String()) + + // Send response OK + w.WriteHeader(http.StatusOK) + _, err = w.Write([]byte("{}")) + assert.NoError(t, err) + return + } + assert.Fail(t, "Unexpected request URI", r.RequestURI) + } +} + func TestReportEcosystemUsageError(t *testing.T) { // No features reporter := NewUsageReporter("", &config.ServerDetails{}).SetSendToArtifactory(false).SetSendToXray(false) reporter.Report() - assert.Error(t, reporter.WaitForResponses()) + assert.NoError(t, reporter.WaitForResponses()) // Empty features reporter.Report(ReportFeature{ FeatureId: "",