Skip to content

Commit

Permalink
Custom status expiry (mattermost#8059)
Browse files Browse the repository at this point in the history
* Made the UI for expiry
Made expiry menu component with all the expiry items
Made the dateTimeInput component with the react-day-picker along with customized navbar
Removed the memoization from the getCustomStatus selector
Solved the propType issue in the menuItemToggleModalRedux

* Review fixes and added timepicker menu
Changed the custom status selector to a simple function and changed its use everywhere
Made constants for the expiry menu items and used them
Changed the expiry menu items to use localized text
Removed the daypicker.scss file and did the changes in _daypicker.scss file
Added all the localization ids in en.json

* Completed the timepicker menu
Made a util for getting current date and time for timezone
Made the function to round current time to nearest 30 min time
Made the function to fill the menu with 30 minute intervals
Unified the separate state variables for day and time to only time
Made the dateTimeInput component to be controlled by the custom status modal
Fixed the styles for the timepicker and daypicker
Fixed lint errors and type check errors

* Fixed some issues in daypicker
Fixed the styling in daypicker
Added logic for disabled days in daypicker
Fixed the issue of prevMonth button not working in daypicker

* Complete expiry support in custom status modal
Changed the custom status type to include duration and expires_at and made enum duration
Removed the duration constants from constants file and used enum type everywhere
Added localization in the expiry menu values
Modified custom status modal to send expiry with the custom status
Modified custom status modal to handle initial custom expiry time
Modified the disable set status variable to handle the confirm button disability
Modified the custom status selector to return custom status only when it's not expired

* Review fixes
Code refactoring in custom status modal and dateTime input and expiry menu

* Changed name of enum Duration to CustomStatusDuration

* MI-1235

* Added expiry support in custom status suggestions
Made durationValues constant to store all duration values with localization ids and default messages
Modified expiry_menu to use durationValues constant
Modified custom status suggestion component to handle duration of status
Added duration in default custom status suggestions
Fixed the calculateExpiryTime function to take right currentTime
Modified handle and clear suggestion functions to include expiry

* Display expiry time in custom status emoji tooltip
Made relative range for tomorrow
Fixed styling for the expiry in suggestions
Added the logic for showing expiry time in custom status emoji component

* Completed showing expiry time in several places
Made custom status util for displaying expiry time
Made getCurrentUserTimezone memoized selector
Refactored custom status emoji and modal to use getCurrentUserTimezone selector
Fixed the custom status modal UI accordingg to the comments on community
Added expiry time in status dropdown and profile popover

* Review fixes
Memoized displayExpiryTime in custom status emoji component

* Added memoization in expiry time in some places
Added memoization in status dropdown and profile popover
Added logic to show tooltip in custom status emoji in channel header
Refactored some code in custom status emoji and custom status selector
Added timezone dependent current time in custom status selector

* Added unit tests and several fixes
Sorted the order of imports in several files
Made unit tests for date_time_input and expiry_menu components
Updated some snapshots

* Fixed custom status emoji unit tests

* Fix types

* Refactored some code
Converted displayExpiryTime util to a separate functional component
Replaced use of util with component everywhere
Fixed styling of date time input
Refactored disabling set status button in custom status modal

* Fixed styles in status dropdown menu

* Fixed types and added some unit tests
Fixed type in custom status emoji tests
Added unit tests in status dropdown and profile popover
Updated snapshots

* Code refactoring and localization ids change
Added localization in Until and changed ids of all expiry dropdown options
Modified ExpiryTime component to accept classname and wihinBrackets prop
Fixed disableSetStatus bug in custom status modal
Fixed styling of expiry time everywhere
Modified status dropdown, profile_popover and custom status emoji according to new ExpiryTime component

* Updated snapshots

* Changed default custom status duration to Today

* Fixed i18n-check failing test

* Fixed failing unit and e2e tests
Refactoed getCustomStatus selector

* Added functionality to handle suggestions without duration

* Review UI fixes
Added font-weight and opacity in expiry value in clear after dropdown
Fixed the styling for clear button in the custom status input
Fixed the padding for the custom status modal and adjusted all components inside it
Changed the opacity for duration in the suggestions
Changed the styling for date and time input titles
Changed the size of the daypicker
Changed the date and time icons from font-awesome to compass icons

* Trigger server creation

* Fixed the timezone issue in date time input

* WIP: Fixing timezone issues for custom status expiry

* Fixed the timezone issue in the custom status modal and date/time input
Made util for getCurrentMomentForTimezone
Changed type for customExpiryTime from Date to Moment
Fixed getRoundedTime and getTimeInIntervals functions and date/time inputs

* Changed value of DONT_CLEAR key of CustomStatusDuration enum

* Changed behavior of Clear after component and showing date/time component and review fixes (mattermost#57)

* WIP : Fixing the flow for the custom date time if status already set

* Changed display value of Clear after value and review fixes
Added prop showPrefix in expiry_time component
Changed the behaviour of timestamp in the expiry_time
Moved custom status constants from constants.jsx to custom_status_modal and date_time_input
Changed the display value in Clear after component if custom status set with custom date/time by adding additional value in CustomStatusDuration enum
Changed the logic of disabling Set Status button and showing/hiding suggestions
Fixed the failing unit tests of expiry_menu and date_time_input
Added localization in date_time_input using useIntl instead of localizeMessage
Added showing expiryTime feature in expiry menu component

* Review fixes with some optimization
Memoized the getCustomStatus selector and made a new selector isCustomStatusExpired for the expiry
Changed the use of getCustomStatus selector in all places
Added the use of isCustomStatusExpired selector in custom_status_emoji, custom_status_modal, status_dropdown, profile_popover, channel_header components
Memoized the time options in the date/time input container by memoizing the handleTimeChange function
Memoized the menu items in expiry menu by keeping them in state and only modifying in componentDidMount
Changed the type of timestampProps and added logic for showing time for tomorrow in expiry_time component
Fixed the memoization in the getCurrentUserTimezone selector by creating getTimezoneForUserProfile function

* Added/Updated unit tests and fixed type check errors
Changed the format of expiry time for periods longer than 6 days
Fixed the styling of custom status in status dropdown and daypicker in custom status modal

* Complete e2e tests (mattermost#58)

* Merge branch 'master' of github.com:mattermost/mattermost-webapp into custom-status-expiry
Resolved conflicts, updated snapshots and fixed type check errors

* WIP : MM-T4063 e2e test for the custom status expiry

* Complete e2e tests for expiry
Modified and completed MM-T4063 test
Completed MM-T4064, MM-T4065, MM-T4066 e2e tests

* Updated snapshot

* Updated custom_status_emoji to pass the tests

* Fixed suggestion without duration bug in the custom status modal

* Code refactoring and some bug fixes

* Updated snapshots

* UI review fixes (mattermost#61)

Changed the text for Date and time to Custom Date and Time
Removed duration from the suggestions if duration is Don't clear
Changed the width of the suggestions dynamically based on the presence of duration
Added a unit test for the suggestion and updated the existing snapshot

* Resolved conflicts and updated snapshots

* Apply suggestions from code review

Co-authored-by: Guillermo Vayá <guivaya@gmail.com>

* Added feature to show year in expiry time if expiry time is after the current year

* Review fixes (mattermost#62)

* Review fixes
Fixed the memoization issue for multiple instances of class components by converting mapStateToProps to makeMapStateToProps
Memoized the handleTimeChange function in the menu items of time picker by binding the function

* Fixed memoization of makeGetCustomStatus in functional components
Fixed memoization in custom_status_emoji, modal and post_header_custom_status components

* Refactored some code

* Fixed type check errors

Co-authored-by: Chetanya Kandhari <chetanya.kandhari@brightscout.com>
Co-authored-by: Guillermo Vayá <guivaya@gmail.com>
(cherry picked from commit 182f462)
  • Loading branch information
manojmalik20 authored and mattermost-build committed Jun 21, 2021
1 parent f8356d1 commit 04451df
Show file tree
Hide file tree
Showing 46 changed files with 2,919 additions and 186 deletions.
2 changes: 1 addition & 1 deletion actions/emoji_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function addRecentEmoji(alias) {
}

export function loadCustomEmojisForCustomStatusesByUserIds(userIds) {
const getCustomStatus = makeGetCustomStatus();
return (dispatch, getState) => {
const state = getState();
const customEmojiEnabled = isCustomEmojiEnabled(state);
Expand All @@ -85,7 +86,6 @@ export function loadCustomEmojisForCustomStatusesByUserIds(userIds) {
return {data: false};
}

const getCustomStatus = makeGetCustomStatus();
const emojisToLoad = new Set();

userIds.forEach((userId) => {
Expand Down
215 changes: 213 additions & 2 deletions components/channel_header/__snapshots__/channel_header.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,217 @@ exports[`components/ChannelHeader should render not active channel files 1`] = `
</div>
`;

exports[`components/ChannelHeader should render properly when custom status is expired 1`] = `
<div
aria-label="channel header region"
className="channel-header alt a11y__region"
data-a11y-sort-order="8"
data-channelid="undefined"
id="channel-header"
role="banner"
tabIndex="-1"
>
<div
className="flex-parent"
>
<div
className="flex-child"
>
<div
className="channel-header__info"
id="channelHeaderInfo"
>
<div
className="channel-header__title dropdown"
>
<div>
<MenuWrapper
animationComponent={[Function]}
className=""
onToggle={[Function]}
>
<div
className="channel-header__top"
id="channelHeaderDropdownButton"
>
<button
aria-label="channel menu"
className="channel-header__trigger style--none "
>
<strong
aria-level="2"
className="heading"
id="channelHeaderTitle"
role="heading"
>
<span>
<ArchiveIcon
className="icon icon__archive icon channel-header-archived-icon svg-text-color"
/>
<FormattedMessage
defaultMessage="{displayname} (you) "
id="channel_header.directchannel.you"
values={
Object {
"displayname": undefined,
}
}
/>
<GuestBadge
className=""
show={false}
/>
</span>
</strong>
<span
aria-label="dropdown icon"
className="icon icon-chevron-down header-dropdown-chevron-icon"
id="channelHeaderDropdownIcon"
/>
</button>
</div>
<ChannelHeaderDropdown />
</MenuWrapper>
</div>
</div>
<div
className="channel-header__description"
dir="auto"
id="channelHeaderDescription"
>
<StatusIcon
button={false}
className=""
status="offline"
/>
<span
className="header-status__text"
>
<FormattedMessage
defaultMessage="Offline"
id="status_dropdown.set_offline"
/>
</span>
<HeaderIconWrapper
ariaLabel={true}
buttonClass="channel-header__icon channel-header__icon--wide channel-header__icon--left"
buttonId="channelHeaderPinButton"
iconComponent={
<i
aria-hidden="true"
className="icon icon-pin-outline channel-header__pin"
/>
}
onClick={[Function]}
tooltipKey="pinnedPosts"
/>
<HeaderIconWrapper
ariaLabel={true}
buttonClass="channel-header__icon channel-header__icon--wide channel-header__icon--left"
buttonId="channelHeaderFilesButton"
iconComponent={
<i
className="icon icon-file-document-outline"
/>
}
onClick={[Function]}
tooltipKey="channelFiles"
/>
<div
className="header-popover-text-measurer"
>
<Connect(Markdown)
message="not the bot description"
options={
Object {
"atMentions": true,
"channelNamesMap": undefined,
"mentionHighlight": false,
"singleline": true,
}
}
/>
</div>
<span
className="header-description__text"
onClick={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<Overlay
animation={[Function]}
onEnter={[Function]}
onHide={[Function]}
placement="bottom"
rootClose={true}
show={false}
target={null}
>
<Popover
className="channel-header__popover chanel-header__popover--new_sidebar"
id="header-popover"
placement="bottom"
popoverSize="lg"
popoverStyle="info"
style={
Object {
"maxWidth": "0px",
"transform": "translate(0px, 0px)",
}
}
>
<span
onClick={[Function]}
>
<Connect(Markdown)
message="not the bot description"
options={
Object {
"atMentions": true,
"channelNamesMap": undefined,
"mentionHighlight": false,
"singleline": false,
}
}
/>
</span>
</Popover>
</Overlay>
<Connect(Markdown)
message="not the bot description"
options={
Object {
"atMentions": true,
"channelNamesMap": undefined,
"mentionHighlight": false,
"singleline": true,
}
}
/>
</span>
</div>
</div>
</div>
<Connect(injectIntl(ChannelHeaderPlug))
channel={
Object {
"header": "not the bot description",
"status": "offline",
"type": "D",
}
}
channelMember={
Object {
"channel_id": "channel_id",
"user_id": "user_id",
}
}
/>
<Connect(RHSSearchNav) />
</div>
</div>
`;

exports[`components/ChannelHeader should render properly when custom status is set 1`] = `
<div
aria-label="channel header region"
Expand Down Expand Up @@ -1335,9 +1546,9 @@ exports[`components/ChannelHeader should render properly when custom status is s
"verticalAlign": "top",
}
}
showTooltip={false}
showTooltip={true}
spanStyle={Object {}}
tooltipDirection="top"
tooltipDirection="bottom"
userID="user_id"
/>
<CustomStatusText
Expand Down
9 changes: 6 additions & 3 deletions components/channel_header/channel_header.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ChannelHeader extends React.PureComponent {
announcementBarCount: PropTypes.number,
customStatus: PropTypes.object,
isCustomStatusEnabled: PropTypes.bool.isRequired,
isCustomStatusExpired: PropTypes.bool.isRequired,
};

constructor(props) {
Expand Down Expand Up @@ -220,16 +221,18 @@ class ChannelHeader extends React.PureComponent {
handleFormattedTextClick = (e) => Utils.handleFormattedTextClick(e, this.props.currentRelativeTeamUrl);

renderCustomStatus = () => {
const {customStatus} = this.props;
const isStatusSet = customStatus && (customStatus.text || customStatus.emoji);
if (!(this.props.isCustomStatusEnabled && isStatusSet)) {
const {customStatus, isCustomStatusEnabled, isCustomStatusExpired} = this.props;
const isStatusSet = !isCustomStatusExpired && (customStatus?.text || customStatus?.emoji);
if (!(isCustomStatusEnabled && isStatusSet)) {
return null;
}

return (
<>
<CustomStatusEmoji
userID={this.props.dmUser.id}
showTooltip={true}
tooltipDirection='bottom'
emojiStyle={{
verticalAlign: 'top',
margin: '0 4px 1px',
Expand Down
27 changes: 27 additions & 0 deletions components/channel_header/channel_header.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('components/ChannelHeader', () => {
teammateNameDisplaySetting: '',
currentRelativeTeamUrl: '',
isCustomStatusEnabled: false,
isCustomStatusExpired: false,
};

const populatedProps = {
Expand Down Expand Up @@ -309,4 +310,30 @@ describe('components/ChannelHeader', () => {
);
expect(wrapper).toMatchSnapshot();
});

test('should render properly when custom status is expired', () => {
const props = {
...populatedProps,
channel: {
header: 'not the bot description',
type: Constants.DM_CHANNEL,
status: 'offline',
},
dmUser: {
id: 'user_id',
is_bot: false,
},
isCustomStatusEnabled: true,
isCustomStatusExpired: true,
customStatus: {
emoji: 'calender',
text: 'In a meeting',
},
};

const wrapper = shallowWithIntl(
<ChannelHeader {...props}/>,
);
expect(wrapper).toMatchSnapshot();
});
});
3 changes: 2 additions & 1 deletion components/channel_header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
showMentions,
closeRightHandSide,
} from 'actions/views/rhs';
import {makeGetCustomStatus, isCustomStatusEnabled} from 'selectors/views/custom_status';
import {makeGetCustomStatus, isCustomStatusEnabled, isCustomStatusExpired} from 'selectors/views/custom_status';
import {getIsRhsOpen, getRhsState} from 'selectors/rhs';
import {isModalOpen} from 'selectors/views/modals';
import {getAnnouncementBarCount} from 'selectors/views/announcement_bar';
Expand Down Expand Up @@ -92,6 +92,7 @@ function makeMapStateToProps() {
announcementBarCount: getAnnouncementBarCount(state),
customStatus,
isCustomStatusEnabled: isCustomStatusEnabled(state),
isCustomStatusExpired: isCustomStatusExpired(state, customStatus),
};
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,34 @@ exports[`components/custom_status/custom_status_emoji should match snapshot 1`]
/>
</div>
`;

exports[`components/custom_status/custom_status_emoji should match snapshot with duration 1`] = `
<div
className="statusSuggestion__row cursor--pointer"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<div
className="statusSuggestion__icon"
>
<Memo(RenderEmoji)
emojiName=""
size={20}
/>
</div>
<CustomStatusText
className="statusSuggestion__text with_duration"
text=""
tooltipDirection="top"
/>
<span
className="statusSuggestion__duration"
>
<FormattedMessage
defaultMessage="Today"
id="custom_status.expiry_dropdown.today"
/>
</span>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`components/custom_status/date_time_input should match snapshot 1`] = `
<DateTimeInputContainer
handleChange={[MockFunction]}
time={"2021-05-03T14:53:39.127Z"}
timezone="Australia/Sydney"
/>
`;
Loading

0 comments on commit 04451df

Please sign in to comment.