Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new api command AddWorkspaceRoom #10863

Merged
merged 40 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8d51541
Add fake owner email constants
neil-marcellini Sep 7, 2022
f8bf291
Create addWorkspaceRoom action
neil-marcellini Sep 7, 2022
29e5449
Use addWorkspaceRoom
neil-marcellini Sep 7, 2022
126095f
Pass the selected policy to addWorkspaceRoom
neil-marcellini Sep 7, 2022
7795cac
Access the policy id properly
neil-marcellini Sep 7, 2022
ae97e15
Convert the workspaceRoom to json before sending
neil-marcellini Sep 7, 2022
e9fe6cf
Add the room's created action optimistically
neil-marcellini Sep 7, 2022
e0080da
Clear the created room pendingAction
neil-marcellini Sep 7, 2022
fc3f25a
Fix clearing the pending action under key 0
neil-marcellini Sep 7, 2022
0285d03
Hide the composer when pending add room or error
neil-marcellini Sep 8, 2022
6aee812
Remove duplicate conditional
neil-marcellini Sep 8, 2022
893fb26
OfflineWithFeedback for add workspace room
neil-marcellini Sep 12, 2022
1ac43d0
Only hide composer for an archived room or error
neil-marcellini Sep 12, 2022
59d991a
Wrap created message in OfflineWithFeedback
neil-marcellini Sep 12, 2022
a565376
Remove OfflineWithFeedback around report contents
neil-marcellini Sep 12, 2022
2fe7dce
Remove actorEmail from created report actions
neil-marcellini Sep 12, 2022
3e48e25
Fix room created pendingAction
neil-marcellini Sep 13, 2022
5384829
Add room offline feedback for composer
neil-marcellini Sep 13, 2022
70bc06e
Merge branch 'main' into neil-AddWorkspaceRoom
neil-marcellini Sep 13, 2022
9bf1106
Use updated function names
neil-marcellini Sep 13, 2022
a1157f9
Fix pendingAction default type
neil-marcellini Sep 13, 2022
edf0158
Room participants from current user and employees
neil-marcellini Sep 13, 2022
ab70b5c
Use Onyx.set so the room exists after navigating
neil-marcellini Sep 14, 2022
f540685
Update comment to explain error case
neil-marcellini Sep 14, 2022
d16b08d
Set optimistic report notification pref to daily
neil-marcellini Sep 15, 2022
6237de8
Pass individual params to AddWorkspaceRoom
neil-marcellini Sep 15, 2022
d8b1efd
Show add workspace room error messages
neil-marcellini Sep 15, 2022
14ab87c
Simplify hiding the composer
neil-marcellini Sep 16, 2022
e5fb0d7
Horizontal padding only on add room error row
neil-marcellini Sep 16, 2022
44d4667
Wrap archived footer in chat footer style again
neil-marcellini Sep 16, 2022
877b45a
Use shouldShowComposeInput again
neil-marcellini Sep 16, 2022
bb6527d
Handle dismissing an add workspace room error
neil-marcellini Sep 16, 2022
ee989bf
Gray out the room when adding it fails
neil-marcellini Sep 16, 2022
2c175af
Fix large screen hidden composer offline indicator
neil-marcellini Sep 16, 2022
f54314f
Space for the hidden composer offline indicator
neil-marcellini Sep 16, 2022
3a9fd3c
Show workspace rooms that failed to add in LHN
neil-marcellini Sep 16, 2022
6066771
Merge branch 'main' into neil-AddWorkspaceRoom
neil-marcellini Sep 19, 2022
41dc249
Remove unnecessary comment
neil-marcellini Sep 19, 2022
069a4c8
Remove unnecessary variable
neil-marcellini Sep 19, 2022
b6f4319
Use backend policyReport naming vs workspaceRoom
neil-marcellini Sep 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ const CONST = {
MAX_PREVIEW_AVATARS: 4,
MAX_ROOM_NAME_LENGTH: 80,
LAST_MESSAGE_TEXT_MAX_LENGTH: 80,
OWNER_EMAIL_FAKE: '__FAKE__',
},
COMPOSER: {
MAX_LINES: 16,
Expand Down Expand Up @@ -677,6 +678,7 @@ const CONST = {
},
ROOM_PREFIX: '#',
CUSTOM_UNIT_RATE_BASE_OFFSET: 100,
OWNER_EMAIL_FAKE: '_FAKE_',
},

CUSTOM_UNITS: {
Expand Down
6 changes: 5 additions & 1 deletion src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,16 @@ function getOptions(reports, personalDetails, activeReportID, {
: '';

const reportContainsIOUDebt = iouReportOwner && iouReportOwner !== currentUserLogin;
const hasAddWorkspaceRoomError = report.errorFields && !_.isEmpty(report.errorFields.addWorkspaceRoom);
const shouldFilterReportIfEmpty = !showReportsWithNoComments && report.lastMessageTimestamp === 0

// We make exceptions for defaultRooms and policyExpenseChats so we can immediately
// highlight them in the LHN when they are created and have no messsages yet. We do
// not give archived rooms this exception since they do not need to be higlihted.
&& !(!ReportUtils.isArchivedRoom(report) && (isDefaultRoom || isPolicyExpenseChat));
&& !(!ReportUtils.isArchivedRoom(report) && (isDefaultRoom || isPolicyExpenseChat))

// Also make an exception for workspace rooms that failed to be added
&& !hasAddWorkspaceRoomError;
Comment on lines 492 to +500
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: this is definitely a complex and difficult to understand condition, I wonder if there are any ways to simplify it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's pretty ugly. It would help to break it out into several descriptive boolean variables. I think @tgolen might be cleaning that up with his LHN cleanup issues? I'm going to leave it as is for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, this logic has actually been moved here in main

const hasAddWorkspaceRoomError = report.errorFields && !_.isEmpty(report.errorFields.addWorkspaceRoom);
const shouldFilterReportIfEmpty = report.lastMessageTimestamp === 0
// We make exceptions for defaultRooms and policyExpenseChats so we can immediately
// highlight them in the LHN when they are created and have no messsages yet. We do
// not give archived rooms this exception since they do not need to be higlihted.
&& !(!ReportUtils.isArchivedRoom(report) && (isDefaultRoom || isPolicyExpenseChat))
// Also make an exception for workspace rooms that failed to be added
&& !hasAddWorkspaceRoomError;

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, let me take a try at cleaning up this logic 👍 I created a GH for it here: https://github.com/Expensify/Expensify/issues/229633


const shouldFilterReportIfRead = hideReadReports && !ReportUtils.isUnread(report);
const shouldFilterReport = shouldFilterReportIfEmpty || shouldFilterReportIfRead;
Expand Down
98 changes: 93 additions & 5 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,16 +593,18 @@ function fetchAllReports(
* @param {String} ownerEmail
* @param {Boolean} isOwnPolicyExpenseChat
* @param {String} oldPolicyName
* @param {String} visibility
* @returns {Object}
*/
function buildOptimisticChatReport(
participantList,
reportName = 'Chat Report',
chatType = '',
policyID = '_FAKE_',
ownerEmail = '__FAKE__',
policyID = CONST.POLICY.OWNER_EMAIL_FAKE,
ownerEmail = CONST.REPORT.OWNER_EMAIL_FAKE,
isOwnPolicyExpenseChat = false,
oldPolicyName = '',
visibility = undefined,
) {
return {
chatType,
Expand All @@ -616,7 +618,7 @@ function buildOptimisticChatReport(
lastMessageTimestamp: 0,
lastVisitedTimestamp: 0,
maxSequenceNumber: 0,
notificationPreference: '',
notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY,
oldPolicyName,
ownerEmail,
participants: participantList,
Expand All @@ -625,7 +627,7 @@ function buildOptimisticChatReport(
reportName,
stateNum: 0,
statusNum: 0,
visibility: undefined,
visibility,
};
}

Expand All @@ -639,7 +641,6 @@ function buildOptimisticCreatedReportAction(ownerEmail) {
0: {
actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
actorEmail: currentUserEmail,
actorAccountID: currentUserAccountID,
message: [
{
Expand Down Expand Up @@ -1449,6 +1450,91 @@ function createPolicyRoom(policyID, reportName, visibility) {
.finally(() => Onyx.set(ONYXKEYS.IS_LOADING_CREATE_POLICY_ROOM, false));
}

/**
* Add a policy report (workspace room) optimistically and navigate to it.
*
* @param {Object} policy
* @param {String} reportName
* @param {String} visibility
*/
function addPolicyReport(policy, reportName, visibility) {
// The participants include the current user (admin) and the employees. Participants must not be empty.
const participants = [currentUserEmail, ...policy.employeeList];
const policyReport = buildOptimisticChatReport(
participants,
reportName,
CONST.REPORT.CHAT_TYPE.POLICY_ROOM,
policy.id,
CONST.REPORT.OWNER_EMAIL_FAKE,
false,
'',
visibility,
);

// Onyx.set is used on the optimistic data so that it is present before navigating to the workspace room. With Onyx.merge the workspace room reportID is not present when
// storeCurrentlyViewedReport is called on the ReportScreen, so fetchChatReportsByIDs is called which is unnecessary since the optimistic data will be stored in Onyx.
// If there was an error creating the room, then fetchChatReportsByIDs throws an error and the user is navigated away from the report instead of showing the RBR error message.
// Therefore, Onyx.set is used instead of Onyx.merge.
const optimisticData = [
{
onyxMethod: CONST.ONYX.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.REPORT}${policyReport.reportID}`,
value: {
pendingFields: {
addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
...policyReport,
},
},
{
onyxMethod: CONST.ONYX.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyReport.reportID}`,
value: buildOptimisticCreatedReportAction(policyReport.ownerEmail),
},
];
const successData = [
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${policyReport.reportID}`,
value: {
pendingFields: {
addWorkspaceRoom: null,
},
},
},
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyReport.reportID}`,
value: {
0: {
pendingAction: null,
},
},
},
];

API.write(
'AddWorkspaceRoom',
{
policyID: policyReport.policyID,
reportName,
visibility,
reportID: policyReport.reportID,
},
{optimisticData, successData},
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming from #20502 - it would have been best to add a failureData here, otherwise if the backend fails to send a proper onyxUpdate in case of an error, the NewDot client won't know that the room wasn't created.

);
Navigation.navigate(ROUTES.getReportRoute(policyReport.reportID));
}

/**
* @param {Number} reportID The reportID of the policy report (workspace room)
*/
function navigateToConciergeChatAndDeletePolicyReport(reportID) {
navigateToConciergeChat();
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null);
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, null);
}

/**
* @param {Object} policyRoomReport
* @param {Number} policyRoomReport.reportID
Expand Down Expand Up @@ -1659,6 +1745,8 @@ export {
handleInaccessibleReport,
setReportWithDraft,
createPolicyRoom,
addPolicyReport,
navigateToConciergeChatAndDeletePolicyReport,
setIsComposerFullSize,
markCommentAsUnread,
readNewestAction,
Expand Down
74 changes: 49 additions & 25 deletions src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import addViewportResizeListener from '../../libs/VisualViewport';
import {withNetwork} from '../../components/OnyxProvider';
import compose from '../../libs/compose';
import networkPropTypes from '../../components/networkPropTypes';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions';
import OfflineIndicator from '../../components/OfflineIndicator';
import OfflineWithFeedback from '../../components/OfflineWithFeedback';
import withDrawerState, {withDrawerPropTypes} from '../../components/withDrawerState';

const propTypes = {
Expand Down Expand Up @@ -82,6 +85,7 @@ const propTypes = {
/** Information about the network */
network: networkPropTypes.isRequired,

...windowDimensionsPropTypes,
...withDrawerPropTypes,
};

Expand Down Expand Up @@ -225,16 +229,24 @@ class ReportScreen extends React.Component {
if (isArchivedRoom) {
reportClosedAction = lodashFindLast(this.props.reportActions, action => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED);
}
const addWorkspaceRoomPendingAction = lodashGet(this.props.report, 'pendingFields.addWorkspaceRoom');
const addWorkspaceRoomErrors = lodashGet(this.props.report, 'errorFields.addWorkspaceRoom');
const hideComposer = isArchivedRoom || !_.isEmpty(addWorkspaceRoomErrors);
return (
<ScreenWrapper
style={[styles.appContent, styles.flex1, {marginTop: this.state.viewportOffsetTop}]}
keyboardAvoidingViewBehavior={Platform.OS === 'android' ? '' : 'padding'}
>
<HeaderView
reportID={reportID}
onNavigationMenuButtonClicked={() => Navigation.navigate(ROUTES.HOME)}
/>

<OfflineWithFeedback
pendingAction={addWorkspaceRoomPendingAction}
errors={addWorkspaceRoomErrors}
errorRowStyles={styles.dNone}
>
<HeaderView
reportID={reportID}
onNavigationMenuButtonClicked={() => Navigation.navigate(ROUTES.HOME)}
/>
</OfflineWithFeedback>
<View
nativeID={CONST.REPORT.DROP_NATIVE_ID}
style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
Expand All @@ -255,27 +267,38 @@ class ReportScreen extends React.Component {
isDrawerOpen={this.props.isDrawerOpen}
/>
)}
{(isArchivedRoom || this.props.session.shouldShowComposeInput) && (
{(isArchivedRoom || hideComposer) && (
<View style={[styles.chatFooter]}>
{isArchivedRoom && (
<ArchivedReportFooter
reportClosedAction={reportClosedAction}
report={this.props.report}
/>
)}
{!this.props.isSmallScreenWidth && (
<View style={styles.offlineIndicatorRow}>
{hideComposer && (
<OfflineIndicator containerStyles={[styles.chatItemComposeSecondaryRow]} />
)}
</View>
)}
</View>
)}
{(!hideComposer && this.props.session.shouldShowComposeInput) && (
<View style={[this.setChatFooterStyles(this.props.network.isOffline), this.props.isComposerFullSize && styles.chatFooterFullCompose]}>
{
isArchivedRoom
? (
<ArchivedReportFooter
reportClosedAction={reportClosedAction}
report={this.props.report}
/>
) : (
<SwipeableView onSwipeDown={Keyboard.dismiss}>
<ReportActionCompose
onSubmit={this.onSubmitComment}
reportID={reportID}
reportActions={this.props.reportActions}
report={this.props.report}
isComposerFullSize={this.props.isComposerFullSize}
/>
</SwipeableView>
)
}
<SwipeableView onSwipeDown={Keyboard.dismiss}>
<OfflineWithFeedback
pendingAction={addWorkspaceRoomPendingAction}
>
<ReportActionCompose
onSubmit={this.onSubmitComment}
reportID={reportID}
reportActions={this.props.reportActions}
report={this.props.report}
isComposerFullSize={this.props.isComposerFullSize}
/>
</OfflineWithFeedback>
</SwipeableView>
</View>
)}
</View>
Expand All @@ -288,6 +311,7 @@ ReportScreen.propTypes = propTypes;
ReportScreen.defaultProps = defaultProps;

export default compose(
withWindowDimensions,
withDrawerState,
withNetwork(),
withOnyx({
Expand Down
45 changes: 29 additions & 16 deletions src/pages/home/report/ReportActionItemCreated.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import {Pressable, View} from 'react-native';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import ONYXKEYS from '../../../ONYXKEYS';
Expand All @@ -8,10 +9,15 @@ import ReportWelcomeText from '../../../components/ReportWelcomeText';
import participantPropTypes from '../../../components/participantPropTypes';
import * as ReportUtils from '../../../libs/ReportUtils';
import styles from '../../../styles/styles';
import OfflineWithFeedback from '../../../components/OfflineWithFeedback';
import * as Report from '../../../libs/actions/Report';

const propTypes = {
/** The report currently being looked at */
report: PropTypes.shape({
/** The id of the report */
reportID: PropTypes.number,

/** Avatars corresponding to a chat */
icons: PropTypes.arrayOf(PropTypes.string),

Expand All @@ -38,24 +44,31 @@ const ReportActionItemCreated = (props) => {
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies);
return (
<View
accessibilityLabel="Chat welcome message"
style={[
styles.chatContent,
styles.pb8,
styles.p5,
]}
<OfflineWithFeedback
pendingAction={lodashGet(props.report, 'pendingFields.addWorkspaceRoom')}
errors={lodashGet(props.report, 'errorFields.addWorkspaceRoom')}
errorRowStyles={styles.addWorkspaceRoomErrorRow}
onClose={() => Report.navigateToConciergeChatAndDeletePolicyReport(props.report.reportID)}
Copy link
Contributor

Choose a reason for hiding this comment

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

any chance report could be null here? I'm personally not sure but to be safe let's use lodashGet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

report will always be non-null because of withOnyx. It will not render the component until the report is read https://github.com/Expensify/react-native-onyx/blob/bac8d308cb2eaf971174837e30a1f27f6b54c71e/lib/withOnyx.js#L159-L161

Copy link
Contributor

Choose a reason for hiding this comment

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

There might still be something to be careful of here though.

It will not render the component until the report is read

While this is true, it does not guarantee that a report is returned. It could read the report from Onyx, find nothing there, then pass null as a prop. In fact, you see that report is not a required prop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh yeah, good point. report is a required prop of ReportActionList, which renders the action items and in turn this component, so I think it's safe. ReportActionList also accesses props.report.reportID without any checks.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, but just calling it "safe" really isn't a good place to leave this. You should mark the report prop as required. Because, it is required, and if a report wasn't provided to props, then this code would crash.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh right, yes. My bad. I will create a follow up PR for that.

>
<View style={[styles.justifyContentCenter, styles.alignItemsCenter, styles.flex1]}>
<Pressable onPress={() => ReportUtils.navigateToDetailsPage(props.report)}>
<RoomHeaderAvatars
icons={icons}
shouldShowLargeAvatars={isPolicyExpenseChat}
/>
</Pressable>
<ReportWelcomeText report={props.report} />
<View
accessibilityLabel="Chat welcome message"
style={[
styles.chatContent,
styles.pb8,
styles.p5,
]}
>
<View style={[styles.justifyContentCenter, styles.alignItemsCenter, styles.flex1]}>
<Pressable onPress={() => ReportUtils.navigateToDetailsPage(props.report)}>
<RoomHeaderAvatars
icons={icons}
shouldShowLargeAvatars={isPolicyExpenseChat}
/>
</Pressable>
<ReportWelcomeText report={props.report} />
</View>
</View>
</View>
</OfflineWithFeedback>
);
};

Expand Down
Loading