Skip to content

Commit 607f8e9

Browse files
authored
Merge pull request #10452 from Expensify/ionatan_DeleteMembersFromWorkspace
Migrate policy API to remove policy members
2 parents de77cc4 + fea93ab commit 607f8e9

File tree

13 files changed

+175
-189
lines changed

13 files changed

+175
-189
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"react-native-image-picker": "^4.8.5",
9595
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972",
9696
"react-native-modal": "^13.0.0",
97-
"react-native-onyx": "1.0.15",
97+
"react-native-onyx": "1.0.17",
9898
"react-native-pdf": "^6.6.2",
9999
"react-native-performance": "^2.0.0",
100100
"react-native-permissions": "^3.0.1",

src/components/BlockingViews/FullPageNotFoundView.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,30 @@ const propTypes = {
1818

1919
/** If true, child components are replaced with a blocking "not found" view */
2020
shouldShow: PropTypes.bool,
21+
22+
/** The key in the translations file to use for the title */
23+
titleKey: PropTypes.string,
24+
25+
/** The key in the translations file to use for the subtitle */
26+
subtitleKey: PropTypes.string,
27+
28+
/** Whether we should show a back icon */
29+
shouldShowBackButton: PropTypes.bool,
30+
31+
/** Whether we should show a close button */
32+
shouldShowCloseButton: PropTypes.bool,
33+
34+
/** Method to trigger when pressing the back button of the header */
35+
onBackButtonPress: PropTypes.func,
2136
};
2237

2338
const defaultProps = {
2439
shouldShow: false,
40+
titleKey: 'notFound.notHere',
41+
subtitleKey: 'notFound.pageNotFound',
42+
shouldShowBackButton: true,
43+
shouldShowCloseButton: true,
44+
onBackButtonPress: () => Navigation.dismissModal(),
2545
};
2646

2747
// eslint-disable-next-line rulesdir/no-negated-variables
@@ -30,15 +50,16 @@ const FullPageNotFoundView = (props) => {
3050
return (
3151
<>
3252
<HeaderWithCloseButton
33-
shouldShowBackButton
34-
onBackButtonPress={() => Navigation.dismissModal()}
53+
shouldShowBackButton={props.shouldShowBackButton}
54+
shouldShowCloseButton={props.shouldShowCloseButton}
55+
onBackButtonPress={props.onBackButtonPress}
3556
onCloseButtonPress={() => Navigation.dismissModal()}
3657
/>
3758
<View style={styles.flex1}>
3859
<BlockingView
3960
icon={Expensicons.QuestionMark}
40-
title={props.translate('notFound.notHere')}
41-
subtitle={props.translate('notFound.pageNotFound')}
61+
title={props.translate(props.titleKey)}
62+
subtitle={props.translate(props.subtitleKey)}
4263
/>
4364
</View>
4465
</>

src/languages/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ export default {
525525
iouReportNotFound: 'The payment details you are looking for cannot be found.',
526526
notHere: "Hmm... it's not here",
527527
pageNotFound: 'That page is nowhere to be found.',
528+
noAccess: 'You don\'t have access to this chat',
528529
},
529530
setPasswordPage: {
530531
enterPassword: 'Enter a password',
@@ -822,6 +823,7 @@ export default {
822823
error: {
823824
genericAdd: 'There was a problem adding this workspace member.',
824825
cannotRemove: 'You cannot remove yourself or the workspace owner.',
826+
genericRemove: 'There was a problem removing that workspace member.',
825827
},
826828
},
827829
card: {

src/languages/es.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ export default {
525525
iouReportNotFound: 'Los detalles del pago que estás buscando no se pudieron encontrar.',
526526
notHere: 'Hmm… no está aquí',
527527
pageNotFound: 'La página que buscas no existe.',
528+
noAccess: 'No tienes acceso a este chat',
528529
},
529530
setPasswordPage: {
530531
enterPassword: 'Escribe una contraseña',
@@ -824,6 +825,7 @@ export default {
824825
error: {
825826
genericAdd: 'Ha ocurrido un problema al agregar el miembro al espacio de trabajo',
826827
cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.',
828+
genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.',
827829
},
828830
},
829831
card: {

src/libs/ActiveClientManager/index.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/**
2+
* When you have many tabs in one browser, the data of Onyx is shared between all of them. Since we persist write requests in Onyx, we need to ensure that
3+
* only one tab is processing those saved requests or we would be duplicating data (or creating errors).
4+
* This file ensures exactly that by tracking all the clientIDs connected, storing the most recent one last and it considers that last clientID the "leader".
5+
*/
6+
17
import _ from 'underscore';
28
import Onyx from 'react-native-onyx';
39
import Str from 'expensify-common/lib/str';
@@ -6,38 +12,47 @@ import * as ActiveClients from '../actions/ActiveClients';
612

713
const clientID = Str.guid();
814
const maxClients = 20;
9-
10-
let activeClients;
11-
12-
let resolveIsReadyPromise;
13-
const isReadyPromise = new Promise((resolve) => {
14-
resolveIsReadyPromise = resolve;
15+
let activeClients = [];
16+
let resolveSavedSelfPromise;
17+
const savedSelfPromise = new Promise((resolve) => {
18+
resolveSavedSelfPromise = resolve;
1519
});
1620

1721
/**
22+
* Determines when the client is ready. We need to wait both till we saved our ID in onyx AND the init method was called
1823
* @returns {Promise}
1924
*/
2025
function isReady() {
21-
return isReadyPromise;
26+
return savedSelfPromise;
2227
}
2328

2429
Onyx.connect({
2530
key: ONYXKEYS.ACTIVE_CLIENTS,
2631
callback: (val) => {
27-
activeClients = !val ? [] : val;
28-
if (activeClients.length >= maxClients) {
32+
activeClients = val;
33+
34+
// Remove from the beginning of the list any clients that are past the limit, to avoid having thousands of them
35+
let removed = false;
36+
while (activeClients.length >= maxClients) {
2937
activeClients.shift();
38+
removed = true;
39+
}
40+
41+
// Save the clients back to onyx, if they changed
42+
if (removed) {
3043
ActiveClients.setActiveClients(activeClients);
3144
}
3245
},
3346
});
3447

3548
/**
36-
* Add our client ID to the list of active IDs
49+
* Add our client ID to the list of active IDs.
50+
* We want to ensure we have no duplicates and that the activeClient gets added at the end of the array (see isClientTheLeader)
3751
*/
3852
function init() {
39-
ActiveClients.addClient(clientID)
40-
.then(resolveIsReadyPromise);
53+
activeClients = _.without(activeClients, clientID);
54+
activeClients.push(clientID);
55+
ActiveClients.setActiveClients(activeClients).then(resolveSavedSelfPromise);
4156
}
4257

4358
/**

src/libs/Navigation/AppNavigator/AuthScreens.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import KeyboardShortcut from '../../KeyboardShortcut';
2020
import Navigation from '../Navigation';
2121
import * as User from '../../actions/User';
2222
import * as Modal from '../../actions/Modal';
23-
import * as Policy from '../../actions/Policy';
2423
import modalCardStyleInterpolator from './modalCardStyleInterpolator';
2524
import createCustomModalStackNavigator from './createCustomModalStackNavigator';
2625

@@ -100,7 +99,6 @@ class AuthScreens extends React.Component {
10099
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
101100
}).then(() => {
102101
User.subscribeToUserEvents();
103-
Policy.subscribeToPolicyEvents();
104102
});
105103

106104
// Listen for report changes and fetch some data we need on initialization

src/libs/actions/ActiveClients.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,13 @@ import ONYXKEYS from '../../ONYXKEYS';
33

44
/**
55
* @param {Array} activeClients
6+
* @return {Promise}
67
*/
78
function setActiveClients(activeClients) {
8-
Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients);
9-
}
10-
11-
/**
12-
* @param {Number} clientID
13-
* @returns {Promise}
14-
*/
15-
function addClient(clientID) {
16-
return Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]);
9+
return Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients);
1710
}
1811

1912
export {
13+
// eslint-disable-next-line import/prefer-default-export
2014
setActiveClients,
21-
addClient,
2215
};

src/libs/actions/Policy.js

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@ import Str from 'expensify-common/lib/str';
66
import * as DeprecatedAPI from '../deprecatedAPI';
77
import * as API from '../API';
88
import ONYXKEYS from '../../ONYXKEYS';
9-
import Growl from '../Growl';
10-
import CONFIG from '../../CONFIG';
119
import CONST from '../../CONST';
1210
import * as Localize from '../Localize';
1311
import Navigation from '../Navigation/Navigation';
1412
import ROUTES from '../../ROUTES';
1513
import * as OptionsListUtils from '../OptionsListUtils';
16-
import * as Report from './Report';
17-
import * as Pusher from '../Pusher/pusher';
1814
import DateUtils from '../DateUtils';
1915
import * as ReportUtils from '../ReportUtils';
2016

@@ -209,29 +205,21 @@ function removeMembers(members, policyID) {
209205
if (members.length === 0) {
210206
return;
211207
}
212-
213-
const employeeListUpdate = {};
214-
_.each(members, login => employeeListUpdate[login] = null);
215-
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, employeeListUpdate);
216-
217-
// Make the API call to remove a login from the policy
218-
DeprecatedAPI.Policy_Employees_Remove({
208+
const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`;
209+
const optimisticData = [{
210+
onyxMethod: CONST.ONYX.METHOD.MERGE,
211+
key: membersListKey,
212+
value: _.object(members, Array(members.length).fill({pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE})),
213+
}];
214+
const failureData = [{
215+
onyxMethod: CONST.ONYX.METHOD.MERGE,
216+
key: membersListKey,
217+
value: _.object(members, Array(members.length).fill({errors: {[DateUtils.getMicroseconds()]: Localize.translateLocal('workspace.people.error.genericRemove')}})),
218+
}];
219+
API.write('DeleteMembersFromWorkspace', {
219220
emailList: members.join(','),
220221
policyID,
221-
})
222-
.then((data) => {
223-
if (data.jsonCode === 200) {
224-
return;
225-
}
226-
227-
// Rollback removal on failure
228-
_.each(members, login => employeeListUpdate[login] = {});
229-
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, employeeListUpdate);
230-
231-
// Show the user feedback that the removal failed
232-
const errorMessage = data.jsonCode === 666 ? data.message : Localize.translateLocal('workspace.people.genericFailureMessage');
233-
Growl.show(errorMessage, CONST.GROWL.ERROR, 5000);
234-
});
222+
}, {optimisticData, failureData});
235223
}
236224

237225
/**
@@ -653,31 +641,6 @@ function updateLastAccessedWorkspace(policyID) {
653641
Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID);
654642
}
655643

656-
/**
657-
* Subscribe to public-policyEditor-[policyID] events.
658-
*/
659-
function subscribeToPolicyEvents() {
660-
_.each(allPolicies, (policy) => {
661-
const pusherChannelName = `public-policyEditor-${policy.id}${CONFIG.PUSHER.SUFFIX}`;
662-
Pusher.subscribe(pusherChannelName, 'policyEmployeeRemoved', ({removedEmails, policyExpenseChatIDs, defaultRoomChatIDs}) => {
663-
// Refetch the policy expense chats to update their state and their actions to get the archive reason
664-
if (!_.isEmpty(policyExpenseChatIDs)) {
665-
Report.fetchChatReportsByIDs(policyExpenseChatIDs);
666-
_.each(policyExpenseChatIDs, (reportID) => {
667-
Report.reconnect(reportID);
668-
});
669-
}
670-
671-
// Remove the default chats if we are one of the users getting removed
672-
if (removedEmails.includes(sessionEmail) && !_.isEmpty(defaultRoomChatIDs)) {
673-
_.each(defaultRoomChatIDs, (chatID) => {
674-
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatID}`, null);
675-
});
676-
}
677-
});
678-
});
679-
}
680-
681644
/**
682645
* Removes an error after trying to delete a member
683646
*
@@ -974,7 +937,6 @@ export {
974937
updateWorkspaceCustomUnit,
975938
updateCustomUnitRate,
976939
updateLastAccessedWorkspace,
977-
subscribeToPolicyEvents,
978940
clearDeleteMemberError,
979941
clearAddMemberError,
980942
clearDeleteWorkspaceError,

src/libs/actions/Report.js

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,6 @@ function fetchChatReportsByIDs(chatList, shouldRedirectIfInaccessible = false) {
331331
// Fetch the personal details if there are any
332332
PersonalDetails.getFromReportParticipants(_.values(simplifiedReports));
333333
return simplifiedReports;
334-
})
335-
.catch((err) => {
336-
if (err.message !== CONST.REPORT.ERROR.INACCESSIBLE_REPORT) {
337-
return;
338-
}
339-
340-
// eslint-disable-next-line no-use-before-define
341-
handleInaccessibleReport();
342334
});
343335
}
344336

@@ -1086,6 +1078,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) {
10861078
isEdited: true,
10871079
html: htmlForNewComment,
10881080
text: textForNewComment,
1081+
type: originalReportAction.message[0].type,
10891082
}],
10901083
},
10911084
};
@@ -1212,14 +1205,6 @@ function navigateToConciergeChat() {
12121205
Navigation.navigate(ROUTES.getReportRoute(conciergeChatReportID));
12131206
}
12141207

1215-
/**
1216-
* Handle the navigation when report is inaccessible
1217-
*/
1218-
function handleInaccessibleReport() {
1219-
Growl.error(Localize.translateLocal('notFound.chatYouLookingForCannotBeFound'));
1220-
navigateToConciergeChat();
1221-
}
1222-
12231208
/**
12241209
* Creates a policy room, fetches it, and navigates to it.
12251210
* @param {String} policyID
@@ -1544,7 +1529,6 @@ export {
15441529
getSimplifiedIOUReport,
15451530
syncChatAndIOUReports,
15461531
navigateToConciergeChat,
1547-
handleInaccessibleReport,
15481532
setReportWithDraft,
15491533
createPolicyRoom,
15501534
addPolicyReport,

0 commit comments

Comments
 (0)