mirror of https://github.com/harness/drone.git
fix: [CODE-2947]: add posthog (#3528)
* changed the object-verb separator char * change env var name for the PH API key * add events for repo import and migrate * rename pr "open" to "create"" * resolve pr comments * add posthogmain
parent
b0de915ae2
commit
d708de0730
|
@ -343,10 +343,12 @@ func (c *Controller) handleEmptyRepoPush(
|
|||
|
||||
if repo.DefaultBranch != oldName {
|
||||
c.repoReporter.DefaultBranchUpdated(ctx, &repoevents.DefaultBranchUpdatedPayload{
|
||||
RepoID: repo.ID,
|
||||
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,
|
||||
OldName: oldName,
|
||||
NewName: repo.DefaultBranch,
|
||||
Base: repoevents.Base{
|
||||
RepoID: repo.ID,
|
||||
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,
|
||||
},
|
||||
OldName: oldName,
|
||||
NewName: repo.DefaultBranch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/services/migrate"
|
||||
"github.com/harness/gitness/app/services/publicaccess"
|
||||
"github.com/harness/gitness/app/services/refcache"
|
||||
|
@ -53,6 +54,7 @@ type Controller struct {
|
|||
repoStore store.RepoStore
|
||||
spaceFinder refcache.SpaceFinder
|
||||
repoFinder refcache.RepoFinder
|
||||
eventReporter *repoevents.Reporter
|
||||
}
|
||||
|
||||
func NewController(
|
||||
|
@ -72,6 +74,7 @@ func NewController(
|
|||
repoStore store.RepoStore,
|
||||
spaceFinder refcache.SpaceFinder,
|
||||
repoFinder refcache.RepoFinder,
|
||||
eventReporter *repoevents.Reporter,
|
||||
) *Controller {
|
||||
return &Controller{
|
||||
authorizer: authorizer,
|
||||
|
@ -90,6 +93,7 @@ func NewController(
|
|||
repoStore: repoStore,
|
||||
spaceFinder: spaceFinder,
|
||||
repoFinder: repoFinder,
|
||||
eventReporter: eventReporter,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
repoCtrl "github.com/harness/gitness/app/api/controller/repo"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/githook"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/audit"
|
||||
|
@ -167,6 +168,14 @@ func (c *Controller) CreateRepo(
|
|||
log.Warn().Msgf("failed to insert audit log for import repository operation: %s", err)
|
||||
}
|
||||
|
||||
c.eventReporter.Created(ctx, &repoevents.CreatedPayload{
|
||||
Base: repoevents.Base{
|
||||
RepoID: repo.ID,
|
||||
PrincipalID: session.Principal.ID,
|
||||
},
|
||||
Type: "migrated",
|
||||
})
|
||||
|
||||
return &repoCtrl.RepositoryOutput{
|
||||
Repository: *repo,
|
||||
IsPublic: isRepoPublic,
|
||||
|
|
|
@ -17,6 +17,7 @@ package migrate
|
|||
import (
|
||||
"github.com/harness/gitness/app/api/controller/limiter"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/services/migrate"
|
||||
"github.com/harness/gitness/app/services/publicaccess"
|
||||
"github.com/harness/gitness/app/services/refcache"
|
||||
|
@ -52,6 +53,7 @@ func ProvideController(
|
|||
repoStore store.RepoStore,
|
||||
spaceFinder refcache.SpaceFinder,
|
||||
repoFinder refcache.RepoFinder,
|
||||
eventReporter *repoevents.Reporter,
|
||||
) *Controller {
|
||||
return NewController(
|
||||
authorizer,
|
||||
|
@ -70,5 +72,6 @@ func ProvideController(
|
|||
repoStore,
|
||||
spaceFinder,
|
||||
repoFinder,
|
||||
eventReporter,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -247,6 +247,13 @@ func ValidateParentRef(parentRef string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func eventBase(repo *types.RepositoryCore, principal *types.Principal) repoevents.Base {
|
||||
return repoevents.Base{
|
||||
RepoID: repo.ID,
|
||||
PrincipalID: principal.ID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) fetchRules(
|
||||
ctx context.Context,
|
||||
session *auth.Session,
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/githook"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/app/services/instrument"
|
||||
|
@ -165,6 +166,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create repository operation: %s", err)
|
||||
}
|
||||
|
||||
err = c.instrumentation.Track(ctx, instrument.Event{
|
||||
Type: instrument.EventTypeRepositoryCreate,
|
||||
Principal: session.Principal.ToPrincipalInfo(),
|
||||
|
@ -178,6 +180,12 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
|
|||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to insert instrumentation record for create repository operation: %s", err)
|
||||
}
|
||||
|
||||
c.eventReporter.Created(ctx, &repoevents.CreatedPayload{
|
||||
Base: eventBase(repo.Core(), &session.Principal),
|
||||
Type: "created",
|
||||
})
|
||||
|
||||
// index repository if files are created
|
||||
if !repo.IsEmpty {
|
||||
err = c.indexer.Index(ctx, repo)
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
|
||||
"github.com/harness/gitness/app/api/controller"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/audit"
|
||||
|
@ -129,10 +128,9 @@ func (c *Controller) UpdateDefaultBranch(
|
|||
}
|
||||
|
||||
c.eventReporter.DefaultBranchUpdated(ctx, &repoevents.DefaultBranchUpdatedPayload{
|
||||
RepoID: repo.ID,
|
||||
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,
|
||||
OldName: oldName,
|
||||
NewName: repo.DefaultBranch,
|
||||
Base: eventBase(repo, &session.Principal),
|
||||
OldName: oldName,
|
||||
NewName: repoFull.DefaultBranch,
|
||||
})
|
||||
|
||||
return repoOutput, nil
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/githook"
|
||||
"github.com/harness/gitness/errors"
|
||||
|
@ -87,7 +88,7 @@ func (c *Controller) PurgeNoAuth(
|
|||
c.eventReporter.Deleted(
|
||||
ctx,
|
||||
&repoevents.DeletedPayload{
|
||||
RepoID: repo.ID,
|
||||
Base: eventBase(repo.Core(), &bootstrap.NewSystemServiceSession().Principal),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/types"
|
||||
|
@ -116,5 +117,13 @@ func (c *Controller) SoftDeleteNoAuth(
|
|||
|
||||
c.repoFinder.MarkChanged(ctx, repo.Core())
|
||||
|
||||
if repo.Deleted != nil {
|
||||
c.eventReporter.SoftDeleted(ctx, &repoevents.SoftDeletedPayload{
|
||||
Base: eventBase(repo.Core(), &session.Principal),
|
||||
RepoPath: repo.Path,
|
||||
Deleted: *repo.Deleted,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/types"
|
||||
|
@ -88,8 +89,6 @@ func (c *Controller) Update(ctx context.Context,
|
|||
return nil, fmt.Errorf("failed to find repository by ID: %w", err)
|
||||
}
|
||||
|
||||
repoClone := repo.Clone()
|
||||
|
||||
if !in.hasChanges(repo) {
|
||||
return GetRepoOutput(ctx, c.publicAccess, repo)
|
||||
}
|
||||
|
@ -108,7 +107,11 @@ func (c *Controller) Update(ctx context.Context,
|
|||
repo.State, *in.State)
|
||||
}
|
||||
|
||||
var repoClone types.Repository
|
||||
|
||||
repo, err = c.repoStore.UpdateOptLock(ctx, repo, func(repo *types.Repository) error {
|
||||
repoClone = *repo
|
||||
|
||||
// update values only if provided
|
||||
if in.Description != nil {
|
||||
repo.Description = *in.Description
|
||||
|
@ -141,6 +144,14 @@ func (c *Controller) Update(ctx context.Context,
|
|||
repo.GitURL = c.urlProvider.GenerateGITCloneURL(ctx, repo.Path)
|
||||
repo.GitSSHURL = c.urlProvider.GenerateGITCloneSSHURL(ctx, repo.Path)
|
||||
|
||||
if repo.State != repoClone.State {
|
||||
c.eventReporter.StateChanged(ctx, &repoevents.StateChangedPayload{
|
||||
Base: eventBase(repo.Core(), &session.Principal),
|
||||
OldState: repoClone.State,
|
||||
NewState: repo.State,
|
||||
})
|
||||
}
|
||||
|
||||
return GetRepoOutput(ctx, c.publicAccess, repo)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/app/auth"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
@ -98,5 +99,11 @@ func (c *Controller) UpdatePublicAccess(ctx context.Context,
|
|||
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update repository operation: %s", err)
|
||||
}
|
||||
|
||||
c.eventReporter.PublicAccessChanged(ctx, &repoevents.PublicAccessChangedPayload{
|
||||
Base: eventBase(repo.Core(), &session.Principal),
|
||||
OldIsPublic: isPublic,
|
||||
NewIsPublic: in.IsPublic,
|
||||
})
|
||||
|
||||
return GetRepoOutputWithAccess(ctx, in.IsPublic, repo), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package events
|
||||
|
||||
type Base struct {
|
||||
RepoID int64 `json:"repo_id"`
|
||||
PrincipalID int64 `json:"principal_id"`
|
||||
}
|
|
@ -18,14 +18,129 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/harness/gitness/events"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const CreatedEvent events.EventType = "created"
|
||||
|
||||
type CreatedPayload struct {
|
||||
Base
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (r *Reporter) Created(ctx context.Context, payload *CreatedPayload) {
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
|
||||
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, CreatedEvent, payload)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Err(err).Msgf("failed to send repo created event")
|
||||
return
|
||||
}
|
||||
|
||||
log.Ctx(ctx).Debug().Msgf("reported repo created event with id '%s'", eventID)
|
||||
}
|
||||
|
||||
func (r *Reader) RegisterCreated(
|
||||
fn events.HandlerFunc[*CreatedPayload],
|
||||
opts ...events.HandlerOption,
|
||||
) error {
|
||||
return events.ReaderRegisterEvent(r.innerReader, CreatedEvent, fn, opts...)
|
||||
}
|
||||
|
||||
const StateChangedEvent events.EventType = "state-changed"
|
||||
|
||||
type StateChangedPayload struct {
|
||||
Base
|
||||
OldState enum.RepoState `json:"old_state"`
|
||||
NewState enum.RepoState `json:"new_state"`
|
||||
}
|
||||
|
||||
func (r *Reporter) StateChanged(ctx context.Context, payload *StateChangedPayload) {
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
|
||||
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, StateChangedEvent, payload)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Err(err).Msgf("failed to send repo srtate change event")
|
||||
return
|
||||
}
|
||||
|
||||
log.Ctx(ctx).Debug().Msgf("reported repo state change event with id '%s'", eventID)
|
||||
}
|
||||
|
||||
func (r *Reader) RegisterStateChanged(
|
||||
fn events.HandlerFunc[*StateChangedPayload],
|
||||
opts ...events.HandlerOption,
|
||||
) error {
|
||||
return events.ReaderRegisterEvent(r.innerReader, StateChangedEvent, fn, opts...)
|
||||
}
|
||||
|
||||
const PublicAccessChangedEvent events.EventType = "public-access-changed"
|
||||
|
||||
type PublicAccessChangedPayload struct {
|
||||
Base
|
||||
OldIsPublic bool `json:"old_is_public"`
|
||||
NewIsPublic bool `json:"new_is_public"`
|
||||
}
|
||||
|
||||
func (r *Reporter) PublicAccessChanged(ctx context.Context, payload *PublicAccessChangedPayload) {
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
|
||||
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, PublicAccessChangedEvent, payload)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Err(err).Msgf("failed to send repo public access changed event")
|
||||
return
|
||||
}
|
||||
|
||||
log.Ctx(ctx).Debug().Msgf("reported repo public access changed event with id '%s'", eventID)
|
||||
}
|
||||
|
||||
func (r *Reader) RegisterPublicAccessChanged(
|
||||
fn events.HandlerFunc[*PublicAccessChangedPayload],
|
||||
opts ...events.HandlerOption,
|
||||
) error {
|
||||
return events.ReaderRegisterEvent(r.innerReader, PublicAccessChangedEvent, fn, opts...)
|
||||
}
|
||||
|
||||
const SoftDeletedEvent events.EventType = "soft-deleted"
|
||||
|
||||
type SoftDeletedPayload struct {
|
||||
Base
|
||||
RepoPath string `json:"repo_path"`
|
||||
Deleted int64 `json:"deleted"`
|
||||
}
|
||||
|
||||
func (r *Reporter) SoftDeleted(ctx context.Context, payload *SoftDeletedPayload) {
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
eventID, err := events.ReporterSendEvent(r.innerReporter, ctx, SoftDeletedEvent, payload)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Err(err).Msgf("failed to send repo soft deleted event")
|
||||
return
|
||||
}
|
||||
|
||||
log.Ctx(ctx).Debug().Msgf("reported repo soft deleted event with id '%s'", eventID)
|
||||
}
|
||||
|
||||
func (r *Reader) RegisterSoftDeleted(
|
||||
fn events.HandlerFunc[*SoftDeletedPayload],
|
||||
opts ...events.HandlerOption,
|
||||
) error {
|
||||
return events.ReaderRegisterEvent(r.innerReader, SoftDeletedEvent, fn, opts...)
|
||||
}
|
||||
|
||||
const DeletedEvent events.EventType = "deleted"
|
||||
|
||||
type DeletedPayload struct {
|
||||
RepoID int64 `json:"repo_id"`
|
||||
Base
|
||||
}
|
||||
|
||||
func (r *Reporter) Deleted(ctx context.Context, payload *DeletedPayload) {
|
||||
|
@ -41,18 +156,19 @@ func (r *Reporter) Deleted(ctx context.Context, payload *DeletedPayload) {
|
|||
log.Ctx(ctx).Debug().Msgf("reported repo deleted event with id '%s'", eventID)
|
||||
}
|
||||
|
||||
func (r *Reader) RegisterRepoDeleted(fn events.HandlerFunc[*DeletedPayload],
|
||||
opts ...events.HandlerOption) error {
|
||||
func (r *Reader) RegisterDeleted(
|
||||
fn events.HandlerFunc[*DeletedPayload],
|
||||
opts ...events.HandlerOption,
|
||||
) error {
|
||||
return events.ReaderRegisterEvent(r.innerReader, DeletedEvent, fn, opts...)
|
||||
}
|
||||
|
||||
const DefaultBranchUpdatedEvent events.EventType = "default-branch-updated"
|
||||
|
||||
type DefaultBranchUpdatedPayload struct {
|
||||
RepoID int64 `json:"repo_id"`
|
||||
PrincipalID int64 `json:"principal_id"`
|
||||
OldName string `json:"old_name"`
|
||||
NewName string `json:"new_name"`
|
||||
Base
|
||||
OldName string `json:"old_name"`
|
||||
NewName string `json:"new_name"`
|
||||
}
|
||||
|
||||
func (r *Reporter) DefaultBranchUpdated(ctx context.Context, payload *DefaultBranchUpdatedPayload) {
|
||||
|
@ -65,7 +181,9 @@ func (r *Reporter) DefaultBranchUpdated(ctx context.Context, payload *DefaultBra
|
|||
log.Ctx(ctx).Debug().Msgf("reported default branch updated event with id '%s'", eventID)
|
||||
}
|
||||
|
||||
func (r *Reader) RegisterDefaultBranchUpdated(fn events.HandlerFunc[*DefaultBranchUpdatedPayload],
|
||||
opts ...events.HandlerOption) error {
|
||||
func (r *Reader) RegisterDefaultBranchUpdated(
|
||||
fn events.HandlerFunc[*DefaultBranchUpdatedPayload],
|
||||
opts ...events.HandlerOption,
|
||||
) error {
|
||||
return events.ReaderRegisterEvent(r.innerReader, DefaultBranchUpdatedEvent, fn, opts...)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/githook"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/app/services/keywordsearch"
|
||||
|
@ -69,6 +70,7 @@ type Repository struct {
|
|||
sseStreamer sse.Streamer
|
||||
indexer keywordsearch.Indexer
|
||||
publicAccess publicaccess.Service
|
||||
eventReporter *repoevents.Reporter
|
||||
auditService audit.Service
|
||||
}
|
||||
|
||||
|
@ -370,6 +372,14 @@ func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo
|
|||
|
||||
r.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeRepositoryImportCompleted, repo)
|
||||
|
||||
r.eventReporter.Created(ctx, &repoevents.CreatedPayload{
|
||||
Base: repoevents.Base{
|
||||
RepoID: repo.ID,
|
||||
PrincipalID: bootstrap.NewSystemServiceSession().Principal.ID,
|
||||
},
|
||||
Type: "imported",
|
||||
})
|
||||
|
||||
err = r.indexer.Index(ctx, repo)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("failed to index repository")
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package importer
|
||||
|
||||
import (
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/services/keywordsearch"
|
||||
"github.com/harness/gitness/app/services/publicaccess"
|
||||
"github.com/harness/gitness/app/services/refcache"
|
||||
|
@ -50,6 +51,7 @@ func ProvideRepoImporter(
|
|||
sseStreamer sse.Streamer,
|
||||
indexer keywordsearch.Indexer,
|
||||
publicAccess publicaccess.Service,
|
||||
eventReporter *repoevents.Reporter,
|
||||
auditService audit.Service,
|
||||
) (*Repository, error) {
|
||||
importer := &Repository{
|
||||
|
@ -66,6 +68,7 @@ func ProvideRepoImporter(
|
|||
sseStreamer: sseStreamer,
|
||||
indexer: indexer,
|
||||
publicAccess: publicAccess,
|
||||
eventReporter: eventReporter,
|
||||
auditService: auditService,
|
||||
}
|
||||
|
||||
|
|
|
@ -22,14 +22,12 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/app/services/settings"
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/job"
|
||||
store2 "github.com/harness/gitness/registry/app/store"
|
||||
registrystore "github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
"github.com/harness/gitness/version"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const jobType = "metric-collector"
|
||||
|
@ -50,12 +48,10 @@ type metricData struct {
|
|||
ArtifactCount int64 `json:"artifact_count"`
|
||||
}
|
||||
|
||||
type Collector struct {
|
||||
hostname string
|
||||
enabled bool
|
||||
endpoint string
|
||||
token string
|
||||
installID string
|
||||
type CollectorJob struct {
|
||||
values *Values
|
||||
endpoint string
|
||||
token string
|
||||
|
||||
userStore store.PrincipalStore
|
||||
repoStore store.RepoStore
|
||||
|
@ -63,29 +59,50 @@ type Collector struct {
|
|||
executionStore store.ExecutionStore
|
||||
scheduler *job.Scheduler
|
||||
gitspaceConfigStore store.GitspaceConfigStore
|
||||
registryStore store2.RegistryRepository
|
||||
artifactStore store2.ArtifactRepository
|
||||
settings *settings.Service
|
||||
registryStore registrystore.RegistryRepository
|
||||
artifactStore registrystore.ArtifactRepository
|
||||
submitter Submitter
|
||||
}
|
||||
|
||||
func (c *Collector) Register(ctx context.Context) error {
|
||||
if !c.enabled {
|
||||
func NewCollectorJob(
|
||||
values *Values,
|
||||
endpoint string,
|
||||
token string,
|
||||
userStore store.PrincipalStore,
|
||||
repoStore store.RepoStore,
|
||||
pipelineStore store.PipelineStore,
|
||||
executionStore store.ExecutionStore,
|
||||
scheduler *job.Scheduler,
|
||||
gitspaceConfigStore store.GitspaceConfigStore,
|
||||
registryStore registrystore.RegistryRepository,
|
||||
artifactStore registrystore.ArtifactRepository,
|
||||
submitter Submitter,
|
||||
) *CollectorJob {
|
||||
return &CollectorJob{
|
||||
values: values,
|
||||
|
||||
endpoint: endpoint,
|
||||
token: token,
|
||||
|
||||
userStore: userStore,
|
||||
repoStore: repoStore,
|
||||
pipelineStore: pipelineStore,
|
||||
executionStore: executionStore,
|
||||
scheduler: scheduler,
|
||||
gitspaceConfigStore: gitspaceConfigStore,
|
||||
registryStore: registryStore,
|
||||
artifactStore: artifactStore,
|
||||
|
||||
submitter: submitter,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CollectorJob) Register(ctx context.Context) error {
|
||||
if !c.values.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := c.settings.SystemGet(ctx, settings.KeyInstallID, &c.installID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find install id: %w", err)
|
||||
}
|
||||
if !ok || c.installID == "" {
|
||||
c.installID = uuid.New().String()
|
||||
err = c.settings.SystemSet(ctx, settings.KeyInstallID, c.installID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update system settings: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = c.scheduler.AddRecurring(ctx, jobType, jobType, "0 0 * * *", time.Minute)
|
||||
err := c.scheduler.AddRecurring(ctx, jobType, jobType, "0 0 * * *", time.Minute)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register recurring job for collector: %w", err)
|
||||
}
|
||||
|
@ -93,15 +110,22 @@ func (c *Collector) Register(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Handle(ctx context.Context, _ string, _ job.ProgressReporter) (string, error) {
|
||||
if !c.enabled {
|
||||
func (c *CollectorJob) Handle(ctx context.Context, _ string, _ job.ProgressReporter) (string, error) {
|
||||
if !c.values.Enabled {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
err := c.submitter.SubmitGroups(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to submit metric groups: %w", err)
|
||||
}
|
||||
|
||||
// get first available user
|
||||
users, err := c.userStore.ListUsers(ctx, &types.UserFilter{
|
||||
Page: 1,
|
||||
Size: 1,
|
||||
Page: 1,
|
||||
Size: 1,
|
||||
Sort: enum.UserAttrCreated,
|
||||
Order: enum.OrderAsc,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -151,8 +175,8 @@ func (c *Collector) Handle(ctx context.Context, _ string, _ job.ProgressReporter
|
|||
}
|
||||
|
||||
data := metricData{
|
||||
Hostname: c.hostname,
|
||||
InstallID: c.installID,
|
||||
Hostname: c.values.Hostname,
|
||||
InstallID: c.values.InstallID,
|
||||
Installer: users[0].Email,
|
||||
Installed: time.UnixMilli(users[0].Created).Format("2006-01-02 15:04:05"),
|
||||
Version: version.Version.String(),
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
type Object string
|
||||
|
||||
const (
|
||||
ObjectRepository Object = "repository"
|
||||
ObjectPullRequest Object = "pull_request"
|
||||
)
|
||||
|
||||
type VerbRepo string
|
||||
|
||||
// Repository verbs.
|
||||
const (
|
||||
VerbRepoCreate VerbRepo = "create"
|
||||
VerbRepoUpdate VerbRepo = "update"
|
||||
VerbRepoDelete VerbRepo = "delete"
|
||||
)
|
||||
|
||||
type VerbPullReq string
|
||||
|
||||
// Pull request verbs.
|
||||
const (
|
||||
VerbPullReqCreate VerbPullReq = "create"
|
||||
VerbPullReqMerge VerbPullReq = "merge"
|
||||
VerbPullReqClose VerbPullReq = "close"
|
||||
VerbPullReqReopen VerbPullReq = "reopen"
|
||||
)
|
||||
|
||||
type Submitter interface {
|
||||
// SubmitGroups should be called once a day to update info about all the groups.
|
||||
SubmitGroups(ctx context.Context) error
|
||||
|
||||
// SubmitForRepo submits an event for a repository.
|
||||
SubmitForRepo(
|
||||
ctx context.Context,
|
||||
user *types.PrincipalInfo,
|
||||
verb VerbRepo,
|
||||
properties map[string]any,
|
||||
) error
|
||||
|
||||
// SubmitForPullReq submits an event for a pull request.
|
||||
SubmitForPullReq(
|
||||
ctx context.Context,
|
||||
user *types.PrincipalInfo,
|
||||
verb VerbPullReq,
|
||||
properties map[string]any,
|
||||
) error
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/services/refcache"
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/events"
|
||||
"github.com/harness/gitness/stream"
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
func registerEventListeners(
|
||||
ctx context.Context,
|
||||
config *types.Config,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
pullReqStore store.PullReqStore,
|
||||
repoReaderFactory *events.ReaderFactory[*repoevents.Reader],
|
||||
pullreqEvReaderFactory *events.ReaderFactory[*pullreqevents.Reader],
|
||||
repoFinder refcache.RepoFinder,
|
||||
submitter Submitter,
|
||||
) error {
|
||||
if submitter == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
const groupMetricsRepo = "gitness:metrics:repo"
|
||||
_, err = repoReaderFactory.Launch(ctx, groupMetricsRepo, config.InstanceID,
|
||||
func(r *repoevents.Reader) error {
|
||||
const idleTimeout = 10 * time.Second
|
||||
r.Configure(
|
||||
stream.WithConcurrency(1),
|
||||
stream.WithHandlerOptions(
|
||||
stream.WithIdleTimeout(idleTimeout),
|
||||
stream.WithMaxRetries(2),
|
||||
))
|
||||
|
||||
h := handlersRepo{
|
||||
principalInfoCache: principalInfoCache,
|
||||
repoFinder: repoFinder,
|
||||
submitter: submitter,
|
||||
}
|
||||
|
||||
_ = r.RegisterCreated(h.Create)
|
||||
_ = r.RegisterDefaultBranchUpdated(h.DefaultBranchUpdate)
|
||||
_ = r.RegisterStateChanged(h.StateChange)
|
||||
_ = r.RegisterPublicAccessChanged(h.PublicAccessChange)
|
||||
_ = r.RegisterSoftDeleted(h.SoftDelete)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const groupMetricsPullReq = "gitness:metrics:pullreq"
|
||||
_, err = pullreqEvReaderFactory.Launch(ctx, groupMetricsPullReq, config.InstanceID,
|
||||
func(r *pullreqevents.Reader) error {
|
||||
const idleTimeout = 10 * time.Second
|
||||
r.Configure(
|
||||
stream.WithConcurrency(1),
|
||||
stream.WithHandlerOptions(
|
||||
stream.WithIdleTimeout(idleTimeout),
|
||||
stream.WithMaxRetries(2),
|
||||
))
|
||||
|
||||
h := handlersPullReq{
|
||||
principalInfoCache: principalInfoCache,
|
||||
repoFinder: repoFinder,
|
||||
pullReqStore: pullReqStore,
|
||||
submitter: submitter,
|
||||
}
|
||||
|
||||
_ = r.RegisterCreated(h.Create)
|
||||
_ = r.RegisterReopened(h.Reopen)
|
||||
_ = r.RegisterClosed(h.Close)
|
||||
_ = r.RegisterMerged(h.Merge)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type handlersRepo struct {
|
||||
principalInfoCache store.PrincipalInfoCache
|
||||
repoFinder refcache.RepoFinder
|
||||
submitter Submitter
|
||||
}
|
||||
|
||||
func (h handlersRepo) Create(ctx context.Context, e *events.Event[*repoevents.CreatedPayload]) error {
|
||||
props := map[string]any{
|
||||
"type": e.Payload.Type,
|
||||
}
|
||||
return h.submit(ctx, e.Payload.RepoID, e.Payload.PrincipalID, VerbRepoCreate, props)
|
||||
}
|
||||
|
||||
func (h handlersRepo) DefaultBranchUpdate(
|
||||
ctx context.Context,
|
||||
e *events.Event[*repoevents.DefaultBranchUpdatedPayload],
|
||||
) error {
|
||||
props := map[string]any{
|
||||
"change": "default_branch",
|
||||
"repo_old_default_branch": e.Payload.OldName,
|
||||
"repo_new_default_branch": e.Payload.NewName,
|
||||
}
|
||||
return h.submit(ctx, e.Payload.RepoID, e.Payload.PrincipalID, VerbRepoUpdate, props)
|
||||
}
|
||||
|
||||
func (h handlersRepo) StateChange(
|
||||
ctx context.Context,
|
||||
e *events.Event[*repoevents.StateChangedPayload],
|
||||
) error {
|
||||
props := map[string]any{
|
||||
"change": "state",
|
||||
"repo_old_state": e.Payload.OldState,
|
||||
"repo_new_state": e.Payload.NewState,
|
||||
}
|
||||
return h.submit(ctx, e.Payload.RepoID, e.Payload.PrincipalID, VerbRepoUpdate, props)
|
||||
}
|
||||
|
||||
func (h handlersRepo) PublicAccessChange(
|
||||
ctx context.Context,
|
||||
e *events.Event[*repoevents.PublicAccessChangedPayload],
|
||||
) error {
|
||||
props := map[string]any{
|
||||
"change": "public_access",
|
||||
"repo_old_is_public": e.Payload.OldIsPublic,
|
||||
"repo_new_is_public": e.Payload.NewIsPublic,
|
||||
}
|
||||
return h.submit(ctx, e.Payload.RepoID, e.Payload.PrincipalID, VerbRepoUpdate, props)
|
||||
}
|
||||
|
||||
func (h handlersRepo) SoftDelete(
|
||||
ctx context.Context,
|
||||
e *events.Event[*repoevents.SoftDeletedPayload],
|
||||
) error {
|
||||
return h.submitDeleted(ctx, e.Payload.RepoPath, e.Payload.Deleted, e.Payload.PrincipalID, VerbRepoDelete, nil)
|
||||
}
|
||||
|
||||
func (h handlersRepo) submit(
|
||||
ctx context.Context,
|
||||
repoID int64,
|
||||
principalID int64,
|
||||
verb VerbRepo,
|
||||
props map[string]any,
|
||||
) error {
|
||||
repo, err := h.repoFinder.FindByID(ctx, repoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find repository")
|
||||
}
|
||||
|
||||
err = h.submitMetric(ctx, repo, principalID, verb, props)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to submit event: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h handlersRepo) submitDeleted(
|
||||
ctx context.Context,
|
||||
repoRef string,
|
||||
deletedAt int64,
|
||||
principalID int64,
|
||||
verb VerbRepo,
|
||||
props map[string]any,
|
||||
) error {
|
||||
repo, err := h.repoFinder.FindDeletedByRef(ctx, repoRef, deletedAt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find delete repo: %w", err)
|
||||
}
|
||||
|
||||
err = h.submitMetric(ctx, repo.Core(), principalID, verb, props)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to submit event: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h handlersRepo) submitMetric(
|
||||
ctx context.Context,
|
||||
repo *types.RepositoryCore,
|
||||
principalID int64,
|
||||
verb VerbRepo,
|
||||
props map[string]any,
|
||||
) error {
|
||||
principal, err := h.principalInfoCache.Get(ctx, principalID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get principal info: %w", err)
|
||||
}
|
||||
|
||||
if props == nil {
|
||||
props = make(map[string]any)
|
||||
}
|
||||
|
||||
props["repo_id"] = repo.ID
|
||||
props["repo_path"] = repo.Path
|
||||
props["repo_parent_id"] = repo.ParentID
|
||||
|
||||
err = h.submitter.SubmitForRepo(ctx, principal, verb, props)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to submit metric data for repositoy: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type handlersPullReq struct {
|
||||
principalInfoCache store.PrincipalInfoCache
|
||||
repoFinder refcache.RepoFinder
|
||||
pullReqStore store.PullReqStore
|
||||
submitter Submitter
|
||||
}
|
||||
|
||||
func (h handlersPullReq) Create(ctx context.Context, e *events.Event[*pullreqevents.CreatedPayload]) error {
|
||||
return h.submit(ctx, e.Payload.PullReqID, e.Payload.PrincipalID, VerbPullReqCreate)
|
||||
}
|
||||
|
||||
func (h handlersPullReq) Close(ctx context.Context, e *events.Event[*pullreqevents.ClosedPayload]) error {
|
||||
return h.submit(ctx, e.Payload.PullReqID, e.Payload.PrincipalID, VerbPullReqClose)
|
||||
}
|
||||
|
||||
func (h handlersPullReq) Reopen(ctx context.Context, e *events.Event[*pullreqevents.ReopenedPayload]) error {
|
||||
return h.submit(ctx, e.Payload.PullReqID, e.Payload.PrincipalID, VerbPullReqReopen)
|
||||
}
|
||||
|
||||
func (h handlersPullReq) Merge(ctx context.Context, e *events.Event[*pullreqevents.MergedPayload]) error {
|
||||
return h.submit(ctx, e.Payload.PullReqID, e.Payload.PrincipalID, VerbPullReqMerge)
|
||||
}
|
||||
|
||||
func (h handlersPullReq) submit(ctx context.Context, pullReqID, principalID int64, verb VerbPullReq) error {
|
||||
pr, err := h.pullReqStore.Find(ctx, pullReqID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find pull request: %w", err)
|
||||
}
|
||||
|
||||
repo, err := h.repoFinder.FindByID(ctx, pr.TargetRepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find pull request: %w", err)
|
||||
}
|
||||
|
||||
principal, err := h.principalInfoCache.Get(ctx, principalID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get principal info: %w", err)
|
||||
}
|
||||
|
||||
author, err := h.principalInfoCache.Get(ctx, pr.CreatedBy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get author principal info: %w", err)
|
||||
}
|
||||
|
||||
props := map[string]any{
|
||||
"repo_id": repo.ID,
|
||||
"repo_path": repo.Path,
|
||||
"repo_parent_id": repo.ParentID,
|
||||
"pullreq_author_email": author.Email,
|
||||
"pullreq_number": pr.Number,
|
||||
"pullreq_target_repo_id": pr.TargetRepoID,
|
||||
"pullreq_source_repo_id": pr.SourceRepoID,
|
||||
"pullreq_target_branch": pr.TargetBranch,
|
||||
"pullreq_source_branch": pr.SourceBranch,
|
||||
}
|
||||
|
||||
if pr.MergeMethod != nil {
|
||||
props["pullreq_merge_method"] = string(*pr.MergeMethod)
|
||||
}
|
||||
|
||||
err = h.submitter.SubmitForPullReq(ctx, principal, verb, props)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to submit metric data for pull request: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const postHogGroupInstall = "install"
|
||||
const postHogServerUserID = "harness-server"
|
||||
|
||||
type PostHog struct {
|
||||
client posthog.Client
|
||||
installID string
|
||||
hostname string
|
||||
principalStore store.PrincipalStore
|
||||
principalInfoCache store.PrincipalInfoCache
|
||||
}
|
||||
|
||||
type group struct {
|
||||
Type string
|
||||
ID string
|
||||
Properties map[string]any
|
||||
}
|
||||
|
||||
func NewPostHog(
|
||||
ctx context.Context,
|
||||
config *types.Config,
|
||||
values *Values,
|
||||
principalStore store.PrincipalStore,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
) (*PostHog, error) {
|
||||
if !values.Enabled || values.InstallID == "" || config.Metric.PostHogProjectAPIKey == "" {
|
||||
return nil, nil //nolint:nilnil // PostHog is disabled
|
||||
}
|
||||
|
||||
logr := log.Ctx(ctx).With().Str("service.name", "posthog").Logger()
|
||||
|
||||
// https://posthog.com/docs/libraries/go#overriding-geoip-properties
|
||||
|
||||
phConfig := posthog.Config{
|
||||
Endpoint: config.Metric.PostHogEndpoint,
|
||||
PersonalApiKey: config.Metric.PostHogPersonalAPIKey,
|
||||
Logger: &logger{Logger: logr},
|
||||
DefaultEventProperties: posthog.NewProperties().Set("install_id", values.InstallID),
|
||||
Callback: nil,
|
||||
}
|
||||
|
||||
client, err := posthog.NewWithConfig(config.Metric.PostHogProjectAPIKey, phConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create PostHog client: %w", err)
|
||||
}
|
||||
|
||||
ph := &PostHog{
|
||||
client: client,
|
||||
installID: values.InstallID,
|
||||
hostname: values.Hostname,
|
||||
principalStore: principalStore,
|
||||
principalInfoCache: principalInfoCache,
|
||||
}
|
||||
|
||||
go ph.submitDefaultGroupOnce(ctx)
|
||||
|
||||
return ph, nil
|
||||
}
|
||||
|
||||
func (ph *PostHog) SubmitGroups(context.Context) error {
|
||||
// No implementation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ph *PostHog) SubmitForRepo(
|
||||
ctx context.Context,
|
||||
user *types.PrincipalInfo,
|
||||
verb VerbRepo,
|
||||
properties map[string]any,
|
||||
) error {
|
||||
return ph.submit(ctx, user, ObjectRepository, string(verb), properties)
|
||||
}
|
||||
|
||||
func (ph *PostHog) SubmitForPullReq(
|
||||
ctx context.Context,
|
||||
user *types.PrincipalInfo,
|
||||
verb VerbPullReq,
|
||||
properties map[string]any,
|
||||
) error {
|
||||
return ph.submit(ctx, user, ObjectPullRequest, string(verb), properties)
|
||||
}
|
||||
|
||||
func (ph *PostHog) uniqueUserID(id string) string {
|
||||
return ph.installID + ":" + id
|
||||
}
|
||||
|
||||
func (ph *PostHog) submit(
|
||||
_ context.Context,
|
||||
user *types.PrincipalInfo,
|
||||
object Object,
|
||||
verb string,
|
||||
properties map[string]any,
|
||||
) error {
|
||||
if ph == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var distinctID string
|
||||
if user != nil {
|
||||
distinctID = ph.uniqueUserID(user.UID)
|
||||
|
||||
p := posthog.NewProperties().Merge(properties)
|
||||
p.Set("$set_once", map[string]any{
|
||||
"type": user.Type,
|
||||
"created": user.Created,
|
||||
})
|
||||
p.Set("$set", map[string]any{
|
||||
"email": user.Email,
|
||||
})
|
||||
|
||||
properties = p
|
||||
}
|
||||
|
||||
err := ph.client.Enqueue(posthog.Capture{
|
||||
DistinctId: distinctID,
|
||||
Event: string(object) + ":" + verb,
|
||||
Groups: posthog.NewGroups().Set(postHogGroupInstall, ph.installID),
|
||||
Properties: properties,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enqueue event; object=%s verb=%s: %w", object, verb, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ph *PostHog) submitGroup(group group) error {
|
||||
err := ph.client.Enqueue(posthog.GroupIdentify{
|
||||
DistinctId: postHogServerUserID,
|
||||
Type: group.Type,
|
||||
Key: group.ID,
|
||||
Properties: group.Properties,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enqueue group identify: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ph *PostHog) submitDefaultGroup(ctx context.Context) error {
|
||||
users, err := ph.principalStore.ListUsers(ctx, &types.UserFilter{
|
||||
Page: 1,
|
||||
Size: 1,
|
||||
Sort: enum.UserAttrCreated,
|
||||
Order: enum.OrderAsc,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list users: %w", err)
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return errors.New("no users found")
|
||||
}
|
||||
|
||||
userFirst := users[0]
|
||||
|
||||
// Note: The PostHog UI identifies a group using the name property.
|
||||
// If the name property is not found, it falls back to the group key.
|
||||
// https://posthog.com/docs/product-analytics/group-analytics#how-to-set-group-properties
|
||||
g := group{
|
||||
Type: postHogGroupInstall,
|
||||
ID: ph.installID,
|
||||
Properties: posthog.NewProperties().
|
||||
Set("name", "install").
|
||||
Set("hostname", ph.hostname).
|
||||
Set("email", userFirst.Email).
|
||||
Set("created", userFirst.Created),
|
||||
}
|
||||
|
||||
err = ph.submitGroup(g)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to submit default group: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ph *PostHog) submitDefaultGroupOnce(ctx context.Context) {
|
||||
timer := time.NewTimer(time.Hour)
|
||||
defer timer.Stop()
|
||||
|
||||
logr := log.Ctx(ctx).With().Str("service.name", "posthog").Logger()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-timer.C:
|
||||
if err := ph.submitDefaultGroup(ctx); err != nil {
|
||||
logr.Err(err).Msg("failed to submit default group")
|
||||
timer.Reset(time.Hour)
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
zerolog.Logger
|
||||
}
|
||||
|
||||
func (l *logger) Logf(format string, args ...interface{}) {
|
||||
l.Info().Msgf(format, args...)
|
||||
}
|
||||
|
||||
func (l *logger) Errorf(format string, args ...interface{}) {
|
||||
l.Error().Msgf(format, args...)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/app/services/settings"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Values struct {
|
||||
Enabled bool
|
||||
Hostname string
|
||||
InstallID string
|
||||
}
|
||||
|
||||
func NewValues(
|
||||
ctx context.Context,
|
||||
config *types.Config,
|
||||
settingsSrv *settings.Service,
|
||||
) (*Values, error) {
|
||||
if !config.Metric.Enabled {
|
||||
return &Values{
|
||||
Enabled: false,
|
||||
InstallID: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
values := Values{
|
||||
Enabled: true,
|
||||
Hostname: config.InstanceID,
|
||||
InstallID: "",
|
||||
}
|
||||
|
||||
ok, err := settingsSrv.SystemGet(ctx, settings.KeyInstallID, &values.InstallID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find install id: %w", err)
|
||||
}
|
||||
|
||||
if !ok || values.InstallID == "" {
|
||||
values.InstallID = uuid.New().String()
|
||||
err = settingsSrv.SystemSet(ctx, settings.KeyInstallID, values.InstallID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update system settings: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &values, nil
|
||||
}
|
|
@ -15,8 +15,15 @@
|
|||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||
repoevents "github.com/harness/gitness/app/events/repo"
|
||||
"github.com/harness/gitness/app/services/refcache"
|
||||
"github.com/harness/gitness/app/services/settings"
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/events"
|
||||
"github.com/harness/gitness/job"
|
||||
registrystore "github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/types"
|
||||
|
@ -25,11 +32,51 @@ import (
|
|||
)
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideCollector,
|
||||
ProvideValues,
|
||||
ProvideSubmitter,
|
||||
ProvideCollectorJob,
|
||||
)
|
||||
|
||||
func ProvideCollector(
|
||||
func ProvideValues(ctx context.Context, config *types.Config, settingsSrv *settings.Service) (*Values, error) {
|
||||
return NewValues(ctx, config, settingsSrv)
|
||||
}
|
||||
|
||||
func ProvideSubmitter(
|
||||
appCtx context.Context,
|
||||
config *types.Config,
|
||||
values *Values,
|
||||
principalStore store.PrincipalStore,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
pullReqStore store.PullReqStore,
|
||||
repoReaderFactory *events.ReaderFactory[*repoevents.Reader],
|
||||
pullreqEvReaderFactory *events.ReaderFactory[*pullreqevents.Reader],
|
||||
repoFinder refcache.RepoFinder,
|
||||
) (Submitter, error) {
|
||||
submitter, err := NewPostHog(appCtx, config, values, principalStore, principalInfoCache)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create posthog metrics submitter: %w", err)
|
||||
}
|
||||
|
||||
err = registerEventListeners(
|
||||
appCtx,
|
||||
config,
|
||||
principalInfoCache,
|
||||
pullReqStore,
|
||||
repoReaderFactory,
|
||||
pullreqEvReaderFactory,
|
||||
repoFinder,
|
||||
submitter,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to register metric event listeners: %w", err)
|
||||
}
|
||||
|
||||
return submitter, nil
|
||||
}
|
||||
|
||||
func ProvideCollectorJob(
|
||||
config *types.Config,
|
||||
values *Values,
|
||||
userStore store.PrincipalStore,
|
||||
repoStore store.RepoStore,
|
||||
pipelineStore store.PipelineStore,
|
||||
|
@ -37,30 +84,29 @@ func ProvideCollector(
|
|||
scheduler *job.Scheduler,
|
||||
executor *job.Executor,
|
||||
gitspaceConfigStore store.GitspaceConfigStore,
|
||||
settings *settings.Service,
|
||||
registryStore registrystore.RegistryRepository,
|
||||
artifactStore registrystore.ArtifactRepository,
|
||||
) (*Collector, error) {
|
||||
job := &Collector{
|
||||
hostname: config.InstanceID,
|
||||
enabled: config.Metric.Enabled,
|
||||
endpoint: config.Metric.Endpoint,
|
||||
token: config.Metric.Token,
|
||||
userStore: userStore,
|
||||
repoStore: repoStore,
|
||||
pipelineStore: pipelineStore,
|
||||
executionStore: executionStore,
|
||||
scheduler: scheduler,
|
||||
gitspaceConfigStore: gitspaceConfigStore,
|
||||
registryStore: registryStore,
|
||||
artifactStore: artifactStore,
|
||||
settings: settings,
|
||||
}
|
||||
submitter Submitter,
|
||||
) (*CollectorJob, error) {
|
||||
collector := NewCollectorJob(
|
||||
values,
|
||||
config.Metric.Endpoint,
|
||||
config.Metric.Token,
|
||||
userStore,
|
||||
repoStore,
|
||||
pipelineStore,
|
||||
executionStore,
|
||||
scheduler,
|
||||
gitspaceConfigStore,
|
||||
registryStore,
|
||||
artifactStore,
|
||||
submitter,
|
||||
)
|
||||
|
||||
err := executor.Register(jobType, job)
|
||||
err := executor.Register(jobType, collector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return job, nil
|
||||
return collector, nil
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ type Services struct {
|
|||
PullReq *pullreq.Service
|
||||
Trigger *trigger.Service
|
||||
JobScheduler *job.Scheduler
|
||||
MetricCollector *metric.Collector
|
||||
MetricCollector *metric.CollectorJob
|
||||
RepoSizeCalculator *repo.SizeCalculator
|
||||
Repo *repo.Service
|
||||
Cleanup *cleanup.Service
|
||||
|
@ -90,7 +90,7 @@ func ProvideServices(
|
|||
pullReqSvc *pullreq.Service,
|
||||
triggerSvc *trigger.Service,
|
||||
jobScheduler *job.Scheduler,
|
||||
metricCollector *metric.Collector,
|
||||
metricCollector *metric.CollectorJob,
|
||||
repoSizeCalculator *repo.SizeCalculator,
|
||||
repo *repo.Service,
|
||||
cleanupSvc *cleanup.Service,
|
||||
|
|
|
@ -236,14 +236,6 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
streamer := sse.ProvideEventsStreaming(pubSub)
|
||||
localIndexSearcher := keywordsearch.ProvideLocalIndexSearcher()
|
||||
indexer := keywordsearch.ProvideIndexer(localIndexSearcher)
|
||||
auditService := audit.ProvideAuditService()
|
||||
repository, err := importer.ProvideRepoImporter(config, provider, gitInterface, transactor, repoStore, pipelineStore, triggerStore, repoFinder, encrypter, jobScheduler, executor, streamer, indexer, publicaccessService, auditService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeownersConfig := server.ProvideCodeOwnerConfig(config)
|
||||
usergroupResolver := usergroup.ProvideUserGroupResolver()
|
||||
codeownersService := codeowners.ProvideCodeOwners(gitInterface, repoStore, codeownersConfig, principalStore, usergroupResolver)
|
||||
eventsConfig := server.ProvideEventsConfig(config)
|
||||
eventsSystem, err := events.ProvideSystem(eventsConfig, universalClient)
|
||||
if err != nil {
|
||||
|
@ -253,6 +245,14 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auditService := audit.ProvideAuditService()
|
||||
repository, err := importer.ProvideRepoImporter(config, provider, gitInterface, transactor, repoStore, pipelineStore, triggerStore, repoFinder, encrypter, jobScheduler, executor, streamer, indexer, publicaccessService, reporter, auditService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeownersConfig := server.ProvideCodeOwnerConfig(config)
|
||||
usergroupResolver := usergroup.ProvideUserGroupResolver()
|
||||
codeownersService := codeowners.ProvideCodeOwners(gitInterface, repoStore, codeownersConfig, principalStore, usergroupResolver)
|
||||
resourceLimiter, err := limiter.ProvideLimiter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -449,7 +449,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
rule := migrate.ProvideRuleImporter(ruleStore, transactor, principalStore)
|
||||
migrateWebhook := migrate.ProvideWebhookImporter(webhookConfig, transactor, webhookStore)
|
||||
migrateLabel := migrate.ProvideLabelImporter(transactor, labelStore, labelValueStore, spaceStore)
|
||||
migrateController := migrate2.ProvideController(authorizer, publicaccessService, gitInterface, provider, pullReq, rule, migrateWebhook, migrateLabel, resourceLimiter, auditService, repoIdentifier, transactor, spaceStore, repoStore, spaceFinder, repoFinder)
|
||||
migrateController := migrate2.ProvideController(authorizer, publicaccessService, gitInterface, provider, pullReq, rule, migrateWebhook, migrateLabel, resourceLimiter, auditService, repoIdentifier, transactor, spaceStore, repoStore, spaceFinder, repoFinder, reporter)
|
||||
openapiService := openapi.ProvideOpenAPIService()
|
||||
storageDriver, err := api2.BlobStorageProvider(config)
|
||||
if err != nil {
|
||||
|
@ -539,11 +539,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collector, err := metric.ProvideCollector(config, principalStore, repoStore, pipelineStore, executionStore, jobScheduler, executor, gitspaceConfigStore, settingsService, registryRepository, artifactRepository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sizeCalculator, err := repo2.ProvideCalculator(config, gitInterface, repoStore, jobScheduler, executor)
|
||||
values, err := metric.ProvideValues(ctx, config, settingsService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -551,6 +547,18 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
submitter, err := metric.ProvideSubmitter(ctx, config, values, principalStore, principalInfoCache, pullReqStore, readerFactory3, eventsReaderFactory, repoFinder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collectorJob, err := metric.ProvideCollectorJob(config, values, principalStore, repoStore, pipelineStore, executionStore, jobScheduler, executor, gitspaceConfigStore, registryRepository, artifactRepository, submitter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sizeCalculator, err := repo2.ProvideCalculator(config, gitInterface, repoStore, jobScheduler, executor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoService, err := repo2.ProvideService(ctx, config, reporter, readerFactory3, repoStore, provider, gitInterface, lockerLocker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -615,7 +623,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
servicesServices := services.ProvideServices(webhookService, pullreqService, triggerService, jobScheduler, collector, sizeCalculator, repoService, cleanupService, notificationService, keywordsearchService, gitspaceServices, instrumentService, consumer, repositoryCount, service2)
|
||||
servicesServices := services.ProvideServices(webhookService, pullreqService, triggerService, jobScheduler, collectorJob, sizeCalculator, repoService, cleanupService, notificationService, keywordsearchService, gitspaceServices, instrumentService, consumer, repositoryCount, service2)
|
||||
serverSystem := server.NewSystem(bootstrapBootstrap, serverServer, sshServer, poller, resolverManager, servicesServices)
|
||||
return serverSystem, nil
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -145,6 +145,7 @@ require (
|
|||
github.com/onsi/gomega v1.27.10 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/posthog/posthog-go v1.3.3 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -636,6 +636,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posthog/posthog-go v1.3.3 h1:0b1JlfPDMKXGRgbTtqkSG6hsrlj8yW2tv2HS6Dn7wFk=
|
||||
github.com/posthog/posthog-go v1.3.3/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
|
|
|
@ -352,6 +352,14 @@ type Config struct {
|
|||
Enabled bool `envconfig:"GITNESS_METRIC_ENABLED" default:"true"`
|
||||
Endpoint string `envconfig:"GITNESS_METRIC_ENDPOINT" default:"https://stats.drone.ci/api/v1/gitness"`
|
||||
Token string `envconfig:"GITNESS_METRIC_TOKEN"`
|
||||
|
||||
// PostHogEndpoint is URL to the PostHog service
|
||||
PostHogEndpoint string `envconfig:"GITNESS_METRIC_POSTHOG_ENDPOINT" default:"https://us.i.posthog.com"`
|
||||
// PostHogProjectAPIKey (starts with "phc_") is public (can be exposed in frontend) token used to submit events.
|
||||
PostHogProjectAPIKey string `envconfig:"GITNESS_METRIC_POSTHOG_PROJECT_APIKEY"`
|
||||
// PostHogPersonalAPIKey (starts with "phx_") is sensitive. It's used to access private access points.
|
||||
// It's not required for submitting events.
|
||||
PostHogPersonalAPIKey string `envconfig:"GITNESS_METRIC_POSTHOG_PERSONAL_APIKEY"`
|
||||
}
|
||||
|
||||
RepoSize struct {
|
||||
|
|
Loading…
Reference in New Issue