fix(governance): handle an expected error (#5653)
This commit is contained in:
parent
0660eda334
commit
053775bef6
112
apps/governance/src/lib/party.test.ts
Normal file
112
apps/governance/src/lib/party.test.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
import {
|
||||||
|
PARTY_NOT_FOUND,
|
||||||
|
filterAcceptableGraphqlErrors,
|
||||||
|
isPartyNotFoundError,
|
||||||
|
} from './party';
|
||||||
|
import type { GraphQLError } from 'graphql';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @returns GraphQLError
|
||||||
|
*/
|
||||||
|
function createMockApolloErrors(message: string): GraphQLError {
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
extensions: {
|
||||||
|
code: message.toUpperCase().replace(/ /g, '_'),
|
||||||
|
},
|
||||||
|
locations: [],
|
||||||
|
originalError: new Error(message),
|
||||||
|
path: [],
|
||||||
|
nodes: [],
|
||||||
|
positions: [1],
|
||||||
|
name: message,
|
||||||
|
source: {
|
||||||
|
body: message,
|
||||||
|
name: message,
|
||||||
|
locationOffset: {
|
||||||
|
line: 1,
|
||||||
|
column: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('filterAcceptableGraphqlErrors', () => {
|
||||||
|
it('should return undefined if the error is a party not found error', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [createMockApolloErrors('failed to get party for ID')],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the error if it is not a party not found error', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [createMockApolloErrors('Some other error')],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the error if there are multiple errors', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [
|
||||||
|
createMockApolloErrors('failed to get party for ID'),
|
||||||
|
createMockApolloErrors('Some other error'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the error if there are no errors', () => {
|
||||||
|
const error: Partial<ApolloError> = {
|
||||||
|
graphQLErrors: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = filterAcceptableGraphqlErrors(error as ApolloError);
|
||||||
|
|
||||||
|
expect(result).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if the error is undefined', () => {
|
||||||
|
const result = filterAcceptableGraphqlErrors(undefined);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isPartyNotFoundError', () => {
|
||||||
|
it('should return true if the error message includes PARTY_NOT_FOUND', () => {
|
||||||
|
const error = { message: 'failed to get party for ID' };
|
||||||
|
|
||||||
|
const result = isPartyNotFoundError(error);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the error message does not include PARTY_NOT_FOUND', () => {
|
||||||
|
const error = { message: 'Some other error' };
|
||||||
|
|
||||||
|
const result = isPartyNotFoundError(error);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Will trip if the error message changes, which should not be a problem, but there
|
||||||
|
// might be logic that depends on it
|
||||||
|
it('expects party not found error to remain consistent', () => {
|
||||||
|
const error = 'failed to get party for ID';
|
||||||
|
|
||||||
|
expect(PARTY_NOT_FOUND).toStrictEqual(error);
|
||||||
|
});
|
||||||
|
});
|
@ -1,3 +1,5 @@
|
|||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
|
||||||
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
||||||
|
|
||||||
export const isPartyNotFoundError = (error: { message: string }) => {
|
export const isPartyNotFoundError = (error: { message: string }) => {
|
||||||
@ -6,3 +8,23 @@ export const isPartyNotFoundError = (error: { message: string }) => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a party has no accounts or data, then this GraphQL query believes it does not exist
|
||||||
|
* Not having any rewards is a valid state, so in some cases we can filter this error out.
|
||||||
|
*
|
||||||
|
* @param error ApolloError | undefined
|
||||||
|
* @returns ApolloError | undefined
|
||||||
|
*/
|
||||||
|
export function filterAcceptableGraphqlErrors(
|
||||||
|
error?: ApolloError
|
||||||
|
): ApolloError | undefined {
|
||||||
|
// Currently the only error we expect is when a party has no accounts
|
||||||
|
if (error && error.graphQLErrors.length === 1) {
|
||||||
|
if (isPartyNotFoundError(error.graphQLErrors[0])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
|||||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||||
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
|
||||||
|
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
|
||||||
|
|
||||||
interface VoteButtonsContainerProps {
|
interface VoteButtonsContainerProps {
|
||||||
voteState: VoteState | null;
|
voteState: VoteState | null;
|
||||||
@ -42,8 +43,10 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
|
|||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
<AsyncRenderer loading={loading} error={filteredErrors} data={data}>
|
||||||
<VoteButtons
|
<VoteButtons
|
||||||
{...props}
|
{...props}
|
||||||
currentStakeAvailable={toBigNum(
|
currentStakeAvailable={toBigNum(
|
||||||
|
@ -10,6 +10,7 @@ import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
|
|||||||
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
|
||||||
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
|
||||||
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
import { useNetworkParam } from '@vegaprotocol/network-parameters';
|
||||||
|
import { filterAcceptableGraphqlErrors } from '../../../lib/party';
|
||||||
|
|
||||||
const EPOCHS_PAGE_SIZE = 10;
|
const EPOCHS_PAGE_SIZE = 10;
|
||||||
|
|
||||||
@ -99,17 +100,24 @@ export const EpochIndividualRewards = ({
|
|||||||
prevEpochIdRef.current = epochId;
|
prevEpochIdRef.current = epochId;
|
||||||
}, [epochId, refetchData]);
|
}, [epochId, refetchData]);
|
||||||
|
|
||||||
|
// Workarounds for the error handling of AsyncRenderer
|
||||||
|
const filteredErrors = filterAcceptableGraphqlErrors(error);
|
||||||
|
const filteredData = data || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={filteredErrors}
|
||||||
data={data}
|
data={filteredData}
|
||||||
render={() => (
|
render={() => (
|
||||||
<div>
|
<div>
|
||||||
<p data-testid="connected-vega-key" className="mb-10">
|
<p data-testid="connected-vega-key" className="mb-10">
|
||||||
{t('Connected Vega key')}:{' '}
|
{t('Connected Vega key')}:{' '}
|
||||||
<span className="text-white">{pubKey}</span>
|
<span className="text-white">{pubKey}</span>
|
||||||
</p>
|
</p>
|
||||||
|
{epochIndividualRewardSummaries.length === 0 && (
|
||||||
|
<p>{t('No rewards for key')}</p>
|
||||||
|
)}
|
||||||
{epochIndividualRewardSummaries.map(
|
{epochIndividualRewardSummaries.map(
|
||||||
(epochIndividualRewardSummary) => (
|
(epochIndividualRewardSummary) => (
|
||||||
<EpochIndividualRewardsTable
|
<EpochIndividualRewardsTable
|
||||||
@ -118,6 +126,7 @@ export const EpochIndividualRewards = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
{epochIndividualRewardSummaries.length > 0 && (
|
||||||
<Pagination
|
<Pagination
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
hasPrevPage={page > 1}
|
hasPrevPage={page > 1}
|
||||||
@ -129,6 +138,7 @@ export const EpochIndividualRewards = ({
|
|||||||
>
|
>
|
||||||
{t('Page')} {page}
|
{t('Page')} {page}
|
||||||
</Pagination>
|
</Pagination>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user