Skip to content

Commit

Permalink
MM-59360 add telemetry for paid features related to guests (mattermos…
Browse files Browse the repository at this point in the history
…t#28295)

* add telemetry for paid features related to guests

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
  • Loading branch information
Willyfrog and mattermost-build authored Oct 7, 2024
1 parent 18388a8 commit 662e598
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 22 deletions.
8 changes: 8 additions & 0 deletions server/channels/app/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"github.com/mattermost/mattermost/server/v8/channels/utils"
"github.com/mattermost/mattermost/server/v8/platform/services/telemetry"

"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
Expand Down Expand Up @@ -1568,6 +1569,13 @@ func (a *App) addUserToChannel(c request.CTX, user *model.User, channel *model.C
return nil, model.NewAppError("AddUserToChannel", "app.channel_member_history.log_join_event.internal_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}

if user.IsGuest() {
a.Srv().telemetryService.SendTelemetryForFeature(
telemetry.TrackGuestFeature,
"add_guest_to_channel",
map[string]any{"user_actual_id": user.Id})
}

a.Srv().Platform().InvalidateChannelCacheForUser(user.Id)
a.invalidateCacheForChannelMembers(channel.Id)

Expand Down
22 changes: 22 additions & 0 deletions server/channels/app/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/platform/services/telemetry"
)

func (a *App) canSendPushNotifications() bool {
Expand Down Expand Up @@ -877,6 +878,27 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
mlog.String("post_id", post.Id),
)

for id, reason := range mentions.Mentions {
user, ok := profileMap[id]
if !ok {
continue
}
if user.IsGuest() {
if reason == KeywordMention {
a.Srv().telemetryService.SendTelemetryForFeature(
telemetry.TrackGuestFeature,
"post_mentioned_guest",
map[string]any{"user_actual_id": user.Id, "post_owner_id": sender.Id},
)
} else if reason == DMMention {
a.Srv().telemetryService.SendTelemetryForFeature(
telemetry.TrackGuestFeature,
"direct_message_to_guest",
map[string]any{"user_actual_id": user.Id, "post_owner_id": sender.Id},
)
}
}
}
return mentionedUsersList, nil
}

Expand Down
54 changes: 53 additions & 1 deletion server/platform/services/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ const (
TrackPlugins = "plugins"
)

type TrackSKU string

const (
TrackProfessionalSKU TrackSKU = "professional"
TrackEnterpriseSKU TrackSKU = "enterprise"
)

type TrackFeature string

const TrackGuestFeature TrackFeature = "guest_accounts"

type ServerIface interface {
Config() *model.Config
IsLeader() bool
Expand All @@ -119,6 +130,15 @@ type RudderConfig struct {
DataplaneURL string
}

type EventFeature struct {
Name TrackFeature `json:"name"`
SKUS []TrackSKU `json:"skus"`
}

var featureSKUS = map[TrackFeature][]TrackSKU{
TrackGuestFeature: {TrackProfessionalSKU, TrackEnterpriseSKU},
}

func New(srv ServerIface, dbStore store.Store, searchEngine *searchengine.Broker, log *mlog.Logger, verbose bool) (*TelemetryService, error) {
service := &TelemetryService{
srv: srv,
Expand Down Expand Up @@ -202,7 +222,7 @@ func (ts *TelemetryService) sendDailyTelemetry(override bool) {
func (ts *TelemetryService) SendTelemetry(event string, properties map[string]any) {
if ts.rudderClient != nil {
var context *rudder.Context
// if we are part of a cloud installation, add it's ID to the tracked event's context
// if we are part of a cloud installation, add its ID to the tracked event's context
if installationId := os.Getenv("MM_CLOUD_INSTALLATION_ID"); installationId != "" {
context = &rudder.Context{Traits: map[string]any{"installationId": installationId}}
}
Expand All @@ -218,6 +238,38 @@ func (ts *TelemetryService) SendTelemetry(event string, properties map[string]an
}
}

func (ts *TelemetryService) SendTelemetryForFeature(featureName TrackFeature, event string, properties map[string]any) {
if ts.rudderClient != nil {
skus, ok := featureSKUS[featureName]
if !ok {
skus = []TrackSKU{}
mlog.Warn("Telemetry SKUS are not defined for the feature", mlog.String("feature", featureName))
}
feature := EventFeature{
Name: featureName,
SKUS: skus,
}

var context *rudder.Context = &rudder.Context{
Extra: map[string]any{"feature": feature},
}
// if we are part of a cloud installation, add its ID to the tracked event's context
if installationId := os.Getenv("MM_CLOUD_INSTALLATION_ID"); installationId != "" {
context.Traits = map[string]any{"installationId": installationId}
}

err := ts.rudderClient.Enqueue(rudder.Track{
Event: event,
UserId: ts.TelemetryID,
Properties: properties,
Context: context,
})
if err != nil {
ts.log.Warn("Error sending telemetry for feature", mlog.Err(err))
}
}
}

func isDefaultArray(setting, defaultValue []string) bool {
if len(setting) != len(defaultValue) {
return false
Expand Down
64 changes: 43 additions & 21 deletions server/platform/services/telemetry/telemetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,33 +35,28 @@ type FakeConfigService struct {
cfg *model.Config
}

type testBatch struct {
MessageId string
UserId string
Event string
Timestamp time.Time
Properties map[string]any
Context map[string]any
}

type testTelemetryPayload struct {
MessageId string
SentAt time.Time
Batch []struct {
MessageId string
UserId string
Event string
Timestamp time.Time
Properties map[string]any
}
Context struct {
Batch []testBatch
Context struct {
Library struct {
Name string
Version string
}
}
}

type testBatch struct {
MessageId string
UserId string
Event string
Timestamp time.Time
Properties map[string]any
}

func assertPayload(t *testing.T, actual testTelemetryPayload, event string, properties map[string]any) {
func assertPayload(t *testing.T, actual testTelemetryPayload, event string, properties map[string]any, featureContext map[string]any) {
t.Helper()
assert.NotEmpty(t, actual.MessageId)
assert.False(t, actual.SentAt.IsZero())
Expand All @@ -75,6 +70,11 @@ func assertPayload(t *testing.T, actual testTelemetryPayload, event string, prop
if properties != nil {
assert.Equal(t, properties, actual.Batch[0].Properties)
}
if featureContext != nil {
actualFeature := actual.Batch[0].Context["feature"].(map[string]any)
assert.Equal(t, featureContext["name"], actualFeature["name"], "feature name must match")
assert.Equal(t, featureContext["skus"], actualFeature["skus"], "SKUs must match")
}
}
assert.Equal(t, "analytics-go", actual.Context.Library.Name)
assert.Equal(t, "3.3.0", actual.Context.Library.Version)
Expand All @@ -85,7 +85,7 @@ func collectBatches(t *testing.T, info *[]testBatch, pchan chan testTelemetryPay
for {
select {
case result := <-pchan:
assertPayload(t, result, "", nil)
assertPayload(t, result, "", nil, nil)
*info = append(*info, result.Batch[0])
case <-time.After(2 * time.Second):
return
Expand Down Expand Up @@ -126,7 +126,7 @@ func makeTelemetryServiceAndReceiver(t *testing.T, cloudLicense bool) (*Telemetr
// initializing rudder send a client identify message
select {
case identifyMessage := <-pchan:
assertPayload(t, identifyMessage, "", nil)
assertPayload(t, identifyMessage, "", nil, nil)
case <-time.After(2 * time.Second):
require.Fail(t, "Did not receive ID message")
}
Expand Down Expand Up @@ -435,7 +435,7 @@ func TestRudderTelemetry(t *testing.T) {
for {
select {
case result := <-pchan:
assertPayload(t, result, "", nil)
assertPayload(t, result, "", nil, nil)
*info = append(*info, result.Batch[0].Event)
case <-time.After(2 * time.Second):
return
Expand All @@ -452,7 +452,29 @@ func TestRudderTelemetry(t *testing.T) {
case result := <-pchan:
assertPayload(t, result, "Testing Telemetry", map[string]any{
"hey": testValue,
})
}, nil)
case <-time.After(2 * time.Second):
require.Fail(t, "Did not receive telemetry")
}
})

t.Run("Send Feature", func(t *testing.T) {
const testEvent = "test-send-value-4567"
const testProperty = "test-property-9876"

service.SendTelemetryForFeature(TrackGuestFeature, testEvent, map[string]any{
"prop": testProperty,
})

select {
case result := <-pchan:
assertPayload(
t,
result,
testEvent,
map[string]any{"prop": testProperty},
map[string]any{"skus": []any{"professional", "enterprise"}, "name": "guest_accounts"},
)
case <-time.After(2 * time.Second):
require.Fail(t, "Did not receive telemetry")
}
Expand Down

0 comments on commit 662e598

Please sign in to comment.