MM-60331 - Scheduled post telemetry (#29132)
* Added scheduled posts telemetry * Tracking scheudled post creation via webapp to filter by platform * Fixed keys * Lint fix
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
65ed87bda0
Коммит
f812104a92
@@ -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;
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user