From 6919761122d2409e805d0279169a7925c6e5687d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20V=C3=A9lez=20Vidal?= Date: Tue, 4 Apr 2023 18:04:06 +0200 Subject: [PATCH] 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 --- .../__snapshots__/actions_menu.test.tsx.snap | 3 - .../actions_menu/actions_menu.test.tsx | 5 - .../components/actions_menu/actions_menu.tsx | 23 -- .../actions_menu/actions_menu_empty.test.tsx | 5 - .../actions_menu/actions_menu_mobile.test.tsx | 5 - .../__snapshots__/dot_menu.test.tsx.snap | 214 ------------------ .../src/components/dot_menu/dot_menu.test.tsx | 27 --- .../src/components/dot_menu/dot_menu.tsx | 31 +-- .../dot_menu/dot_menu_empty.test.tsx | 1 - .../dot_menu/dot_menu_mobile.test.tsx | 1 - .../channels/src/components/dot_menu/index.ts | 4 - webapp/channels/src/components/post/index.tsx | 8 +- .../src/components/post/post_component.tsx | 2 - .../src/components/post/post_options.tsx | 37 +-- .../thread_viewer_row.tsx | 1 - .../virtualized_thread_viewer.tsx | 1 - .../src/selectors/views/custom_status.test.ts | 29 +++ .../src/selectors/views/custom_status.ts | 7 +- webapp/channels/src/utils/utils.tsx | 51 ++++- 19 files changed, 94 insertions(+), 361 deletions(-) diff --git a/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap b/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap index 60367b08bf..26709a01ad 100644 --- a/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap +++ b/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap @@ -39,7 +39,6 @@ exports[`components/actions_menu/ActionsMenu has actions - marketplace disabled className="post-menu__item post-menu__item--active" id="center_actions_button_post_id_1" key="more-actions-button" - onClick={[Function]} type="button" > { isSysAdmin: true, pluginMenuItems: [], post: TestHelper.getPostMock({id: 'post_id_1', is_pinned: false, type: '' as PostType}), - showTutorialTip: false, components: {}, - handleOpenTip: jest.fn(), - handleNextTip: jest.fn(), - handleDismissTip: jest.fn(), - showPulsatingDot: false, location: 'center', canOpenMarketplace: false, actions: { diff --git a/webapp/channels/src/components/actions_menu/actions_menu.tsx b/webapp/channels/src/components/actions_menu/actions_menu.tsx index e20023c77b..d141f1f121 100644 --- a/webapp/channels/src/components/actions_menu/actions_menu.tsx +++ b/webapp/channels/src/components/actions_menu/actions_menu.tsx @@ -17,7 +17,6 @@ import {AppCallResponseTypes} from 'mattermost-redux/constants/apps'; import {HandleBindingClick, PostEphemeralCallResponseForPost, OpenAppsModal} from 'types/apps'; import {Locations, Constants, ModalIdentifiers} from 'utils/constants'; import Permissions from 'mattermost-redux/constants/permissions'; -import {ActionsTutorialTip} from 'components/actions_menu/actions_menu_tutorial_tip'; import {ModalData} from 'types/actions'; import MarketplaceModal, {OpenedFromType} from 'components/plugin_marketplace/marketplace_modal'; import OverlayTrigger from 'components/overlay_trigger'; @@ -44,11 +43,6 @@ export type Props = { pluginMenuItems?: PluginComponent[]; post: Post; teamId: string; - handleOpenTip: () => void; - handleNextTip: (e: React.MouseEvent) => void; - handleDismissTip: () => void; - showPulsatingDot?: boolean; - showTutorialTip: boolean; canOpenMarketplace: boolean; /** @@ -236,14 +230,6 @@ export class ActionMenuClass extends React.PureComponent { ); } - handleActionsIconClick = (e: React.MouseEvent) => { - if (this.props.showPulsatingDot || this.props.showTutorialTip) { - this.props.handleOpenTip(); - e.preventDefault(); - e.stopPropagation(); - } - }; - renderDivider = (suffix: string): React.ReactNode => { return (
  • { })} type='button' aria-expanded='false' - onClick={this.handleActionsIconClick} > - {this.props.showPulsatingDot && - - } { }, appBindings: [], pluginMenuItems: [], - handleOpenTip: jest.fn(), - handleNextTip: jest.fn(), - handleDismissTip: jest.fn(), - showPulsatingDot: false, - showTutorialTip: false, appsEnabled: false, isSysAdmin: true, canOpenMarketplace: false, diff --git a/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx b/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx index 3f4f3c3534..cdd71a8390 100644 --- a/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx +++ b/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx @@ -37,11 +37,6 @@ describe('components/actions_menu/ActionsMenu on mobile view', () => { }, appBindings: [], pluginMenuItems: [], - handleOpenTip: jest.fn(), - handleNextTip: jest.fn(), - handleDismissTip: jest.fn(), - showPulsatingDot: false, - showTutorialTip: false, appsEnabled: false, isSysAdmin: true, canOpenMarketplace: false, diff --git a/webapp/channels/src/components/dot_menu/__snapshots__/dot_menu.test.tsx.snap b/webapp/channels/src/components/dot_menu/__snapshots__/dot_menu.test.tsx.snap index 9970088867..490e16e2da 100644 --- a/webapp/channels/src/components/dot_menu/__snapshots__/dot_menu.test.tsx.snap +++ b/webapp/channels/src/components/dot_menu/__snapshots__/dot_menu.test.tsx.snap @@ -107,113 +107,6 @@ Object { } `; -exports[`components/dot_menu/DotMenu should match snapshot, hide "New" badge on forward post 1`] = ` -Object { - "asFragment": [Function], - "baseElement": -
    - -
    - , - "container":
    - -
    , - "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, show "New" badge on forward post 1`] = ` -Object { - "asFragment": [Function], - "baseElement": -
    - -
    - , - "container":
    - -
    , - "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], -} -`; diff --git a/webapp/channels/src/components/dot_menu/dot_menu.test.tsx b/webapp/channels/src/components/dot_menu/dot_menu.test.tsx index e807940ead..402cf954b1 100644 --- a/webapp/channels/src/components/dot_menu/dot_menu.test.tsx +++ b/webapp/channels/src/components/dot_menu/dot_menu.test.tsx @@ -146,7 +146,6 @@ describe('components/dot_menu/DotMenu', () => { threadReplyCount: 0, userId: 'user_id_1', isMilitaryTime: false, - showForwardPostNewLabel: false, }; test('should match snapshot, on Center', () => { @@ -181,32 +180,6 @@ describe('components/dot_menu/DotMenu', () => { expect(wrapper).toMatchSnapshot(); }); - test('should match snapshot, show "New" badge on forward post', () => { - const props = { - ...baseProps, - showForwardPostNewLabel: true, - }; - const wrapper = renderWithIntlAndStore( - , - initialState, - ); - - expect(wrapper).toMatchSnapshot(); - }); - - test('should match snapshot, hide "New" badge on forward post', () => { - const props = { - ...baseProps, - showForwardPostNewLabel: false, - }; - const wrapper = renderWithIntlAndStore( - , - initialState, - ); - - expect(wrapper).toMatchSnapshot(); - }); - test('should show mark as unread when channel is not archived', () => { const props = { ...baseProps, diff --git a/webapp/channels/src/components/dot_menu/dot_menu.tsx b/webapp/channels/src/components/dot_menu/dot_menu.tsx index 135fb04593..df164cf18f 100644 --- a/webapp/channels/src/components/dot_menu/dot_menu.tsx +++ b/webapp/channels/src/components/dot_menu/dot_menu.tsx @@ -25,7 +25,7 @@ import { 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 DelayedAction from 'utils/delayed_action'; import * as PostUtils from 'utils/post_utils'; @@ -33,12 +33,10 @@ import * as Menu from 'components/menu'; import * as Utils from 'utils/utils'; import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate'; import {ModalData} from 'types/actions'; -import {PluginComponent} from 'types/store/plugins'; import {UserThread} from '@mattermost/types/threads'; import {Post} from '@mattermost/types/posts'; import ForwardPostModal from '../forward_post_modal'; -import Tag from '../widgets/tag/tag'; import {ChangeEvent, trackDotMenuEvent} from './utils'; @@ -70,20 +68,11 @@ type Props = { postEditTimeLimit?: string; // 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 - currentTeamUrl?: string; // TechDebt: Made non-mandatory while converting to typescript teamUrl?: string; // TechDebt: Made non-mandatory while converting to typescript isMobileView: boolean; - showForwardPostNewLabel: boolean; timezone?: string; isMilitaryTime: boolean; - /** - * Components for overriding provided by plugins - */ - components: { - [componentName: string]: PluginComponent[]; - }; - actions: { /** @@ -292,9 +281,6 @@ export class DotMenuClass extends React.PureComponent { }, }; - if (this.props.showForwardPostNewLabel) { - this.props.actions.setGlobalItem(Preferences.FORWARD_POST_VIEWED, false); - } this.props.actions.openModal(forwardPostModalData); } @@ -441,15 +427,6 @@ export class DotMenuClass extends React.PureComponent { id='forward_post_button.label' defaultMessage='Forward' /> - {this.props.showForwardPostNewLabel && ( - - )} ); @@ -519,12 +496,12 @@ export class DotMenuClass extends React.PureComponent { class: classNames('post-menu__item', { '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: , }} menu={{ 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, width: '264px', onToggle: this.handleMenuToggle, @@ -532,7 +509,7 @@ export class DotMenuClass extends React.PureComponent { }} menuButtonTooltip={{ 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', }} > diff --git a/webapp/channels/src/components/dot_menu/dot_menu_empty.test.tsx b/webapp/channels/src/components/dot_menu/dot_menu_empty.test.tsx index dd9b38594b..c5b2e454d1 100644 --- a/webapp/channels/src/components/dot_menu/dot_menu_empty.test.tsx +++ b/webapp/channels/src/components/dot_menu/dot_menu_empty.test.tsx @@ -59,7 +59,6 @@ describe('components/dot_menu/DotMenu returning empty ("")', () => { threadId: 'post_id_1', userId: 'user_id_1', isMilitaryTime: false, - showForwardPostNewLabel: false, }; const wrapper = shallow( diff --git a/webapp/channels/src/components/dot_menu/dot_menu_mobile.test.tsx b/webapp/channels/src/components/dot_menu/dot_menu_mobile.test.tsx index a6ecb1761c..cd3f1d23a8 100644 --- a/webapp/channels/src/components/dot_menu/dot_menu_mobile.test.tsx +++ b/webapp/channels/src/components/dot_menu/dot_menu_mobile.test.tsx @@ -59,7 +59,6 @@ describe('components/dot_menu/DotMenu on mobile view', () => { threadId: 'post_id_1', userId: 'user_id_1', isMilitaryTime: false, - showForwardPostNewLabel: false, }; const wrapper = shallow( diff --git a/webapp/channels/src/components/dot_menu/index.ts b/webapp/channels/src/components/dot_menu/index.ts index fa1623fa2c..e9d765ba59 100644 --- a/webapp/channels/src/components/dot_menu/index.ts +++ b/webapp/channels/src/components/dot_menu/index.ts @@ -44,7 +44,6 @@ import {matchUserMentionTriggersWithMessageMentions} from 'utils/post_utils'; import {Post} from '@mattermost/types/posts'; import {setGlobalItem} from '../../actions/storage'; -import {getGlobalItem} from '../../selectors/storage'; import DotMenu from './dot_menu'; @@ -110,8 +109,6 @@ function makeMapStateToProps() { } } - const showForwardPostNewLabel = getGlobalItem(state, Preferences.FORWARD_POST_VIEWED, true); - return { channelIsArchived: isArchivedChannel(channel), components: state.plugins.components, @@ -129,7 +126,6 @@ function makeMapStateToProps() { isCollapsedThreadsEnabled: collapsedThreads, threadReplyCount, isMobileView: getIsMobileView(state), - showForwardPostNewLabel, timezone: getCurrentUserTimezone(state), isMilitaryTime, }; diff --git a/webapp/channels/src/components/post/index.tsx b/webapp/channels/src/components/post/index.tsx index 2bd3324d9f..39c664736f 100644 --- a/webapp/channels/src/components/post/index.tsx +++ b/webapp/channels/src/components/post/index.tsx @@ -4,7 +4,6 @@ import {connect, ConnectedProps} from 'react-redux'; import {AnyAction, bindActionCreators, Dispatch} from 'redux'; -import {showActionsDropdownPulsatingDot} from 'selectors/actions_menu'; import {setActionsMenuInitialisationState} from 'mattermost-redux/actions/preferences'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {getPost, makeGetCommentCountForPost, makeIsPostCommentMention, isPostAcknowledgementsEnabled, isPostPriorityEnabled, UserActivityPost} from 'mattermost-redux/selectors/entities/posts'; @@ -13,7 +12,6 @@ import { get, getBool, isCollapsedThreadsEnabled, - onboardingTourTipsEnabled, } from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentTeam, getCurrentTeamId, getTeam, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users'; @@ -129,8 +127,7 @@ function makeMapStateToProps() { const user = getUser(state, post.user_id); const isBot = Boolean(user && user.is_bot); const highlightedPostId = getHighlightedPostId(state); - const showActionsMenuPulsatingDot = showActionsDropdownPulsatingDot(state); - const tourTipsEnabled = onboardingTourTipsEnabled(state); + const selectedCard = getSelectedPostCard(state); 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, colorizeUsernames: get(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLORIZE_USERNAMES, Preferences.COLORIZE_USERNAMES_DEFAULT) === 'true', shouldShowActionsMenu: shouldShowActionsMenu(state, post), - showActionsMenuPulsatingDot, - tourTipsEnabled, + shortcutReactToLastPostEmittedFrom, isBot, collapsedThreadsEnabled: isCollapsedThreadsEnabled(state), diff --git a/webapp/channels/src/components/post/post_component.tsx b/webapp/channels/src/components/post/post_component.tsx index 2559cc56e1..907792dcd1 100644 --- a/webapp/channels/src/components/post/post_component.tsx +++ b/webapp/channels/src/components/post/post_component.tsx @@ -114,8 +114,6 @@ export type Props = { isPostAcknowledgementsEnabled: boolean; isPostPriorityEnabled: boolean; isCardOpen?: boolean; - shouldShowDotMenu: boolean; - tourTipsEnabled: boolean; }; const PostComponent = (props: Props): JSX.Element => { diff --git a/webapp/channels/src/components/post/post_options.tsx b/webapp/channels/src/components/post/post_options.tsx index b5e12801b8..d163c8c4bd 100644 --- a/webapp/channels/src/components/post/post_options.tsx +++ b/webapp/channels/src/components/post/post_options.tsx @@ -5,7 +5,7 @@ import React, {ReactNode, useEffect, useRef, useState} from 'react'; 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 {Locations} from 'utils/constants'; @@ -34,8 +34,6 @@ type Props = { handleDropdownOpened?: (e: boolean) => void; collapsedThreadsEnabled?: boolean; shouldShowActionsMenu?: boolean; - showActionsMenuPulsatingDot?: boolean; - tourTipsEnabled: boolean; oneClickReactionsEnabled?: boolean; recentEmojis: Emoji[]; isExpanded?: boolean; @@ -51,7 +49,6 @@ type Props = { shortcutReactToLastPostEmittedFrom?: string; isPostHeaderVisible?: boolean | null; isPostBeingEdited?: boolean; - shouldShowDotMenu: boolean; actions: { emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void; }; @@ -63,7 +60,6 @@ const PostOptions = (props: Props): JSX.Element => { const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [showDotMenu, setShowDotMenu] = useState(false); const [showActionsMenu, setShowActionsMenu] = useState(false); - const [showActionTip, setShowActionTip] = useState(false); useEffect(() => { if (props.isLastPost && @@ -80,8 +76,6 @@ const PostOptions = (props: Props): JSX.Element => { isReadOnly, post, oneClickReactionsEnabled, - showActionsMenuPulsatingDot, - tourTipsEnabled, isMobileView, } = props; @@ -102,34 +96,14 @@ const PostOptions = (props: Props): JSX.Element => { }; const handleActionsMenuOpened = (open: boolean) => { - if (tourTipsEnabled && showActionsMenuPulsatingDot) { - setShowActionTip(true); - return; - } setShowActionsMenu(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 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 || hoverLocal || (!post.root_id && Boolean(props.hasReplies)) || props.isFirstReply) && props.location === Locations.CENTER); @@ -197,11 +171,6 @@ const PostOptions = (props: Props): JSX.Element => { location={props.location} handleDropdownOpened={handleActionsMenuOpened} isMenuOpen={showActionsMenu} - showPulsatingDot={tourTipsEnabled && showActionsMenuPulsatingDot} - showTutorialTip={tourTipsEnabled && showActionTip} - handleOpenTip={handleActionsMenuTipOpened} - handleNextTip={handleActionsMenuGotItClick} - handleDismissTip={handleTipDismissed} /> ); const dotMenu = ( @@ -231,7 +200,7 @@ const PostOptions = (props: Props): JSX.Element => { ); - } else if (isPostDeleted || !props.shouldShowDotMenu) { + } else if (isPostDeleted) { options = null; } else if (props.location === Locations.SEARCH) { const hasCRTFooter = props.collapsedThreadsEnabled && !post.root_id && (post.reply_count > 0 || post.is_following); diff --git a/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx b/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx index b26051f723..4917f2fa79 100644 --- a/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx +++ b/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx @@ -27,7 +27,6 @@ type Props = { previousPostId: string; teamId: string; timestampProps?: Partial; - lastPost: Post; }; function noop() {} diff --git a/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx b/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx index 09fc561d00..4cafae3823 100644 --- a/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx +++ b/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx @@ -403,7 +403,6 @@ class ThreadViewerVirtualized extends PureComponent { previousPostId={getPreviousPostId(data, index)} teamId={this.props.teamId} timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined} - lastPost={this.props.lastPost} /> ); diff --git a/webapp/channels/src/selectors/views/custom_status.test.ts b/webapp/channels/src/selectors/views/custom_status.test.ts index fdbf13af94..8642fbc0ed 100644 --- a/webapp/channels/src/selectors/views/custom_status.test.ts +++ b/webapp/channels/src/selectors/views/custom_status.test.ts @@ -11,6 +11,7 @@ import {makeGetCustomStatus, getRecentCustomStatuses, isCustomStatusEnabled, sho import {TestHelper} from 'utils/test_helper'; 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/general'); @@ -86,6 +87,7 @@ describe('isCustomStatusEnabled', () => { }); describe('showStatusDropdownPulsatingDot and showPostHeaderUpdateStatusButton', () => { + const user = TestHelper.getUserMock(); const preference = { myPreference: { value: '', @@ -104,4 +106,31 @@ describe('showStatusDropdownPulsatingDot and showPostHeaderUpdateStatusButton', (PreferenceSelectors.get as jest.Mock).mockReturnValue(preference.myPreference.value); 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(); + }); }); diff --git a/webapp/channels/src/selectors/views/custom_status.ts b/webapp/channels/src/selectors/views/custom_status.ts index 80b2a93215..6d7742ccb4 100644 --- a/webapp/channels/src/selectors/views/custom_status.ts +++ b/webapp/channels/src/selectors/views/custom_status.ts @@ -12,6 +12,8 @@ import {get} from 'mattermost-redux/selectors/entities/preferences'; import {Preferences} from 'mattermost-redux/constants'; import {CustomStatusDuration, UserCustomStatus} from '@mattermost/types/users'; +import {isDateWithinDaysRange, TimeInformation} from 'utils/utils'; + import {GlobalState} from 'types/store'; import {getCurrentUserTimezone} from 'selectors/general'; import {getCurrentMomentForTimezone} from 'utils/timezone'; @@ -56,9 +58,12 @@ export function isCustomStatusEnabled(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 modalAlreadyViewed = customStatusTutorialState && JSON.parse(customStatusTutorialState)[Preferences.CUSTOM_STATUS_MODAL_VIEWED]; - return !modalAlreadyViewed; + return !modalAlreadyViewed && hasUserCreationMoreThanSevenDays; } export function showStatusDropdownPulsatingDot(state: GlobalState) { diff --git a/webapp/channels/src/utils/utils.tsx b/webapp/channels/src/utils/utils.tsx index 4cdc34e57c..9f71b304d5 100644 --- a/webapp/channels/src/utils/utils.tsx +++ b/webapp/channels/src/utils/utils.tsx @@ -90,6 +90,23 @@ const CLICKABLE_ELEMENTS = [ 'audio', '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; +export type TimeDirection = TimeInformation.FUTURE | TimeInformation.PAST; export function isMac() { return navigator.platform.toUpperCase().indexOf('MAC') >= 0; @@ -256,7 +273,6 @@ export function getTimestamp(): number { } export function getRemainingDaysFromFutureTimestamp(timestamp?: number): number { - const MS_PER_DAY = 24 * 60 * 60 * 1000; const futureDate = new Date(timestamp as number); const utcFuture = Date.UTC(futureDate.getFullYear(), futureDate.getMonth(), futureDate.getDate()); const today = new Date(); @@ -265,6 +281,39 @@ export function getRemainingDaysFromFutureTimestamp(timestamp?: number): number 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 = '') { if (!timestamp) { return moment.now();