Unverified Commit 90c88f4d authored by Hossein Ahmadian-Yazdi's avatar Hossein Ahmadian-Yazdi Committed by GitHub
Browse files

[MM-27249] Add telemetry to team and channel pages (#6009) (#6039)



* Add telemetry data to team and channel config pages

* Add a single telemetry event for filters applied to channel list

* Update events as per dennis comments

* Link / unlink groups telemetry events

* Send proper count for groups added and removed

* Update components/admin_console/team_channel_settings/channel/list/channel_list.tsx
Co-authored-by: default avatarMartin Kraft <martinkraft@gmail.com>
Co-authored-by: default avatarMartin Kraft <martinkraft@gmail.com>
Co-authored-by: default avatarFarhan Munshi <3207297+fmunshi@users.noreply.github.com>
Co-authored-by: default avatarMartin Kraft <martinkraft@gmail.com>
parent 9989d649
......@@ -20,6 +20,7 @@ import BlockableLink from 'components/admin_console/blockable_link';
import FormError from 'components/form_error';
import Constants from 'utils/constants';
import {browserHistory} from 'utils/browser_history';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import {NeedGroupsError, UsersWillBeRemovedError} from '../../errors';
import ConvertConfirmModal from '../../convert_confirm_modal';
......@@ -372,7 +373,7 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
private handleSubmit = async () => {
this.setState({showConvertConfirmModal: false, showRemoveConfirmModal: false, showConvertAndRemoveConfirmModal: false, showArchiveConfirmModal: false, saving: true});
const {groups, isSynced, isPublic, isPrivacyChanging, channelPermissions, usersToAdd, usersToRemove, rolesToUpdate} = this.state;
let serverError = null;
let serverError: JSX.Element | null = null;
let saveNeeded = false;
const {groups: origGroups, channelID, actions, channel} = this.props;
......@@ -381,6 +382,8 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
if ('error' in result) {
serverError = <FormError error={result.error.message}/>;
saveNeeded = true;
} else {
trackEvent('admin_channel_config_page', 'channel_archived', {channel_id: channelID});
}
this.setState({serverError, saving: false, saveNeeded, isPrivacyChanging: false, usersToRemoveCount: 0, rolesToUpdate: {}, usersToAdd: {}, usersToRemove: {}}, () => {
actions.setNavigationBlocked(saveNeeded);
......@@ -393,6 +396,8 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
const result = await actions.unarchiveChannel(channel.id);
if ('error' in result) {
serverError = <FormError error={result.error.message}/>;
} else {
trackEvent('admin_channel_config_page', 'channel_unarchived', {channel_id: channelID});
}
this.setState({serverError, previousServerError: null});
}
......@@ -446,26 +451,36 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
}).
map((g) => actions.linkGroupSyncable(g.id, channelID, Groups.SYNCABLE_TYPE_CHANNEL, {auto_add: true, scheme_admin: g.scheme_admin}));
const result = await Promise.all([...promises, ...patchChannelSyncable, ...unlink, ...link]);
const resultWithError = result.find((r) => 'error' in r);
if (resultWithError && 'error' in resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
} else {
const actionsToAwait: any[] = [actions.getGroups(channelID)];
if (isPrivacyChanging) {
// If the privacy is changing update the manage_members value for the channel moderation widget
actionsToAwait.push(
actions.getChannelModerations(channelID).then(() => {
const manageMembersIndex = channelPermissions!.findIndex((element) => element.name === Permissions.CHANNEL_MODERATED_PERMISSIONS.MANAGE_MEMBERS);
if (channelPermissions) {
const updatedManageMembers = this.props.channelPermissions!.find((element) => element.name === Permissions.CHANNEL_MODERATED_PERMISSIONS.MANAGE_MEMBERS);
channelPermissions[manageMembersIndex] = updatedManageMembers || channelPermissions[manageMembersIndex];
}
this.setState({channelPermissions});
}),
);
const groupActions = [...promises, ...patchChannelSyncable, ...unlink, ...link];
if (groupActions.length > 0) {
const result = await Promise.all(groupActions);
const resultWithError = result.find((r) => 'error' in r);
if (resultWithError && 'error' in resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
} else {
if (unlink.length > 0) {
trackEvent('admin_channel_config_page', 'groups_removed_from_channel', {count: unlink.length, channel_id: channelID});
}
if (link.length > 0) {
trackEvent('admin_channel_config_page', 'groups_added_to_channel', {count: link.length, channel_id: channelID});
}
const actionsToAwait: any[] = [actions.getGroups(channelID)];
if (isPrivacyChanging) {
// If the privacy is changing update the manage_members value for the channel moderation widget
actionsToAwait.push(
actions.getChannelModerations(channelID).then(() => {
const manageMembersIndex = channelPermissions!.findIndex((element) => element.name === Permissions.CHANNEL_MODERATED_PERMISSIONS.MANAGE_MEMBERS);
if (channelPermissions) {
const updatedManageMembers = this.props.channelPermissions!.find((element) => element.name === Permissions.CHANNEL_MODERATED_PERMISSIONS.MANAGE_MEMBERS);
channelPermissions[manageMembersIndex] = updatedManageMembers || channelPermissions[manageMembersIndex];
}
this.setState({channelPermissions});
}),
);
}
await Promise.all(actionsToAwait);
}
await Promise.all(actionsToAwait);
}
const patchChannelPermissionsArray: Array<ChannelModerationPatch> = channelPermissions!.map((p) => {
......@@ -494,36 +509,78 @@ export default class ChannelDetails extends React.PureComponent<ChannelDetailsPr
const userRolesToUpdate = Object.keys(rolesToUpdate);
const usersToUpdate = usersToAddList.length > 0 || usersToRemoveList.length > 0 || userRolesToUpdate.length > 0;
if (usersToUpdate && !isSynced) {
const userActions: any[] = [];
const addUserActions: any[] = [];
const removeUserActions: any[] = [];
const rolesToPromote: any[] = [];
const rolesToDemote: any[] = [];
const {addChannelMember, removeChannelMember, updateChannelMemberSchemeRoles} = this.props.actions;
usersToAddList.forEach((user) => {
userActions.push(addChannelMember(channelID, user.id));
addUserActions.push(addChannelMember(channelID, user.id));
});
usersToRemoveList.forEach((user) => {
userActions.push(removeChannelMember(channelID, user.id));
removeUserActions.push(removeChannelMember(channelID, user.id));
});
userRolesToUpdate.forEach((userId) => {
const {schemeUser, schemeAdmin} = rolesToUpdate[userId];
if (schemeAdmin) {
rolesToPromote.push(updateChannelMemberSchemeRoles(channelID, userId, schemeUser, schemeAdmin));
} else {
rolesToDemote.push(updateChannelMemberSchemeRoles(channelID, userId, schemeUser, schemeAdmin));
}
});
let userResult = await Promise.all(userActions);
let userResultWithError = userResult.find((r) => 'error' in r);
if (userResultWithError && 'error' in userResultWithError) {
serverError = <FormError error={userResultWithError.error.message}/>;
} else {
const roleActions: any[] = [];
userRolesToUpdate.forEach((userId) => {
const {schemeUser, schemeAdmin} = rolesToUpdate[userId];
roleActions.push(updateChannelMemberSchemeRoles(channelID, userId, schemeUser, schemeAdmin));
});
userResult = await Promise.all(roleActions);
userResultWithError = userResult.find((r) => 'error' in r);
if (userResultWithError && 'error' in userResultWithError) {
serverError = <FormError error={userResultWithError.error.message}/>;
if (addUserActions.length > 0) {
const result = await Promise.all(addUserActions);
const resultWithError = result.find((r) => 'error' in r);
const count = result.filter((r) => 'data' in r).length;
if (resultWithError && 'error' in resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_channel_config_page', 'members_added_to_channel', {count, channel_id: channelID});
}
}
if (removeUserActions.length > 0) {
const result = await Promise.all(removeUserActions);
const resultWithError = result.find((r) => 'error' in r);
const count = result.filter((r) => 'data' in r).length;
if (resultWithError && 'error' in resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_channel_config_page', 'members_removed_from_channel', {count, channel_id: channelID});
}
}
if (rolesToPromote.length > 0) {
const result = await Promise.all(rolesToPromote);
const resultWithError = result.find((r) => 'error' in r);
const count = result.filter((r) => 'data' in r).length;
if (resultWithError && 'error' in resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_channel_config_page', 'members_elevated_to_channel_admin', {count, channel_id: channelID});
}
}
if (rolesToDemote.length > 0) {
const result = await Promise.all(rolesToDemote);
const resultWithError = result.find((r) => 'error' in r);
const count = result.filter((r) => 'data' in r).length;
if (resultWithError && 'error' in resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_channel_config_page', 'admins_demoted_to_channel_member', {count, channel_id: channelID});
}
}
}
this.setState({serverError, saving: false, saveNeeded, isPrivacyChanging: privacyChanging, usersToRemoveCount: 0, rolesToUpdate: {}, usersToAdd: {}, usersToRemove: {}}, () => {
actions.setNavigationBlocked(saveNeeded);
if (!saveNeeded) {
if (!saveNeeded && serverError === null) {
browserHistory.push('/admin_console/user_management/channels');
}
});
......
......@@ -13,6 +13,7 @@ import GeneralConstants from 'mattermost-redux/constants/general';
import {t} from 'utils/i18n';
import Constants from 'utils/constants';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import AdminPanel from 'components/widgets/admin_console/admin_panel';
import UserGrid from 'components/admin_console/user_grid/user_grid';
......@@ -174,6 +175,10 @@ export default class ChannelMembers extends React.PureComponent<Props, State> {
if (channelRoles.length > 0) {
filters = {...filters, channel_roles: channelRoles};
}
[...systemRoles, ...channelRoles].forEach((role) => {
trackEvent('admin_channel_config_page', `${role}_filter_applied_to_members_block`, {channel_id: this.props.channelId});
});
this.props.actions.setUserGridFilters(filters);
this.props.actions.getFilteredUsersStats({in_channel: this.props.channelId, include_bots: true, ...filters});
} else {
......
......@@ -10,6 +10,7 @@ import {ChannelWithTeamData, ChannelSearchOpts} from 'mattermost-redux/types/cha
import {debounce} from 'mattermost-redux/actions/helpers';
import {browserHistory} from 'utils/browser_history';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import {Constants} from 'utils/constants';
import {isArchivedChannel} from 'utils/channel_utils';
......@@ -238,13 +239,35 @@ export default class ChannelList extends React.PureComponent<ChannelListProps, C
const {team_ids: teamIds} = filterOptions.teams.values;
if (publicChannels.value || privateChannels.value || deleted.value || groupConstrained.value || excludeGroupConstrained.value || (teamIds.value as string[]).length) {
filters.public = publicChannels.value as boolean;
if (filters.public) {
trackEvent('admin_channels_page', 'public_filter_applied_to_channel_list');
}
filters.private = privateChannels.value as boolean;
if (filters.private) {
trackEvent('admin_channels_page', 'private_filter_applied_to_channel_list');
}
filters.deleted = deleted.value as boolean;
if (filters.deleted) {
trackEvent('admin_channels_page', 'archived_filter_applied_to_channel_list');
}
if (!(groupConstrained.value && excludeGroupConstrained.value)) {
filters.group_constrained = groupConstrained.value as boolean;
if (filters.group_constrained) {
trackEvent('admin_channels_page', 'group_sync_filter_applied_to_channel_list');
}
filters.exclude_group_constrained = excludeGroupConstrained.value as boolean;
if (filters.exclude_group_constrained) {
trackEvent('admin_channels_page', 'manual_invites_filter_applied_to_channel_list');
}
}
filters.team_ids = teamIds.value as string[];
if (filters.team_ids.length > 0) {
trackEvent('admin_channels_page', 'team_id_filter_applied_to_channel_list');
}
}
this.loadPage(0, this.state.term, filters);
}
......
......@@ -10,8 +10,8 @@ import {Groups} from 'mattermost-redux/constants';
import {browserHistory} from 'utils/browser_history';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import BlockableLink from 'components/admin_console/blockable_link';
import FormError from 'components/form_error';
import RemoveConfirmModal from '../../remove_confirm_modal';
......@@ -143,6 +143,12 @@ export default class TeamDetails extends React.PureComponent {
if (resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
} else {
if (unlink.length > 0) {
trackEvent('admin_team_config_page', 'groups_removed_from_team', {count: unlink.length, team_id: teamID});
}
if (link.length > 0) {
trackEvent('admin_team_config_page', 'groups_added_to_team', {count: link.length, team_id: teamID});
}
await actions.getGroups(teamID);
}
}
......@@ -152,36 +158,78 @@ export default class TeamDetails extends React.PureComponent {
const userRolesToUpdate = Object.keys(rolesToUpdate);
const usersToUpdate = usersToAddList.length > 0 || usersToRemoveList.length > 0 || userRolesToUpdate.length > 0;
if (usersToUpdate && !syncChecked) {
const userActions = [];
const addUserActions = [];
const removeUserActions = [];
const rolesToPromote = [];
const rolesToDemote = [];
const {addUserToTeam, removeUserFromTeam, updateTeamMemberSchemeRoles} = this.props.actions;
usersToAddList.forEach((user) => {
userActions.push(addUserToTeam(teamID, user.id));
addUserActions.push(addUserToTeam(teamID, user.id));
});
usersToRemoveList.forEach((user) => {
userActions.push(removeUserFromTeam(teamID, user.id));
removeUserActions.push(removeUserFromTeam(teamID, user.id));
});
userRolesToUpdate.forEach((userId) => {
const {schemeUser, schemeAdmin} = rolesToUpdate[userId];
if (schemeAdmin) {
rolesToPromote.push(updateTeamMemberSchemeRoles(teamID, userId, schemeUser, schemeAdmin));
} else {
rolesToDemote.push(updateTeamMemberSchemeRoles(teamID, userId, schemeUser, schemeAdmin));
}
});
let result = await Promise.all(userActions);
let resultWithError = result.find((r) => r.error);
if (resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
} else {
const roleActions = [];
userRolesToUpdate.forEach((userId) => {
const {schemeUser, schemeAdmin} = rolesToUpdate[userId];
roleActions.push(updateTeamMemberSchemeRoles(teamID, userId, schemeUser, schemeAdmin));
});
result = await Promise.all(roleActions);
resultWithError = result.find((r) => r.error);
if (addUserActions.length > 0) {
const result = await Promise.all(addUserActions);
const resultWithError = result.find((r) => r.error);
const count = result.filter((r) => r.data).length;
if (resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_team_config_page', 'members_added_to_team', {count, team_id: teamID});
}
}
if (removeUserActions.length > 0) {
const result = await Promise.all(removeUserActions);
const resultWithError = result.find((r) => r.error);
const count = result.filter((r) => r.data).length;
if (resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_team_config_page', 'members_removed_from_team', {count, team_id: teamID});
}
}
if (rolesToPromote.length > 0) {
const result = await Promise.all(rolesToPromote);
const resultWithError = result.find((r) => r.error);
const count = result.filter((r) => r.data).length;
if (resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_team_config_page', 'members_elevated_to_team_admin', {count, team_id: teamID});
}
}
if (rolesToDemote.length > 0) {
const result = await Promise.all(rolesToDemote);
const resultWithError = result.find((r) => r.error);
const count = result.filter((r) => r.data).length;
if (resultWithError) {
serverError = <FormError error={resultWithError.error.message}/>;
}
if (count > 0) {
trackEvent('admin_team_config_page', 'admins_demoted_to_team_member', {count, team_id: teamID});
}
}
}
this.setState({usersToRemoveCount: 0, rolesToUpdate: {}, usersToAdd: {}, usersToRemove: {}, serverError, saving: false, saveNeeded}, () => {
actions.setNavigationBlocked(saveNeeded);
if (!saveNeeded) {
if (!saveNeeded && !serverError) {
browserHistory.push('/admin_console/user_management/teams');
}
});
......
......@@ -12,6 +12,7 @@ import GeneralConstants from 'mattermost-redux/constants/general';
import {t} from 'utils/i18n';
import Constants from 'utils/constants';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import AdminPanel from 'components/widgets/admin_console/admin_panel';
import UserGrid from 'components/admin_console/user_grid/user_grid';
......@@ -170,6 +171,9 @@ export default class TeamMembers extends React.PureComponent<Props, State> {
if (teamRoles.length > 0) {
filters = {...filters, team_roles: teamRoles};
}
[...systemRoles, ...teamRoles].forEach((role) => {
trackEvent('admin_team_config_page', `${role}_filter_applied_to_members_block`, {team_id: this.props.teamId});
});
this.props.actions.setUserGridFilters({roles: systemRoles, team_roles: teamRoles});
this.props.actions.getFilteredUsersStats({in_team: this.props.teamId, include_bots: true, ...filters});
} else {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment