MM-59360 add telemetry for paid features related to guests (#28295)
* add telemetry for paid features related to guests --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
18388a8c64
Коммит
662e59865c
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/utils"
|
"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/model"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
@@ -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)
|
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.Srv().Platform().InvalidateChannelCacheForUser(user.Id)
|
||||||
a.invalidateCacheForChannelMembers(channel.Id)
|
a.invalidateCacheForChannelMembers(channel.Id)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||||
|
"github.com/mattermost/mattermost/server/v8/platform/services/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) canSendPushNotifications() bool {
|
func (a *App) canSendPushNotifications() bool {
|
||||||
@@ -877,6 +878,27 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
|
|||||||
mlog.String("post_id", post.Id),
|
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
|
return mentionedUsersList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,17 @@ const (
|
|||||||
TrackPlugins = "plugins"
|
TrackPlugins = "plugins"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TrackSKU string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TrackProfessionalSKU TrackSKU = "professional"
|
||||||
|
TrackEnterpriseSKU TrackSKU = "enterprise"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrackFeature string
|
||||||
|
|
||||||
|
const TrackGuestFeature TrackFeature = "guest_accounts"
|
||||||
|
|
||||||
type ServerIface interface {
|
type ServerIface interface {
|
||||||
Config() *model.Config
|
Config() *model.Config
|
||||||
IsLeader() bool
|
IsLeader() bool
|
||||||
@@ -119,6 +130,15 @@ type RudderConfig struct {
|
|||||||
DataplaneURL string
|
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) {
|
func New(srv ServerIface, dbStore store.Store, searchEngine *searchengine.Broker, log *mlog.Logger, verbose bool) (*TelemetryService, error) {
|
||||||
service := &TelemetryService{
|
service := &TelemetryService{
|
||||||
srv: srv,
|
srv: srv,
|
||||||
@@ -202,7 +222,7 @@ func (ts *TelemetryService) sendDailyTelemetry(override bool) {
|
|||||||
func (ts *TelemetryService) SendTelemetry(event string, properties map[string]any) {
|
func (ts *TelemetryService) SendTelemetry(event string, properties map[string]any) {
|
||||||
if ts.rudderClient != nil {
|
if ts.rudderClient != nil {
|
||||||
var context *rudder.Context
|
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 != "" {
|
if installationId := os.Getenv("MM_CLOUD_INSTALLATION_ID"); installationId != "" {
|
||||||
context = &rudder.Context{Traits: map[string]any{"installationId": installationId}}
|
context = &rudder.Context{Traits: map[string]any{"installationId": installationId}}
|
||||||
}
|
}
|
||||||
@@ -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 {
|
func isDefaultArray(setting, defaultValue []string) bool {
|
||||||
if len(setting) != len(defaultValue) {
|
if len(setting) != len(defaultValue) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -35,16 +35,19 @@ type FakeConfigService struct {
|
|||||||
cfg *model.Config
|
cfg *model.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type testTelemetryPayload struct {
|
type testBatch struct {
|
||||||
MessageId string
|
|
||||||
SentAt time.Time
|
|
||||||
Batch []struct {
|
|
||||||
MessageId string
|
MessageId string
|
||||||
UserId string
|
UserId string
|
||||||
Event string
|
Event string
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
Properties map[string]any
|
Properties map[string]any
|
||||||
|
Context map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testTelemetryPayload struct {
|
||||||
|
MessageId string
|
||||||
|
SentAt time.Time
|
||||||
|
Batch []testBatch
|
||||||
Context struct {
|
Context struct {
|
||||||
Library struct {
|
Library struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -53,15 +56,7 @@ type testTelemetryPayload struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type testBatch struct {
|
func assertPayload(t *testing.T, actual testTelemetryPayload, event string, properties map[string]any, featureContext map[string]any) {
|
||||||
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) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
assert.NotEmpty(t, actual.MessageId)
|
assert.NotEmpty(t, actual.MessageId)
|
||||||
assert.False(t, actual.SentAt.IsZero())
|
assert.False(t, actual.SentAt.IsZero())
|
||||||
@@ -75,6 +70,11 @@ func assertPayload(t *testing.T, actual testTelemetryPayload, event string, prop
|
|||||||
if properties != nil {
|
if properties != nil {
|
||||||
assert.Equal(t, properties, actual.Batch[0].Properties)
|
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, "analytics-go", actual.Context.Library.Name)
|
||||||
assert.Equal(t, "3.3.0", actual.Context.Library.Version)
|
assert.Equal(t, "3.3.0", actual.Context.Library.Version)
|
||||||
@@ -85,7 +85,7 @@ func collectBatches(t *testing.T, info *[]testBatch, pchan chan testTelemetryPay
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case result := <-pchan:
|
case result := <-pchan:
|
||||||
assertPayload(t, result, "", nil)
|
assertPayload(t, result, "", nil, nil)
|
||||||
*info = append(*info, result.Batch[0])
|
*info = append(*info, result.Batch[0])
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
return
|
return
|
||||||
@@ -126,7 +126,7 @@ func makeTelemetryServiceAndReceiver(t *testing.T, cloudLicense bool) (*Telemetr
|
|||||||
// initializing rudder send a client identify message
|
// initializing rudder send a client identify message
|
||||||
select {
|
select {
|
||||||
case identifyMessage := <-pchan:
|
case identifyMessage := <-pchan:
|
||||||
assertPayload(t, identifyMessage, "", nil)
|
assertPayload(t, identifyMessage, "", nil, nil)
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
require.Fail(t, "Did not receive ID message")
|
require.Fail(t, "Did not receive ID message")
|
||||||
}
|
}
|
||||||
@@ -435,7 +435,7 @@ func TestRudderTelemetry(t *testing.T) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case result := <-pchan:
|
case result := <-pchan:
|
||||||
assertPayload(t, result, "", nil)
|
assertPayload(t, result, "", nil, nil)
|
||||||
*info = append(*info, result.Batch[0].Event)
|
*info = append(*info, result.Batch[0].Event)
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(2 * time.Second):
|
||||||
return
|
return
|
||||||
@@ -452,7 +452,29 @@ func TestRudderTelemetry(t *testing.T) {
|
|||||||
case result := <-pchan:
|
case result := <-pchan:
|
||||||
assertPayload(t, result, "Testing Telemetry", map[string]any{
|
assertPayload(t, result, "Testing Telemetry", map[string]any{
|
||||||
"hey": testValue,
|
"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):
|
case <-time.After(2 * time.Second):
|
||||||
require.Fail(t, "Did not receive telemetry")
|
require.Fail(t, "Did not receive telemetry")
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user