Feat/1010: show rejected reason (and error details) on propose form results (#1086)

* feat/1010: Show proposal rejected reason in transaction dialog

* feat/1010: Show wallet rejection details in transaction dialog

* Feat/1010: Updated wallet types

* Feat/1010: Ensuring rejected proposals get the correct transaction dialog title

* Feat/1010: Fixing linting warning

* Feat/1010: Skipping node switcher tests for now
This commit is contained in:
Sam Keen 2022-08-26 16:05:16 +01:00 committed by GitHub
parent 95c1526aa3
commit 171babc2c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 87 additions and 13 deletions

View File

@ -3,7 +3,7 @@ const nodeErrorMsg = 'node-error-message';
const nodeId = 'node-url-0'; const nodeId = 'node-url-0';
const customNodeBtn = 'custom-node'; const customNodeBtn = 'custom-node';
context('Node switcher', function () { context.skip('Node switcher', function () {
beforeEach('visit home page', function () { beforeEach('visit home page', function () {
cy.intercept('GET', 'https://static.vega.xyz/assets/capsule-network.json', { cy.intercept('GET', 'https://static.vega.xyz/assets/capsule-network.json', {
hosts: ['http://localhost:3028/query'], hosts: ['http://localhost:3028/query'],

View File

@ -175,6 +175,7 @@
"noOpenProposals": "There are no open or yet to enact proposals", "noOpenProposals": "There are no open or yet to enact proposals",
"noClosedProposals": "There are no enacted or rejected proposals", "noClosedProposals": "There are no enacted or rejected proposals",
"noRejectedProposals": "No rejected proposals", "noRejectedProposals": "No rejected proposals",
"Proposal rejected": "Proposal rejected",
"participationNotMet": "Participation not met", "participationNotMet": "Participation not met",
"majorityNotMet": "Majority not met", "majorityNotMet": "Majority not met",
"noProposals": "There are no active network change proposals", "noProposals": "There are no active network change proposals",

View File

@ -113,7 +113,22 @@ describe('ProposalForm', () => {
); );
setup(mockSendTx); setup(mockSendTx);
const inputJSON = '{}'; const inputJSON = JSON.stringify({
rationale: {
description: 'Update governance.proposal.freeform.minVoterBalance',
title: 'testing 123',
},
terms: {
updateNetworkParameter: {
changes: {
key: 'governance.proposal.freeform.minVoterBalance',
value: '300',
},
},
closingTimestamp: 1657721401,
enactmentTimestamp: 1657807801,
},
});
fireEvent.change(screen.getByTestId('proposal-data'), { fireEvent.change(screen.getByTestId('proposal-data'), {
target: { value: inputJSON }, target: { value: inputJSON },
}); });
@ -145,7 +160,7 @@ describe('ProposalForm', () => {
}); });
expect(screen.getByTestId('dialog-title')).toHaveTextContent( expect(screen.getByTestId('dialog-title')).toHaveTextContent(
'Proposal submitted' 'Proposal rejected'
); );
}); });

View File

@ -12,6 +12,7 @@ import {
getProposalDialogTitle, getProposalDialogTitle,
} from '../utils'; } from '../utils';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { ProposalState } from '@vegaprotocol/types';
export interface FormFields { export interface FormFields {
proposalData: string; proposalData: string;
@ -71,11 +72,22 @@ export const ProposalForm = () => {
> >
{isSubmitting ? t('Submitting') : t('Submit')} {t('Proposal')} {isSubmitting ? t('Submitting') : t('Submit')} {t('Proposal')}
</Button> </Button>
{finalizedProposal?.rejectionReason ? (
<TransactionDialog
title={t('Proposal rejected')}
intent={getProposalDialogIntent(ProposalState.STATE_REJECTED)}
icon={getProposalDialogIcon(ProposalState.STATE_REJECTED)}
>
<p>{finalizedProposal.rejectionReason}</p>
</TransactionDialog>
) : (
<TransactionDialog <TransactionDialog
title={getProposalDialogTitle(finalizedProposal?.state)} title={getProposalDialogTitle(finalizedProposal?.state)}
intent={getProposalDialogIntent(finalizedProposal?.state)} intent={getProposalDialogIntent(finalizedProposal?.state)}
icon={getProposalDialogIcon(finalizedProposal?.state)} icon={getProposalDialogIcon(finalizedProposal?.state)}
/> />
)}
</form> </form>
); );
}; };

View File

@ -177,6 +177,12 @@ export class RestConnector implements VegaConnector {
} }
if (res.error) { if (res.error) {
if (res.details) {
return {
error: res.error,
details: res.details,
};
}
return { return {
error: res.error, error: res.error,
}; };
@ -230,10 +236,24 @@ export class RestConnector implements VegaConnector {
return t('Something went wrong'); return t('Something went wrong');
} }
/** Parse error details array into a single string */
private parseErrorDetails(err: TransactionError): string | null {
if (err.details && err.details.length > 0) {
return err.details.join(', ');
}
return null;
}
private async request( private async request(
endpoint: Endpoints, endpoint: Endpoints,
options: RequestInit options: RequestInit
): Promise<{ status?: number; data?: unknown; error?: string }> { ): Promise<{
status?: number;
data?: unknown;
error?: string;
details?: string;
}> {
try { try {
const fetchResult = await fetch(`${this.url}/${endpoint}`, { const fetchResult = await fetch(`${this.url}/${endpoint}`, {
...options, ...options,
@ -246,6 +266,16 @@ export class RestConnector implements VegaConnector {
if (!fetchResult.ok) { if (!fetchResult.ok) {
const errorData = await fetchResult.json(); const errorData = await fetchResult.json();
const error = this.parseError(errorData); const error = this.parseError(errorData);
const errorDetails = this.parseErrorDetails(errorData);
if (errorDetails) {
return {
status: fetchResult.status,
error,
details: errorDetails,
};
}
return { return {
status: fetchResult.status, status: fetchResult.status,
error, error,

View File

@ -32,7 +32,9 @@ export interface VegaWalletContextShape {
/** Send a transaction to the network, only order submissions for now */ /** Send a transaction to the network, only order submissions for now */
sendTx: ( sendTx: (
tx: TransactionSubmission tx: TransactionSubmission
) => Promise<TransactionResponse | { error: string }> | null; ) => Promise<
TransactionResponse | { error: string; details?: string[] }
> | null;
} }
export const VegaWalletContext = createContext< export const VegaWalletContext = createContext<

View File

@ -23,6 +23,7 @@ export enum VegaTxStatus {
export interface VegaTxState { export interface VegaTxState {
status: VegaTxStatus; status: VegaTxStatus;
error: string | null; error: string | null;
details?: string[] | null;
txHash: string | null; txHash: string | null;
signature: string | null; signature: string | null;
dialogOpen: boolean; dialogOpen: boolean;
@ -31,6 +32,7 @@ export interface VegaTxState {
export const initialState = { export const initialState = {
status: VegaTxStatus.Default, status: VegaTxStatus.Default,
error: null, error: null,
details: null,
txHash: null, txHash: null,
signature: null, signature: null,
dialogOpen: false, dialogOpen: false,
@ -59,6 +61,7 @@ export const useVegaTransaction = () => {
async (tx: TransactionSubmission) => { async (tx: TransactionSubmission) => {
setTransaction({ setTransaction({
error: null, error: null,
details: null,
txHash: null, txHash: null,
signature: null, signature: null,
status: VegaTxStatus.Requested, status: VegaTxStatus.Requested,
@ -74,7 +77,11 @@ export const useVegaTransaction = () => {
} }
if (isError(res)) { if (isError(res)) {
setTransaction({ error: res.error, status: VegaTxStatus.Error }); setTransaction({
error: res.error,
details: res.details,
status: VegaTxStatus.Error,
});
return; return;
} }
@ -114,11 +121,12 @@ export const useVegaTransaction = () => {
transaction, transaction,
reset, reset,
setComplete, setComplete,
setTransaction,
TransactionDialog, TransactionDialog,
}; };
}; };
const isError = (error: unknown): error is { error: string } => { export const isError = (error: unknown): error is { error: string } => {
if (error !== null && typeof error === 'object' && 'error' in error) { if (error !== null && typeof error === 'object' && 'error' in error) {
return true; return true;
} }

View File

@ -17,6 +17,7 @@ describe('VegaTransactionDialog', () => {
isOpen: true, isOpen: true,
onChange: () => false, onChange: () => false,
transaction: { transaction: {
dialogOpen: true,
status: VegaTxStatus.Requested, status: VegaTxStatus.Requested,
error: null, error: null,
txHash: null, txHash: null,

View File

@ -73,6 +73,9 @@ export const VegaDialog = ({ transaction }: VegaDialogProps) => {
return ( return (
<div data-testid={transaction.status}> <div data-testid={transaction.status}>
<p>{transaction.error && formatLabel(transaction.error)}</p> <p>{transaction.error && formatLabel(transaction.error)}</p>
{transaction.details && (
<p>{formatLabel(transaction.details.join(', '))}</p>
)}
</div> </div>
); );
} }

View File

@ -228,7 +228,7 @@ interface Buy {
export interface ProposalSubmission { export interface ProposalSubmission {
rationale: { rationale: {
description: string; description: string;
title?: string; title: string;
}; };
terms: terms:
| ProposalFreeformTerms | ProposalFreeformTerms
@ -262,7 +262,9 @@ export type TransactionError =
errors: { errors: {
[key: string]: string[]; [key: string]: string[];
}; };
details?: string[];
} }
| { | {
error: string; error: string;
details?: string[];
}; };