Skip to content

Commit

Permalink
dApp: improve error handling for quick pay route
Browse files Browse the repository at this point in the history
The transfer action was handled a little special in contrast to the
other actions. This included to show the progress inline. This got
unified in combination with a better error visualization.
All actions show their progress in a dialog now. The transfer action
just has some empty translations to make it less obtrusively as intended
before already. This allows to re-use the nice looking error
visualization of the action progress card in the dialog. It provides
more information and looks better. Before this was hidden by a second
error dialog on top of it.

This change did also allow to get rid of the extra functionality to hide
and display again the action message (which was only for the inline
progress of the transfer action).

Though there is still an error dialog remaining to handle all errors
which are not caused by actions themselves. For the moment this is only
the case for invalid query parameters.

Moreover does the changing of query parameters during an active action
not cancel this action anymore. Furthermore do action errors take
precedence.

Finally the closing of the action progress dialog will also cause the
direction. This solves a missing feature where the user was stuck for
failed actions, waiting for ever to get redirected. Second this allows
the user to get quicker redirected in case of a success by closing the
dialog manually. This became possible by a recent extension of the
action mixin.
  • Loading branch information
weilbith committed Sep 13, 2021
1 parent 5eca63a commit 4a768d6
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 68 deletions.
87 changes: 48 additions & 39 deletions raiden-dapp/src/views/QuickPayRoute.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@
class="quick-pay__action__component"
:transfer-token-amount="tokenAmount"
:payment-identifier="paymentIdentifier"
:show-progress-in-dialog="showActionProgressInDialog"
:dialog-title="actionDialogTitle"
:completion-delay-timeout="1500"
show-progress-in-dialog
@started="setActionInProgress"
@completed="redirect"
@failed="handleActionError"
@dialogClosed="redirect"
>
<channel-action-form
:token-address="tokenAddress"
Expand All @@ -56,7 +58,7 @@
/>
</component>

<i18n v-if="!actionInProgress" class="quick-pay__action__message" :path="actionMessagePath">
<i18n class="quick-pay__action__message" :path="actionMessagePath">
<template #deposit>
<amount-display :amount="depositAmountInput" :token="token" inline />
</template>
Expand All @@ -66,7 +68,7 @@
</i18n>
</div>

<error-dialog v-if="error" :error="error" @dismiss="redirect" />
<error-dialog v-if="errorVisible" :error="error" @dismiss="redirect" />
</div>
</template>

Expand Down Expand Up @@ -129,18 +131,19 @@ export default class QuickPayRoute extends Mixins(NavigationMixin) {
| typeof ChannelOpenAndTransferAction
| typeof ChannelDepositAndTransferAction = TransferAction;
actionInProgress = false;
error: Error | null = null;
actionFailed = false;
error: Error | null = null; // Note that action errors are not set here.
get tokenAddress(): string {
return getAddress(this.$route.query.token);
return getAddress(this.$route.query.tokenAddress);
}
get token(): Token | null {
return this.getToken(this.tokenAddress);
}
get targetAddress(): string {
return getAddress(this.$route.query.target);
return getAddress(this.$route.query.targetAddress);
}
get tokenAmount(): BigNumber | undefined {
Expand Down Expand Up @@ -171,16 +174,16 @@ export default class QuickPayRoute extends Mixins(NavigationMixin) {
}
get paymentIdentifier(): BigNumber | undefined {
return getPaymentId(this.$route.query.paymentId);
return getPaymentId(this.$route.query.identifier);
}
get redirectionTarget(): string {
return this.$route.query.redirectTo as string;
}
get redirectionUrl(): string {
let url = `${this.redirectionTarget}?paymentId=${this.paymentIdentifier}`;
url += this.error ? '&failed=true' : `&payerAddress=${this.$raiden.getAccount()}`;
let url = `${this.redirectionTarget}?identifier=${this.paymentIdentifier}`;
url += this.actionFailed ? '&failed=true' : `&payerAddress=${this.$raiden.getAccount()}`;
return url;
}
Expand All @@ -193,6 +196,10 @@ export default class QuickPayRoute extends Mixins(NavigationMixin) {
);
}
get errorVisible(): boolean {
return this.error != null && !this.actionInProgress && !this.actionFailed;
}
get directChannelWithTarget(): RaidenChannel | undefined {
const channels = this.getChannels(this.tokenAddress);
return channels.filter(
Expand All @@ -216,48 +223,50 @@ export default class QuickPayRoute extends Mixins(NavigationMixin) {
}
get actionHeader(): string {
return this.actionComponent === TransferAction
? '' // Empty on purpose. No header for this case.
: this.actionComponent === ChannelDepositAndTransferAction
? (this.$t('quick-pay.action-titles.channel-deposit') as string)
: this.actionComponent === ChannelOpenAndTransferAction
? (this.$t('quick-pay.action-titles.channel-open') as string)
: '';
}
get showActionProgressInDialog(): boolean {
return this.actionComponent !== TransferAction;
switch (this.actionComponent) {
case TransferAction:
return ''; // Empty on purpose. No header for this case.
case ChannelDepositAndTransferAction:
return this.$t('quick-pay.action-titles.channel-deposit') as string;
case ChannelOpenAndTransferAction:
return this.$t('quick-pay.action-titles.channel-open') as string;
default:
return ''; // Necessary for TypeScript
}
}
get actionDialogTitle(): string {
return this.actionComponent === TransferAction
? '' // Empty on purpose. No title for this case.
: this.actionComponent === ChannelDepositAndTransferAction
? (this.$t('quick-pay.action-titles.channel-deposit-and-transfer') as string)
: this.actionComponent === ChannelOpenAndTransferAction
? (this.$t('quick-pay.action-titles.channel-open-and-transfer') as string)
: '';
switch (this.actionComponent) {
case TransferAction:
return ''; // Empty on purpose. No title for this case.
case ChannelDepositAndTransferAction:
return this.$t('quick-pay.action-titles.channel-deposit-and-transfer') as string;
case ChannelOpenAndTransferAction:
return this.$t('quick-pay.action-titles.channel-open-and-transfer') as string;
default:
return ''; // Necessary for TypeScript
}
}
get actionMessagePath(): string {
return this.actionComponent === TransferAction
? 'quick-pay.action-messages.transfer'
: this.actionComponent === ChannelDepositAndTransferAction
? 'quick-pay.action-messages.channel-deposit-and-transfer'
: this.actionComponent === ChannelOpenAndTransferAction
? 'quick-pay.action-messages.channel-open-and-transfer'
: '';
switch (this.actionComponent) {
case TransferAction:
return 'quick-pay.action-messages.transfer';
case ChannelDepositAndTransferAction:
return 'quick-pay.action-messages.channel-deposit-and-transfer';
case ChannelOpenAndTransferAction:
return 'quick-pay.action-messages.channel-open-and-transfer';
default:
return ''; // Necessary for TypeScript
}
}
@Watch('$route.query', { immediate: true })
onRouteQueryChanged(): void {
if (this.actionInProgress) {
return;
}
if (this.anyRequiredQueryParameterInvalid) {
this.error = new Error(this.$t('quick-pay.invalid-parameter-error') as string);
} else {
this.error = null;
this.decideOnActionComponent();
}
}
Expand Down Expand Up @@ -290,7 +299,7 @@ export default class QuickPayRoute extends Mixins(NavigationMixin) {
if (isRecoverableTransferError(error)) {
this.decideToDepositOrOpenChannel();
} else {
this.error = error;
this.actionFailed = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Wrapper } from '@vue/test-utils';
import { shallowMount } from '@vue/test-utils';
import flushPromises from 'flush-promises';
Expand Down
35 changes: 6 additions & 29 deletions raiden-dapp/tests/unit/views/quick-pay-route.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,17 +305,6 @@ describe('QuickPayRoute.vue', () => {
});

describe('action events', () => {
test('hides action message when action starts', async () => {
const wrapper = createWrapper();
const action = wrapper.get('.quick-pay__action__component');
expect(wrapper.find('.quick-pay__action__message').exists()).toBeTruthy();

action.vm.$emit('started');
await wrapper.vm.$nextTick();

expect(wrapper.find('.quick-pay__action__message').exists()).toBeFalsy();
});

describe('redirections', () => {
const originalWindowLocation = global.window.location;
let windowReplaceSpy: jest.Mock;
Expand Down Expand Up @@ -403,28 +392,16 @@ describe('QuickPayRoute.vue', () => {
});

describe('failed action handling', () => {
test('displays action message again on any error', async () => {
const wrapper = createWrapper();
const action = wrapper.get('.quick-pay__action__component');

const error = new Error('Any transfer error');
action.vm.$emit('failed', error);
await wrapper.vm.$nextTick();

const message = wrapper.find('.quick-pay__action__message');
expect(message.exists()).toBeTruthy();
});

test('displays error dialog when error is not recoverable', async () => {
const wrapper = createWrapper();
const action = wrapper.get('.quick-pay__action__component');
test('remains same action when error is not recoverable', async () => {
const wrapper = createWrapper(optionsForStraightTransfer);
const firstAction = wrapper.getComponent(TransferAction);

const error = new Error('The requested target is offline.');
action.vm.$emit('failed', error);
firstAction.vm.$emit('failed', error);
await wrapper.vm.$nextTick();

const errorDialog = wrapper.findComponent(ErrorDialog);
expect(errorDialog.exists()).toBeTruthy();
const secondAction = wrapper.get('.quick-pay__action__component');
expect(secondAction.vm).toEqual(firstAction.vm);
});

describe('can recover from no valid routes found error', () => {
Expand Down

0 comments on commit 4a768d6

Please # to comment.