mukan-ignite/ignite/internal/analytics/matomo.go
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

265 lines
6.5 KiB
Go

package analytics
import (
"crypto/rand"
"fmt"
"math"
"math/big"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/go-querystring/query"
"github.com/ignite/cli/v29/ignite/pkg/errors"
)
type (
// MatomoClient is a matomo client.
MatomoClient struct {
endpoint string
idSite uint // Matomo ID Site.
tokenAuth string // Matomo Token Auth.
source string
httpClient http.Client
}
// MatomoParams analytics metrics body.
MatomoParams struct {
IDSite uint `url:"idsite"`
Rec uint `url:"rec"`
ActionName string `url:"action_name"`
APIVersion uint `url:"apiv"`
TokenAuth string `url:"token_auth,omitempty"`
Rand uint64 `url:"rand,omitempty"`
URL string `url:"url,omitempty"`
UTMSource string `url:"utm_source,omitempty"`
UTMMedium string `url:"utm_medium,omitempty"`
UTMCampaign string `url:"utm_campaign,omitempty"`
UTMContent string `url:"utm_content,omitempty"`
UserID string `url:"uid,omitempty"`
UserAgent string `url:"ua,omitempty"`
Hour int `url:"h,omitempty"`
Minute int `url:"m,omitempty"`
Second int `url:"s,omitempty"`
// Dimension1 development mode boolean.
// 1 = devMode ON | 0 = devMode OFF.
Dimension1 uint `url:"dimension1"`
// Dimension2 internal boolean.
// 1 = internal ON not supported at present | 0 = internal OFF.
Dimension2 uint `url:"dimension2"`
// Dimension3 is deprecated.
// Should always be 0.
Dimension3 uint `url:"dimension3"`
// Dimension4 ignite version
Dimension4 string `url:"dimension4,omitempty"`
// Dimension6 ignite config version
Dimension6 string `url:"dimension6,omitempty"`
// Dimension7 full cli command
Dimension7 string `url:"dimension7,omitempty"`
// Dimension11 scaffold customization type
Dimension11 string `url:"dimension11,omitempty"`
// Dimension13 command level 1.
Dimension13 string `url:"dimension13,omitempty"`
// Dimension14 command level 2.
Dimension14 string `url:"dimension14,omitempty"`
// Dimension15 command level 3.
Dimension15 string `url:"dimension15,omitempty"`
// Dimension16 command level 4.
Dimension16 string `url:"dimension16,omitempty"`
// Dimension17 cosmos-sdk version.
Dimension17 string `url:"dimension17,omitempty"`
// Dimension18 operational system.
Dimension18 string `url:"dimension18,omitempty"`
// Dimension19 system architecture.
Dimension19 string `url:"dimension19,omitempty"`
// Dimension20 golang version.
Dimension20 string `url:"dimension20,omitempty"`
// Dimension21 command level 5.
Dimension21 string `url:"dimension21,omitempty"`
// Dimension22 command level 6.
Dimension22 string `url:"dimension22,omitempty"`
}
// Metric represents a custom data.
Metric struct {
Name string
Cmd string
OS string
Arch string
Version string
CLIVersion string
GoVersion string
SDKVersion string
BuildDate string
SourceHash string
ConfigVersion string
Uname string
CWD string
ScaffoldType string
BuildFromSource bool
IsCI bool
}
)
// Option configures code generation.
type Option func(*MatomoClient)
// WithIDSite adds an id site.
func WithIDSite(idSite uint) Option {
return func(c *MatomoClient) {
c.idSite = idSite
}
}
// WithTokenAuth adds a matomo token authentication.
func WithTokenAuth(tokenAuth string) Option {
return func(c *MatomoClient) {
c.tokenAuth = tokenAuth
}
}
// WithSource adds a matomo URL source.
func WithSource(source string) Option {
return func(c *MatomoClient) {
c.source = source
}
}
// NewMatomoClient creates a new Matomo client.
func NewMatomoClient(endpoint string, opts ...Option) MatomoClient {
c := MatomoClient{
endpoint: endpoint,
source: endpoint,
httpClient: http.Client{
Timeout: 1500 * time.Millisecond,
},
}
// apply analytics options.
for _, o := range opts {
o(&c)
}
return c
}
// Send sends metric event to analytics.
func (c MatomoClient) Send(params MatomoParams) error {
requestURL, err := url.Parse(c.endpoint)
if err != nil {
return err
}
// encode request parameters.
queryParams, err := query.Values(params)
if err != nil {
return err
}
requestURL.RawQuery = queryParams.Encode()
// Create an HTTP request with the payload.
resp, err := c.httpClient.Get(requestURL.String())
if err != nil {
return errors.Wrapf(err, "error creating HTTP request: %s", requestURL.String())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.Errorf("error to add matomo analytics metric. Status code: %d", resp.StatusCode)
}
return nil
}
// SendMetric build the metrics and send to analytics.
func (c MatomoClient) SendMetric(sessionID string, metric Metric) error {
var (
now = time.Now()
r, _ = rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
utmMedium = "dev"
)
if !metric.BuildFromSource {
utmMedium = "binary"
}
cmd := splitCommand(metric.Cmd)
return c.Send(MatomoParams{
IDSite: c.idSite,
Rec: 1,
APIVersion: 1,
TokenAuth: c.tokenAuth,
Rand: r.Uint64(),
URL: c.metricURL(metric.Cmd),
UTMSource: "source-code-github",
UTMMedium: utmMedium,
UTMCampaign: metric.CLIVersion,
UTMContent: fmt.Sprintf("commit-%s", metric.SourceHash),
UserID: sessionID,
UserAgent: "Go-http-client",
ActionName: metric.Cmd,
Hour: now.Hour(),
Minute: now.Minute(),
Second: now.Second(),
Dimension1: 0,
Dimension2: formatBool(metric.IsCI),
Dimension4: metric.Version,
Dimension6: metric.ConfigVersion,
Dimension7: metric.Cmd,
Dimension11: metric.ScaffoldType,
Dimension13: cmd[0],
Dimension14: cmd[1],
Dimension15: cmd[2],
Dimension16: cmd[3],
Dimension17: metric.SDKVersion,
Dimension18: metric.OS,
Dimension19: metric.Arch,
Dimension20: metric.GoVersion,
Dimension21: cmd[4],
Dimension22: cmd[5],
})
}
// formatBool returns "1" or "0" according to the value of b.
func formatBool(b bool) uint {
if b {
return 1
}
return 0
}
// splitCommand splice the command into a slice with length 6.
func splitCommand(cmd string) []string {
var (
splitCmd = strings.Split(cmd, " ")
cmdLevels = make([]string, 6)
)
for i := 0; i < len(cmdLevels); i++ {
if i >= len(splitCmd) {
break
}
cmdLevels[i] = splitCmd[i]
}
return cmdLevels
}
// metricURL build the metric URL.
func (c MatomoClient) metricURL(cmd string) string {
return fmt.Sprintf("%s/%s", c.source, strings.ReplaceAll(cmd, " ", "_"))
}