MDL-64002 message: add error handling for failed messages
Add inline error messages when messages fail to send. Also add support for deleting messages that failed to send.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -864,16 +864,28 @@ function(
|
||||
*/
|
||||
var deleteSelectedMessages = function() {
|
||||
var messageIds = viewState.pendingDeleteMessageIds;
|
||||
var sentMessages = viewState.messages.filter(function(message) {
|
||||
// If a message sendState is null then it means it was loaded from the server or if it's
|
||||
// set to sent then it means the user has successfully sent it in this page load.
|
||||
return messageIds.indexOf(message.id) >= 0 && (message.sendState == 'sent' || message.sendState === null);
|
||||
});
|
||||
var newState = StateManager.setLoadingConfirmAction(viewState, true);
|
||||
|
||||
render(newState);
|
||||
|
||||
var deleteMessagesPromise = null;
|
||||
var deleteMessagesPromise = $.Deferred().resolve().promise();
|
||||
|
||||
if (newState.deleteMessagesForAllUsers) {
|
||||
deleteMessagesPromise = Repository.deleteMessagesForAllUsers(viewState.loggedInUserId, messageIds);
|
||||
} else {
|
||||
deleteMessagesPromise = Repository.deleteMessages(viewState.loggedInUserId, messageIds);
|
||||
if (sentMessages.length) {
|
||||
// We only need to send a request to the server if we're trying to delete messages that
|
||||
// have successfully been sent.
|
||||
var sentMessageIds = sentMessages.map(function(message) {
|
||||
return message.id;
|
||||
});
|
||||
if (newState.deleteMessagesForAllUsers) {
|
||||
deleteMessagesPromise = Repository.deleteMessagesForAllUsers(viewState.loggedInUserId, sentMessageIds);
|
||||
} else {
|
||||
deleteMessagesPromise = Repository.deleteMessages(viewState.loggedInUserId, sentMessageIds);
|
||||
}
|
||||
}
|
||||
|
||||
return deleteMessagesPromise.then(function() {
|
||||
@@ -894,7 +906,8 @@ function(
|
||||
}
|
||||
|
||||
return render(newState);
|
||||
});
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1021,7 +1034,7 @@ function(
|
||||
// We're already sending messages so nothing to do.
|
||||
return;
|
||||
}
|
||||
if (!sendMessageBuffer.length) {
|
||||
if (!sendMessageBuffer.length) {
|
||||
// No messages waiting to send. Nothing to do.
|
||||
return;
|
||||
}
|
||||
@@ -1063,14 +1076,36 @@ function(
|
||||
var newMessageIds = messages.map(function(message) {
|
||||
return message.id;
|
||||
});
|
||||
var data = messagesToSend.map(function(oldMessage, index) {
|
||||
var data = [];
|
||||
var selectedToRemove = [];
|
||||
var selectedToAdd = [];
|
||||
|
||||
messagesToSend.forEach(function(oldMessage, index) {
|
||||
var newMessage = messages[index];
|
||||
// Update messages expects and array of arrays where the first value
|
||||
// is the old message to update and the second value is the new values
|
||||
// to set.
|
||||
return [oldMessage, messages[index]];
|
||||
data.push([oldMessage, newMessage]);
|
||||
|
||||
if (viewState.selectedMessageIds.indexOf(oldMessage.id) >= 0) {
|
||||
// If the message was added to the "selected messages" list while it was still
|
||||
// being sent then we should update it's id in that list now to make sure future
|
||||
// actions work.
|
||||
selectedToRemove.push(oldMessage.id);
|
||||
selectedToAdd.push(newMessage.id);
|
||||
}
|
||||
});
|
||||
var newState = StateManager.updateMessages(viewState, data);
|
||||
newState = StateManager.setMessagesSendSuccessById(newState, newMessageIds);
|
||||
|
||||
if (selectedToRemove.length) {
|
||||
newState = StateManager.removeSelectedMessagesById(newState, selectedToRemove);
|
||||
}
|
||||
|
||||
if (selectedToAdd.length) {
|
||||
newState = StateManager.addSelectedMessagesById(newState, selectedToAdd);
|
||||
}
|
||||
|
||||
var conversation = formatConversationForEvent(newState);
|
||||
|
||||
if (!newState.id) {
|
||||
@@ -1089,14 +1124,32 @@ function(
|
||||
isSendingMessage = false;
|
||||
processSendMessageBuffer();
|
||||
PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);
|
||||
return;
|
||||
})
|
||||
.catch(function() {
|
||||
// We failed to create messages so remove the old messages from the pending queue
|
||||
// and update the UI to indicate that the message failed.
|
||||
var newState = StateManager.setMessagesSendFailById(viewState, messageIds);
|
||||
render(newState);
|
||||
isSendingMessage = false;
|
||||
processSendMessageBuffer();
|
||||
.catch(function(e) {
|
||||
if (e.message) {
|
||||
var errorMessage = $.Deferred().resolve(e.message).promise();
|
||||
} else {
|
||||
var errorMessage = Str.get_string('unknownerror', 'core');
|
||||
}
|
||||
|
||||
var handleFailedMessages = function(errorMessage) {
|
||||
// We failed to create messages so remove the old messages from the pending queue
|
||||
// and update the UI to indicate that the message failed.
|
||||
var newState = StateManager.setMessagesSendFailById(viewState, messageIds, errorMessage);
|
||||
render(newState);
|
||||
isSendingMessage = false;
|
||||
processSendMessageBuffer();
|
||||
};
|
||||
|
||||
errorMessage.then(handleFailedMessages)
|
||||
.catch(function(e) {
|
||||
// Hrmm, we can't even load the error messages string! We'll have to
|
||||
// hard code something in English here if we still haven't got a message
|
||||
// to show.
|
||||
var finalError = e.message || 'Something went wrong!';
|
||||
handleFailedMessages(finalError);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1347,6 +1400,8 @@ function(
|
||||
}
|
||||
|
||||
data.originalEvent.preventDefault();
|
||||
data.originalEvent.stopPropagation();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,6 +63,8 @@ define([], function() {
|
||||
DAY_MESSAGES_CONTAINER: '[data-region="day-messages-container"]',
|
||||
DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE: '[data-region="delete-messages-for-all-users-toggle"]',
|
||||
DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER: '[data-region="delete-messages-for-all-users-toggle-container"]',
|
||||
ERROR_MESSAGE_CONTAINER: '[data-region="error-message-container"]',
|
||||
ERROR_MESSAGE: '[data-region="error-message"]',
|
||||
FAVOURITE_ICON_CONTAINER: '[data-region="favourite-icon-container"]',
|
||||
FOOTER_CONTAINER: '[data-region="content-messages-footer-container"]',
|
||||
HEADER: '[data-region="header-content"]',
|
||||
|
||||
@@ -220,13 +220,13 @@ function(
|
||||
return isObjectEqual(
|
||||
{
|
||||
id: a.id,
|
||||
state: a.state,
|
||||
state: a.sendState,
|
||||
text: a.text,
|
||||
timeCreated: a.timeCreated
|
||||
},
|
||||
{
|
||||
id: b.id,
|
||||
state: b.state,
|
||||
state: b.sendState,
|
||||
text: b.text,
|
||||
timeCreated: b.timeCreated
|
||||
}
|
||||
@@ -294,7 +294,7 @@ function(
|
||||
// If the IDs match or it was a state change (i.e. message with a temp
|
||||
// ID goes from pending to sent and receives an actual id) then they are
|
||||
// the same message which should be an update not an add/remove.
|
||||
return a.id == b.id || (a.state != b.state && a.timeAdded == b.timeAdded);
|
||||
return a.id == b.id || (a.sendState != b.sendState && a.timeAdded == b.timeAdded);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -643,26 +643,32 @@ function(
|
||||
element.find(SELECTORS.TIME_CREATED).text(formattedTime).removeClass('hidden');
|
||||
}
|
||||
|
||||
if (before.state != after.state) {
|
||||
if (before.sendState != after.sendState) {
|
||||
var loading = element.find(SELECTORS.LOADING_ICON_CONTAINER);
|
||||
var time = element.find(SELECTORS.TIME_CREATED);
|
||||
var retry = element.find(SELECTORS.RETRY_SEND);
|
||||
|
||||
loading.addClass('hidden');
|
||||
loading.attr('aria-hidden', 'true');
|
||||
time.addClass('hidden');
|
||||
time.attr('aria-hidden', 'true');
|
||||
retry.addClass('hidden');
|
||||
retry.attr('aria-hidden', 'true');
|
||||
element.removeClass('border border-danger');
|
||||
|
||||
switch (after.state) {
|
||||
switch (after.sendState) {
|
||||
case 'pending':
|
||||
loading.removeClass('hidden');
|
||||
loading.attr('aria-hidden', 'false');
|
||||
break;
|
||||
case 'error':
|
||||
retry.removeClass('hidden');
|
||||
retry.attr('aria-hidden', 'false');
|
||||
element.addClass('border border-danger');
|
||||
break;
|
||||
case 'sent':
|
||||
time.removeClass('hidden');
|
||||
time.attr('aria-hidden', 'false');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -670,6 +676,21 @@ function(
|
||||
if (before.text != after.text) {
|
||||
element.find(SELECTORS.TEXT_CONTAINER).html(after.text);
|
||||
}
|
||||
|
||||
if (before.errorMessage != after.errorMessage) {
|
||||
var messageContainer = element.find(SELECTORS.ERROR_MESSAGE_CONTAINER);
|
||||
var message = messageContainer.find(SELECTORS.ERROR_MESSAGE);
|
||||
|
||||
if (after.errorMessage) {
|
||||
messageContainer.removeClass('hidden');
|
||||
messageContainer.attr('aria-hidden', 'false');
|
||||
message.text(after.errorMessage);
|
||||
} else {
|
||||
messageContainer.addClass('hidden');
|
||||
messageContainer.attr('aria-hidden', 'true');
|
||||
message.text('');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -158,8 +158,9 @@ define(['jquery'], function($) {
|
||||
var newState = cloneState(state);
|
||||
var formattedMessages = formatMessages(messages, state.loggedInUserId, state.members);
|
||||
formattedMessages = formattedMessages.map(function(message) {
|
||||
message.state = 'pending';
|
||||
message.sendState = null;
|
||||
message.timeAdded = Date.now();
|
||||
message.errorMessage = null;
|
||||
return message;
|
||||
});
|
||||
var allMessages = state.messages.concat(formattedMessages);
|
||||
@@ -476,7 +477,8 @@ define(['jquery'], function($) {
|
||||
});
|
||||
newState.messages.forEach(function(message) {
|
||||
if (messageIds.indexOf(message.id) >= 0) {
|
||||
message.state = 'pending';
|
||||
message.sendState = 'pending';
|
||||
message.errorMessage = null;
|
||||
}
|
||||
});
|
||||
return newState;
|
||||
@@ -496,7 +498,8 @@ define(['jquery'], function($) {
|
||||
});
|
||||
newState.messages.forEach(function(message) {
|
||||
if (messageIds.indexOf(message.id) >= 0) {
|
||||
message.state = 'sent';
|
||||
message.sendState = 'sent';
|
||||
message.errorMessage = null;
|
||||
}
|
||||
});
|
||||
return newState;
|
||||
@@ -509,14 +512,15 @@ define(['jquery'], function($) {
|
||||
* @param {Array} messageIds Messages to delete.
|
||||
* @return {Object} New state with array of pending delete message ids.
|
||||
*/
|
||||
var setMessagesSendFailById = function(state, messageIds) {
|
||||
var setMessagesSendFailById = function(state, messageIds, errorMessage) {
|
||||
var newState = cloneState(state);
|
||||
messageIds = messageIds.map(function(id) {
|
||||
return "" + id;
|
||||
});
|
||||
newState.messages.forEach(function(message) {
|
||||
if (messageIds.indexOf(message.id) >= 0) {
|
||||
message.state = 'error';
|
||||
message.sendState = 'error';
|
||||
message.errorMessage = errorMessage;
|
||||
}
|
||||
});
|
||||
return newState;
|
||||
@@ -684,6 +688,9 @@ define(['jquery'], function($) {
|
||||
*/
|
||||
var addSelectedMessagesById = function(state, messageIds) {
|
||||
var newState = cloneState(state);
|
||||
messageIds = messageIds.map(function(id) {
|
||||
return "" + id;
|
||||
});
|
||||
newState.selectedMessageIds = newState.selectedMessageIds.concat(messageIds);
|
||||
return newState;
|
||||
};
|
||||
|
||||
@@ -66,11 +66,30 @@
|
||||
>
|
||||
{{formattedtime}}
|
||||
</div>
|
||||
<div class="ml-auto small {{#formattedtime}}hidden{{/formattedtime}}" data-region="loading-icon-container">{{> core/loading }}</div>
|
||||
<a href="#" class="ml-auto small hidden text-primary" data-region="retry-send">{{#str}} retry, core {{/str}}</a>
|
||||
<div
|
||||
class="ml-auto small {{#formattedtime}}hidden{{/formattedtime}}"
|
||||
{{#formattedtime}}aria-hidden="true"{{/formattedtime}}
|
||||
{{^formattedtime}}aria-hidden="false"{{/formattedtime}}
|
||||
data-region="loading-icon-container"
|
||||
>
|
||||
{{> core/loading }}
|
||||
</div>
|
||||
<div role="alert" class="text-danger hidden small" aria-hidden="true" data-region="error-message-container">
|
||||
<div class="d-flex align-items-center">
|
||||
{{#pix}} req, core {{/pix}}
|
||||
<span class="ml-1" data-region="error-message"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="ml-auto btn btn-link p-0 line-height-3 hidden"
|
||||
aria-hidden="true"
|
||||
data-region="retry-send"
|
||||
title="{{#str}} retry, core {{/str}}"
|
||||
>
|
||||
<span class="text-primary small">{{#str}} retry, core {{/str}}</span>
|
||||
</button>
|
||||
<span class="hidden ml-2 small text-dark" data-region="not-selected-icon">{{#pix}} i/uncheckedcircle, core {{/pix}}</span>
|
||||
<span class="hidden ml-2 small text-primary" data-region="selected-icon">{{#pix}} i/checkedcircle, core {{/pix}}</span>
|
||||
</div>
|
||||
<div dir="auto" align="initial" data-region="text-container">{{{text}}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user