Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

MM-37299 - invite members ab testing #8491

Merged
merged 7 commits into from
Jul 31, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions components/sidebar/__snapshots__/invite_members.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`components/sidebar/invite_members_button should match snapshot 1`] = `
<InviteMembersButton
buttonType="none"
/>
`;
6 changes: 6 additions & 0 deletions components/sidebar/channel_navigator/channel_navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import * as Utils from 'utils/utils';
import {isDesktopApp} from 'utils/user_agent';
import AddChannelDropdown from '../add_channel_dropdown';
import ChannelFilter from '../channel_filter';
import InviteMembersButton from '../invite_members_button';
import {InviteMembersBtnLocations} from 'mattermost-redux/constants/config';

type Props = {
canGoForward: boolean;
Expand Down Expand Up @@ -88,6 +90,8 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
/>
);

const inviteMembersUserIcon = (<InviteMembersButton buttonType={InviteMembersBtnLocations.USER_ICON}/>);

let layout;
if (isDesktopApp()) {
const historyArrows = (
Expand Down Expand Up @@ -120,6 +124,7 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
{!this.props.showUnreadsCategory && <div className='SidebarChannelNavigator_divider'/>}
{historyArrows}
</div>
{inviteMembersUserIcon}
{addChannelDropdown}
</div>
</div>
Expand All @@ -129,6 +134,7 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
<div className={'SidebarChannelNavigator webapp'}>
{!this.props.showUnreadsCategory && <ChannelFilter/>}
{jumpToButton}
{inviteMembersUserIcon}
{addChannelDropdown}
</div>
);
Expand Down
88 changes: 88 additions & 0 deletions components/sidebar/invite_members.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';

import {Provider} from 'react-redux';
import configureStore from 'redux-mock-store';

import {mountWithIntl} from 'tests/helpers/intl-test-helper';

import {InviteMembersBtnLocations} from 'mattermost-redux/constants/config';

import InviteMembersButton from 'components/sidebar/invite_members_button';

import * as preferences from 'mattermost-redux/selectors/entities/preferences';

describe('components/sidebar/invite_members_button', () => {
// required state to mount using the provider
const state = {
entities: {
general: {
config: {
FeatureFlagInviteMembersButton: 'user_icon',
},
},
},
};

const mockStore = configureStore();
const store = mockStore(state);

test('should match snapshot', () => {
const wrapper = mountWithIntl(
<InviteMembersButton buttonType={InviteMembersBtnLocations.NONE}/>,
);

expect(wrapper).toMatchSnapshot();
});

test('should return the user icon button when button type is USER_ICON', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
preferences.getInviteMembersButtonLocation = jest.fn().mockReturnValue('user_icon');

const wrapper = mountWithIntl(
<Provider store={store}>
<InviteMembersButton buttonType={InviteMembersBtnLocations.USER_ICON}/>
</Provider>,
);
expect(wrapper.find('i').prop('className')).toBe('icon-account-plus-outline');
});

test('should return the left hand side button when button type is LHS_BUTTON', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
preferences.getInviteMembersButtonLocation = jest.fn().mockReturnValue('lhs_button');
const wrapper = mountWithIntl(
<Provider store={store}>
<InviteMembersButton buttonType={InviteMembersBtnLocations.LHS_BUTTON}/>
</Provider>,
);
expect(wrapper.find('i').prop('className')).toBe('icon-plus-box');
});

test('should return the sticky to the bottom button when button type is STICKY', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
preferences.getInviteMembersButtonLocation = jest.fn().mockReturnValue('sticky_button');
const wrapper = mountWithIntl(
<Provider store={store}>
<InviteMembersButton buttonType={InviteMembersBtnLocations.STICKY}/>
</Provider>,
);
expect(wrapper.find('i').prop('className')).toBe('icon-account-plus-outline');
});

test('should returnnothing when button type is NONE', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
preferences.getInviteMembersButtonLocation = jest.fn().mockReturnValue('none');
const wrapper = mountWithIntl(
<Provider store={store}>
<InviteMembersButton buttonType={InviteMembersBtnLocations.NONE}/>
</Provider>,
);
expect(wrapper.find('i').exists()).toBeFalsy();
});
});
136 changes: 136 additions & 0 deletions components/sidebar/invite_members_button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React from 'react';
import {Tooltip} from 'react-bootstrap';
import {useIntl, FormattedMessage} from 'react-intl';

import store from 'stores/redux_store.jsx';

import {InviteMembersBtnLocations} from 'mattermost-redux/constants/config';

import OverlayTrigger from 'components/overlay_trigger';
import ToggleModalButtonRedux from 'components/toggle_modal_button_redux';
import InvitationModal from 'components/invitation_modal';

import {trackEvent} from 'actions/telemetry_actions.jsx';

import {ModalIdentifiers} from 'utils/constants';

import {getInviteMembersButtonLocation} from 'mattermost-redux/selectors/entities/preferences';

type Props = {
buttonType: string;
};

const InviteMembersButton: React.FC<Props> = (props: Props): JSX.Element | null => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some unit tests for this new component

const intl = useIntl();
const inviteMembersButtonLocation = getInviteMembersButtonLocation(store.getState());

const tooltip = (
<Tooltip
id='new-group-tooltip'
className='hidden-xs'
>
<FormattedMessage
id={'sidebar_left.inviteUsers'}
defaultMessage='Invite Users'
/>
</Tooltip>
);

const addTelemetry = () => {
trackEvent('invite_members_button', props.buttonType);
};

const OpenModal = (props: {children: React.ReactNode}) => {
return (
<ToggleModalButtonRedux
accessibilityLabel={intl.formatMessage({id: 'sidebar_left.inviteUsers', defaultMessage: 'Invite Users'})}
id='introTextInvite'
className='intro-links color--link cursor--pointer'
modalId={ModalIdentifiers.INVITATION}
dialogType={InvitationModal}
onClick={addTelemetry}
>
{props.children}
</ToggleModalButtonRedux>
);
};

const userIcon = (
<OverlayTrigger
delayShow={500}
placement='top'
overlay={tooltip}
>
<OpenModal>
<div
className='SidebarChannelNavigator_inviteUsers'
aria-label={intl.formatMessage({id: 'sidebar_left.sidebar_channel_navigator.inviteUsers', defaultMessage: 'Invite Users'})}
>
<i className='icon-account-plus-outline'/>
</div>
</OpenModal>
</OverlayTrigger>
);

const lhsButton = (
<OpenModal>
<li
className='SidebarChannelNavigator_inviteMembersLhsButton'
aria-label={intl.formatMessage({id: 'sidebar_left.sidebar_channel_navigator.inviteUsers', defaultMessage: 'Invite Members'})}
>
<i className='icon-plus-box'/>
<FormattedMessage
id={'sidebar_left.inviteMembers'}
defaultMessage='Invite Members'
/>
</li>
</OpenModal>
);

const stickyButton = (
<div
className='SidebarChannelNavigator_inviteUsersSticky'
aria-label={intl.formatMessage({id: 'sidebar_left.sidebar_channel_navigator.inviteUsers', defaultMessage: 'Invite Members'})}
>
<OpenModal>
<i className='icon-account-plus-outline'/>
<FormattedMessage
id={'sidebar_left.inviteMembers'}
defaultMessage='Invite Members'
/>
</OpenModal>
</div>
);

let inviteButton;

switch (props.buttonType) {
case InviteMembersBtnLocations.USER_ICON:
inviteButton = userIcon;
break;
case InviteMembersBtnLocations.STICKY:
inviteButton = stickyButton;
break;
case InviteMembersBtnLocations.LHS_BUTTON:
inviteButton = lhsButton;
break;
default:
inviteButton = null;
break;
}

if (inviteMembersButtonLocation !== props.buttonType || inviteMembersButtonLocation === InviteMembersBtnLocations.NONE) {
inviteButton = null;
}

return (
<>
{inviteButton}
</>
);
};

export default InviteMembersButton;
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ exports[`components/sidebar/sidebar_category should match snapshot when sorting
>
<Component />
</Connect(Droppable)>
<InviteMembersButton
buttonType="lhs_button"
/>
</div>
`;

Expand Down Expand Up @@ -372,6 +375,9 @@ exports[`components/sidebar/sidebar_category should match snapshot when the cate
>
<Component />
</Connect(Droppable)>
<InviteMembersButton
buttonType="lhs_button"
/>
</div>
`;

Expand Down
6 changes: 5 additions & 1 deletion components/sidebar/sidebar_category/sidebar_category.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import Constants, {A11yCustomEventTypes, DraggingStateTypes, DraggingStates} fro
import {t} from 'utils/i18n';
import {isKeyPressed} from 'utils/utils';

import {InviteMembersBtnLocations} from 'mattermost-redux/constants/config';

import SidebarChannel from '../sidebar_channel';
import {SidebarCategoryHeader} from '../sidebar_category_header';
import InviteMembersButton from '../invite_members_button';

import SidebarCategorySortingMenu from './sidebar_category_sorting_menu';

Expand Down Expand Up @@ -103,7 +106,6 @@ export default class SidebarCategory extends React.PureComponent<Props, State> {

renderChannel = (channelId: string, index: number) => {
const {setChannelRef, getChannelRef, category, draggingState} = this.props;

return (
<SidebarChannel
key={channelId}
Expand Down Expand Up @@ -334,6 +336,7 @@ export default class SidebarCategory extends React.PureComponent<Props, State> {
disableInteractiveElementBlocking={true}
>
{(provided, snapshot) => {
const inviteMembersButton = category.type === 'direct_messages' ? <InviteMembersButton buttonType={InviteMembersBtnLocations.LHS_BUTTON}/> : null;
return (
<div
className={classNames('SidebarChannelGroup a11y__section', {
Expand Down Expand Up @@ -389,6 +392,7 @@ export default class SidebarCategory extends React.PureComponent<Props, State> {
);
}}
</Droppable>
{inviteMembersButton}
</div>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ exports[`SidebarChannelList should match snapshot 1`] = `
<Component />
</Connect(Droppable)>
</DragDropContext>
<InviteMembersButton
buttonType="sticky_button"
/>
</Scrollbars>
</div>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {General} from 'mattermost-redux/constants';
import {Channel} from 'mattermost-redux/types/channels';
import {ChannelCategory} from 'mattermost-redux/types/channel_categories';
import {Team} from 'mattermost-redux/types/teams';
import {InviteMembersBtnLocations} from 'mattermost-redux/constants/config';

import {trackEvent} from 'actions/telemetry_actions';
import {DraggingState} from 'types/store';
Expand All @@ -24,6 +25,7 @@ import * as ChannelUtils from 'utils/channel_utils.jsx';
import SidebarCategory from '../sidebar_category';
import UnreadChannelIndicator from '../unread_channel_indicator';
import UnreadChannels from '../unread_channels';
import InviteMembersButton from '../invite_members_button';

import GlobalThreadsLink from 'components/threading/global_threads_link';

Expand Down Expand Up @@ -568,6 +570,7 @@ export default class SidebarChannelList extends React.PureComponent<Props, State
>
{channelList}
</Scrollbars>
<InviteMembersButton buttonType={InviteMembersBtnLocations.STICKY}/>
</div>
</>
);
Expand Down
3 changes: 3 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4117,6 +4117,8 @@
"sidebar_left.channel_navigator.goBackLabel": "Back",
"sidebar_left.channel_navigator.goForwardLabel": "Forward",
"sidebar_left.channel_navigator.jumpTo": "Find channel",
"sidebar_left.inviteMembers": "Invite Members",
"sidebar_left.inviteUsers": "Invite Users",
"sidebar_left.sidebar_category_menu.createCategory": "Create New Category",
"sidebar_left.sidebar_category_menu.deleteCategory": "Delete Category",
"sidebar_left.sidebar_category_menu.dropdownAriaLabel": "Edit category menu",
Expand All @@ -4141,6 +4143,7 @@
"sidebar_left.sidebar_channel_menu.unfavoriteChannel": "Unfavorite",
"sidebar_left.sidebar_channel_menu.unmuteChannel": "Unmute Channel",
"sidebar_left.sidebar_channel_menu.unmuteConversation": "Unmute Conversation",
"sidebar_left.sidebar_channel_navigator.inviteUsers": "Invite Users",
"sidebar_left.sidebar_channel.selectedCount": "{count} selected",
"sidebar_next_steps.gettingStarted": "Getting Started",
"sidebar_next_steps.otherAreasToExplore": "A few other areas to explore",
Expand Down
9 changes: 8 additions & 1 deletion packages/mattermost-redux/src/constants/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const enum CollapsedThreads {
export enum CollapsedThreads {
DISABLED = 'disabled',
DEFAULT_ON = 'default_on',
DEFAULT_OFF = 'default_off',
}

export enum InviteMembersBtnLocations {
NONE = 'none',
STICKY = 'sticky_button',
LHS_BUTTON = 'lhs_button',
USER_ICON = 'user_icon',
}
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,7 @@ export function isTimedDNDEnabled(state: GlobalState): boolean {
getFeatureFlagValue(state, 'TimedDND') === 'true'
);
}

export function getInviteMembersButtonLocation(state: GlobalState): string | undefined {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return InviteMembersBtnLocations?

return getFeatureFlagValue(state, 'InviteMembersButton');
}
Loading