-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Migrate policy API to remove policy members #10452
Changes from all commits
e6f1619
a1ae9f5
fc96430
ec7e20d
9ad6ec9
41ad34c
c92093d
48c9543
f1a5ba4
faae0c7
77cd2f3
ff75e67
11b8404
8a1c3e9
e32fc79
c351e69
f995ec4
d20176e
42bd676
4bc6ad9
10b97ca
2f08c2c
c694ae7
57b6cb8
93b977c
67e8509
fea93ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,9 @@ | ||
/** | ||
* 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 | ||
* only one tab is processing those saved requests or we would be duplicating data (or creating errors). | ||
* 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". | ||
*/ | ||
|
||
import _ from 'underscore'; | ||
import Onyx from 'react-native-onyx'; | ||
import Str from 'expensify-common/lib/str'; | ||
|
@@ -6,38 +12,47 @@ import * as ActiveClients from '../actions/ActiveClients'; | |
|
||
const clientID = Str.guid(); | ||
const maxClients = 20; | ||
|
||
let activeClients; | ||
|
||
let resolveIsReadyPromise; | ||
const isReadyPromise = new Promise((resolve) => { | ||
resolveIsReadyPromise = resolve; | ||
let activeClients = []; | ||
let resolveSavedSelfPromise; | ||
const savedSelfPromise = new Promise((resolve) => { | ||
resolveSavedSelfPromise = resolve; | ||
}); | ||
|
||
/** | ||
* Determines when the client is ready. We need to wait both till we saved our ID in onyx AND the init method was called | ||
* @returns {Promise} | ||
*/ | ||
function isReady() { | ||
return isReadyPromise; | ||
return savedSelfPromise; | ||
} | ||
|
||
Onyx.connect({ | ||
key: ONYXKEYS.ACTIVE_CLIENTS, | ||
callback: (val) => { | ||
activeClients = !val ? [] : val; | ||
if (activeClients.length >= maxClients) { | ||
activeClients = val; | ||
|
||
// Remove from the beginning of the list any clients that are past the limit, to avoid having thousands of them | ||
let removed = false; | ||
while (activeClients.length >= maxClients) { | ||
activeClients.shift(); | ||
removed = true; | ||
} | ||
|
||
// Save the clients back to onyx, if they changed | ||
if (removed) { | ||
ActiveClients.setActiveClients(activeClients); | ||
} | ||
}, | ||
}); | ||
|
||
/** | ||
* Add our client ID to the list of active IDs | ||
* Add our client ID to the list of active IDs. | ||
* We want to ensure we have no duplicates and that the activeClient gets added at the end of the array (see isClientTheLeader) | ||
*/ | ||
function init() { | ||
ActiveClients.addClient(clientID) | ||
.then(resolveIsReadyPromise); | ||
activeClients = _.without(activeClients, clientID); | ||
activeClients.push(clientID); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the intention of doing it like this is to ensure that the clientID is always added at the end of the array and its only added once. Would you mind updating the method description to talk about that? I know it's mentioned pretty well in the comment at the top of the file, but having the method doc comment talk about it is a little better IMO since it's closer to the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually don't get why are we concerned with it only being added once. We only add more clients and take them away to keep the list of IDs from growing infinitely. The active client will always be the last one regardless of whether we clean the current user clientID. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with Marc, but it's a NAB for me on this PR. Maybe a follow-up |
||
ActiveClients.setActiveClients(activeClients).then(resolveSavedSelfPromise); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,15 +6,11 @@ import Str from 'expensify-common/lib/str'; | |
import * as DeprecatedAPI from '../deprecatedAPI'; | ||
import * as API from '../API'; | ||
import ONYXKEYS from '../../ONYXKEYS'; | ||
import Growl from '../Growl'; | ||
import CONFIG from '../../CONFIG'; | ||
import CONST from '../../CONST'; | ||
import * as Localize from '../Localize'; | ||
import Navigation from '../Navigation/Navigation'; | ||
import ROUTES from '../../ROUTES'; | ||
import * as OptionsListUtils from '../OptionsListUtils'; | ||
import * as Report from './Report'; | ||
import * as Pusher from '../Pusher/pusher'; | ||
import DateUtils from '../DateUtils'; | ||
import * as ReportUtils from '../ReportUtils'; | ||
|
||
|
@@ -209,29 +205,21 @@ function removeMembers(members, policyID) { | |
if (members.length === 0) { | ||
return; | ||
} | ||
|
||
const employeeListUpdate = {}; | ||
_.each(members, login => employeeListUpdate[login] = null); | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, employeeListUpdate); | ||
|
||
// Make the API call to remove a login from the policy | ||
DeprecatedAPI.Policy_Employees_Remove({ | ||
const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`; | ||
const optimisticData = [{ | ||
onyxMethod: CONST.ONYX.METHOD.MERGE, | ||
key: membersListKey, | ||
value: _.object(members, Array(members.length).fill({pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE})), | ||
}]; | ||
const failureData = [{ | ||
onyxMethod: CONST.ONYX.METHOD.MERGE, | ||
key: membersListKey, | ||
value: _.object(members, Array(members.length).fill({errors: {[DateUtils.getMicroseconds()]: Localize.translateLocal('workspace.people.error.genericRemove')}})), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a question with this failureData. It looks good to me, but assume it fails when removing a user. Assume the the failure happens in PHP/auth layer. Now your PHP code here will throw an ExpError with an
Anyway my concern is mostly point 1, showing user1 and user3 back even though they were successfully removed, because push notification would run first to remove them and then failureData from the ExpError of user2 would add them back, right? Let me know if that makes sense, if so I think maybe we should not have failureData over here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also for any es device won't it be weird that the PHP layer is providing the ExpError in English while the app adds There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We don't care. We are not supporting multiple languages. As for the other comment, you are incorrect on point 1, here's a test I did by adding a throw before calling sharePolicy on a specific user (I had removed 2 users): As you can see, you are correct on point 2. I thought we would replace it, but that's not correct since we merge... this is a problem in many other APIs, since this is the pattern we are following in lots of them... I don't have a great solution for it right now, because:
Any ideas? In any case, I don't think I would want to block on this, given this problem is endemic and must probably be fixed in many other places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB, but does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, agree |
||
}]; | ||
API.write('DeleteMembersFromWorkspace', { | ||
emailList: members.join(','), | ||
policyID, | ||
}) | ||
.then((data) => { | ||
if (data.jsonCode === 200) { | ||
return; | ||
} | ||
|
||
// Rollback removal on failure | ||
_.each(members, login => employeeListUpdate[login] = {}); | ||
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, employeeListUpdate); | ||
|
||
// Show the user feedback that the removal failed | ||
const errorMessage = data.jsonCode === 666 ? data.message : Localize.translateLocal('workspace.people.genericFailureMessage'); | ||
Growl.show(errorMessage, CONST.GROWL.ERROR, 5000); | ||
}); | ||
}, {optimisticData, failureData}); | ||
} | ||
|
||
/** | ||
|
@@ -653,31 +641,6 @@ function updateLastAccessedWorkspace(policyID) { | |
Onyx.set(ONYXKEYS.LAST_ACCESSED_WORKSPACE_POLICY_ID, policyID); | ||
} | ||
|
||
/** | ||
* Subscribe to public-policyEditor-[policyID] events. | ||
*/ | ||
function subscribeToPolicyEvents() { | ||
_.each(allPolicies, (policy) => { | ||
const pusherChannelName = `public-policyEditor-${policy.id}${CONFIG.PUSHER.SUFFIX}`; | ||
Pusher.subscribe(pusherChannelName, 'policyEmployeeRemoved', ({removedEmails, policyExpenseChatIDs, defaultRoomChatIDs}) => { | ||
// Refetch the policy expense chats to update their state and their actions to get the archive reason | ||
if (!_.isEmpty(policyExpenseChatIDs)) { | ||
Report.fetchChatReportsByIDs(policyExpenseChatIDs); | ||
_.each(policyExpenseChatIDs, (reportID) => { | ||
Report.reconnect(reportID); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB, I did not see the Web PR, but curious if we kept this logic here to fetch the report data and report history for any policyExpenseChats? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Policy expense chats should be sent via onyx when adding, removing or changing the role of users (and AFAIK we are doing that, at least the remove part is done in my web PR) |
||
}); | ||
} | ||
|
||
// Remove the default chats if we are one of the users getting removed | ||
if (removedEmails.includes(sessionEmail) && !_.isEmpty(defaultRoomChatIDs)) { | ||
_.each(defaultRoomChatIDs, (chatID) => { | ||
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatID}`, null); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB, are we doing this part via the server now? What does it look like for the user if they are looking at a chat and it is set to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. Added a screenshot of how it looks to the description |
||
} | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Removes an error after trying to delete a member | ||
* | ||
|
@@ -974,7 +937,6 @@ export { | |
updateWorkspaceCustomUnit, | ||
updateCustomUnitRate, | ||
updateLastAccessedWorkspace, | ||
subscribeToPolicyEvents, | ||
clearDeleteMemberError, | ||
clearAddMemberError, | ||
clearDeleteWorkspaceError, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -331,14 +331,6 @@ function fetchChatReportsByIDs(chatList, shouldRedirectIfInaccessible = false) { | |
// Fetch the personal details if there are any | ||
PersonalDetails.getFromReportParticipants(_.values(simplifiedReports)); | ||
return simplifiedReports; | ||
}) | ||
.catch((err) => { | ||
if (err.message !== CONST.REPORT.ERROR.INACCESSIBLE_REPORT) { | ||
return; | ||
} | ||
|
||
// eslint-disable-next-line no-use-before-define | ||
handleInaccessibleReport(); | ||
}); | ||
} | ||
|
||
|
@@ -1086,6 +1078,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { | |
isEdited: true, | ||
html: htmlForNewComment, | ||
text: textForNewComment, | ||
type: originalReportAction.message[0].type, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this change in this PR? It seems unrelated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not. Messages is an array and we were not sending the type in the optimistic action (neither in the action from the server), so when we replace the message fully now, this property was not set and was causing issues (we would render the comment as |
||
}], | ||
}, | ||
}; | ||
|
@@ -1212,14 +1205,6 @@ function navigateToConciergeChat() { | |
Navigation.navigate(ROUTES.getReportRoute(conciergeChatReportID)); | ||
} | ||
|
||
/** | ||
* Handle the navigation when report is inaccessible | ||
*/ | ||
function handleInaccessibleReport() { | ||
Growl.error(Localize.translateLocal('notFound.chatYouLookingForCannotBeFound')); | ||
navigateToConciergeChat(); | ||
} | ||
|
||
/** | ||
* Creates a policy room, fetches it, and navigates to it. | ||
* @param {String} policyID | ||
|
@@ -1544,7 +1529,6 @@ export { | |
getSimplifiedIOUReport, | ||
syncChatAndIOUReports, | ||
navigateToConciergeChat, | ||
handleInaccessibleReport, | ||
setReportWithDraft, | ||
createPolicyRoom, | ||
addPolicyReport, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love the description 👍