From 0f14d89257861a4fbed4dcd0ad3ecad4a9603307 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 6 Mar 2019 13:58:47 +0000 Subject: [PATCH 1/3] Ability to bulk accept all invites --- .../tabs/user/_SecurityUserSettingsTab.scss | 4 + .../tabs/user/SecurityUserSettingsTab.js | 95 ++++++++++++++----- src/i18n/strings/en_EN.json | 1 + 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss index 4835640904..1c0a4b5864 100644 --- a/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_SecurityUserSettingsTab.scss @@ -40,6 +40,10 @@ limitations under the License. margin-right: 10px; } +.mx_SecurityUserSettingsTab_bulkOptions .mx_AccessibleButton { + margin-right: 10px; +} + .mx_SecurityUserSettingsTab_importExportButtons { margin-bottom: 15px; } diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 7f68a1d6d7..02adeb1307 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -26,6 +26,9 @@ import Promise from "bluebird"; import Modal from "../../../../../Modal"; import sdk from "../../../../.."; +// Delay between failing invite acceptance/rejections to avoid rate-limiting +const INVITE_MANAGEMENT_DELAY = 10000; + export class IgnoredUser extends React.Component { static propTypes = { userId: PropTypes.string.isRequired, @@ -52,9 +55,13 @@ export default class SecurityUserSettingsTab extends React.Component { constructor() { super(); + // Get number of rooms we're invited to + const invitedRooms = this._getInvitedRooms(); + this.state = { ignoredUserIds: MatrixClientPeg.get().getIgnoredUsers(), - rejectingInvites: false, + managingInvites: false, + invitedRoomAmt: invitedRooms.length, }; } @@ -93,24 +100,62 @@ export default class SecurityUserSettingsTab extends React.Component { this.setState({ignoredUsers}); }; - _onRejectAllInvitesClicked = (rooms, ev) => { + _getInvitedRooms = () => { + return MatrixClientPeg.get().getRooms().filter((r) => { + return r.hasMembershipState(MatrixClientPeg.get().getUserId(), "invite"); + }); + }; + + _manageInvites = async (accept) => { this.setState({ - rejectingInvites: true, + managingInvites: true, }); - // reject the invites - const promises = rooms.map((room) => { - return MatrixClientPeg.get().leave(room.roomId).catch((e) => { - // purposefully drop errors to the floor: we'll just have a non-zero number on the UI - // after trying to reject all the invites. - }); - }); - Promise.all(promises).then(() => { - this.setState({ - rejectingInvites: false, + + // Compile array of invitation room ids + const invitedRoomIds = this._getInvitedRooms().map((room) => { + return room.roomId; + }); + + // Execute all acceptances/rejections sequentially + const self = this; + const cli = MatrixClientPeg.get(); + const action = accept ? cli.joinRoom.bind(cli) : cli.leave.bind(cli); + for (let i = 0; i < invitedRoomIds.length; i++) { + const roomId = invitedRoomIds[i]; + + // Accept/reject invite + await action(roomId).then(() => { + // No error, update invited rooms button + this.setState({invitedRoomAmt: self.state.invitedRoomAmt - 1}); + }, async (e) => { + // Action failure + if (e.errcode === "M_LIMIT_EXCEEDED") { + // Add a delay between each invite change in order to avoid rate + // limiting by the server. + await Promise.delay(INVITE_MANAGEMENT_DELAY); + + // Redo last action + i--; + } else { + // Print out error with joining/leaving room + console.warn(e); + } }); + } + + this.setState({ + managingInvites: false, }); }; + _onAcceptAllInvitesClicked = (ev) => { + this._manageInvites(true); + }; + + _onRejectAllInvitesClicked = (ev) => { + this._manageInvites(false); + }; + _renderCurrentDeviceInfo() { const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag'); @@ -173,21 +218,25 @@ export default class SecurityUserSettingsTab extends React.Component { ); } - _renderRejectInvites() { - const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => { - return r.hasMembershipState(MatrixClientPeg.get().getUserId(), "invite"); - }); - if (invitedRooms.length === 0) { + _renderManageInvites() { + if (this.state.invitedRoomAmt === 0) { return null; } - const onClick = this._onRejectAllInvitesClicked.bind(this, invitedRooms); + const invitedRooms = this._getInvitedRooms(); + const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); + const onClickAccept = this._onAcceptAllInvitesClicked.bind(this, invitedRooms); + const onClickReject = this._onRejectAllInvitesClicked.bind(this, invitedRooms); return ( -
+
{_t('Bulk options')} - - {_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})} + + {_t("Accept all %(invitedRooms)s invites", {invitedRooms: this.state.invitedRoomAmt})} + + {_t("Reject all %(invitedRooms)s invites", {invitedRooms: this.state.invitedRoomAmt})} + + {this.state.managingInvites ? :
}
); } @@ -232,7 +281,7 @@ export default class SecurityUserSettingsTab extends React.Component { onChange={this._updateAnalytics} />
{this._renderIgnoredUsers()} - {this._renderRejectInvites()} + {this._renderManageInvites()}
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a17576826c..d4bf6178ae 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -559,6 +559,7 @@ "Device key:": "Device key:", "Ignored users": "Ignored users", "Bulk options": "Bulk options", + "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", "Key backup": "Key backup", "Security & Privacy": "Security & Privacy", From c5198418b018b336473590ff332ebe22eaf7dbdb Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Wed, 6 Mar 2019 15:37:06 +0000 Subject: [PATCH 2/3] Use retry_after_ms instead of hardcoded delay --- .../views/settings/tabs/user/SecurityUserSettingsTab.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 02adeb1307..fdca4d169e 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -26,9 +26,6 @@ import Promise from "bluebird"; import Modal from "../../../../../Modal"; import sdk from "../../../../.."; -// Delay between failing invite acceptance/rejections to avoid rate-limiting -const INVITE_MANAGEMENT_DELAY = 10000; - export class IgnoredUser extends React.Component { static propTypes = { userId: PropTypes.string.isRequired, @@ -132,7 +129,7 @@ export default class SecurityUserSettingsTab extends React.Component { if (e.errcode === "M_LIMIT_EXCEEDED") { // Add a delay between each invite change in order to avoid rate // limiting by the server. - await Promise.delay(INVITE_MANAGEMENT_DELAY); + await Promise.delay(e.retry_after_ms); // Redo last action i--; From 71261fc7a71d6219ab1a5885ac4dac3a602a6797 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Thu, 7 Mar 2019 11:12:16 +0000 Subject: [PATCH 3/3] Add 2500 as default delay --- .../views/settings/tabs/user/SecurityUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index fdca4d169e..c18f5bda53 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -129,7 +129,7 @@ export default class SecurityUserSettingsTab extends React.Component { if (e.errcode === "M_LIMIT_EXCEEDED") { // Add a delay between each invite change in order to avoid rate // limiting by the server. - await Promise.delay(e.retry_after_ms); + await Promise.delay(e.retry_after_ms || 2500); // Redo last action i--;