Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba0af64bef | ||
|
|
be4fb779ef | ||
|
|
f2eedc434d | ||
|
|
fde49e5446 | ||
|
|
45283161aa | ||
|
|
4eacbbba1c | ||
|
|
375d447fa8 | ||
|
|
1dd97a2bce | ||
|
|
e9188344cf | ||
|
|
16c18b972d | ||
|
|
d4a1a9b193 | ||
|
|
376c5241a8 | ||
|
|
8e6d8517cf |
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: Feature Epic
|
||||||
|
about:
|
||||||
|
A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||||
|
title: 'Epic: '
|
||||||
|
labels: feature-epic
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Feature
|
||||||
|
|
||||||
|
<Name>
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Define high level requirements
|
||||||
|
- [ ] Create basic mockups
|
||||||
|
- [ ] Update "API Requirements" in core spec
|
||||||
|
- [ ] Update "User-Interface Spec" in relevant front end repo
|
||||||
|
- [ ] Create detailed user stories using normal template
|
||||||
|
|
||||||
|
## High Level Requirements
|
||||||
|
|
||||||
|
## Basic Mockups
|
||||||
|
|
||||||
|
## Link to API Requirements in Core spec
|
||||||
|
|
||||||
|
## Link to User Interface Specs
|
||||||
|
|
||||||
|
## Linked User Stories
|
||||||
@@ -29,6 +29,12 @@ jobs:
|
|||||||
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
||||||
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
||||||
echo IS_S3_RELEASE=false >> $GITHUB_ENV
|
echo IS_S3_RELEASE=false >> $GITHUB_ENV
|
||||||
|
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Is dev image
|
||||||
|
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
|
||||||
|
run: |
|
||||||
|
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Is PR
|
- name: Is PR
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
@@ -75,7 +81,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to the Container registry (docker hub)
|
- name: Log in to the Container registry (docker hub)
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -173,7 +179,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
id: dockerhub-push
|
id: dockerhub-push
|
||||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/node-outside-docker.Dockerfile
|
file: docker/node-outside-docker.Dockerfile
|
||||||
@@ -183,7 +189,7 @@ jobs:
|
|||||||
ENV_NAME=${{ env.ENV_NAME }}
|
ENV_NAME=${{ env.ENV_NAME }}
|
||||||
tags: |
|
tags: |
|
||||||
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
||||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
|
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
|
||||||
|
|
||||||
- name: Publish dist as docker image (ghcr - retry)
|
- name: Publish dist as docker image (ghcr - retry)
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
Icon,
|
Icon,
|
||||||
Button,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type { Dispatch, SetStateAction } from 'react';
|
import type { Dispatch, SetStateAction } from 'react';
|
||||||
import { FilterLabel } from './tx-filter-label';
|
import { FilterLabel } from './tx-filter-label';
|
||||||
@@ -100,15 +99,13 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
|
|||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
modal={false}
|
modal={false}
|
||||||
trigger={
|
trigger={
|
||||||
<DropdownMenuTrigger className="ml-0">
|
<DropdownMenuTrigger>
|
||||||
<Button size="xs" data-testid="filter-trigger">
|
<FilterLabel filters={filters} />
|
||||||
<FilterLabel filters={filters} />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{filters.size > 0 ? null : (
|
{filters.size > 1 ? null : (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
|
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const TxsListNavigation = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
disabled={!hasMoreTxs || loading}
|
disabled={!hasMoreTxs}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
nextPage();
|
nextPage();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -86,14 +86,18 @@ describe('Txs infinite list item', () => {
|
|||||||
render(
|
render(
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<TxsInfiniteListItem
|
<table>
|
||||||
type="testType"
|
<tbody>
|
||||||
submitter="testPubKey"
|
<TxsInfiniteListItem
|
||||||
hash="testTxHash"
|
type="testType"
|
||||||
block="1"
|
submitter="testPubKey"
|
||||||
code={0}
|
hash="testTxHash"
|
||||||
command={{}}
|
block="1"
|
||||||
/>
|
code={0}
|
||||||
|
command={{}}
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ const DEFAULT_TRUNCATE_LENGTH = 7;
|
|||||||
|
|
||||||
export function getIdTruncateLength(screen: Screen): number {
|
export function getIdTruncateLength(screen: Screen): number {
|
||||||
if (['xxxl', 'xxl'].includes(screen)) {
|
if (['xxxl', 'xxl'].includes(screen)) {
|
||||||
return 64;
|
|
||||||
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
|
||||||
return 32;
|
return 32;
|
||||||
|
} else if (['xl', 'lg', 'md'].includes(screen)) {
|
||||||
|
return 16;
|
||||||
}
|
}
|
||||||
return DEFAULT_TRUNCATE_LENGTH;
|
return DEFAULT_TRUNCATE_LENGTH;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex
|
|||||||
import { Side } from '@vegaprotocol/types';
|
import { Side } from '@vegaprotocol/types';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
|
||||||
|
const generateHash = (): string =>
|
||||||
|
Array.from(
|
||||||
|
{ length: 64 },
|
||||||
|
() => '0123456789ABCDEF'[Math.floor(Math.random() * 16)]
|
||||||
|
).join('');
|
||||||
|
|
||||||
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
|
||||||
return Array.from(Array(number)).map((_) => ({
|
return Array.from(Array(number)).map((_) => ({
|
||||||
block: '87901',
|
block: '87901',
|
||||||
index: 2,
|
index: 2,
|
||||||
hash: '0F8B98DA0923A50786B852D9CA11E051CACC4C733E1DB93D535C7D81DBD10F6F',
|
hash: generateHash(),
|
||||||
submitter:
|
submitter:
|
||||||
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
|
||||||
type: 'Submit Order',
|
type: 'Submit Order',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Table, TableRow } from '../table';
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||||
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
|
||||||
import { getTxsDataUrl } from '../../hooks/use-txs-data';
|
import { getTxsDataUrl } from '../../hooks/get-txs-data-url';
|
||||||
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
|
||||||
import EmptyList from '../empty-list/empty-list';
|
import EmptyList from '../empty-list/empty-list';
|
||||||
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
import { TxsInfiniteListItem } from './txs-infinite-list-item';
|
||||||
@@ -14,7 +14,7 @@ interface TxsPerBlockProps {
|
|||||||
|
|
||||||
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
|
||||||
const filters = `filters[block.height]=${blockHeight}`;
|
const filters = `filters[block.height]=${blockHeight}`;
|
||||||
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
|
const url = getTxsDataUrl({ filters, count: txCount });
|
||||||
const {
|
const {
|
||||||
state: { data, loading, error },
|
state: { data, loading, error },
|
||||||
} = useFetch<BlockExplorerTransactions>(url);
|
} = useFetch<BlockExplorerTransactions>(url);
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { DATA_SOURCES } from '../config';
|
||||||
|
|
||||||
|
type IGetTxsDataFirstPage = {
|
||||||
|
baseUrl?: string;
|
||||||
|
count?: number;
|
||||||
|
party?: string;
|
||||||
|
filters?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IGetTxsDataPrevious extends IGetTxsDataFirstPage {
|
||||||
|
before: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGetTxsDataNext extends IGetTxsDataFirstPage {
|
||||||
|
after: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type IGetTxsDataUrl =
|
||||||
|
| IGetTxsDataPrevious
|
||||||
|
| IGetTxsDataNext
|
||||||
|
| IGetTxsDataFirstPage;
|
||||||
|
|
||||||
|
export const BE_TXS_PER_REQUEST = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properly encodes the filters and parameters for a request to the block explorer
|
||||||
|
* API for transactions. As the API uses a slightly less common format for encoding
|
||||||
|
* filters, some of it is more manual than you might expect.
|
||||||
|
*
|
||||||
|
* @param params An object containing the pagination and filters
|
||||||
|
* @returns string URL to call
|
||||||
|
*/
|
||||||
|
export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
|
||||||
|
const baseUrl =
|
||||||
|
params.baseUrl || `${DATA_SOURCES.blockExplorerUrl}/transactions`;
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
const count = `${params.count || BE_TXS_PER_REQUEST}`;
|
||||||
|
|
||||||
|
if ('before' in params && params.before?.length > 0) {
|
||||||
|
url.searchParams.append('last', count);
|
||||||
|
url.searchParams.append('before', params.before);
|
||||||
|
} else if ('after' in params && params.after?.length > 0) {
|
||||||
|
url.searchParams.append('first', count);
|
||||||
|
url.searchParams.append('after', params.after);
|
||||||
|
} else {
|
||||||
|
url.searchParams.append('first', count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hacky fix for param as array
|
||||||
|
let urlAsString = url.toString();
|
||||||
|
if (params.filters && params.filters?.length > 0) {
|
||||||
|
urlAsString += '&' + params.filters.replaceAll(' ', '%20');
|
||||||
|
}
|
||||||
|
if (params.party && params.party?.length > 0) {
|
||||||
|
urlAsString += `&filters[tx.submitter]=${params.party}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlAsString;
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { getTxsDataUrl } from './get-txs-data-url'; // import the function to be tested
|
||||||
|
|
||||||
|
describe('getTxsDataUrl', () => {
|
||||||
|
it('should return the correct URL without filters and party', () => {
|
||||||
|
const params = {
|
||||||
|
count: 10,
|
||||||
|
baseUrl: 'https://example.com/transactions',
|
||||||
|
};
|
||||||
|
const expectedUrl = 'https://example.com/transactions?first=10';
|
||||||
|
|
||||||
|
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct URL with "before" in params', () => {
|
||||||
|
const params = {
|
||||||
|
count: 5,
|
||||||
|
before: '100.1',
|
||||||
|
baseUrl: 'https://example.com/transactions',
|
||||||
|
};
|
||||||
|
const expectedUrl = 'https://example.com/transactions?last=5&before=100.1';
|
||||||
|
|
||||||
|
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct URL with "after" in params', () => {
|
||||||
|
const params = {
|
||||||
|
count: 5,
|
||||||
|
after: '222.1',
|
||||||
|
baseUrl: 'https://example.com/transactions',
|
||||||
|
};
|
||||||
|
const expectedUrl = 'https://example.com/transactions?first=5&after=222.1';
|
||||||
|
|
||||||
|
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct URL with filters and party', () => {
|
||||||
|
const params = {
|
||||||
|
count: 10,
|
||||||
|
filters: 'filters[cmd.type]=Made Up Transaction',
|
||||||
|
party: '1234',
|
||||||
|
baseUrl: 'https://example.com/transactions',
|
||||||
|
};
|
||||||
|
const expectedUrl =
|
||||||
|
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
|
||||||
|
|
||||||
|
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,130 +1,141 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import type { URLSearchParamsInit } from 'react-router-dom';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||||
import type {
|
import type {
|
||||||
BlockExplorerTransactionResult,
|
BlockExplorerTransactionResult,
|
||||||
BlockExplorerTransactions,
|
BlockExplorerTransactions,
|
||||||
} from '../routes/types/block-explorer-response';
|
} from '../routes/types/block-explorer-response';
|
||||||
import { DATA_SOURCES } from '../config';
|
|
||||||
import isNumber from 'lodash/isNumber';
|
import isNumber from 'lodash/isNumber';
|
||||||
|
import { AllFilterOptions } from '../components/txs/tx-filter';
|
||||||
|
import type { FilterOption } from '../components/txs/tx-filter';
|
||||||
|
import { BE_TXS_PER_REQUEST, getTxsDataUrl } from './get-txs-data-url';
|
||||||
|
|
||||||
export interface TxsStateProps {
|
export function getTypeFilters(filters?: Set<FilterOption>) {
|
||||||
txsData: BlockExplorerTransactionResult[];
|
if (!filters) {
|
||||||
hasMoreTxs: boolean;
|
return '';
|
||||||
cursor: string;
|
} else if (filters.size > 1) {
|
||||||
previousCursors: string[];
|
return '';
|
||||||
hasPreviousPage: boolean;
|
}
|
||||||
|
|
||||||
|
const forcedSingleFilter = Array.from(filters)[0];
|
||||||
|
return `filters[cmd.type]=${forcedSingleFilter}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUseTxsData {
|
export interface IUseTxsData {
|
||||||
limit: number;
|
count?: number;
|
||||||
filters?: string;
|
before?: string;
|
||||||
|
after?: string;
|
||||||
|
party?: string;
|
||||||
|
filters?: Set<FilterOption>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGetTxsDataUrl {
|
export const useTxsData = ({
|
||||||
limit: string;
|
count = 25,
|
||||||
filters?: string;
|
before,
|
||||||
}
|
after,
|
||||||
|
filters,
|
||||||
|
party,
|
||||||
|
}: IUseTxsData) => {
|
||||||
|
const [, setSearchParams] = useSearchParams();
|
||||||
|
let hasMoreTxs = true;
|
||||||
|
let txsData: BlockExplorerTransactionResult[] = [];
|
||||||
|
|
||||||
export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
|
const url = getTxsDataUrl({
|
||||||
const url = new URL(`${DATA_SOURCES.blockExplorerUrl}/transactions`);
|
filters: getTypeFilters(filters),
|
||||||
|
count,
|
||||||
if (limit) {
|
before,
|
||||||
url.searchParams.append('limit', limit);
|
after,
|
||||||
}
|
party,
|
||||||
|
|
||||||
// Hacky fix for param as array
|
|
||||||
let urlAsString = url.toString();
|
|
||||||
if (filters) {
|
|
||||||
urlAsString += '&' + filters.replace(' ', '%20');
|
|
||||||
}
|
|
||||||
|
|
||||||
return urlAsString;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
|
|
||||||
const [
|
|
||||||
{ txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage },
|
|
||||||
setTxsState,
|
|
||||||
] = useState<TxsStateProps>({
|
|
||||||
txsData: [],
|
|
||||||
hasMoreTxs: false,
|
|
||||||
previousCursors: [],
|
|
||||||
cursor: '',
|
|
||||||
hasPreviousPage: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = getTxsDataUrl({ limit: limit.toString(), filters });
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
state: { data, error, loading },
|
state: { data, error, loading },
|
||||||
refetch,
|
refetch,
|
||||||
} = useFetch<BlockExplorerTransactions>(url, {}, true);
|
} = useFetch<BlockExplorerTransactions>(url, {}, true);
|
||||||
|
|
||||||
useEffect(() => {
|
if (!loading && data && isNumber(data.transactions.length)) {
|
||||||
if (!loading && data && isNumber(data.transactions.length)) {
|
hasMoreTxs = data.transactions.length >= count;
|
||||||
setTxsState((prev) => {
|
txsData = data.transactions;
|
||||||
return {
|
}
|
||||||
...prev,
|
|
||||||
txsData: data.transactions,
|
|
||||||
hasMoreTxs: data.transactions.length >= limit,
|
|
||||||
cursor: data?.transactions.at(-1)?.cursor || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [loading, setTxsState, data, limit]);
|
|
||||||
|
|
||||||
const nextPage = useCallback(() => {
|
const nextPage = useCallback(() => {
|
||||||
const c = data?.transactions.at(0)?.cursor;
|
const after = data?.transactions.at(-1)?.cursor || '';
|
||||||
const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
|
const params: URLSearchParamsInit = { after };
|
||||||
|
if (filters) {
|
||||||
setTxsState((prev) => ({
|
params.filters = Array.from(filters).join(',');
|
||||||
...prev,
|
}
|
||||||
hasPreviousPage: true,
|
setSearchParams(params);
|
||||||
previousCursors: newPreviousCursors,
|
}, [filters, data, setSearchParams]);
|
||||||
}));
|
|
||||||
|
|
||||||
return refetch({
|
|
||||||
limit,
|
|
||||||
before: cursor,
|
|
||||||
});
|
|
||||||
}, [data, previousCursors, cursor, limit, refetch]);
|
|
||||||
|
|
||||||
const previousPage = useCallback(() => {
|
const previousPage = useCallback(() => {
|
||||||
const previousCursor = [...previousCursors].pop();
|
const before = data?.transactions[0]?.cursor || '';
|
||||||
const newPreviousCursors = previousCursors.slice(0, -1);
|
const params: URLSearchParamsInit = { before };
|
||||||
setTxsState((prev) => ({
|
if (filters && filters.size > 0 && filters.size === 1) {
|
||||||
...prev,
|
params.filters = Array.from(filters)[0];
|
||||||
hasPreviousPage: newPreviousCursors.length > 0,
|
}
|
||||||
previousCursors: newPreviousCursors,
|
setSearchParams(params);
|
||||||
}));
|
}, [filters, data, setSearchParams]);
|
||||||
return refetch({
|
|
||||||
limit,
|
|
||||||
before: previousCursor,
|
|
||||||
});
|
|
||||||
}, [previousCursors, limit, refetch]);
|
|
||||||
|
|
||||||
const refreshTxs = useCallback(async () => {
|
const refreshTxs = useCallback(async () => {
|
||||||
setTxsState(() => ({
|
const params: URLSearchParamsInit = {};
|
||||||
txsData: [],
|
if (filters && filters.size > 0 && filters.size === 1) {
|
||||||
cursor: '',
|
params.filters = Array.from(filters)[0];
|
||||||
previousCursors: [],
|
}
|
||||||
hasMoreTxs: false,
|
setSearchParams(params);
|
||||||
hasPreviousPage: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
refetch({ limit });
|
refetch({ count: BE_TXS_PER_REQUEST });
|
||||||
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [setSearchParams, refetch, filters]);
|
||||||
|
|
||||||
|
const updateFilters = useCallback(
|
||||||
|
(newFilters: Set<FilterOption>) => {
|
||||||
|
const params: URLSearchParamsInit = {};
|
||||||
|
if (newFilters && newFilters.size === 1) {
|
||||||
|
params.filters = Array.from(newFilters)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams(params);
|
||||||
|
},
|
||||||
|
[setSearchParams]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
updateFilters,
|
||||||
txsData,
|
txsData,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
hasMoreTxs,
|
hasMoreTxs,
|
||||||
hasPreviousPage,
|
|
||||||
previousCursors,
|
|
||||||
cursor,
|
|
||||||
refreshTxs,
|
refreshTxs,
|
||||||
nextPage,
|
nextPage,
|
||||||
previousPage,
|
previousPage,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Set of filters based on the URLSearchParams, or
|
||||||
|
* defaults to all.
|
||||||
|
* @param params
|
||||||
|
* @returns Set
|
||||||
|
*/
|
||||||
|
export function getInitialFilters(params: URLSearchParams): Set<FilterOption> {
|
||||||
|
const defaultFilters = new Set(AllFilterOptions);
|
||||||
|
|
||||||
|
const p = params.get('filters');
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
return defaultFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters = new Set<FilterOption>();
|
||||||
|
p.split(',').forEach((f) => {
|
||||||
|
if (AllFilterOptions.includes(f as FilterOption)) {
|
||||||
|
filters.add(f as FilterOption);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filters.size === 0) {
|
||||||
|
return defaultFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ describe('Block', () => {
|
|||||||
(useFetch as jest.Mock).mockReturnValue({
|
(useFetch as jest.Mock).mockReturnValue({
|
||||||
state: { data: createBlockResponse(1), loading: false, error: null },
|
state: { data: createBlockResponse(1), loading: false, error: null },
|
||||||
});
|
});
|
||||||
|
|
||||||
render(renderComponent(1));
|
render(renderComponent(1));
|
||||||
await waitFor(() => screen.getByTestId('block-header'));
|
await waitFor(() => screen.getByTestId('block-header'));
|
||||||
expect(screen.getByTestId('previous-block-button')).toHaveAttribute(
|
expect(screen.getByTestId('previous-block-button')).toHaveAttribute(
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ import { PartyBlockAccounts } from './components/party-block-accounts';
|
|||||||
import { isValidPartyId } from './components/party-id-error';
|
import { isValidPartyId } from './components/party-id-error';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||||
|
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
const Party = () => {
|
const Party = () => {
|
||||||
|
const [params] = useSearchParams();
|
||||||
|
|
||||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
||||||
const { party } = useParams<{ party: string }>();
|
const { party } = useParams<{ party: string }>();
|
||||||
|
|
||||||
@@ -27,24 +31,21 @@ const Party = () => {
|
|||||||
const partyId = toNonHex(party ? party : '');
|
const partyId = toNonHex(party ? party : '');
|
||||||
const { isMobile } = useScreenDimensions();
|
const { isMobile } = useScreenDimensions();
|
||||||
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
|
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
|
||||||
const baseFilters = `filters[tx.submitter]=${partyId}`;
|
|
||||||
const f =
|
|
||||||
filters && filters.size === 1
|
|
||||||
? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}`
|
|
||||||
: baseFilters;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasMoreTxs,
|
|
||||||
nextPage,
|
nextPage,
|
||||||
|
refreshTxs,
|
||||||
previousPage,
|
previousPage,
|
||||||
error,
|
error,
|
||||||
refreshTxs,
|
|
||||||
loading,
|
loading,
|
||||||
txsData,
|
txsData,
|
||||||
hasPreviousPage,
|
hasMoreTxs,
|
||||||
|
updateFilters,
|
||||||
} = useTxsData({
|
} = useTxsData({
|
||||||
limit: 25,
|
filters: filters.size === 1 ? filters : undefined,
|
||||||
filters: f,
|
before: params.get('before') || undefined,
|
||||||
|
after: !params.get('before') ? params.get('after') || undefined : undefined,
|
||||||
|
party: partyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||||
@@ -102,15 +103,21 @@ const Party = () => {
|
|||||||
refreshTxs={refreshTxs}
|
refreshTxs={refreshTxs}
|
||||||
nextPage={nextPage}
|
nextPage={nextPage}
|
||||||
previousPage={previousPage}
|
previousPage={previousPage}
|
||||||
hasPreviousPage={hasPreviousPage}
|
hasPreviousPage={true}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
hasMoreTxs={hasMoreTxs}
|
hasMoreTxs={hasMoreTxs}
|
||||||
>
|
>
|
||||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
<TxsFilter
|
||||||
|
filters={filters}
|
||||||
|
setFilters={(f) => {
|
||||||
|
setFilters(f);
|
||||||
|
updateFilters(f as Set<FilterOption>);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</TxsListNavigation>
|
</TxsListNavigation>
|
||||||
{!error && txsData ? (
|
{!error && txsData ? (
|
||||||
<TxsInfiniteList
|
<TxsInfiniteList
|
||||||
hasMoreTxs={hasMoreTxs}
|
hasMoreTxs={true}
|
||||||
areTxsLoading={loading}
|
areTxsLoading={loading}
|
||||||
txs={txsData}
|
txs={txsData}
|
||||||
loadMoreTxs={nextPage}
|
loadMoreTxs={nextPage}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { RouteTitle } from '../../../components/route-title';
|
import { RouteTitle } from '../../../components/route-title';
|
||||||
import { TxsInfiniteList } from '../../../components/txs';
|
import { TxsInfiniteList } from '../../../components/txs';
|
||||||
import { useTxsData } from '../../../hooks/use-txs-data';
|
import { useTxsData, getInitialFilters } from '../../../hooks/use-txs-data';
|
||||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
|
import { TxsFilter } from '../../../components/txs/tx-filter';
|
||||||
|
import type { FilterOption } from '../../../components/txs/tx-filter';
|
||||||
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
const BE_TXS_PER_REQUEST = 25;
|
|
||||||
|
|
||||||
export const TxsList = () => {
|
export const TxsList = () => {
|
||||||
useDocumentTitle(['Transactions']);
|
useDocumentTitle(['Transactions']);
|
||||||
@@ -27,25 +27,22 @@ export const TxsList = () => {
|
|||||||
* @returns {JSX.Element} Transaction List and controls
|
* @returns {JSX.Element} Transaction List and controls
|
||||||
*/
|
*/
|
||||||
export const TxsListFiltered = () => {
|
export const TxsListFiltered = () => {
|
||||||
const [filters, setFilters] = useState(new Set(AllFilterOptions));
|
const [params] = useSearchParams();
|
||||||
|
const [filters, setFilters] = useState(getInitialFilters(params));
|
||||||
const f =
|
|
||||||
filters && filters.size === 1
|
|
||||||
? `filters[cmd.type]=${Array.from(filters)[0]}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hasMoreTxs,
|
|
||||||
nextPage,
|
nextPage,
|
||||||
previousPage,
|
previousPage,
|
||||||
error,
|
error,
|
||||||
refreshTxs,
|
refreshTxs,
|
||||||
loading,
|
loading,
|
||||||
txsData,
|
txsData,
|
||||||
hasPreviousPage,
|
hasMoreTxs,
|
||||||
|
updateFilters,
|
||||||
} = useTxsData({
|
} = useTxsData({
|
||||||
limit: BE_TXS_PER_REQUEST,
|
filters: filters.size === 1 ? filters : undefined,
|
||||||
filters: f,
|
before: params.get('before') || undefined,
|
||||||
|
after: !params.get('before') ? params.get('after') || undefined : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -54,11 +51,17 @@ export const TxsListFiltered = () => {
|
|||||||
refreshTxs={refreshTxs}
|
refreshTxs={refreshTxs}
|
||||||
nextPage={nextPage}
|
nextPage={nextPage}
|
||||||
previousPage={previousPage}
|
previousPage={previousPage}
|
||||||
hasPreviousPage={hasPreviousPage}
|
hasPreviousPage={true}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
hasMoreTxs={hasMoreTxs}
|
hasMoreTxs={hasMoreTxs}
|
||||||
>
|
>
|
||||||
<TxsFilter filters={filters} setFilters={setFilters} />
|
<TxsFilter
|
||||||
|
filters={filters}
|
||||||
|
setFilters={(f) => {
|
||||||
|
setFilters(f);
|
||||||
|
updateFilters(f as Set<FilterOption>);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</TxsListNavigation>
|
</TxsListNavigation>
|
||||||
<TxsInfiniteList
|
<TxsInfiniteList
|
||||||
hasFilters={filters.size > 0}
|
hasFilters={filters.size > 0}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/announcements",
|
"name": "@vegaprotocol/announcements",
|
||||||
"version": "0.0.1"
|
"version": "0.0.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/i18n",
|
"name": "@vegaprotocol/i18n",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"type": "commonjs"
|
"type": "commonjs"
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-114
@@ -7,123 +7,125 @@ import {
|
|||||||
} from './utils';
|
} from './utils';
|
||||||
import * as Types from '@vegaprotocol/types';
|
import * as Types from '@vegaprotocol/types';
|
||||||
|
|
||||||
describe('getOrderToastTitle', () => {
|
describe('WithdrawalsTable', () => {
|
||||||
it('should return the correct title', () => {
|
describe('getOrderToastTitle', () => {
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe(
|
it('should return the correct title', () => {
|
||||||
'Order submitted'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe(
|
||||||
);
|
'Order submitted'
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe(
|
);
|
||||||
'Order filled'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe(
|
||||||
);
|
'Order filled'
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe(
|
);
|
||||||
'Order partially filled'
|
expect(
|
||||||
);
|
getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe(
|
).toBe('Order partially filled');
|
||||||
'Order parked'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe(
|
||||||
);
|
'Order parked'
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe(
|
);
|
||||||
'Order stopped'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe(
|
||||||
);
|
'Order stopped'
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe(
|
);
|
||||||
'Order cancelled'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe(
|
||||||
);
|
'Order cancelled'
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe(
|
);
|
||||||
'Order expired'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe(
|
||||||
);
|
'Order expired'
|
||||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe(
|
);
|
||||||
'Order rejected'
|
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe(
|
||||||
);
|
'Order rejected'
|
||||||
expect(getOrderToastTitle(undefined)).toBe(undefined);
|
);
|
||||||
});
|
expect(getOrderToastTitle(undefined)).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getOrderToastIntent', () => {
|
|
||||||
it('should return the correct intent', () => {
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_PARKED)).toBe(
|
|
||||||
Intent.Warning
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_EXPIRED)).toBe(
|
|
||||||
Intent.Warning
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe(
|
|
||||||
Intent.Warning
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_REJECTED)).toBe(
|
|
||||||
Intent.Danger
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_STOPPED)).toBe(
|
|
||||||
Intent.Danger
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_FILLED)).toBe(
|
|
||||||
Intent.Success
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_ACTIVE)).toBe(
|
|
||||||
Intent.Success
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(Types.OrderStatus.STATUS_CANCELLED)).toBe(
|
|
||||||
Intent.Success
|
|
||||||
);
|
|
||||||
expect(getOrderToastIntent(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getRejectionReason', () => {
|
|
||||||
it('should return the correct rejection reason for insufficient asset balance', () => {
|
|
||||||
expect(
|
|
||||||
getRejectionReason({
|
|
||||||
rejectionReason:
|
|
||||||
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
|
|
||||||
status: Types.OrderStatus.STATUS_REJECTED,
|
|
||||||
id: '',
|
|
||||||
createdAt: undefined,
|
|
||||||
size: '',
|
|
||||||
price: '',
|
|
||||||
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
|
||||||
side: Types.Side.SIDE_BUY,
|
|
||||||
marketId: '',
|
|
||||||
})
|
|
||||||
).toBe('Insufficient asset balance');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the correct rejection reason when order is stopped', () => {
|
describe('getOrderToastIntent', () => {
|
||||||
expect(
|
it('should return the correct intent', () => {
|
||||||
getRejectionReason({
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_PARKED)).toBe(
|
||||||
rejectionReason: null,
|
Intent.Warning
|
||||||
status: Types.OrderStatus.STATUS_STOPPED,
|
);
|
||||||
id: '',
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_EXPIRED)).toBe(
|
||||||
createdAt: undefined,
|
Intent.Warning
|
||||||
size: '',
|
);
|
||||||
price: '',
|
expect(
|
||||||
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
getOrderToastIntent(Types.OrderStatus.STATUS_PARTIALLY_FILLED)
|
||||||
side: Types.Side.SIDE_BUY,
|
).toBe(Intent.Warning);
|
||||||
marketId: '',
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_REJECTED)).toBe(
|
||||||
})
|
Intent.Danger
|
||||||
).toBe(
|
);
|
||||||
'Your Fill or Kill (FOK) order was not filled and it has been stopped'
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_STOPPED)).toBe(
|
||||||
);
|
Intent.Warning
|
||||||
|
);
|
||||||
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_FILLED)).toBe(
|
||||||
|
Intent.Success
|
||||||
|
);
|
||||||
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_ACTIVE)).toBe(
|
||||||
|
Intent.Success
|
||||||
|
);
|
||||||
|
expect(getOrderToastIntent(Types.OrderStatus.STATUS_CANCELLED)).toBe(
|
||||||
|
Intent.Success
|
||||||
|
);
|
||||||
|
expect(getOrderToastIntent(undefined)).toBe(undefined);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('timeInForceLabel', () => {
|
describe('getRejectionReason', () => {
|
||||||
it('should return the correct label for time in force', () => {
|
it('should return the correct rejection reason for insufficient asset balance', () => {
|
||||||
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(
|
expect(
|
||||||
`Fill or Kill (FOK)`
|
getRejectionReason({
|
||||||
);
|
rejectionReason:
|
||||||
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(
|
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
|
||||||
`Good 'til Cancelled (GTC)`
|
status: Types.OrderStatus.STATUS_REJECTED,
|
||||||
);
|
id: '',
|
||||||
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(
|
createdAt: undefined,
|
||||||
`Immediate or Cancel (IOC)`
|
size: '',
|
||||||
);
|
price: '',
|
||||||
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(
|
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||||
`Good 'til Time (GTT)`
|
side: Types.Side.SIDE_BUY,
|
||||||
);
|
marketId: '',
|
||||||
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(
|
})
|
||||||
`Good for Auction (GFA)`
|
).toBe('Insufficient asset balance');
|
||||||
);
|
});
|
||||||
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(
|
|
||||||
`Good for Normal (GFN)`
|
it('should return the correct rejection reason when order is stopped', () => {
|
||||||
);
|
expect(
|
||||||
expect(timeInForceLabel('')).toBe('');
|
getRejectionReason({
|
||||||
|
rejectionReason: null,
|
||||||
|
status: Types.OrderStatus.STATUS_STOPPED,
|
||||||
|
id: '',
|
||||||
|
createdAt: undefined,
|
||||||
|
size: '',
|
||||||
|
price: '',
|
||||||
|
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||||
|
side: Types.Side.SIDE_BUY,
|
||||||
|
marketId: '',
|
||||||
|
})
|
||||||
|
).toBe(
|
||||||
|
'Your Fill or Kill (FOK) order was not filled and it has been stopped'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('timeInForceLabel', () => {
|
||||||
|
it('should return the correct label for time in force', () => {
|
||||||
|
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(
|
||||||
|
`Fill or Kill (FOK)`
|
||||||
|
);
|
||||||
|
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(
|
||||||
|
`Good 'til Cancelled (GTC)`
|
||||||
|
);
|
||||||
|
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(
|
||||||
|
`Immediate or Cancel (IOC)`
|
||||||
|
);
|
||||||
|
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(
|
||||||
|
`Good 'til Time (GTT)`
|
||||||
|
);
|
||||||
|
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(
|
||||||
|
`Good for Auction (GFA)`
|
||||||
|
);
|
||||||
|
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(
|
||||||
|
`Good for Normal (GFN)`
|
||||||
|
);
|
||||||
|
expect(timeInForceLabel('')).toBe('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const getRejectionReason = (
|
|||||||
default:
|
default:
|
||||||
return order.rejectionReason
|
return order.rejectionReason
|
||||||
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
|
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
|
||||||
: null;
|
: '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,9 +79,9 @@ export const getOrderToastIntent = (
|
|||||||
case Schema.OrderStatus.STATUS_PARKED:
|
case Schema.OrderStatus.STATUS_PARKED:
|
||||||
case Schema.OrderStatus.STATUS_EXPIRED:
|
case Schema.OrderStatus.STATUS_EXPIRED:
|
||||||
case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
|
case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
|
||||||
|
case Schema.OrderStatus.STATUS_STOPPED:
|
||||||
return Intent.Warning;
|
return Intent.Warning;
|
||||||
case Schema.OrderStatus.STATUS_REJECTED:
|
case Schema.OrderStatus.STATUS_REJECTED:
|
||||||
case Schema.OrderStatus.STATUS_STOPPED:
|
|
||||||
return Intent.Danger;
|
return Intent.Danger;
|
||||||
case Schema.OrderStatus.STATUS_FILLED:
|
case Schema.OrderStatus.STATUS_FILLED:
|
||||||
case Schema.OrderStatus.STATUS_ACTIVE:
|
case Schema.OrderStatus.STATUS_ACTIVE:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/react-helpers",
|
"name": "@vegaprotocol/react-helpers",
|
||||||
"version": "0.2.4"
|
"version": "0.2.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/tailwindcss-config",
|
"name": "@vegaprotocol/tailwindcss-config",
|
||||||
"version": "0.0.3"
|
"version": "0.0.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
|
|||||||
'.rtl-dir': {
|
'.rtl-dir': {
|
||||||
direction: 'rtl',
|
direction: 'rtl',
|
||||||
},
|
},
|
||||||
|
'.dashed-background': {
|
||||||
|
background: `repeating-linear-gradient(0deg, transparent, transparent 2px, ${colors.neutral[900]} 2px, ${colors.neutral[900]} 4px)`,
|
||||||
|
},
|
||||||
|
'.dark .dashed-background': {
|
||||||
|
background: `repeating-linear-gradient(0deg, transparent, transparent 2px, ${colors.neutral[200]} 2px, ${colors.neutral[200]} 4px)`,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/types",
|
"name": "@vegaprotocol/types",
|
||||||
"version": "0.0.3"
|
"version": "0.0.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/ui-toolkit",
|
"name": "@vegaprotocol/ui-toolkit",
|
||||||
"version": "0.12.6"
|
"version": "0.12.7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,55 @@ const Target = ({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'health-target w-0.5 bg-black group-hover:scale-x-150 group-hover:scale-y-108',
|
'health-target w-0.5 bg-vega-dark-100 dark:bg-vega-light-100 group-hover:scale-x-150 group-hover:scale-y-108',
|
||||||
|
{
|
||||||
|
'h-6': !isLarge,
|
||||||
|
'h-12': isLarge,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AuctionTarget = ({
|
||||||
|
trigger,
|
||||||
|
isLarge,
|
||||||
|
rangeLimit,
|
||||||
|
decimals,
|
||||||
|
}: {
|
||||||
|
isLarge: boolean;
|
||||||
|
trigger: number;
|
||||||
|
rangeLimit: number;
|
||||||
|
decimals: number;
|
||||||
|
}) => {
|
||||||
|
const leftPosition = new BigNumber(trigger).div(rangeLimit).multipliedBy(100);
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
<div className="text-vega-dark-100 dark:text-vega-light-200">
|
||||||
|
<div className="mt-1.5 inline-flex">
|
||||||
|
<Indicator variant={Intent.None} />
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
{t('Auction Trigger stake')}{' '}
|
||||||
|
{addDecimalsFormatNumber(trigger, decimals)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute top-1/2 left-1/2 -translate-x-2/4 -translate-y-1/2 px-1.5 group'
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
left: `${leftPosition}%`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'health-target w-0.5 group-hover:scale-x-150 group-hover:scale-y-108 dashed-background',
|
||||||
{
|
{
|
||||||
'h-6': !isLarge,
|
'h-6': !isLarge,
|
||||||
'h-12': isLarge,
|
'h-12': isLarge,
|
||||||
@@ -133,16 +181,21 @@ export const HealthBar = ({
|
|||||||
levels,
|
levels,
|
||||||
size = 'small',
|
size = 'small',
|
||||||
intent,
|
intent,
|
||||||
|
triggerRatio,
|
||||||
}: {
|
}: {
|
||||||
target: string;
|
target: string;
|
||||||
decimals: number;
|
decimals: number;
|
||||||
levels: Levels[];
|
levels: Levels[];
|
||||||
size?: 'small' | 'large';
|
size?: 'small' | 'large';
|
||||||
intent: Intent;
|
intent: Intent;
|
||||||
|
triggerRatio?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const targetNumber = parseInt(target, 10);
|
const targetNumber = parseInt(target, 10);
|
||||||
const rangeLimit = targetNumber * 2;
|
const rangeLimit = targetNumber * 2;
|
||||||
|
|
||||||
|
const triggerRatioNumber = triggerRatio ? parseFloat(triggerRatio) : 0;
|
||||||
|
const auctionTrigger = targetNumber * triggerRatioNumber;
|
||||||
|
|
||||||
let lastVisibleLevel = 0;
|
let lastVisibleLevel = 0;
|
||||||
const committedNumber = levels
|
const committedNumber = levels
|
||||||
.reduce((total, current, index) => {
|
.reduce((total, current, index) => {
|
||||||
@@ -174,7 +227,10 @@ export const HealthBar = ({
|
|||||||
>
|
>
|
||||||
<Full />
|
<Full />
|
||||||
|
|
||||||
<div className="health-bars h-[inherit] flex w-full gap-0.5">
|
<div
|
||||||
|
className="health-bars h-[inherit] flex w-full
|
||||||
|
gap-0.5 outline outline-vega-light-200 dark:outline-vega-dark-200"
|
||||||
|
>
|
||||||
{levels.map((p, index) => {
|
{levels.map((p, index) => {
|
||||||
const { commitmentAmount, fee } = p;
|
const { commitmentAmount, fee } = p;
|
||||||
const prevLevel = levels[index - 1]?.commitmentAmount;
|
const prevLevel = levels[index - 1]?.commitmentAmount;
|
||||||
@@ -206,6 +262,15 @@ export const HealthBar = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{triggerRatio && (
|
||||||
|
<AuctionTarget
|
||||||
|
isLarge={isLarge}
|
||||||
|
trigger={auctionTrigger}
|
||||||
|
rangeLimit={rangeLimit}
|
||||||
|
decimals={decimals}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Target isLarge={isLarge} target={target} decimals={decimals} />
|
<Target isLarge={isLarge} target={target} decimals={decimals} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@vegaprotocol/utils",
|
"name": "@vegaprotocol/utils",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"type": "commonjs"
|
"type": "commonjs"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import type { Side } from '@vegaprotocol/types';
|
|||||||
import { OrderStatusMapping } from '@vegaprotocol/types';
|
import { OrderStatusMapping } from '@vegaprotocol/types';
|
||||||
import { Size } from '@vegaprotocol/datagrid';
|
import { Size } from '@vegaprotocol/datagrid';
|
||||||
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
|
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
const intentMap: { [s in VegaTxStatus]: Intent } = {
|
const intentMap: { [s in VegaTxStatus]: Intent } = {
|
||||||
Default: Intent.Primary,
|
Default: Intent.Primary,
|
||||||
@@ -508,17 +509,27 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tx.order && tx.order.rejectionReason) {
|
if (tx.order && tx.order.rejectionReason) {
|
||||||
const rejectionReason =
|
const rejectionReason = getRejectionReason(tx.order);
|
||||||
getRejectionReason(tx.order) || tx.order.rejectionReason || '';
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
|
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
|
||||||
{rejectionReason ? (
|
{rejectionReason ? (
|
||||||
<p>
|
<p>
|
||||||
{t('Your order has been rejected because: %s', [rejectionReason])}
|
{t('Your order has been %s because: %s', [
|
||||||
|
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
|
||||||
|
? 'stopped'
|
||||||
|
: 'rejected',
|
||||||
|
rejectionReason,
|
||||||
|
])}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p>{t('Your order has been rejected.')}</p>
|
<p>
|
||||||
|
{t('Your order has been %s.', [
|
||||||
|
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
|
||||||
|
? 'stopped'
|
||||||
|
: 'rejected',
|
||||||
|
])}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
{tx.txHash && (
|
{tx.txHash && (
|
||||||
<p className="break-all">
|
<p className="break-all">
|
||||||
|
|||||||
@@ -18,109 +18,113 @@ const generateJsx = (props: TypedDataAgGrid<WithdrawalFieldsFragment>) => (
|
|||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
describe('renders the correct columns', () => {
|
describe('Withdrawals', () => {
|
||||||
it('incomplete withdrawal', async () => {
|
describe('renders the correct columns', () => {
|
||||||
const withdrawal = generateWithdrawal();
|
it('incomplete withdrawal', async () => {
|
||||||
await act(async () => {
|
const withdrawal = generateWithdrawal();
|
||||||
render(generateJsx({ rowData: [withdrawal] }));
|
await act(async () => {
|
||||||
|
render(generateJsx({ rowData: [withdrawal] }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = screen.getAllByRole('columnheader');
|
||||||
|
expect(headers).toHaveLength(7);
|
||||||
|
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
||||||
|
'Asset',
|
||||||
|
'Amount',
|
||||||
|
'Recipient',
|
||||||
|
'Created',
|
||||||
|
'Completed',
|
||||||
|
'Status',
|
||||||
|
'Transaction',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const cells = screen.getAllByRole('gridcell');
|
||||||
|
const expectedValues = [
|
||||||
|
'asset-symbol',
|
||||||
|
'1.00',
|
||||||
|
'123456…123456',
|
||||||
|
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
|
||||||
|
'-',
|
||||||
|
'Pending',
|
||||||
|
'Complete withdrawal',
|
||||||
|
];
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const headers = screen.getAllByRole('columnheader');
|
it('completed withdrawal', async () => {
|
||||||
expect(headers).toHaveLength(7);
|
const withdrawal = generateWithdrawal({
|
||||||
expect(headers.map((h) => h.textContent?.trim())).toEqual([
|
txHash: '0x1234567891011121314',
|
||||||
'Asset',
|
withdrawnTimestamp: '2022-04-21T00:00:00',
|
||||||
'Amount',
|
status: Schema.WithdrawalStatus.STATUS_FINALIZED,
|
||||||
'Recipient',
|
});
|
||||||
'Created',
|
|
||||||
'Completed',
|
|
||||||
'Status',
|
|
||||||
'Transaction',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const cells = screen.getAllByRole('gridcell');
|
await act(async () => {
|
||||||
const expectedValues = [
|
render(generateJsx({ rowData: [withdrawal] }));
|
||||||
'asset-symbol',
|
});
|
||||||
'1.00',
|
|
||||||
'123456…123456',
|
const cells = screen.getAllByRole('gridcell');
|
||||||
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
|
const expectedValues = [
|
||||||
'-',
|
'asset-symbol',
|
||||||
'Pending',
|
'1.00',
|
||||||
'Complete withdrawal',
|
'123456…123456',
|
||||||
];
|
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
|
||||||
cells.forEach((cell, i) => {
|
getTimeFormat().format(
|
||||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
new Date(withdrawal.withdrawnTimestamp as string)
|
||||||
|
),
|
||||||
|
'Completed',
|
||||||
|
'0x1234…121314',
|
||||||
|
];
|
||||||
|
cells.forEach((cell, i) => {
|
||||||
|
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completed withdrawal', async () => {
|
describe('StatusCell', () => {
|
||||||
const withdrawal = generateWithdrawal({
|
let props: { data: WithdrawalFieldsFragment };
|
||||||
txHash: '0x1234567891011121314',
|
let withdrawal: WithdrawalFieldsFragment;
|
||||||
withdrawnTimestamp: '2022-04-21T00:00:00',
|
|
||||||
status: Schema.WithdrawalStatus.STATUS_FINALIZED,
|
beforeEach(() => {
|
||||||
|
withdrawal = generateWithdrawal();
|
||||||
|
props = {
|
||||||
|
data: withdrawal,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
it('Open', () => {
|
||||||
render(generateJsx({ rowData: [withdrawal] }));
|
props.data.pendingOnForeignChain = false;
|
||||||
|
props.data.txHash = null;
|
||||||
|
render(<StatusCell {...props} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Pending')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
const cells = screen.getAllByRole('gridcell');
|
it('Pending', () => {
|
||||||
const expectedValues = [
|
props.data.pendingOnForeignChain = true;
|
||||||
'asset-symbol',
|
props.data.txHash = '0x123';
|
||||||
'1.00',
|
render(<StatusCell {...props} />);
|
||||||
'123456…123456',
|
|
||||||
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
|
expect(screen.getByText('Pending')).toBeInTheDocument();
|
||||||
getTimeFormat().format(new Date(withdrawal.withdrawnTimestamp as string)),
|
});
|
||||||
'Completed',
|
|
||||||
'0x1234…121314',
|
it('Completed', () => {
|
||||||
];
|
props.data.pendingOnForeignChain = false;
|
||||||
cells.forEach((cell, i) => {
|
props.data.txHash = '0x123';
|
||||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
props.data.status = Schema.WithdrawalStatus.STATUS_FINALIZED;
|
||||||
|
render(<StatusCell {...props} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Completed')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Rejected', () => {
|
||||||
|
props.data.pendingOnForeignChain = false;
|
||||||
|
props.data.txHash = '0x123';
|
||||||
|
props.data.status = Schema.WithdrawalStatus.STATUS_REJECTED;
|
||||||
|
render(<StatusCell {...props} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Rejected')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('StatusCell', () => {
|
|
||||||
let props: { data: WithdrawalFieldsFragment };
|
|
||||||
let withdrawal: WithdrawalFieldsFragment;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
withdrawal = generateWithdrawal();
|
|
||||||
props = {
|
|
||||||
data: withdrawal,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Open', () => {
|
|
||||||
props.data.pendingOnForeignChain = false;
|
|
||||||
props.data.txHash = null;
|
|
||||||
render(<StatusCell {...props} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('Pending')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Pending', () => {
|
|
||||||
props.data.pendingOnForeignChain = true;
|
|
||||||
props.data.txHash = '0x123';
|
|
||||||
render(<StatusCell {...props} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('Pending')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Completed', () => {
|
|
||||||
props.data.pendingOnForeignChain = false;
|
|
||||||
props.data.txHash = '0x123';
|
|
||||||
props.data.status = Schema.WithdrawalStatus.STATUS_FINALIZED;
|
|
||||||
render(<StatusCell {...props} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('Completed')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Rejected', () => {
|
|
||||||
props.data.pendingOnForeignChain = false;
|
|
||||||
props.data.txHash = '0x123';
|
|
||||||
props.data.status = Schema.WithdrawalStatus.STATUS_REJECTED;
|
|
||||||
render(<StatusCell {...props} />);
|
|
||||||
|
|
||||||
expect(screen.getByText('Rejected')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user