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

🍒 Cherry pick PR #19094 to staging 🍒 #19466

Merged
merged 2 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1001031700
versionName "1.3.17-0"
versionCode 1001031701
versionName "1.3.17-1"
}

splits {
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.3.17.0</string>
<string>1.3.17.1</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensifyTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.3.17.0</string>
<string>1.3.17.1</string>
</dict>
</plist>
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
"version": "1.3.17-0",
"version": "1.3.17-1",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
5 changes: 5 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,11 @@ const CONST = {
DAILY: 'daily',
ALWAYS: 'always',
},
// Options for which room members can post
WRITE_CAPABILITIES: {
ALL: 'all',
ADMINS: 'admins',
},
VISIBILITY: {
PUBLIC: 'public',
PUBLIC_ANNOUNCE: 'public_announce',
Expand Down
2 changes: 2 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ export default {
REPORT_SETTINGS: 'r/:reportID/settings',
REPORT_SETTINGS_ROOM_NAME: 'r/:reportID/settings/room-name',
REPORT_SETTINGS_NOTIFICATION_PREFERENCES: 'r/:reportID/settings/notification-preferences',
REPORT_SETTINGS_WRITE_CAPABILITY: 'r/:reportID/settings/who-can-post',
getReportSettingsRoute: (reportID) => `r/${reportID}/settings`,
getReportSettingsRoomNameRoute: (reportID) => `r/${reportID}/settings/room-name`,
getReportSettingsNotificationPreferencesRoute: (reportID) => `r/${reportID}/settings/notification-preferences`,
getReportSettingsWriteCapabilityRoute: (reportID) => `r/${reportID}/settings/who-can-post`,
TRANSITION_FROM_OLD_DOT: 'transition',
VALIDATE_LOGIN: 'v/:accountID/:validateCode',
GET_ASSISTANCE: 'get-assistance/:taskID',
Expand Down
7 changes: 7 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,13 @@ export default {
`This workspace chat is no longer active because ${displayName} is no longer a member of the ${policyName} workspace.`,
[CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}) => `This workspace chat is no longer active because ${policyName} is no longer an active workspace.`,
},
writeCapabilityPage: {
label: 'Who can post',
writeCapability: {
all: 'All members',
admins: 'Admins only',
},
},
sidebarScreen: {
fabAction: 'New chat',
newChat: 'New chat',
Expand Down
7 changes: 7 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,13 @@ export default {
`Este chat de espacio de trabajo esta desactivado porque ${displayName} ha dejado de ser miembro del espacio de trabajo ${policyName}.`,
[CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}) => `Este chat de espacio de trabajo esta desactivado porque el espacio de trabajo ${policyName} se ha eliminado.`,
},
writeCapabilityPage: {
label: 'Quién puede postear',
writeCapability: {
all: 'Todos los miembros',
admins: 'Solo administradores',
},
},
sidebarScreen: {
fabAction: 'Nuevo chat',
newChat: 'Nuevo chat',
Expand Down
7 changes: 7 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ const ReportSettingsModalStackNavigator = createModalStackNavigator([
},
name: 'Report_Settings_Notification_Preferences',
},
{
getComponent: () => {
const WriteCapabilityPage = require('../../../pages/settings/Report/WriteCapabilityPage').default;
return WriteCapabilityPage;
},
name: 'Report_Settings_Write_Capability',
},
]);

const TaskModalStackNavigator = createModalStackNavigator([
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ export default {
Report_Settings_Notification_Preferences: {
path: ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES,
},
Report_Settings_Write_Capability: {
path: ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY,
},
},
},
NewGroup: {
Expand Down
21 changes: 21 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,26 @@ function getPolicyName(report) {
return policy.name || report.oldPolicyName || Localize.translateLocal('workspace.common.unavailable');
}

/**
* Checks if the current user is allowed to comment on the given report.
* @param {Object} report
* @param {String} [report.writeCapability]
* @returns {Boolean}
*/
function isAllowedToComment(report) {
// Default to allowing all users to post
const capability = lodashGet(report, 'writeCapability', CONST.REPORT.WRITE_CAPABILITIES.ALL) || CONST.REPORT.WRITE_CAPABILITIES.ALL;

if (capability === CONST.REPORT.WRITE_CAPABILITIES.ALL) {
return true;
}

// If we've made it here, commenting on this report is restricted.
// If the user is an admin, allow them to post.
const policy = allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`];
return lodashGet(policy, 'role', '') === CONST.POLICY.ROLE.ADMIN;
}

/**
* Checks if the current user is the admin of the policy given the policy expense chat.
* @param {Object} report
Expand Down Expand Up @@ -2303,5 +2323,6 @@ export {
shouldReportShowSubscript,
isReportDataReady,
isSettled,
isAllowedToComment,
getMoneyRequestAction,
};
30 changes: 30 additions & 0 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,35 @@ function updateNotificationPreferenceAndNavigate(reportID, previousValue, newVal
Navigation.drawerGoBack(ROUTES.getReportSettingsRoute(reportID));
}

/**
* @param {Object} report
* @param {String} newValue
*/
function updateWriteCapabilityAndNavigate(report, newValue) {
if (report.writeCapability === newValue) {
Navigation.drawerGoBack(ROUTES.getReportSettingsRoute(report.reportID));
return;
}

const optimisticData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`,
value: {writeCapability: newValue},
},
];
const failureData = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`,
value: {writeCapability: report.writeCapability},
},
];
API.write('UpdateReportWriteCapability', {reportID: report.reportID, writeCapability: newValue}, {optimisticData, failureData});
// Return to the report settings page since this field utilizes push-to-page
Navigation.drawerGoBack(ROUTES.getReportSettingsRoute(report.reportID));
}

/**
* Navigates to the 1:1 report with Concierge
*/
Expand Down Expand Up @@ -1639,6 +1668,7 @@ export {
addComment,
addAttachment,
reconnect,
updateWriteCapabilityAndNavigate,
updateNotificationPreferenceAndNavigate,
subscribeToReportTypingEvents,
unsubscribeFromReportChannel,
Expand Down
1 change: 1 addition & 0 deletions src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ class ReportScreen extends React.Component {
report={this.props.report}
isComposerFullSize={this.props.isComposerFullSize}
onSubmitComment={this.onSubmitComment}
policies={this.props.policies}
/>
</>
)}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/home/report/ReportFooter.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ class ReportFooter extends React.Component {

render() {
const isArchivedRoom = ReportUtils.isArchivedRoom(this.props.report);
const hideComposer = isArchivedRoom || !_.isEmpty(this.props.errors);
const isAllowedToComment = ReportUtils.isAllowedToComment(this.props.report);
const hideComposer = isArchivedRoom || !_.isEmpty(this.props.errors) || !isAllowedToComment;

return (
<>
Expand Down
3 changes: 3 additions & 0 deletions src/pages/reportPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,7 @@ export default PropTypes.shape({

/** The status of the current report */
statusNum: PropTypes.oneOf(_.values(CONST.REPORT.STATUS)),

/** Which user role is capable of posting messages on the report */
writeCapability: PropTypes.oneOf(_.values(CONST.REPORT.WRITE_CAPABILITIES)),
});
26 changes: 26 additions & 0 deletions src/pages/settings/Report/ReportSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class ReportSettingsPage extends Component {
const linkedWorkspace = _.find(this.props.policies, (policy) => policy && policy.id === this.props.report.policyID);
const shouldDisableRename = this.shouldDisableRename(linkedWorkspace) || ReportUtils.isThread(this.props.report);
const notificationPreference = this.props.translate(`notificationPreferencesPage.notificationPreferences.${this.props.report.notificationPreference}`);
const writeCapability = this.props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL;
const writeCapabilityText = this.props.translate(`writeCapabilityPage.writeCapability.${writeCapability}`);
const shouldAllowWriteCapabilityEditing = lodashGet(linkedWorkspace, 'role', '') === CONST.POLICY.ROLE.ADMIN;

return (
<ScreenWrapper>
Expand Down Expand Up @@ -131,6 +134,29 @@ class ReportSettingsPage extends Component {
)}
</OfflineWithFeedback>
)}
{shouldAllowWriteCapabilityEditing ? (
<MenuItemWithTopDescription
shouldShowRightIcon
title={writeCapabilityText}
description={this.props.translate('writeCapabilityPage.label')}
onPress={() => Navigation.navigate(ROUTES.getReportSettingsWriteCapabilityRoute(this.props.report.reportID))}
/>
) : (
<View style={[styles.ph5, styles.pv3]}>
<Text
style={[styles.textLabelSupporting, styles.lh16, styles.mb1]}
numberOfLines={1}
>
{this.props.translate('writeCapabilityPage.label')}
</Text>
<Text
numberOfLines={1}
style={[styles.optionAlternateText, styles.pre]}
>
{writeCapabilityText}
</Text>
</View>
)}
<View style={[styles.ph5]}>
{Boolean(linkedWorkspace) && (
<View style={[styles.pv3]}>
Expand Down
67 changes: 67 additions & 0 deletions src/pages/settings/Report/WriteCapabilityPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import _ from 'underscore';
import CONST from '../../../CONST';
import ScreenWrapper from '../../../components/ScreenWrapper';
import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import styles from '../../../styles/styles';
import OptionsList from '../../../components/OptionsList';
import Navigation from '../../../libs/Navigation/Navigation';
import compose from '../../../libs/compose';
import withReportOrNotFound from '../../home/report/withReportOrNotFound';
import reportPropTypes from '../../reportPropTypes';
import ROUTES from '../../../ROUTES';
import * as Report from '../../../libs/actions/Report';
import * as Expensicons from '../../../components/Icon/Expensicons';
import themeColors from '../../../styles/themes/default';

const propTypes = {
...withLocalizePropTypes,

/** The report for which we are setting write capability */
report: reportPropTypes.isRequired,
};
const greenCheckmark = {src: Expensicons.Checkmark, color: themeColors.success};

const WriteCapabilityPage = (props) => {
const writeCapabilityOptions = _.map(CONST.REPORT.WRITE_CAPABILITIES, (value) => ({
value,
text: props.translate(`writeCapabilityPage.writeCapability.${value}`),
keyForList: value,

// Include the green checkmark icon to indicate the currently selected value
customIcon: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL) ? greenCheckmark : null,

// This property will make the currently selected value have bold text
boldStyle: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL),
}));

return (
<ScreenWrapper includeSafeAreaPaddingBottom={false}>
<HeaderWithCloseButton
title={props.translate('writeCapabilityPage.label')}
shouldShowBackButton
onBackButtonPress={() => Navigation.navigate(ROUTES.getReportSettingsRoute(props.report.reportID))}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<OptionsList
sections={[{data: writeCapabilityOptions}]}
onSelectRow={(option) => Report.updateWriteCapabilityAndNavigate(props.report, option.value)}
hideSectionHeaders
optionHoveredStyle={{
...styles.hoveredComponentBG,
...styles.mhn5,
...styles.ph5,
}}
shouldHaveOptionSeparator
shouldDisableRowInnerPadding
contentContainerStyles={[styles.ph5]}
/>
</ScreenWrapper>
);
};

WriteCapabilityPage.displayName = 'WriteCapabilityPage';
WriteCapabilityPage.propTypes = propTypes;

export default compose(withLocalize, withReportOrNotFound)(WriteCapabilityPage);