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 UpdatePolicyRoomName api command #10237

Merged
merged 36 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
162ef4e
Create updatePolicyRoomName for api refactor
neil-marcellini Aug 3, 2022
0e38b5a
Use reportName param for WAF
neil-marcellini Aug 8, 2022
303a0a3
Use new updatePolicyRoomName command
neil-marcellini Aug 8, 2022
f265e7e
Add offline feedback / errors for policy room name
neil-marcellini Aug 9, 2022
124f8f5
Merge branch 'main' into neil-UpdatePolicyRoomName
neil-marcellini Aug 9, 2022
15fbbf8
Use rbr pending action update constant
neil-marcellini Aug 9, 2022
e657bd8
Fix JSDoc param type
neil-marcellini Aug 9, 2022
2fd2a55
Pass policyRoomName to UpdatePolicyRoomName
neil-marcellini Aug 9, 2022
7bdd021
Merge branch 'main' into neil-UpdatePolicyRoomName
neil-marcellini Aug 11, 2022
1847e5a
Check report errors and errorFields also
neil-marcellini Aug 11, 2022
f4a7b45
Add brick road indicator to header view
neil-marcellini Aug 11, 2022
f952838
Create hasReportNameError
neil-marcellini Aug 11, 2022
a77f503
Use hasReportNameError in HeaderView
neil-marcellini Aug 11, 2022
b7ad9f8
Fix hasReportNameError
neil-marcellini Aug 11, 2022
5070cbd
Allow empty string brickRoadIndicator
neil-marcellini Aug 11, 2022
20fdbdb
Brick road indicator on report details settings
neil-marcellini Aug 11, 2022
b388315
Use empty string for empty brickRoadIndicator
neil-marcellini Aug 11, 2022
292c524
Allow the error message to use the full row
neil-marcellini Aug 11, 2022
b493390
Name the onClose callback for what it does
neil-marcellini Aug 11, 2022
837f5db
Remove redundant comments
neil-marcellini Aug 11, 2022
45fa867
Remove deprecated rename report
neil-marcellini Aug 11, 2022
2a0683e
Remove policy room renamed growl
neil-marcellini Aug 11, 2022
3693e27
Rename to match api command name
neil-marcellini Aug 11, 2022
1bfae69
Merge branch 'main' into neil-UpdatePolicyRoomName
neil-marcellini Aug 11, 2022
acb83ab
Simplify brickRoadIndicator comment
neil-marcellini Aug 12, 2022
d33ef6a
Rename error vars that are specific to the report
neil-marcellini Aug 12, 2022
e4cbb45
Add a key to menu items for brick road indicator
neil-marcellini Aug 12, 2022
2eea06a
Remove unused props
neil-marcellini Aug 12, 2022
2a3c71f
Make default brickRoadIndicator match prop type
neil-marcellini Aug 12, 2022
924afb9
Clear reportName errors optimistically
neil-marcellini Aug 12, 2022
e0497af
Set roomNameInputRef inline for simplicity
neil-marcellini Aug 12, 2022
04cd7b4
Fix resetting the room name input on native
neil-marcellini Aug 12, 2022
06dd5d8
Use minimum vertical space for RBR error text
neil-marcellini Aug 12, 2022
eddd6ce
Add accessed report fields to JSDoc
neil-marcellini Aug 15, 2022
106ad15
Use constants for report details menu items
neil-marcellini Aug 15, 2022
4c150ff
Use constant
neil-marcellini Aug 15, 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
6 changes: 3 additions & 3 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const defaultProps = {
onPress: () => {},
interactive: true,
fallbackIcon: Expensicons.FallbackAvatar,
brickRoadIndicator: undefined,
brickRoadIndicator: null,
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
};

const MenuItem = props => (
Expand Down Expand Up @@ -132,11 +132,11 @@ const MenuItem = props => (
</Text>
</View>
)}
{props.brickRoadIndicator && (
{Boolean(props.brickRoadIndicator) && (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Treat this as a boolean in case it's an empty string.

<View style={[styles.alignItemsCenter, styles.justifyContentCenter]}>
<Icon
src={Expensicons.DotIndicator}
fill={props.brickRoadIndicator === 'error' ? colors.red : colors.green}
fill={props.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR ? colors.red : colors.green}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
/>
Expand Down
10 changes: 9 additions & 1 deletion src/components/RoomNameInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const propTypes = {
/** ID of the policy */
id: PropTypes.string,
}).isRequired,

/** A ref forwarded to the TextInput */
forwardedRef: PropTypes.func,
};

const defaultProps = {
Expand All @@ -51,6 +54,7 @@ const defaultProps = {
disabled: false,
errorText: '',
...fullPolicyDefaultProps,
forwardedRef: () => {},
};

class RoomNameInput extends Component {
Expand Down Expand Up @@ -91,6 +95,7 @@ class RoomNameInput extends Component {
render() {
return (
<TextInput
ref={this.props.forwardedRef}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a ref so the input value can be cleared from the parent.

disabled={this.props.disabled}
label={this.props.translate('newRoomPage.roomName')}
prefixCharacter={CONST.POLICY.ROOM_PREFIX}
Expand Down Expand Up @@ -118,4 +123,7 @@ export default compose(
key: ONYXKEYS.COLLECTION.POLICY,
},
}),
)(RoomNameInput);
)(React.forwardRef((props, ref) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<RoomNameInput {...props} forwardedRef={ref} />
)));
6 changes: 3 additions & 3 deletions src/components/menuItemPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ const propTypes = {
/** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
fallbackIcon: PropTypes.func,

/** If we need to show a brick road indicator or not. For now only value allowed is `error`,
* but we will add `success` later for manual requests */
brickRoadIndicator: PropTypes.oneOf(['error']),
/** If we need to show a brick road indicator or not. For now only value allowed is 'error' or '',
* but we will add 'success' later for manual requests */
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
brickRoadIndicator: PropTypes.oneOf([CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, '']),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The empty string is the empty value so that functions that get the brick road indicator property can always return a string.

};

export default propTypes;
14 changes: 11 additions & 3 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,26 @@ function hasReportDraftComment(report, reportsWithDraft = {}) {
}

/**
* If the report or the report actions have errors, return
* CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, otherwise an empty string.
*
* @param {Object} report
* @param {Object} reportActions
* @returns {String}
*/
function getBrickRoadIndicatorStatusForReport(report, reportActions) {
const errors = lodashGet(report, 'errors', {});
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
const errorFields = lodashGet(report, 'errorFields', {});
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
const reportID = lodashGet(report, 'reportID');
const reportsActions = lodashGet(reportActions, `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {});
if (_.isEmpty(reportsActions)) {

const hasFieldErrors = _.some(errorFields, fieldErrors => !_.isEmpty(fieldErrors));
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
const hasReportActionErrors = _.some(reportsActions, action => !_.isEmpty(action.errors));

if (_.isEmpty(errors) && !hasFieldErrors && !hasReportActionErrors) {
return '';
}

return _.find(reportsActions, action => !_.isEmpty(action.errors)) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
return CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
}

/**
Expand Down
9 changes: 9 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,14 @@ function generateReportID() {
return Math.floor(Math.random() * (Number.MAX_SAFE_INTEGER - 98000000)) + 98000000;
}

/**
* @param {Object} report
* @returns {Boolean}
*/
function hasReportNameError(report) {
return !_.isEmpty(lodashGet(report, 'errorFields.reportName', {}));
}

export {
getReportParticipantsTitle,
isReportMessageAttachment,
Expand Down Expand Up @@ -552,4 +560,5 @@ export {
getReportName,
navigateToDetailsPage,
generateReportID,
hasReportNameError,
};
64 changes: 64 additions & 0 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,68 @@ function renameReport(reportID, reportName) {
.finally(() => Onyx.set(ONYXKEYS.IS_LOADING_RENAME_POLICY_ROOM, false));
}

/**
* Update the policy room name.
*
* @param {Object} policyRoomReport
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
* @param {String} policyRoomName The updated name for the policy room
*/
function updatePolicyRoomName(policyRoomReport, policyRoomName) {
const reportID = policyRoomReport.reportID;
const previousName = policyRoomReport.reportName;
const optimisticData = [
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
reportName: policyRoomName,
pendingFields: {
reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
];
const successData = [
{

onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
pendingFields: {
reportName: null,
},
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
},
},
];
const failureData = [
{

onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
reportName: previousName,
},
},
];
API.write('UpdatePolicyRoomName', {reportID, policyRoomName}, {optimisticData, successData, failureData});
}

/**
* Clear policy room name errors.
*
* @param {Number} reportID The reportID of the policy room.
*/
function clearPolicyRoomNameErrors(reportID) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
errorFields: {
reportName: null,
},
pendingFields: {
reportName: null,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm gonna guess that we are setting the reportName to null instead of the errorFields and pendingFields to null because we might have other report errors shown and we don't want to clear them all? Let me know if that sounds right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that's exactly it.

});
}

/**
* @param {Number} reportID
* @param {Boolean} isComposerFullSize
Expand Down Expand Up @@ -1555,4 +1617,6 @@ export {
openReport,
openPaymentDetailsPage,
createOptimisticReport,
updatePolicyRoomName,
clearPolicyRoomNameErrors,
};
8 changes: 8 additions & 0 deletions src/pages/ReportDetailsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as Expensicons from '../components/Icon/Expensicons';
import ROUTES from '../ROUTES';
import MenuItem from '../components/MenuItem';
import Text from '../components/Text';
import CONST from '../CONST';

const propTypes = {
...withLocalizePropTypes,
Expand Down Expand Up @@ -156,6 +157,12 @@ class ReportDetailsPage extends Component {
</View>
{_.map(this.menuItems, (item) => {
const keyTitle = item.translationKey ? this.props.translate(item.translationKey) : item.title;
const brickRoadIndicator = (
ReportUtils.hasReportNameError(this.props.report)
&& item.translationKey === 'common.settings'
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
)
? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR
: '';
return (
<MenuItem
key={keyTitle}
Expand All @@ -166,6 +173,7 @@ class ReportDetailsPage extends Component {
iconStyles={item.iconStyles}
iconFill={item.iconFill}
shouldShowRightIcon
brickRoadIndicator={brickRoadIndicator}
/>
);
})}
Expand Down
108 changes: 72 additions & 36 deletions src/pages/ReportSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import {View, ScrollView} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import CONST from '../CONST';
import ONYXKEYS from '../ONYXKEYS';
import styles from '../styles/styles';
Expand All @@ -20,6 +21,7 @@ import Picker from '../components/Picker';
import withFullPolicy, {fullPolicyDefaultProps, fullPolicyPropTypes} from './workspace/withFullPolicy';
import * as ValidationUtils from '../libs/ValidationUtils';
import Growl from '../libs/Growl';
import OfflineWithFeedback from '../components/OfflineWithFeedback';

const propTypes = {
/** Route params */
Expand Down Expand Up @@ -107,14 +109,41 @@ class ReportSettingsPage extends Component {
},
};

this.roomNameInputRef = null;

this.state = {
newRoomName: this.props.report.reportName,
errors: {},
};

this.setRoomNameInputRef = this.setRoomNameInputRef.bind(this);
this.resetToPreviousName = this.resetToPreviousName.bind(this);
this.validateAndRenameReport = this.validateAndRenameReport.bind(this);
}

/**
* Set the room name input ref
*
* @param {Element} el
*/
setRoomNameInputRef(el) {
this.roomNameInputRef = el;
}
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved

/**
* When the user dismisses the error updating the policy room name,
* reset the report name to the previously saved name and clear errors.
*/
resetToPreviousName() {
this.setState({newRoomName: this.props.report.reportName});

// Reset the input's value back to the previously saved report name
if (this.roomNameInputRef) {
this.roomNameInputRef.value = this.props.report.reportName.replace(CONST.POLICY.ROOM_PREFIX, '');
neil-marcellini marked this conversation as resolved.
Show resolved Hide resolved
}
Report.clearPolicyRoomNameErrors(this.props.report.reportID);
}

validateAndRenameReport() {
if (!this.validate()) {
return;
Expand All @@ -123,7 +152,7 @@ class ReportSettingsPage extends Component {
Growl.success(this.props.translate('newRoomPage.policyRoomRenamed'));
return;
}
Report.renameReport(this.props.report.reportID, this.state.newRoomName);
Report.updatePolicyRoomName(this.props.report, this.state.newRoomName);
}

validate() {
Expand Down Expand Up @@ -195,42 +224,49 @@ class ReportSettingsPage extends Component {
</View>
{shouldShowRoomName && (
<View style={styles.mt4}>
<View style={[styles.flexRow]}>
<View style={[styles.flex3]}>
{shouldDisableRename ? (
<View>
<Text style={[styles.textLabelSupporting, styles.lh16, styles.mb1]} numberOfLines={1}>
{this.props.translate('newRoomPage.roomName')}
</Text>
<Text numberOfLines={1} style={[styles.optionAlternateText]}>
{this.state.newRoomName}
</Text>
</View>
)
: (
<RoomNameInput
initialValue={this.state.newRoomName}
policyID={linkedWorkspace && linkedWorkspace.id}
errorText={this.state.errors.newRoomName}
onChangeText={newRoomName => this.clearErrorAndSetValue('newRoomName', newRoomName)}
disabled={shouldDisableRename}
/>
)}
<OfflineWithFeedback
pendingAction={lodashGet(this.props.report, 'pendingFields.reportName', null)}
errors={lodashGet(this.props.report, 'errorFields.reportName', null)}
onClose={this.resetToPreviousName}
>
<View style={[styles.flexRow]}>
<View style={[styles.flex3]}>
{shouldDisableRename ? (
<View>
<Text style={[styles.textLabelSupporting, styles.lh16, styles.mb1]} numberOfLines={1}>
{this.props.translate('newRoomPage.roomName')}
</Text>
<Text numberOfLines={1} style={[styles.optionAlternateText]}>
{this.state.newRoomName}
</Text>
</View>
)
: (
<RoomNameInput
ref={this.setRoomNameInputRef}
initialValue={this.state.newRoomName}
policyID={linkedWorkspace && linkedWorkspace.id}
errorText={this.state.errors.newRoomName}
onChangeText={newRoomName => this.clearErrorAndSetValue('newRoomName', newRoomName)}
disabled={shouldDisableRename}
/>
)}
</View>
{!shouldDisableRename && (
<Button
large
success={!shouldDisableRename}
text={this.props.translate('common.save')}
onPress={this.validateAndRenameReport}
style={[styles.ml2, styles.flex1]}
textStyles={[styles.label]}
innerStyles={[styles.ph5]}
isLoading={this.props.isLoadingRenamePolicyRoom}
isDisabled={shouldDisableRename}
/>
)}
</View>
{!shouldDisableRename && (
<Button
large
success={!shouldDisableRename}
text={this.props.translate('common.save')}
onPress={this.validateAndRenameReport}
style={[styles.ml2, styles.flex1]}
textStyles={[styles.label]}
innerStyles={[styles.ph5]}
isLoading={this.props.isLoadingRenamePolicyRoom}
isDisabled={shouldDisableRename}
/>
)}
</View>
</OfflineWithFeedback>
</View>
)}
{linkedWorkspace && (
Expand Down
Loading