Skip to content

Migrate policy API to remove policy members #10452

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

Merged
merged 27 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6f1619
Remove unnneded code for DeleteMembersFromWorkspace
iwiznia Aug 18, 2022
a1ae9f5
Merge from main
iwiznia Sep 2, 2022
fc96430
Use employeeList in members page
iwiznia Sep 2, 2022
ec7e20d
Merge branch 'main' into ionatan_DeleteMembersFromWorkspace
iwiznia Sep 7, 2022
9ad6ec9
Transalate error
iwiznia Sep 7, 2022
41ad34c
Update to new Onyx version and fix editing comments
iwiznia Sep 13, 2022
c92093d
Fix active clients
iwiznia Sep 13, 2022
48c9543
Merge branch 'main' into ionatan_DeleteMembersFromWorkspace
iwiznia Sep 13, 2022
f1a5ba4
Address review comments
iwiznia Sep 13, 2022
faae0c7
Use each instead of map
iwiznia Sep 15, 2022
77cd2f3
Merge branch 'main' into ionatan_DeleteMembersFromWorkspace
iwiznia Sep 15, 2022
ff75e67
Merge branch 'main' into ionatan_DeleteMembersFromWorkspace
iwiznia Sep 16, 2022
11b8404
Fix activeClients bug and inviting new users
iwiznia Sep 16, 2022
8a1c3e9
Lint
iwiznia Sep 19, 2022
e32fc79
Remove unnecessary code
iwiznia Sep 19, 2022
c351e69
Update src/libs/ActiveClientManager/index.js
iwiznia Sep 20, 2022
f995ec4
Simplify ActiveClientManager
iwiznia Sep 20, 2022
d20176e
Merge branch 'ionatan_DeleteMembersFromWorkspace' of github.com:Expen…
iwiznia Sep 20, 2022
42bd676
Move promise to correct place
iwiznia Sep 20, 2022
4bc6ad9
Linting
iwiznia Sep 20, 2022
10b97ca
Add comment about init method
iwiznia Sep 20, 2022
2f08c2c
Merge branch 'main' into ionatan_DeleteMembersFromWorkspace
iwiznia Sep 23, 2022
c694ae7
Show no access page when you lose access to a chat
iwiznia Sep 28, 2022
57b6cb8
Merge branch 'main' into ionatan_DeleteMembersFromWorkspace
iwiznia Sep 28, 2022
93b977c
Lint
iwiznia Sep 28, 2022
67e8509
Merge with main
iwiznia Sep 30, 2022
fea93ab
Fix not found page showing close
iwiznia Sep 30, 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
14 changes: 7 additions & 7 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
Expand Up @@ -95,7 +95,7 @@
"react-native-image-picker": "^4.8.5",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#6b5ab5110dc3ed554f8eafbc38d7d87c17147972",
"react-native-modal": "^13.0.0",
"react-native-onyx": "1.0.15",
"react-native-onyx": "1.0.16",
"react-native-pdf": "^6.6.2",
"react-native-performance": "^2.0.0",
"react-native-permissions": "^3.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,7 @@ export default {
selectAll: 'Select all',
error: {
cannotRemove: 'You cannot remove yourself or the workspace owner.',
genericRemove: 'There was a problem removing that workspace member.',
},
},
card: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@ export default {
selectAll: 'Seleccionar todo',
error: {
cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.',
genericRemove: 'Ha ocurrido un problema al eliminar al miembro del espacio de trabajo.',
},
},
card: {
Expand Down
6 changes: 4 additions & 2 deletions src/libs/ActiveClientManager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as ActiveClients from '../actions/ActiveClients';
const clientID = Str.guid();
const maxClients = 20;

let activeClients;
const activeClients = [];

let resolveIsReadyPromise;
const isReadyPromise = new Promise((resolve) => {
Expand All @@ -24,7 +24,9 @@ function isReady() {
Onyx.connect({
key: ONYXKEYS.ACTIVE_CLIENTS,
callback: (val) => {
activeClients = !val ? [] : val;
if (val && activeClients.indexOf(val) < 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Whats this change for? When i test your PR the command DeleteMembersFromWorkspace never gets called in the network tab and I think its because of the code here.

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 had to refactor this a bit because this was using the array merging feature of onyx that we changed. It is not called in the flow of that api command.

activeClients.push(val);
}
if (activeClients.length >= maxClients) {
activeClients.shift();
ActiveClients.setActiveClients(activeClients);
Expand Down
53 changes: 19 additions & 34 deletions src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,29 +218,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')}})),
Copy link
Contributor

Choose a reason for hiding this comment

The 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 errorMessage.
Since the API is a failure, the failureData here will get triggered to insert all members including removed ones with the error. Additionally the workspace.people.error.genericRemove is also inserted.
So if you removed 3 users, and the PHP layer failed on user 2.

  1. Won't all 3 users get workspace.people.error.genericRemove added to them since the API is considered a failure?
  2. Won't user 2 show workspace.people.error.genericRemove and errorMessage i.e., two error messages which could be confusing?

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.

Copy link
Contributor

@chiragsalian chiragsalian Sep 20, 2022

Choose a reason for hiding this comment

The 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 workspace.people.error.genericRemove in Spanish? and then the app would display both.

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 for any es device won't it be weird that the PHP layer is providing the ExpError in English while the app adds workspace.people.error.genericRemove in Spanish? and then the app would display both.

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):
image

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:

  • If we add the generic error, we get this behavior
  • If we don't add the generic error, we risk not showing any error at all if what we got from the request was a failure with no data back

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

NAB, but does failureData still set this generic error if one is provided from the server? I haven't personally relied on any default error messages like this yet. It's pretty unclear to me when I should use a default error message or when I should rely on the server. But maybe something we could be more consistent about or provide guidance on in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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});
}

/**
Expand Down Expand Up @@ -699,21 +691,14 @@ function updateLastAccessedWorkspace(policyID) {
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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

@iwiznia iwiznia Sep 30, 2022

Choose a reason for hiding this comment

The 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)

});
}

Pusher.subscribe(pusherChannelName, 'policyEmployeeRemoved', ({removedEmails, defaultRoomChatIDs}) => {
// 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);
});
Copy link
Contributor

Choose a reason for hiding this comment

The 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 null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Added a screenshot of how it looks to the description

if (!removedEmails.includes(sessionEmail) || _.isEmpty(defaultRoomChatIDs)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to do this logic in PHP so that the logic on the client can be more simple? Also, why isn't PHP sending Onyx instructions that are passed to Onyx.update()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, we are doing this in PHP already, we can now remove all this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated

return;
}
_.each(defaultRoomChatIDs, (chatID) => {
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatID}`, null);
});
});
});
}
Expand Down
1 change: 1 addition & 0 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) {
isEdited: true,
html: htmlForNewComment,
text: textForNewComment,
type: originalReportAction.message[0].type,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this change in this PR? It seems unrelated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 fragment.text here

}],
},
};
Expand Down
22 changes: 14 additions & 8 deletions src/pages/workspace/WorkspaceMembersPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,20 @@ class WorkspaceMembersPage extends React.Component {
}

render() {
const policyMemberList = _.keys(lodashGet(this.props, 'policyMemberList', {}));
const removableMembers = _.without(policyMemberList, this.props.session.email, this.props.policy.owner);
const data = _.chain(policyMemberList)
.map(email => this.props.personalDetails[email])
.filter()
.sortBy(person => person.displayName.toLowerCase())
.map(person => ({...person})) // TODO: here we will add the pendingAction and errors prop
.value();
const policyMemberList = lodashGet(this.props, 'policyMemberList', {});
const removableMembers = [];
let data = [];
_.each(policyMemberList, (policyMember, email) => {
Copy link
Contributor

@chiragsalian chiragsalian Sep 15, 2022

Choose a reason for hiding this comment

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

The update here to each alone is not correct. Notice in the previous code we filter to remove empty values but that's not here.

So over here I think you should not update data if details is empty otherwise you'll still get the same error when testing locally because it cannot do lowercase if value is empty. Sample data provided here. This will result in an app crash when members page is clicked which is bad,
image

So my suggestion is to update this, to be,

if (details) {
    data.push({
        ...policyMember,
        ...details,
    });
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was a hack that someone added because the app crashed and instead of fixing the real issue, they just removed the detail from the list, which is wrong. This works if you are using the latest version of auth, that returns personal details for all emails, even if they don't have explicitly set a name.

Well, at least I hope the crash you saw was due to outdated auth, sounds like it because it's the same I was getting before sending the auth fix.

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, you also need the web PR of course

Copy link
Contributor

Choose a reason for hiding this comment

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

Odd, im sure i updates all repos and did a makeall yesterday. Maybe its buggy on an existing workspace, I'll try again on a fresh workspace.
If its buggy on existing data we still have to consider it so that users existing workspaces don't 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.

Should not be buggy on existing data. Maybe.you need to sign out and back in though, not sure.

if (email !== this.props.session.email && email !== this.props.policy.owner) {
removableMembers.push(email);
}
const details = this.props.personalDetails[email];
data.push({
...policyMember,
...details,
});
});
data = _.sortBy(data, value => value.displayName.toLowerCase());
const policyID = lodashGet(this.props.route, 'params.policyID');
const policyName = lodashGet(this.props.policy, 'name');

Expand Down
4 changes: 0 additions & 4 deletions src/pages/workspace/withFullPolicy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import CONST from '../../CONST';
import getComponentDisplayName from '../../libs/getComponentDisplayName';
import * as Policy from '../../libs/actions/Policy';
import ONYXKEYS from '../../ONYXKEYS';
import policyMemberPropType from '../policyMemberPropType';

let previousRouteName = '';
let previousRoutePolicyID = '';
Expand Down Expand Up @@ -73,9 +72,6 @@ const fullPolicyPropTypes = {
*/
errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
}),

/** The policy member list for the current route */
policyMemberList: PropTypes.objectOf(policyMemberPropType),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Damn, good question. If I have to be honest, I don't quite recall... 🤔

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 I remember! Because the member list is now it's own key, not inside the policy anymore

Copy link
Collaborator

@Santhosh-Sellavel Santhosh-Sellavel Sep 21, 2022

Choose a reason for hiding this comment

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

It's already a separate key along with the policy

Screenshot 2022-09-21 at 11 25 11 PM

Copy link
Collaborator

Choose a reason for hiding this comment

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

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 what I said too 😄

};

const fullPolicyDefaultProps = {
Expand Down