Unverified Commit 47dbf165 authored by Sudheer's avatar Sudheer Committed by GitHub
Browse files

MM-26609 Create a new single entry point to get help, with a new "Ask ...


MM-26609 Create a new single entry point to get help, with a new "Ask  Mattermost Community" menu item (#5843)

* MM-26609 Create a new single entry point to get help, with a new "Ask Mattermost Community" menu item

 * Add new icon for user guide help
 * Add e2e tests for click and checking hrefs

* Update snapshots

* Address feedback

* Update snapshots

* Update svg placement
Co-authored-by: default avatarMattermod <mattermod@users.noreply.github.com>
parent 514dfc05
......@@ -206,6 +206,7 @@ exports[`components/ChannelHeader should render active flagged posts 1`] = `
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -416,6 +417,7 @@ exports[`components/ChannelHeader should render active mentions posts 1`] = `
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -626,6 +628,7 @@ exports[`components/ChannelHeader should render active pinned posts 1`] = `
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -775,6 +778,7 @@ exports[`components/ChannelHeader should render archived view 1`] = `
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -1020,6 +1024,7 @@ exports[`components/ChannelHeader should render correct menu when muted 1`] = `
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -1236,6 +1241,7 @@ exports[`components/ChannelHeader should render properly when populated 1`] = `
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -1484,6 +1490,7 @@ exports[`components/ChannelHeader should render properly when populated with cha
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -1702,6 +1709,7 @@ exports[`components/ChannelHeader should render the pinned icon with the pinned
onClick={[Function]}
tooltipKey="flaggedPosts"
/>
<Connect(injectIntl(UserGuideDropdown)) />
</div>
</div>
`;
......@@ -44,6 +44,7 @@ import * as Utils from 'utils/utils';
import ChannelHeaderPlug from 'plugins/channel_header_plug';
import HeaderIconWrapper from './components/header_icon_wrapper';
import UserGuideDropdown from './components/user_guide_dropdown';
const headerMarkdownOptions = {singleline: true, mentionHighlight: false, atMentions: true};
const popoverMarkdownOptions = {singleline: false, mentionHighlight: false, atMentions: true};
......@@ -811,6 +812,7 @@ class ChannelHeader extends React.PureComponent {
onClick={this.getFlagged}
tooltipKey={'flaggedPosts'}
/>
<UserGuideDropdown/>
</div>
</div>
);
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/channel_header/components/UserGuideDropdown should match snapshot 1`] = `
<MenuWrapper
animationComponent={[Function]}
className="userGuideHelp"
onToggle={[Function]}
>
<button
aria-expanded="true"
className="channel-header__icon"
id="channelHeaderUserGuideButton"
type="button"
>
<OverlayTrigger
defaultOverlayShown={false}
delayShow={500}
overlay={
<Tooltip
bsClass="tooltip"
className="hidden-xs"
id="userGuideHelpTooltip"
placement="right"
>
<FormattedMessage
defaultMessage="Help"
id="channel_header.userHelpGuide"
values={Object {}}
/>
</Tooltip>
}
placement="bottom"
trigger={
Array [
"hover",
"focus",
]
}
>
<UserGuideIcon
className="icon"
/>
</OverlayTrigger>
</button>
<Menu
ariaLabel="Add Channel Dropdown"
id="AddChannelDropdown"
openLeft={true}
openUp={false}
>
<MenuGroup>
<MenuItemExternalLink
id="askTheCommunityLink"
show={true}
text="Ask the community"
url="https://mattermost.com/pl/default-ask-mattermost-community/"
/>
<MenuItemExternalLink
id="helpResourcesLink"
show={true}
text="Help resources"
url="helpLink"
/>
<MenuItemExternalLink
id="reportAProblemLink"
show={true}
text="Report a problem"
url="reportAProblemLink"
/>
<MenuItemAction
id="keyboardShortcuts"
onClick={[Function]}
show={true}
text="Keyboard shortcuts"
/>
</MenuGroup>
</Menu>
</MenuWrapper>
`;
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {GlobalState} from 'types/store';
import UserGuideDropdown from './user_guide_dropdown';
function mapStateToProps(state: GlobalState) {
const {HelpLink, ReportAProblemLink} = getConfig(state);
return {
helpLink: HelpLink!,
reportAProblemLink: ReportAProblemLink!,
};
}
export default connect(mapStateToProps)(UserGuideDropdown);
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import Menu from 'components/widgets/menu/menu';
import UserGuideDropdown from 'components/channel_header/components/user_guide_dropdown/user_guide_dropdown';
import * as GlobalActions from 'actions/global_actions.jsx';
jest.mock('actions/global_actions', () => ({
toggleShortcutsModal: jest.fn(),
}));
describe('components/channel_header/components/UserGuideDropdown', () => {
const baseProps = {
helpLink: 'helpLink',
reportAProblemLink: 'reportAProblemLink',
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(
<UserGuideDropdown {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
});
test('Should set state buttonActive on toggle of MenuWrapper', () => {
const wrapper = shallowWithIntl(
<UserGuideDropdown {...baseProps}/>,
);
expect(wrapper.state('buttonActive')).toBe(false);
wrapper.find(MenuWrapper).prop('onToggle')(true);
expect(wrapper.state('buttonActive')).toBe(true);
});
test('Should set state buttonActive on toggle of MenuWrapper', () => {
const wrapper = shallowWithIntl(
<UserGuideDropdown {...baseProps}/>,
);
wrapper.find(Menu.ItemAction).prop('onClick')({preventDefault: jest.fn()});
expect(GlobalActions.toggleShortcutsModal).toHaveBeenCalled();
});
});
// 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 {FormattedMessage, injectIntl, IntlShape} from 'react-intl';
import classNames from 'classnames';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import UserGuideIcon from 'components/widgets/icons/user_guide_icon';
import Menu from 'components/widgets/menu/menu';
import OverlayTrigger from 'components/overlay_trigger';
import * as GlobalActions from 'actions/global_actions.jsx';
const askTheCommunityUrl = 'https://mattermost.com/pl/default-ask-mattermost-community/';
type Props = {
intl: IntlShape;
helpLink: string;
reportAProblemLink: string;
};
type State = {
buttonActive: boolean;
};
class UserGuideDropdown extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
buttonActive: false
};
}
toggleShortcutsModal = (e: MouseEvent) => {
e.preventDefault();
GlobalActions.toggleShortcutsModal();
}
buttonToggleState = (menuActive: boolean) => {
this.setState({
buttonActive: menuActive,
});
}
renderDropdownItems = (): React.ReactNode => {
const {intl} = this.props;
return (
<Menu.Group>
<Menu.ItemExternalLink
id='askTheCommunityLink'
url={askTheCommunityUrl}
text={intl.formatMessage({id: 'userGuideHelp.askTheCommunity', defaultMessage: 'Ask the community'})}
/>
<Menu.ItemExternalLink
id='helpResourcesLink'
url={this.props.helpLink}
text={intl.formatMessage({id: 'userGuideHelp.helpResources', defaultMessage: 'Help resources'})}
/>
<Menu.ItemExternalLink
id='reportAProblemLink'
url={this.props.reportAProblemLink}
text={intl.formatMessage({id: 'userGuideHelp.reportAProblem', defaultMessage: 'Report a problem'})}
/>
<Menu.ItemAction
id='keyboardShortcuts'
onClick={this.toggleShortcutsModal}
text={intl.formatMessage({id: 'userGuideHelp.keyboardShortcuts', defaultMessage: 'Keyboard shortcuts'})}
/>
</Menu.Group>
);
}
render() {
const {intl} = this.props;
const tooltip = (
<Tooltip
id='userGuideHelpTooltip'
className='hidden-xs'
>
<FormattedMessage
id={'channel_header.userHelpGuide'}
defaultMessage='Help'
/>
</Tooltip>
);
return (
<MenuWrapper
className='userGuideHelp'
onToggle={this.buttonToggleState}
>
<button
id='channelHeaderUserGuideButton'
className={classNames('channel-header__icon', {'channel-header__icon--active': this.state.buttonActive})}
type='button'
aria-expanded='true'
>
<OverlayTrigger
delayShow={500}
placement='bottom'
overlay={this.state.buttonActive ? <></> : tooltip}
>
<UserGuideIcon className='icon'/>
</OverlayTrigger>
</button>
<Menu
openLeft={true}
openUp={false}
id='AddChannelDropdown'
ariaLabel={intl.formatMessage({id: 'sidebar_left.add_channel_dropdown.dropdownAriaLabel', defaultMessage: 'Add Channel Dropdown'})}
>
{this.renderDropdownItems()}
</Menu>
</MenuWrapper>
);
}
}
export default injectIntl(UserGuideDropdown);
......@@ -21,6 +21,7 @@ import MentionsIcon from 'components/widgets/icons/mentions_icon';
import SearchIcon from 'components/widgets/icons/search_icon';
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
import Popover from 'components/widgets/popover';
import UserGuideDropdown from 'components/channel_header/components/user_guide_dropdown';
const {KeyCodes} = Constants;
......@@ -312,6 +313,7 @@ export default class SearchBar extends React.PureComponent {
render() {
let mentionBtn;
let flagBtn;
let userGuideBtn;
if (this.props.showMentionFlagBtns) {
mentionBtn = (
<HeaderIconWrapper
......@@ -347,6 +349,8 @@ export default class SearchBar extends React.PureComponent {
isRhsOpen={this.props.isRhsOpen}
/>
);
userGuideBtn = (<UserGuideDropdown/>);
}
let searchFormClass = 'search__form';
......@@ -422,6 +426,7 @@ export default class SearchBar extends React.PureComponent {
</div>
{mentionBtn}
{flagBtn}
{userGuideBtn}
</div>
);
}
......
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
export default class UserGuideIcon extends React.PureComponent {
render() {
return (
<span {...this.props}>
<FormattedMessage
id='generic_icons.userGuide'
defaultMessage='Help'
>
{(ariaLabel) => (
<svg
width='18px'
height='18px'
viewBox='1 1 22 22'
role='img'
aria-label={ariaLabel}
style={{width: '18px', height: '18px'}}
>
<path d='M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z'/>
</svg>
)}
</FormattedMessage>
</span>
);
}
}
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @menu
describe('Main menu', () => {
let testTeam;
let testUser;
let testConfig;
before(() => {
cy.apiGetConfig().then((response) => {
testConfig = response.body;
});
cy.apiInitSetup().then(({team, user}) => {
testTeam = team;
testUser = user;
});
});
describe('user help guide', () => {
it('Should have all the menu items on click', () => {
cy.apiLogin(testUser);
cy.visit(`/${testTeam.name}/channels/town-square`);
cy.get('#channel-header').should('be.visible').then(() => {
cy.get('#channelHeaderUserGuideButton').click();
cy.get('.dropdown-menu').should('be.visible').then(() => {
cy.get('#askTheCommunityLink').should('be.visible');
cy.get('#askTheCommunityLink a').should('have.attr', 'href', 'https://mattermost.com/pl/default-ask-mattermost-community/');
cy.get('#helpResourcesLink').should('be.visible');
cy.get('#helpResourcesLink a').should('have.attr', 'href', testConfig.SupportSettings.HelpLink);
cy.get('#reportAProblemLink').should('be.visible');
cy.get('#reportAProblemLink a').should('have.attr', 'href', testConfig.SupportSettings.ReportAProblemLink);
cy.get('#keyboardShortcuts').should('be.visible');
cy.get('#keyboardShortcuts button').click();
cy.get('#shortcutsModalLabel').should('be.visible');
});
});
});
});
});
......@@ -2092,6 +2092,7 @@
"channel_header.setPurpose": "Edit Channel Purpose",
"channel_header.unarchive": "Unarchive Channel",
"channel_header.unmute": "Unmute Channel",
"channel_header.userHelpGuide": "Help",
"channel_header.viewMembers": "View Members",
"channel_info.about": "About",
"channel_info.header": "Header:",
......@@ -2534,6 +2535,7 @@
"generic_icons.settings": "Settings Icon",
"generic_icons.success": "Success Icon",
"generic_icons.upload": "Upload Icon",
"generic_icons.userGuide": "Help",
"generic_icons.warning": "Warning Icon",
"get_app.continueToBrowser": "View in Browser",
"get_app.dontHaveTheDesktopApp": "Don't have the Desktop App?",
......@@ -3941,6 +3943,10 @@
"user.settings.tokens.tokenId": "Token ID: ",
"user.settings.tokens.tokenLoading": "Loading...",
"user.settings.tokens.userAccessTokensNone": "No personal access tokens.",
"userGuideHelp.askTheCommunity": "Ask the community",
"userGuideHelp.helpResources": "Help resources",
"userGuideHelp.keyboardShortcuts": "Keyboard shortcuts",
"userGuideHelp.reportAProblem": "Report a problem",
"version_bar.new": "A new version of Mattermost is available.",
"version_bar.refresh": "Refresh the app now",
"view_image_popover.download": "Download",
......
......@@ -52,4 +52,8 @@
.channel-header__icon--wide {
width: auto;
padding: 0 6px;
}
\ No newline at end of file
}
.userGuideHelp {
display: inline-table;
}
......@@ -124,7 +124,7 @@
}
.inner-wrap.move--left & {
width: 306px;
width: 274px;
}
.search-bar {
......
......@@ -12,21 +12,21 @@
.channel-header {
.search-bar__container {
.search__form--focused {
width: 406px;
max-width: 406px;;
width: 374px;
max-width: 374px;;
}
}
}
.search-help-popover {
max-width: 406px;
width: 406px;
max-width: 374px;
width: 374px;
}
.inner-wrap {
&.move--left {
.search__form {
width: 406px;
width: 374px;
}
#post-list {
......
......@@ -134,7 +134,7 @@
// Tablet and desktop
@media screen and (min-width: 769px) {
.search-help-popover {
width: 306px;
width: 274px;
}
.channel-header__popover {
......@@ -154,7 +154,7 @@
.search-bar__container {
.search__form {
&--focused {
width: 306px;
width: 274px;
}
}
}
......@@ -587,4 +587,4 @@
}
}
}
}
\ No newline at end of file
}
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