MM-49862 - remove no longer needed webapp calls to action and pulsating dots (#22699)

* MM-49862 - remove no longer needed webapp calls to action and pulsating dots

* fix snapshots

* improve function logic based on pr comments

* fix unit tests

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Pablo Andrés Vélez Vidal
2023-04-04 18:04:06 +02:00
коммит произвёл GitHub
родитель 9736304633
Коммит 6919761122
19 изменённых файлов: 94 добавлений и 361 удалений

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

@@ -39,7 +39,6 @@ exports[`components/actions_menu/ActionsMenu has actions - marketplace disabled
className="post-menu__item post-menu__item--active" className="post-menu__item post-menu__item--active"
id="center_actions_button_post_id_1" id="center_actions_button_post_id_1"
key="more-actions-button" key="more-actions-button"
onClick={[Function]}
type="button" type="button"
> >
<i <i
@@ -107,7 +106,6 @@ exports[`components/actions_menu/ActionsMenu has actions - marketplace enabled a
className="post-menu__item post-menu__item--active" className="post-menu__item post-menu__item--active"
id="center_actions_button_post_id_1" id="center_actions_button_post_id_1"
key="more-actions-button" key="more-actions-button"
onClick={[Function]}
type="button" type="button"
> >
<i <i
@@ -194,7 +192,6 @@ exports[`components/actions_menu/ActionsMenu no actions - sysadmin - menu should
className="post-menu__item post-menu__item--active" className="post-menu__item post-menu__item--active"
id="center_actions_button_post_id_1" id="center_actions_button_post_id_1"
key="more-actions-button" key="more-actions-button"
onClick={[Function]}
type="button" type="button"
> >
<i <i

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

@@ -38,12 +38,7 @@ describe('components/actions_menu/ActionsMenu', () => {
isSysAdmin: true, isSysAdmin: true,
pluginMenuItems: [], pluginMenuItems: [],
post: TestHelper.getPostMock({id: 'post_id_1', is_pinned: false, type: '' as PostType}), post: TestHelper.getPostMock({id: 'post_id_1', is_pinned: false, type: '' as PostType}),
showTutorialTip: false,
components: {}, components: {},
handleOpenTip: jest.fn(),
handleNextTip: jest.fn(),
handleDismissTip: jest.fn(),
showPulsatingDot: false,
location: 'center', location: 'center',
canOpenMarketplace: false, canOpenMarketplace: false,
actions: { actions: {

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

@@ -17,7 +17,6 @@ import {AppCallResponseTypes} from 'mattermost-redux/constants/apps';
import {HandleBindingClick, PostEphemeralCallResponseForPost, OpenAppsModal} from 'types/apps'; import {HandleBindingClick, PostEphemeralCallResponseForPost, OpenAppsModal} from 'types/apps';
import {Locations, Constants, ModalIdentifiers} from 'utils/constants'; import {Locations, Constants, ModalIdentifiers} from 'utils/constants';
import Permissions from 'mattermost-redux/constants/permissions'; import Permissions from 'mattermost-redux/constants/permissions';
import {ActionsTutorialTip} from 'components/actions_menu/actions_menu_tutorial_tip';
import {ModalData} from 'types/actions'; import {ModalData} from 'types/actions';
import MarketplaceModal, {OpenedFromType} from 'components/plugin_marketplace/marketplace_modal'; import MarketplaceModal, {OpenedFromType} from 'components/plugin_marketplace/marketplace_modal';
import OverlayTrigger from 'components/overlay_trigger'; import OverlayTrigger from 'components/overlay_trigger';
@@ -44,11 +43,6 @@ export type Props = {
pluginMenuItems?: PluginComponent[]; pluginMenuItems?: PluginComponent[];
post: Post; post: Post;
teamId: string; teamId: string;
handleOpenTip: () => void;
handleNextTip: (e: React.MouseEvent) => void;
handleDismissTip: () => void;
showPulsatingDot?: boolean;
showTutorialTip: boolean;
canOpenMarketplace: boolean; canOpenMarketplace: boolean;
/** /**
@@ -236,14 +230,6 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
); );
} }
handleActionsIconClick = (e: React.MouseEvent) => {
if (this.props.showPulsatingDot || this.props.showTutorialTip) {
this.props.handleOpenTip();
e.preventDefault();
e.stopPropagation();
}
};
renderDivider = (suffix: string): React.ReactNode => { renderDivider = (suffix: string): React.ReactNode => {
return ( return (
<li <li
@@ -413,17 +399,8 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
})} })}
type='button' type='button'
aria-expanded='false' aria-expanded='false'
onClick={this.handleActionsIconClick}
> >
<i className={'icon icon-apps'}/> <i className={'icon icon-apps'}/>
{this.props.showPulsatingDot &&
<ActionsTutorialTip
showTip={this.props.showTutorialTip}
handleNext={this.props.handleNextTip}
handleOpen={this.props.handleOpenTip}
handleDismiss={this.props.handleDismissTip}
/>
}
</button> </button>
</OverlayTrigger> </OverlayTrigger>
<Menu <Menu

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

@@ -37,11 +37,6 @@ describe('components/actions_menu/ActionsMenu returning empty ("")', () => {
}, },
appBindings: [], appBindings: [],
pluginMenuItems: [], pluginMenuItems: [],
handleOpenTip: jest.fn(),
handleNextTip: jest.fn(),
handleDismissTip: jest.fn(),
showPulsatingDot: false,
showTutorialTip: false,
appsEnabled: false, appsEnabled: false,
isSysAdmin: true, isSysAdmin: true,
canOpenMarketplace: false, canOpenMarketplace: false,

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

@@ -37,11 +37,6 @@ describe('components/actions_menu/ActionsMenu on mobile view', () => {
}, },
appBindings: [], appBindings: [],
pluginMenuItems: [], pluginMenuItems: [],
handleOpenTip: jest.fn(),
handleNextTip: jest.fn(),
handleDismissTip: jest.fn(),
showPulsatingDot: false,
showTutorialTip: false,
appsEnabled: false, appsEnabled: false,
isSysAdmin: true, isSysAdmin: true,
canOpenMarketplace: false, canOpenMarketplace: false,

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

@@ -107,113 +107,6 @@ Object {
} }
`; `;
exports[`components/dot_menu/DotMenu should match snapshot, hide "New" badge on forward post 1`] = `
Object {
"asFragment": [Function],
"baseElement": <body>
<div>
<button
aria-controls="CENTER_dropdown_post_id_1"
aria-expanded="false"
aria-haspopup="true"
aria-label="Actions"
class="post-menu__item"
data-testid="PostDotMenu-Button-post_id_1"
id="CENTER_button_post_id_1"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"
/>
</svg>
</button>
</div>
</body>,
"container": <div>
<button
aria-controls="CENTER_dropdown_post_id_1"
aria-expanded="false"
aria-haspopup="true"
aria-label="Actions"
class="post-menu__item"
data-testid="PostDotMenu-Button-post_id_1"
id="CENTER_button_post_id_1"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"
/>
</svg>
</button>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"rerender": [Function],
"unmount": [Function],
}
`;
exports[`components/dot_menu/DotMenu should match snapshot, on Center 1`] = ` exports[`components/dot_menu/DotMenu should match snapshot, on Center 1`] = `
<Menu <Menu
menu={ menu={
@@ -483,110 +376,3 @@ exports[`components/dot_menu/DotMenu should match snapshot, on Center 1`] = `
/> />
</Menu> </Menu>
`; `;
exports[`components/dot_menu/DotMenu should match snapshot, show "New" badge on forward post 1`] = `
Object {
"asFragment": [Function],
"baseElement": <body>
<div>
<button
aria-controls="CENTER_dropdown_post_id_1"
aria-expanded="false"
aria-haspopup="true"
aria-label="Actions"
class="post-menu__item"
data-testid="PostDotMenu-Button-post_id_1"
id="CENTER_button_post_id_1"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"
/>
</svg>
</button>
</div>
</body>,
"container": <div>
<button
aria-controls="CENTER_dropdown_post_id_1"
aria-expanded="false"
aria-haspopup="true"
aria-label="Actions"
class="post-menu__item"
data-testid="PostDotMenu-Button-post_id_1"
id="CENTER_button_post_id_1"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z"
/>
</svg>
</button>
</div>,
"debug": [Function],
"findAllByAltText": [Function],
"findAllByDisplayValue": [Function],
"findAllByLabelText": [Function],
"findAllByPlaceholderText": [Function],
"findAllByRole": [Function],
"findAllByTestId": [Function],
"findAllByText": [Function],
"findAllByTitle": [Function],
"findByAltText": [Function],
"findByDisplayValue": [Function],
"findByLabelText": [Function],
"findByPlaceholderText": [Function],
"findByRole": [Function],
"findByTestId": [Function],
"findByText": [Function],
"findByTitle": [Function],
"getAllByAltText": [Function],
"getAllByDisplayValue": [Function],
"getAllByLabelText": [Function],
"getAllByPlaceholderText": [Function],
"getAllByRole": [Function],
"getAllByTestId": [Function],
"getAllByText": [Function],
"getAllByTitle": [Function],
"getByAltText": [Function],
"getByDisplayValue": [Function],
"getByLabelText": [Function],
"getByPlaceholderText": [Function],
"getByRole": [Function],
"getByTestId": [Function],
"getByText": [Function],
"getByTitle": [Function],
"queryAllByAltText": [Function],
"queryAllByDisplayValue": [Function],
"queryAllByLabelText": [Function],
"queryAllByPlaceholderText": [Function],
"queryAllByRole": [Function],
"queryAllByTestId": [Function],
"queryAllByText": [Function],
"queryAllByTitle": [Function],
"queryByAltText": [Function],
"queryByDisplayValue": [Function],
"queryByLabelText": [Function],
"queryByPlaceholderText": [Function],
"queryByRole": [Function],
"queryByTestId": [Function],
"queryByText": [Function],
"queryByTitle": [Function],
"rerender": [Function],
"unmount": [Function],
}
`;

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

@@ -146,7 +146,6 @@ describe('components/dot_menu/DotMenu', () => {
threadReplyCount: 0, threadReplyCount: 0,
userId: 'user_id_1', userId: 'user_id_1',
isMilitaryTime: false, isMilitaryTime: false,
showForwardPostNewLabel: false,
}; };
test('should match snapshot, on Center', () => { test('should match snapshot, on Center', () => {
@@ -181,32 +180,6 @@ describe('components/dot_menu/DotMenu', () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('should match snapshot, show "New" badge on forward post', () => {
const props = {
...baseProps,
showForwardPostNewLabel: true,
};
const wrapper = renderWithIntlAndStore(
<DotMenu {...props}/>,
initialState,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot, hide "New" badge on forward post', () => {
const props = {
...baseProps,
showForwardPostNewLabel: false,
};
const wrapper = renderWithIntlAndStore(
<DotMenu {...props}/>,
initialState,
);
expect(wrapper).toMatchSnapshot();
});
test('should show mark as unread when channel is not archived', () => { test('should show mark as unread when channel is not archived', () => {
const props = { const props = {
...baseProps, ...baseProps,

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

@@ -25,7 +25,7 @@ import {
import Permissions from 'mattermost-redux/constants/permissions'; import Permissions from 'mattermost-redux/constants/permissions';
import {Locations, ModalIdentifiers, Constants, TELEMETRY_LABELS, Preferences} from 'utils/constants'; import {Locations, ModalIdentifiers, Constants, TELEMETRY_LABELS} from 'utils/constants';
import DeletePostModal from 'components/delete_post_modal'; import DeletePostModal from 'components/delete_post_modal';
import DelayedAction from 'utils/delayed_action'; import DelayedAction from 'utils/delayed_action';
import * as PostUtils from 'utils/post_utils'; import * as PostUtils from 'utils/post_utils';
@@ -33,12 +33,10 @@ import * as Menu from 'components/menu';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate'; import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
import {ModalData} from 'types/actions'; import {ModalData} from 'types/actions';
import {PluginComponent} from 'types/store/plugins';
import {UserThread} from '@mattermost/types/threads'; import {UserThread} from '@mattermost/types/threads';
import {Post} from '@mattermost/types/posts'; import {Post} from '@mattermost/types/posts';
import ForwardPostModal from '../forward_post_modal'; import ForwardPostModal from '../forward_post_modal';
import Tag from '../widgets/tag/tag';
import {ChangeEvent, trackDotMenuEvent} from './utils'; import {ChangeEvent, trackDotMenuEvent} from './utils';
@@ -70,20 +68,11 @@ type Props = {
postEditTimeLimit?: string; // TechDebt: Made non-mandatory while converting to typescript postEditTimeLimit?: string; // TechDebt: Made non-mandatory while converting to typescript
enableEmojiPicker?: boolean; // TechDebt: Made non-mandatory while converting to typescript enableEmojiPicker?: boolean; // TechDebt: Made non-mandatory while converting to typescript
channelIsArchived?: boolean; // TechDebt: Made non-mandatory while converting to typescript channelIsArchived?: boolean; // TechDebt: Made non-mandatory while converting to typescript
currentTeamUrl?: string; // TechDebt: Made non-mandatory while converting to typescript
teamUrl?: string; // TechDebt: Made non-mandatory while converting to typescript teamUrl?: string; // TechDebt: Made non-mandatory while converting to typescript
isMobileView: boolean; isMobileView: boolean;
showForwardPostNewLabel: boolean;
timezone?: string; timezone?: string;
isMilitaryTime: boolean; isMilitaryTime: boolean;
/**
* Components for overriding provided by plugins
*/
components: {
[componentName: string]: PluginComponent[];
};
actions: { actions: {
/** /**
@@ -292,9 +281,6 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
}, },
}; };
if (this.props.showForwardPostNewLabel) {
this.props.actions.setGlobalItem(Preferences.FORWARD_POST_VIEWED, false);
}
this.props.actions.openModal(forwardPostModalData); this.props.actions.openModal(forwardPostModalData);
} }
@@ -441,15 +427,6 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
id='forward_post_button.label' id='forward_post_button.label'
defaultMessage='Forward' defaultMessage='Forward'
/> />
{this.props.showForwardPostNewLabel && (
<Tag
variant='success'
text={formatMessage({
id: 'tag.default.new',
defaultMessage: 'NEW',
})}
/>
)}
</span> </span>
); );
@@ -519,12 +496,12 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
class: classNames('post-menu__item', { class: classNames('post-menu__item', {
'post-menu__item--active': this.props.isMenuOpen, 'post-menu__item--active': this.props.isMenuOpen,
}), }),
'aria-label': this.props.intl.formatMessage({id: 'post_info.dot_menu.tooltip.more_actions', defaultMessage: 'Actions'}), 'aria-label': formatMessage({id: 'post_info.dot_menu.tooltip.more_actions', defaultMessage: 'Actions'}),
children: <DotsHorizontalIcon size={16}/>, children: <DotsHorizontalIcon size={16}/>,
}} }}
menu={{ menu={{
id: `${this.props.location}_dropdown_${this.props.post.id}`, id: `${this.props.location}_dropdown_${this.props.post.id}`,
'aria-label': this.props.intl.formatMessage({id: 'post_info.menuAriaLabel', defaultMessage: 'Post extra options'}), 'aria-label': formatMessage({id: 'post_info.menuAriaLabel', defaultMessage: 'Post extra options'}),
onKeyDown: this.onShortcutKeyDown, onKeyDown: this.onShortcutKeyDown,
width: '264px', width: '264px',
onToggle: this.handleMenuToggle, onToggle: this.handleMenuToggle,
@@ -532,7 +509,7 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
}} }}
menuButtonTooltip={{ menuButtonTooltip={{
id: `PostDotMenu-ButtonTooltip-${this.props.post.id}`, id: `PostDotMenu-ButtonTooltip-${this.props.post.id}`,
text: this.props.intl.formatMessage({id: 'post_info.dot_menu.tooltip.more_actions', defaultMessage: 'More'}), text: formatMessage({id: 'post_info.dot_menu.tooltip.more_actions', defaultMessage: 'More'}),
class: 'hidden-xs', class: 'hidden-xs',
}} }}
> >

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

@@ -59,7 +59,6 @@ describe('components/dot_menu/DotMenu returning empty ("")', () => {
threadId: 'post_id_1', threadId: 'post_id_1',
userId: 'user_id_1', userId: 'user_id_1',
isMilitaryTime: false, isMilitaryTime: false,
showForwardPostNewLabel: false,
}; };
const wrapper = shallow( const wrapper = shallow(

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

@@ -59,7 +59,6 @@ describe('components/dot_menu/DotMenu on mobile view', () => {
threadId: 'post_id_1', threadId: 'post_id_1',
userId: 'user_id_1', userId: 'user_id_1',
isMilitaryTime: false, isMilitaryTime: false,
showForwardPostNewLabel: false,
}; };
const wrapper = shallow( const wrapper = shallow(

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

@@ -44,7 +44,6 @@ import {matchUserMentionTriggersWithMessageMentions} from 'utils/post_utils';
import {Post} from '@mattermost/types/posts'; import {Post} from '@mattermost/types/posts';
import {setGlobalItem} from '../../actions/storage'; import {setGlobalItem} from '../../actions/storage';
import {getGlobalItem} from '../../selectors/storage';
import DotMenu from './dot_menu'; import DotMenu from './dot_menu';
@@ -110,8 +109,6 @@ function makeMapStateToProps() {
} }
} }
const showForwardPostNewLabel = getGlobalItem(state, Preferences.FORWARD_POST_VIEWED, true);
return { return {
channelIsArchived: isArchivedChannel(channel), channelIsArchived: isArchivedChannel(channel),
components: state.plugins.components, components: state.plugins.components,
@@ -129,7 +126,6 @@ function makeMapStateToProps() {
isCollapsedThreadsEnabled: collapsedThreads, isCollapsedThreadsEnabled: collapsedThreads,
threadReplyCount, threadReplyCount,
isMobileView: getIsMobileView(state), isMobileView: getIsMobileView(state),
showForwardPostNewLabel,
timezone: getCurrentUserTimezone(state), timezone: getCurrentUserTimezone(state),
isMilitaryTime, isMilitaryTime,
}; };

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

@@ -4,7 +4,6 @@
import {connect, ConnectedProps} from 'react-redux'; import {connect, ConnectedProps} from 'react-redux';
import {AnyAction, bindActionCreators, Dispatch} from 'redux'; import {AnyAction, bindActionCreators, Dispatch} from 'redux';
import {showActionsDropdownPulsatingDot} from 'selectors/actions_menu';
import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences'; import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences';
import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled, UserActivityPost} from 'mattermost-redux/selectors/entities/posts'; import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled, UserActivityPost} from 'mattermost-redux/selectors/entities/posts';
@@ -13,7 +12,6 @@ import {
get, get,
getBool, getBool,
isCollapsedThreadsEnabled, isCollapsedThreadsEnabled,
onboardingTourTipsEnabled,
} from 'mattermost-redux/selectors/entities/preferences'; } from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeam, getCurrentTeamId, getTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeam, getCurrentTeamId, getTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
@@ -129,8 +127,7 @@ function makeMapStateToProps() {
const user = getUser(state, post.user_id); const user = getUser(state, post.user_id);
const isBot = Boolean(user && user.is_bot); const isBot = Boolean(user && user.is_bot);
const highlightedPostId = getHighlightedPostId(state); const highlightedPostId = getHighlightedPostId(state);
const showActionsMenuPulsatingDot = showActionsDropdownPulsatingDot(state);
const tourTipsEnabled = onboardingTourTipsEnabled(state);
const selectedCard = getSelectedPostCard(state); const selectedCard = getSelectedPostCard(state);
let emojis: Emoji[] = []; let emojis: Emoji[] = [];
@@ -203,8 +200,7 @@ function makeMapStateToProps() {
compactDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT, compactDisplay: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.MESSAGE_DISPLAY, Preferences.MESSAGE_DISPLAY_DEFAULT) === Preferences.MESSAGE_DISPLAY_COMPACT,
colorizeUsernames: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT) === 'true', colorizeUsernames: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT) === 'true',
shouldShowActionsMenu: shouldShowActionsMenu(state, post), shouldShowActionsMenu: shouldShowActionsMenu(state, post),
showActionsMenuPulsatingDot,
tourTipsEnabled,
shortcutReactToLastPostEmittedFrom, shortcutReactToLastPostEmittedFrom,
isBot, isBot,
collapsedThreadsEnabled: isCollapsedThreadsEnabled(state), collapsedThreadsEnabled: isCollapsedThreadsEnabled(state),

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

@@ -114,8 +114,6 @@ export type Props = {
isPostAcknowledgementsEnabled: boolean; isPostAcknowledgementsEnabled: boolean;
isPostPriorityEnabled: boolean; isPostPriorityEnabled: boolean;
isCardOpen?: boolean; isCardOpen?: boolean;
shouldShowDotMenu: boolean;
tourTipsEnabled: boolean;
}; };
const PostComponent = (props: Props): JSX.Element => { const PostComponent = (props: Props): JSX.Element => {

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

@@ -5,7 +5,7 @@ import React, {ReactNode, useEffect, useRef, useState} from 'react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import {Posts, Preferences} from 'mattermost-redux/constants/index'; import {Posts} from 'mattermost-redux/constants/index';
import {isPostEphemeral} from 'mattermost-redux/utils/post_utils'; import {isPostEphemeral} from 'mattermost-redux/utils/post_utils';
import {Locations} from 'utils/constants'; import {Locations} from 'utils/constants';
@@ -34,8 +34,6 @@ type Props = {
handleDropdownOpened?: (e: boolean) => void; handleDropdownOpened?: (e: boolean) => void;
collapsedThreadsEnabled?: boolean; collapsedThreadsEnabled?: boolean;
shouldShowActionsMenu?: boolean; shouldShowActionsMenu?: boolean;
showActionsMenuPulsatingDot?: boolean;
tourTipsEnabled: boolean;
oneClickReactionsEnabled?: boolean; oneClickReactionsEnabled?: boolean;
recentEmojis: Emoji[]; recentEmojis: Emoji[];
isExpanded?: boolean; isExpanded?: boolean;
@@ -51,7 +49,6 @@ type Props = {
shortcutReactToLastPostEmittedFrom?: string; shortcutReactToLastPostEmittedFrom?: string;
isPostHeaderVisible?: boolean | null; isPostHeaderVisible?: boolean | null;
isPostBeingEdited?: boolean; isPostBeingEdited?: boolean;
shouldShowDotMenu: boolean;
actions: { actions: {
emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void; emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void;
}; };
@@ -63,7 +60,6 @@ const PostOptions = (props: Props): JSX.Element => {
const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [showDotMenu, setShowDotMenu] = useState(false); const [showDotMenu, setShowDotMenu] = useState(false);
const [showActionsMenu, setShowActionsMenu] = useState(false); const [showActionsMenu, setShowActionsMenu] = useState(false);
const [showActionTip, setShowActionTip] = useState(false);
useEffect(() => { useEffect(() => {
if (props.isLastPost && if (props.isLastPost &&
@@ -80,8 +76,6 @@ const PostOptions = (props: Props): JSX.Element => {
isReadOnly, isReadOnly,
post, post,
oneClickReactionsEnabled, oneClickReactionsEnabled,
showActionsMenuPulsatingDot,
tourTipsEnabled,
isMobileView, isMobileView,
} = props; } = props;
@@ -102,34 +96,14 @@ const PostOptions = (props: Props): JSX.Element => {
}; };
const handleActionsMenuOpened = (open: boolean) => { const handleActionsMenuOpened = (open: boolean) => {
if (tourTipsEnabled && showActionsMenuPulsatingDot) {
setShowActionTip(true);
return;
}
setShowActionsMenu(open); setShowActionsMenu(open);
props.handleDropdownOpened!(open); props.handleDropdownOpened!(open);
}; };
const handleActionsMenuTipOpened = () => {
setShowActionTip(true);
props.handleDropdownOpened!(true);
};
const handleActionsMenuGotItClick = () => {
props.setActionsMenuInitialisationState?.(({[Preferences.ACTIONS_MENU_VIEWED]: true}));
setShowActionTip(false);
props.handleDropdownOpened!(false);
};
const handleTipDismissed = () => {
setShowActionTip(false);
props.handleDropdownOpened!(false);
};
const getDotMenuRef = () => dotMenuRef.current; const getDotMenuRef = () => dotMenuRef.current;
const isPostDeleted = post && post.state === Posts.POST_DELETED; const isPostDeleted = post && post.state === Posts.POST_DELETED;
const hoverLocal = props.hover || showEmojiPicker || showDotMenu || showActionsMenu || showActionTip; const hoverLocal = props.hover || showEmojiPicker || showDotMenu || showActionsMenu;
const showCommentIcon = isFromAutoResponder || (!systemMessage && (isMobileView || const showCommentIcon = isFromAutoResponder || (!systemMessage && (isMobileView ||
hoverLocal || (!post.root_id && Boolean(props.hasReplies)) || hoverLocal || (!post.root_id && Boolean(props.hasReplies)) ||
props.isFirstReply) && props.location === Locations.CENTER); props.isFirstReply) && props.location === Locations.CENTER);
@@ -197,11 +171,6 @@ const PostOptions = (props: Props): JSX.Element => {
location={props.location} location={props.location}
handleDropdownOpened={handleActionsMenuOpened} handleDropdownOpened={handleActionsMenuOpened}
isMenuOpen={showActionsMenu} isMenuOpen={showActionsMenu}
showPulsatingDot={tourTipsEnabled && showActionsMenuPulsatingDot}
showTutorialTip={tourTipsEnabled && showActionTip}
handleOpenTip={handleActionsMenuTipOpened}
handleNextTip={handleActionsMenuGotItClick}
handleDismissTip={handleTipDismissed}
/> />
); );
const dotMenu = ( const dotMenu = (
@@ -231,7 +200,7 @@ const PostOptions = (props: Props): JSX.Element => {
</button> </button>
</div> </div>
); );
} else if (isPostDeleted || !props.shouldShowDotMenu) { } else if (isPostDeleted) {
options = null; options = null;
} else if (props.location === Locations.SEARCH) { } else if (props.location === Locations.SEARCH) {
const hasCRTFooter = props.collapsedThreadsEnabled && !post.root_id && (post.reply_count > 0 || post.is_following); const hasCRTFooter = props.collapsedThreadsEnabled && !post.root_id && (post.reply_count > 0 || post.is_following);

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

@@ -27,7 +27,6 @@ type Props = {
previousPostId: string; previousPostId: string;
teamId: string; teamId: string;
timestampProps?: Partial<TimestampProps>; timestampProps?: Partial<TimestampProps>;
lastPost: Post;
}; };
function noop() {} function noop() {}

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

@@ -403,7 +403,6 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
previousPostId={getPreviousPostId(data, index)} previousPostId={getPreviousPostId(data, index)}
teamId={this.props.teamId} teamId={this.props.teamId}
timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined} timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined}
lastPost={this.props.lastPost}
/> />
</div> </div>
); );

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

@@ -11,6 +11,7 @@ import {makeGetCustomStatus, getRecentCustomStatuses, isCustomStatusEnabled, sho
import {TestHelper} from 'utils/test_helper'; import {TestHelper} from 'utils/test_helper';
import {CustomStatusDuration} from '@mattermost/types/users'; import {CustomStatusDuration} from '@mattermost/types/users';
import {addTimeToTimestamp, TimeInformation} from 'utils/utils';
jest.mock('mattermost-redux/selectors/entities/users'); jest.mock('mattermost-redux/selectors/entities/users');
jest.mock('mattermost-redux/selectors/entities/general'); jest.mock('mattermost-redux/selectors/entities/general');
@@ -86,6 +87,7 @@ describe('isCustomStatusEnabled', () => {
}); });
describe('showStatusDropdownPulsatingDot and showPostHeaderUpdateStatusButton', () => { describe('showStatusDropdownPulsatingDot and showPostHeaderUpdateStatusButton', () => {
const user = TestHelper.getUserMock();
const preference = { const preference = {
myPreference: { myPreference: {
value: '', value: '',
@@ -104,4 +106,31 @@ describe('showStatusDropdownPulsatingDot and showPostHeaderUpdateStatusButton',
(PreferenceSelectors.get as jest.Mock).mockReturnValue(preference.myPreference.value); (PreferenceSelectors.get as jest.Mock).mockReturnValue(preference.myPreference.value);
expect(showPostHeaderUpdateStatusButton(store.getState())).toBeFalsy(); expect(showPostHeaderUpdateStatusButton(store.getState())).toBeFalsy();
}); });
it('should return false if user was created less than seven days before from today', async () => {
const store = await configureStore();
(PreferenceSelectors.get as jest.Mock).mockReturnValue(preference.myPreference.value);
const todayTimestamp = new Date().getTime();
// set the user create date to 6 days in the past from today
const todayMinusSixDays = addTimeToTimestamp(todayTimestamp, TimeInformation.DAYS, 6, TimeInformation.PAST);
const newUser = {...user, create_at: todayMinusSixDays};
newUser.props.customStatus = JSON.stringify(customStatus);
(UserSelectors.getCurrentUser as jest.Mock).mockReturnValue(newUser);
expect(showStatusDropdownPulsatingDot(store.getState())).toBeFalsy();
});
it('should return true if user was created more than seven days before from today', async () => {
const store = await configureStore();
preference.myPreference.value = JSON.stringify({[Preferences.CUSTOM_STATUS_MODAL_VIEWED]: false});
(PreferenceSelectors.get as jest.Mock).mockReturnValue(preference.myPreference.value);
const todayTimestamp = new Date().getTime();
// set the user create date to 8 days in the past from today
const todayMinusEightDays = addTimeToTimestamp(todayTimestamp, TimeInformation.DAYS, 8, TimeInformation.PAST);
const newUser = {...user, create_at: todayMinusEightDays};
newUser.props.customStatus = JSON.stringify(customStatus);
(UserSelectors.getCurrentUser as jest.Mock).mockReturnValue(newUser);
expect(showStatusDropdownPulsatingDot(store.getState())).toBeTruthy();
});
}); });

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

@@ -12,6 +12,8 @@ import {get} from 'mattermost-redux/selectors/entities/preferences';
import {Preferences} from 'mattermost-redux/constants'; import {Preferences} from 'mattermost-redux/constants';
import {CustomStatusDuration, UserCustomStatus} from '@mattermost/types/users'; import {CustomStatusDuration, UserCustomStatus} from '@mattermost/types/users';
import {isDateWithinDaysRange, TimeInformation} from 'utils/utils';
import {GlobalState} from 'types/store'; import {GlobalState} from 'types/store';
import {getCurrentUserTimezone} from 'selectors/general'; import {getCurrentUserTimezone} from 'selectors/general';
import {getCurrentMomentForTimezone} from 'utils/timezone'; import {getCurrentMomentForTimezone} from 'utils/timezone';
@@ -56,9 +58,12 @@ export function isCustomStatusEnabled(state: GlobalState) {
} }
function showCustomStatusPulsatingDotAndPostHeader(state: GlobalState) { function showCustomStatusPulsatingDotAndPostHeader(state: GlobalState) {
// only show this for users after the first seven days
const currentUser = getCurrentUser(state);
const hasUserCreationMoreThanSevenDays = isDateWithinDaysRange(currentUser?.create_at, 7, TimeInformation.FUTURE);
const customStatusTutorialState = get(state, Preferences.CATEGORY_CUSTOM_STATUS, Preferences.NAME_CUSTOM_STATUS_TUTORIAL_STATE); const customStatusTutorialState = get(state, Preferences.CATEGORY_CUSTOM_STATUS, Preferences.NAME_CUSTOM_STATUS_TUTORIAL_STATE);
const modalAlreadyViewed = customStatusTutorialState && JSON.parse(customStatusTutorialState)[Preferences.CUSTOM_STATUS_MODAL_VIEWED]; const modalAlreadyViewed = customStatusTutorialState && JSON.parse(customStatusTutorialState)[Preferences.CUSTOM_STATUS_MODAL_VIEWED];
return !modalAlreadyViewed; return !modalAlreadyViewed && hasUserCreationMoreThanSevenDays;
} }
export function showStatusDropdownPulsatingDot(state: GlobalState) { export function showStatusDropdownPulsatingDot(state: GlobalState) {

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

@@ -90,6 +90,23 @@ const CLICKABLE_ELEMENTS = [
'audio', 'audio',
'video', 'video',
]; ];
const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
const MS_PER_DAY = 24 * MS_PER_HOUR;
export enum TimeInformation {
MILLISECONDS = 'm',
SECONDS = 's',
MINUTES = 'x',
HOURS = 'h',
DAYS = 'd',
FUTURE = 'f',
PAST = 'p'
}
export type TimeUnit = Exclude<TimeInformation, TimeInformation.FUTURE | TimeInformation.PAST>;
export type TimeDirection = TimeInformation.FUTURE | TimeInformation.PAST;
export function isMac() { export function isMac() {
return navigator.platform.toUpperCase().indexOf('MAC') >= 0; return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
@@ -256,7 +273,6 @@ export function getTimestamp(): number {
} }
export function getRemainingDaysFromFutureTimestamp(timestamp?: number): number { export function getRemainingDaysFromFutureTimestamp(timestamp?: number): number {
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const futureDate = new Date(timestamp as number); const futureDate = new Date(timestamp as number);
const utcFuture = Date.UTC(futureDate.getFullYear(), futureDate.getMonth(), futureDate.getDate()); const utcFuture = Date.UTC(futureDate.getFullYear(), futureDate.getMonth(), futureDate.getDate());
const today = new Date(); const today = new Date();
@@ -265,6 +281,39 @@ export function getRemainingDaysFromFutureTimestamp(timestamp?: number): number
return Math.floor((utcFuture - utcToday) / MS_PER_DAY); return Math.floor((utcFuture - utcToday) / MS_PER_DAY);
} }
export function addTimeToTimestamp(timestamp: number, type: TimeUnit, diff: number, timeline: TimeDirection) {
let modifier = 1;
switch (type) {
case TimeInformation.SECONDS:
modifier = MS_PER_SECOND;
break;
case TimeInformation.MINUTES:
modifier = MS_PER_MINUTE;
break;
case TimeInformation.HOURS:
modifier = MS_PER_HOUR;
break;
case TimeInformation.DAYS:
modifier = MS_PER_DAY;
break;
}
return timeline === TimeInformation.FUTURE ? timestamp + (diff * modifier) : timestamp - (diff * modifier);
}
/**
* Verifies if a date is in a particular given range of days from today
* @param timestamp date you want to check is in the range of the provided number of days from today
* @param days number of days you want to check your date against to
* @param timeline 'f' represents future, 'p' represents past
* @returns boolean, true if your date is in the range of the provided number of days
*/
export function isDateWithinDaysRange(timestamp: number, days: number, timeline: TimeDirection): boolean {
const today = new Date().getTime();
const daysSince = Math.round((today - timestamp) / MS_PER_DAY);
return timeline === TimeInformation.PAST ? daysSince <= days : daysSince >= days;
}
export function getLocaleDateFromUTC(timestamp: number, format = 'YYYY/MM/DD HH:mm:ss', userTimezone = '') { export function getLocaleDateFromUTC(timestamp: number, format = 'YYYY/MM/DD HH:mm:ss', userTimezone = '') {
if (!timestamp) { if (!timestamp) {
return moment.now(); return moment.now();