Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xray usage event report #1046

Merged
merged 9 commits into from
Nov 29, 2023
68 changes: 62 additions & 6 deletions utils/usage/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -46,6 +49,7 @@ func NewUsageReporter(productId string, serverDetails *config.ServerDetails) *Us
serverDetails: serverDetails,
reportWaitGroup: new(errgroup.Group),
sendToEcosystem: true,
sendToXray: true,
sendToArtifactory: true,
}
}
Expand Down Expand Up @@ -80,19 +84,31 @@ 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
})
}
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
})
Expand Down Expand Up @@ -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...)
}

Expand Down Expand Up @@ -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{}
Expand Down
60 changes: 59 additions & 1 deletion utils/usage/usage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}
attiasas marked this conversation as resolved.
Show resolved Hide resolved
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: "",
Expand Down
Loading