MM-60331 - Scheduled post telemetry (#29132)

* Added scheduled posts telemetry

* Tracking scheudled post creation via webapp to filter by platform

* Fixed keys

* Lint fix
Этот коммит содержится в:
Harshil Sharma
2024-11-06 16:09:52 +05:30
коммит произвёл GitHub
родитель 65ed87bda0
Коммит f812104a92
9 изменённых файлов: 73 добавлений и 15 удалений

Просмотреть файл

@@ -8,6 +8,8 @@ import (
"net/http" "net/http"
"time" "time"
"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/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"
@@ -337,6 +339,12 @@ func (a *App) handleSuccessfulScheduledPosts(rctx request.CTX, successfulSchedul
) )
return errors.Wrap(err, "App.handleSuccessfulScheduledPosts: failed to delete successfully posted scheduled posts") return errors.Wrap(err, "App.handleSuccessfulScheduledPosts: failed to delete successfully posted scheduled posts")
} }
a.Srv().telemetryService.SendTelemetryForFeature(
telemetry.TrackScheduledPosts,
"scheduled_posts_success",
map[string]any{"count": len(successfulScheduledPostIDs)},
)
} }
return nil return nil
@@ -354,4 +362,12 @@ func (a *App) handleFailedScheduledPosts(rctx request.CTX, failedScheduledPosts
) )
} }
} }
if len(failedScheduledPosts) > 0 {
a.Srv().telemetryService.SendTelemetryForFeature(
telemetry.TrackScheduledPosts,
"scheduled_posts_failed",
map[string]any{"count": len(failedScheduledPosts)},
)
}
} }

Просмотреть файл

@@ -107,6 +107,7 @@ const (
TrackGroupsFeature TrackFeature = "custom_groups" TrackGroupsFeature TrackFeature = "custom_groups"
TrackReadOnlyFeature TrackFeature = "read_only_channels" TrackReadOnlyFeature TrackFeature = "read_only_channels"
TrackSharedChannelsFeature TrackFeature = "shared_channels" TrackSharedChannelsFeature TrackFeature = "shared_channels"
TrackScheduledPosts TrackFeature = "scheduled_posts"
) )
const ( const (
@@ -114,6 +115,7 @@ const (
TrackPropertyGroup = "group_id" TrackPropertyGroup = "group_id"
TrackPropertyChannel = "channel_id" TrackPropertyChannel = "channel_id"
TrackPropertyPostAuthor = "post_owner_id" TrackPropertyPostAuthor = "post_owner_id"
TrackPropertyUserAgent = "user_agent"
) )
type ServerIface interface { type ServerIface interface {

Просмотреть файл

@@ -2,11 +2,18 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import moment from 'moment'; import moment from 'moment';
import React, {memo, useCallback} from 'react'; import React, {memo, useCallback, useEffect} from 'react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {useSelector} from 'react-redux'; import {useSelector} from 'react-redux';
import {
TrackPropertyUser, TrackPropertyUserAgent,
TrackScheduledPostsFeature,
} from 'mattermost-redux/constants/telemetry';
import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone'; import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {trackFeatureEvent} from 'actions/telemetry_actions';
import * as Menu from 'components/menu'; import * as Menu from 'components/menu';
import Timestamp from 'components/timestamp'; import Timestamp from 'components/timestamp';
@@ -17,6 +24,21 @@ type Props = {
function CoreMenuOptions({handleOnSelect}: Props) { function CoreMenuOptions({handleOnSelect}: Props) {
const userTimezone = useSelector(getCurrentTimezone); const userTimezone = useSelector(getCurrentTimezone);
const currentUserId = useSelector(getCurrentUserId);
useEffect(() => {
// tracking opening of scheduled posts option menu.
// Since MUI menu has no `onOpen` event, we are tracking it here.
// useEffect ensures that it is tracked only once.
trackFeatureEvent(
TrackScheduledPostsFeature,
'scheduled_posts_menu_opened',
{
[TrackPropertyUser]: currentUserId,
[TrackPropertyUserAgent]: 'webapp',
},
);
}, [currentUserId]);
const today = moment().tz(userTimezone); const today = moment().tz(userTimezone);
const tomorrow9amTime = moment(). const tomorrow9amTime = moment().

Просмотреть файл

@@ -181,7 +181,8 @@ function DraftRow({
// if scheduled posts was being sent, delete the scheduled post after it's been sent // if scheduled posts was being sent, delete the scheduled post after it's been sent
if (isScheduledPostBeingSent.current && response.created && !response.error) { if (isScheduledPostBeingSent.current && response.created && !response.error) {
dispatch(deleteScheduledPost((item as ScheduledPost).id, connectionId)); const scheduledPost = item as ScheduledPost;
dispatch(deleteScheduledPost(scheduledPost.user_id, scheduledPost.id, connectionId));
isScheduledPostBeingSent.current = false; isScheduledPostBeingSent.current = false;
} }
}, [connectionId, dispatch, handleOnDelete, item]); }, [connectionId, dispatch, handleOnDelete, item]);
@@ -262,8 +263,8 @@ function DraftRow({
const handleSchedulePostOnDelete = useCallback(async () => { const handleSchedulePostOnDelete = useCallback(async () => {
handleCancelEdit(); handleCancelEdit();
const scheduledPostId = (item as ScheduledPost).id; const scheduledPost = item as ScheduledPost;
const result = await dispatch(deleteScheduledPost(scheduledPostId, connectionId)); const result = await dispatch(deleteScheduledPost(scheduledPost.user_id, scheduledPost.id, connectionId));
return { return {
error: result.error?.message, error: result.error?.message,
}; };

Просмотреть файл

@@ -74,10 +74,10 @@ export function updateScheduledPost(scheduledPost: ScheduledPost, connectionId:
}; };
} }
export function deleteScheduledPost(scheduledPostId: string, connectionId: string) { export function deleteScheduledPost(userId: string, scheduledPostId: string, connectionId: string) {
return async (dispatch: DispatchFunc) => { return async (dispatch: DispatchFunc) => {
try { try {
const deletedScheduledPost = await Client4.deleteScheduledPost(scheduledPostId, connectionId); const deletedScheduledPost = await Client4.deleteScheduledPost(userId, scheduledPostId, connectionId);
dispatch({ dispatch({
type: ScheduledPostTypes.SCHEDULED_POST_DELETED, type: ScheduledPostTypes.SCHEDULED_POST_DELETED,

Просмотреть файл

@@ -7,7 +7,7 @@ import * as rudderAnalytics from 'rudder-sdk-js';
import type {TelemetryHandler} from '@mattermost/client'; import type {TelemetryHandler} from '@mattermost/client';
import {TrackMiscCategory, eventCategory, eventSKUs} from 'mattermost-redux/constants/telemetry'; import {TrackMiscCategory, eventCategory, eventSKUs, TrackPropertyUser} from 'mattermost-redux/constants/telemetry';
import {isSystemAdmin} from 'mattermost-redux/utils/user_utils'; import {isSystemAdmin} from 'mattermost-redux/utils/user_utils';
export {rudderAnalytics}; export {rudderAnalytics};
@@ -18,7 +18,7 @@ export class RudderTelemetryHandler implements TelemetryHandler {
category, category,
type: event, type: event,
user_actual_role: getActualRoles(userRoles), user_actual_role: getActualRoles(userRoles),
user_actual_id: userId, [TrackPropertyUser]: userId,
}, props); }, props);
const options = { const options = {
context: { context: {
@@ -41,7 +41,7 @@ export class RudderTelemetryHandler implements TelemetryHandler {
const properties = Object.assign({ const properties = Object.assign({
category: getEventCategory(event), category: getEventCategory(event),
type: event, type: event,
user_actual_id: userId, [TrackPropertyUser]: userId,
user_actual_role: getActualRoles(userRoles), user_actual_role: getActualRoles(userRoles),
}, props); }, props);
@@ -68,7 +68,7 @@ export class RudderTelemetryHandler implements TelemetryHandler {
title: '', title: '',
url: '', url: '',
user_actual_role: getActualRoles(userRoles), user_actual_role: getActualRoles(userRoles),
user_actual_id: userId, [TrackPropertyUser]: userId,
}, },
{ {
context: { context: {

Просмотреть файл

@@ -8,6 +8,7 @@ export const TrackEnterpriseSKU = 'enterprise';
// Features // Features
export const TrackGroupsFeature = 'custom_groups'; export const TrackGroupsFeature = 'custom_groups';
export const TrackPassiveKeywordsFeature = 'passive_keywords'; export const TrackPassiveKeywordsFeature = 'passive_keywords';
export const TrackScheduledPostsFeature = 'scheduled_posts';
// Events // Events
export const TrackInviteGroupEvent = 'invite_group_to_channel__add_member'; export const TrackInviteGroupEvent = 'invite_group_to_channel__add_member';
@@ -17,6 +18,10 @@ export const TrackPassiveKeywordsEvent = 'update_passive_keywords';
export const TrackActionCategory = 'action'; export const TrackActionCategory = 'action';
export const TrackMiscCategory = 'miscellaneous'; export const TrackMiscCategory = 'miscellaneous';
// Properties
export const TrackPropertyUser = 'user_actual_id';
export const TrackPropertyUserAgent = 'user_agent';
export const eventSKUs: {[event: string]: string[]} = { export const eventSKUs: {[event: string]: string[]} = {
[TrackInviteGroupEvent]: [TrackProfessionalSKU, TrackEnterpriseSKU], [TrackInviteGroupEvent]: [TrackProfessionalSKU, TrackEnterpriseSKU],
[TrackPassiveKeywordsEvent]: [TrackProfessionalSKU, TrackEnterpriseSKU], [TrackPassiveKeywordsEvent]: [TrackProfessionalSKU, TrackEnterpriseSKU],

Просмотреть файл

@@ -4,6 +4,10 @@
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import FormData from 'form-data'; import FormData from 'form-data';
import {
TrackPropertyUser,
TrackPropertyUserAgent, TrackScheduledPostsFeature,
} from 'mattermost-webapp/src/packages/mattermost-redux/src/constants/telemetry';
import type {ClusterInfo, AnalyticsRow, SchemaMigration, LogFilterQuery} from '@mattermost/types/admin'; import type {ClusterInfo, AnalyticsRow, SchemaMigration, LogFilterQuery} from '@mattermost/types/admin';
import type {AppBinding, AppCallRequest, AppCallResponse} from '@mattermost/types/apps'; import type {AppBinding, AppCallRequest, AppCallResponse} from '@mattermost/types/apps';
@@ -2174,7 +2178,7 @@ export default class Client4 {
`${this.getPostsRoute()}`, `${this.getPostsRoute()}`,
{method: 'post', body: JSON.stringify(post)}, {method: 'post', body: JSON.stringify(post)},
); );
const analyticsData = {channel_id: result.channel_id, post_id: result.id, user_actual_id: result.user_id, root_id: result.root_id} as PostAnalytics; const analyticsData = {channel_id: result.channel_id, post_id: result.id, [TrackPropertyUser]: result.user_id, root_id: result.root_id} as PostAnalytics;
if (post.metadata?.priority) { if (post.metadata?.priority) {
analyticsData.priority = post.metadata.priority.priority; analyticsData.priority = post.metadata.priority.priority;
analyticsData.requested_ack = post.metadata.priority.requested_ack; analyticsData.requested_ack = post.metadata.priority.requested_ack;
@@ -3885,7 +3889,7 @@ export default class Client4 {
context: { context: {
...call.context, ...call.context,
track_as_submit: trackAsSubmit, track_as_submit: trackAsSubmit,
user_agent: 'webapp', [TrackPropertyUserAgent]: 'webapp',
}, },
}; };
return this.doFetch<AppCallResponse>( return this.doFetch<AppCallResponse>(
@@ -3898,7 +3902,7 @@ export default class Client4 {
const params = { const params = {
channel_id: channelID, channel_id: channelID,
team_id: teamID, team_id: teamID,
user_agent: 'webapp', [TrackPropertyUserAgent]: 'webapp',
}; };
return this.doFetch<AppBinding[]>( return this.doFetch<AppBinding[]>(
@@ -4423,6 +4427,8 @@ export default class Client4 {
// Schedule Post methods // Schedule Post methods
createScheduledPost = (schedulePost: ScheduledPost, connectionId: string) => { createScheduledPost = (schedulePost: ScheduledPost, connectionId: string) => {
this.trackFeatureEvent(TrackScheduledPostsFeature, 'create_scheduled_post', {[TrackPropertyUser]: schedulePost.user_id, [TrackPropertyUserAgent]: 'desktop'});
return this.doFetchWithResponse<ScheduledPost>( return this.doFetchWithResponse<ScheduledPost>(
`${this.getPostsRoute()}/schedule`, `${this.getPostsRoute()}/schedule`,
{method: 'post', body: JSON.stringify(schedulePost), headers: {'Connection-Id': connectionId}}, {method: 'post', body: JSON.stringify(schedulePost), headers: {'Connection-Id': connectionId}},
@@ -4438,13 +4444,17 @@ export default class Client4 {
}; };
updateScheduledPost = (schedulePost: ScheduledPost, connectionId: string) => { updateScheduledPost = (schedulePost: ScheduledPost, connectionId: string) => {
this.trackFeatureEvent(TrackScheduledPostsFeature, 'update_scheduled_post', {[TrackPropertyUser]: schedulePost.user_id, [TrackPropertyUserAgent]: 'desktop'});
return this.doFetchWithResponse<ScheduledPost>( return this.doFetchWithResponse<ScheduledPost>(
`${this.getPostsRoute()}/schedule/${schedulePost.id}`, `${this.getPostsRoute()}/schedule/${schedulePost.id}`,
{method: 'put', body: JSON.stringify(schedulePost), headers: {'Connection-Id': connectionId}}, {method: 'put', body: JSON.stringify(schedulePost), headers: {'Connection-Id': connectionId}},
); );
}; };
deleteScheduledPost = (schedulePostId: string, connectionId: string) => { deleteScheduledPost = (userId: string, schedulePostId: string, connectionId: string) => {
this.trackFeatureEvent(TrackScheduledPostsFeature, 'delete_scheduled_post', {[TrackPropertyUser]: userId, [TrackPropertyUserAgent]: 'desktop'});
return this.doFetchWithResponse<ScheduledPost>( return this.doFetchWithResponse<ScheduledPost>(
`${this.getPostsRoute()}/schedule/${schedulePostId}`, `${this.getPostsRoute()}/schedule/${schedulePostId}`,
{method: 'delete', headers: {'Connection-Id': connectionId}}, {method: 'delete', headers: {'Connection-Id': connectionId}},

Просмотреть файл

@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import type {TrackPropertyUser} from 'mattermost-webapp/src/packages/mattermost-redux/src/constants/telemetry';
import type {Channel, ChannelType} from './channels'; import type {Channel, ChannelType} from './channels';
import type {CustomEmoji} from './emojis'; import type {CustomEmoji} from './emojis';
import type {FileInfo} from './files'; import type {FileInfo} from './files';
@@ -199,7 +201,7 @@ export declare type TeamsUsageResponse = {
export type PostAnalytics = { export type PostAnalytics = {
channel_id: string; channel_id: string;
post_id: string; post_id: string;
user_actual_id: string; [TrackPropertyUser]: string;
root_id: string; root_id: string;
priority?: PostPriority|''; priority?: PostPriority|'';
requested_ack?: boolean; requested_ack?: boolean;