Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dbd96ef1f | ||
|
|
7030b3c9cf | ||
|
|
3cca711eea | ||
|
|
5032d87f98 | ||
|
|
b15d9cb7dc | ||
|
|
6aa7cc5bab | ||
|
|
fd69a4915b | ||
|
|
72a2a1be99 | ||
|
|
97d923c94d | ||
|
|
c37f9ebe66 | ||
|
|
2b6e7bcfab | ||
|
|
e4139f6d36 | ||
|
|
768b3b29f0 | ||
|
|
378946f22b | ||
|
|
523598645a | ||
|
|
a9267de653 | ||
|
|
ce22da1c9a | ||
|
|
4033ed960e | ||
|
|
e23dd57ec5 | ||
|
|
acdf209cef | ||
|
|
e0d1412562 | ||
|
|
2460a62a3e | ||
|
|
395d05b4b8 | ||
|
|
f2ad04e126 | ||
|
|
ddaebea67d | ||
|
|
e89e37e5b6 | ||
|
|
bfcd66248c | ||
|
|
f5f214aaff | ||
|
|
44658b00d5 | ||
|
|
32a70a69a3 |
@@ -0,0 +1,16 @@
|
||||
name: 'Check if branch is shorter than 52 chars'
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
branch-naming-rules:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# echo "branches that are longer than 51 chars can't be parsed by kubernetes to create previews. Each app has prefix of it's name like: 'governance-' (12 chars), what leaves 51 max branch length"
|
||||
# current parsable length: $( git rev-parse --abbrev-ref HEAD | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | wc -c)
|
||||
- uses: deepakputhraya/action-branch-name@master
|
||||
with:
|
||||
# regex: '([a-z])+\/([a-z])+' # Regex the branch should match. This example enforces grouping
|
||||
# allowed_prefixes: 'feature,stable,fix' # All branches should start with the given prefix
|
||||
# ignore: master,develop # Ignore exactly matching branch names from convention
|
||||
min_length: 1 # Min length of the branch name
|
||||
max_length: 51 # Max length of the branch name
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
|
||||
echo -n "Affected projects: $affected"
|
||||
|
||||
branch_slug="$(echo ${{ github.head_ref || github.ref_name }} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g )"
|
||||
branch_slug="$(echo ${{ github.head_ref || github.ref_name }} | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
|
||||
projects_e2e=""
|
||||
preview_governance="not deployed"
|
||||
preview_trading="not deployed"
|
||||
|
||||
@@ -89,6 +89,6 @@ export function mockNetworkUpgradeProposal() {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Nodes', nodeData);
|
||||
aliasGQLQuery(req, 'Proposals', proposalsData);
|
||||
aliasGQLQuery(req, 'ProtocolUpgrades', upgradeProposalsData);
|
||||
aliasGQLQuery(req, 'ProtocolUpgradeProposals', upgradeProposalsData);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import Routes from '../routes';
|
||||
import { ExternalLinks, removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||
import { useProtocolUpgradesQuery } from '../proposals/protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import {
|
||||
getNotRejectedProposals,
|
||||
getNotRejectedProtocolUpgradeProposals,
|
||||
@@ -25,7 +24,8 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { RouteChildProps } from '..';
|
||||
import type { ProposalFieldsFragment } from '../proposals/proposals/__generated__/Proposals';
|
||||
import type { NodesFragmentFragment } from '../staking/home/__generated__/Nodes';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../proposals/protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const nodesToShow = 6;
|
||||
|
||||
@@ -181,7 +181,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
|
||||
data: protocolUpgradesData,
|
||||
loading: protocolUpgradesLoading,
|
||||
error: protocolUpgradesError,
|
||||
} = useProtocolUpgradesQuery({
|
||||
} = useProtocolUpgradeProposalsQuery({
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ExternalLinks } from '@vegaprotocol/utils';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
||||
RoundedWrapper,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
|
||||
export interface ProtocolUpgradeProposalDetailInfoProps {
|
||||
proposal: ProtocolUpgradeProposalFieldsFragment;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { ProtocolUpgradeProposalsListItem } from './protocol-upgrade-proposals-list-item';
|
||||
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
|
||||
const proposal = {
|
||||
status:
|
||||
|
||||
+1
-1
@@ -11,8 +11,8 @@ import { stripFullStops } from '@vegaprotocol/utils';
|
||||
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import Routes from '../../../routes';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
|
||||
interface ProtocolProposalsListItemProps {
|
||||
proposal: ProtocolUpgradeProposalFieldsFragment;
|
||||
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
} from '@vegaprotocol/types';
|
||||
import type { NodeConnection, NodeEdge } from '@vegaprotocol/utils';
|
||||
import type { ProposalFieldsFragment } from './__generated__/Proposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { useProtocolUpgradesQuery } from '../protocol-upgrade/__generated__/ProtocolUpgradeProposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const orderByDate = (arr: ProposalFieldsFragment[]) =>
|
||||
orderBy(
|
||||
@@ -73,7 +72,7 @@ export const ProposalsContainer = () => {
|
||||
data: protocolUpgradesData,
|
||||
loading: protocolUpgradesLoading,
|
||||
error: protocolUpgradesError,
|
||||
} = useProtocolUpgradesQuery({
|
||||
} = useProtocolUpgradeProposalsQuery({
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
|
||||
Generated
-59
@@ -1,59 +0,0 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ProtocolUpgradeProposalFieldsFragment = { __typename?: 'ProtocolUpgradeProposal', upgradeBlockHeight: string, vegaReleaseTag: string, approvers: Array<string>, status: Types.ProtocolUpgradeProposalStatus };
|
||||
|
||||
export type ProtocolUpgradesQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ProtocolUpgradesQuery = { __typename?: 'Query', lastBlockHeight: string, protocolUpgradeProposals?: { __typename?: 'ProtocolUpgradeProposalConnection', edges?: Array<{ __typename?: 'ProtocolUpgradeProposalEdge', node: { __typename?: 'ProtocolUpgradeProposal', upgradeBlockHeight: string, vegaReleaseTag: string, approvers: Array<string>, status: Types.ProtocolUpgradeProposalStatus } }> | null } | null };
|
||||
|
||||
export const ProtocolUpgradeProposalFieldsFragmentDoc = gql`
|
||||
fragment ProtocolUpgradeProposalFields on ProtocolUpgradeProposal {
|
||||
upgradeBlockHeight
|
||||
vegaReleaseTag
|
||||
approvers
|
||||
status
|
||||
}
|
||||
`;
|
||||
export const ProtocolUpgradesDocument = gql`
|
||||
query ProtocolUpgrades {
|
||||
lastBlockHeight
|
||||
protocolUpgradeProposals {
|
||||
edges {
|
||||
node {
|
||||
...ProtocolUpgradeProposalFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ProtocolUpgradeProposalFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useProtocolUpgradesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useProtocolUpgradesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useProtocolUpgradesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useProtocolUpgradesQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useProtocolUpgradesQuery(baseOptions?: Apollo.QueryHookOptions<ProtocolUpgradesQuery, ProtocolUpgradesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ProtocolUpgradesQuery, ProtocolUpgradesQueryVariables>(ProtocolUpgradesDocument, options);
|
||||
}
|
||||
export function useProtocolUpgradesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProtocolUpgradesQuery, ProtocolUpgradesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ProtocolUpgradesQuery, ProtocolUpgradesQueryVariables>(ProtocolUpgradesDocument, options);
|
||||
}
|
||||
export type ProtocolUpgradesQueryHookResult = ReturnType<typeof useProtocolUpgradesQuery>;
|
||||
export type ProtocolUpgradesLazyQueryHookResult = ReturnType<typeof useProtocolUpgradesLazyQuery>;
|
||||
export type ProtocolUpgradesQueryResult = Apollo.QueryResult<ProtocolUpgradesQuery, ProtocolUpgradesQueryVariables>;
|
||||
+2
-2
@@ -6,14 +6,14 @@ import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
import { ProtocolUpgradeProposal } from './protocol-upgrade-proposal';
|
||||
import { ProposalNotFound } from '../components/proposal-not-found';
|
||||
import { useProtocolUpgradesQuery } from './__generated__/ProtocolUpgradeProposals';
|
||||
import { useNodesQuery } from '../../staking/home/__generated__/Nodes';
|
||||
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
|
||||
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
export const ProtocolUpgradeProposalContainer = () => {
|
||||
const params = useParams<{ proposalReleaseTag: string }>();
|
||||
|
||||
const { data, loading, error, refetch } = useProtocolUpgradesQuery({
|
||||
const { data, loading, error, refetch } = useProtocolUpgradeProposalsQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
skip: !params.proposalReleaseTag,
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import {
|
||||
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
|
||||
import { getNormalisedVotingPower } from '../../staking/shared';
|
||||
import type { NodesFragmentFragment } from '../../staking/home/__generated__/Nodes';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from './__generated__/ProtocolUpgradeProposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
|
||||
const mockProposal: ProtocolUpgradeProposalFieldsFragment = {
|
||||
vegaReleaseTag: 'v0.1.234',
|
||||
|
||||
@@ -3,8 +3,8 @@ import { ProtocolUpgradeProposalDetailHeader } from '../components/protocol-upgr
|
||||
import { ProtocolUpdateProposalDetailApprovals } from '../components/protocol-upgrade-proposal-detail-approvals';
|
||||
import { ProtocolUpgradeProposalDetailInfo } from '../components/protocol-upgrade-proposal-detail-info';
|
||||
import { getNormalisedVotingPower } from '../../staking/shared';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from './__generated__/ProtocolUpgradeProposals';
|
||||
import type { NodesFragmentFragment } from '../../staking/home/__generated__/Nodes';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
|
||||
export interface ProtocolUpgradeProposalProps {
|
||||
proposal: ProtocolUpgradeProposalFieldsFragment;
|
||||
|
||||
@@ -6,6 +6,7 @@ import '@testing-library/jest-dom';
|
||||
import dev from './i18n/translations/dev.json';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
// Set up i18n instance so that components have the correct default
|
||||
// en translations
|
||||
@@ -22,3 +23,5 @@ i18n.use(initReactI18next).init({
|
||||
ns: ['translations'],
|
||||
defaultNS: 'translations',
|
||||
});
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -40,18 +40,6 @@
|
||||
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||
}
|
||||
|
||||
/* Light variables */
|
||||
.ag-theme-balham {
|
||||
--ag-background-color: theme(colors.white);
|
||||
--ag-border-color: theme(colors.neutral[300]);
|
||||
--ag-header-background-color: theme(colors.white);
|
||||
--ag-odd-row-background-color: theme(colors.white);
|
||||
--ag-header-column-separator-color: theme(colors.neutral[300]);
|
||||
--ag-row-border-color: theme(colors.white);
|
||||
--ag-row-hover-color: theme(colors.neutral[100]);
|
||||
--ag-font-size: 12px;
|
||||
}
|
||||
|
||||
/* Dark variables */
|
||||
.ag-theme-balham-dark {
|
||||
--ag-background-color: theme(colors.black);
|
||||
@@ -63,3 +51,10 @@
|
||||
--ag-row-hover-color: theme(colors.neutral[800]);
|
||||
--ag-font-size: 12px;
|
||||
}
|
||||
|
||||
.validators-table .ag-theme-balham-dark .ag-body-horizontal-scroll {
|
||||
opacity: 0.75;
|
||||
}
|
||||
.validators-table .ag-theme-balham-dark *:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"tranche_end": "2023-05-20T00:00:00.000Z",
|
||||
"total_added": "19242.125",
|
||||
"total_removed": "959.3245960538025",
|
||||
"locked_amount": "10578.4844209587189695125",
|
||||
"locked_amount": "9937.5330975115729338",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "188",
|
||||
@@ -845,7 +845,7 @@
|
||||
"tranche_end": "2023-05-06T00:00:00.000Z",
|
||||
"total_added": "14520",
|
||||
"total_removed": "5079.76864115505",
|
||||
"locked_amount": "1206.465231481482324",
|
||||
"locked_amount": "722.8069444444451328",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "111",
|
||||
@@ -4715,7 +4715,7 @@
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "86666.297",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "51166.9974210106919556663",
|
||||
"locked_amount": "50929.7231498068801776055",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "86666.297",
|
||||
@@ -4781,7 +4781,7 @@
|
||||
"tranche_end": "2023-06-01T00:00:00.000Z",
|
||||
"total_added": "2500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "391.38319724257215",
|
||||
"locked_amount": "377.65663156288155",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "2500",
|
||||
@@ -4814,7 +4814,7 @@
|
||||
"tranche_end": "2023-11-01T00:00:00.000Z",
|
||||
"total_added": "15000.000000000000015",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "14795.60027928744001479560027928744",
|
||||
"locked_amount": "14714.136096014493014714136096014493",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1.5e-14",
|
||||
@@ -4902,7 +4902,7 @@
|
||||
"tranche_end": "2023-09-01T00:00:00.000Z",
|
||||
"total_added": "17500",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "11459.903224386071",
|
||||
"locked_amount": "11364.8616772342995",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "12500",
|
||||
@@ -5169,7 +5169,7 @@
|
||||
"tranche_end": "2023-08-01T00:00:00.000Z",
|
||||
"total_added": "37500",
|
||||
"total_removed": "17836.1796921",
|
||||
"locked_amount": "18541.30457719459875",
|
||||
"locked_amount": "18334.268531307550125",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "7500",
|
||||
@@ -5577,7 +5577,7 @@
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "129999.45",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "51120.313973942497539045",
|
||||
"locked_amount": "50883.256185656170147137",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129999.45",
|
||||
@@ -5610,7 +5610,7 @@
|
||||
"tranche_end": "2024-04-01T00:00:00.000Z",
|
||||
"total_added": "54144.7663",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "49335.74898575644407623565",
|
||||
"locked_amount": "49187.91691173374041346364",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "54144.7663",
|
||||
@@ -5643,7 +5643,7 @@
|
||||
"tranche_end": "2023-09-03T00:00:00.000Z",
|
||||
"total_added": "62600",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "21008.336485286656638",
|
||||
"locked_amount": "20836.950722983256762",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "10000",
|
||||
@@ -5836,7 +5836,7 @@
|
||||
"tranche_end": "2023-09-17T00:00:00.000Z",
|
||||
"total_added": "5000",
|
||||
"total_removed": "0",
|
||||
"locked_amount": "1869.76296930492115",
|
||||
"locked_amount": "1856.07401065448985",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "5000",
|
||||
@@ -6894,7 +6894,7 @@
|
||||
"tranche_end": "2023-06-02T00:00:00.000Z",
|
||||
"total_added": "1939928.38",
|
||||
"total_removed": "1709370.7872515768348",
|
||||
"locked_amount": "156749.9162904687626235986",
|
||||
"locked_amount": "151438.7964147451270015398",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1852091.69",
|
||||
@@ -40766,7 +40766,7 @@
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "3732368.4671",
|
||||
"total_removed": "715655.108029600523393",
|
||||
"locked_amount": "265371.371543329369775774863",
|
||||
"locked_amount": "257209.934903168881888467304",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "1998.95815",
|
||||
@@ -42158,8 +42158,8 @@
|
||||
"tranche_start": "2022-06-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-12-05T00:00:00.000Z",
|
||||
"total_added": "15870102.715470999700000001",
|
||||
"total_removed": "867610.78878511272497352",
|
||||
"locked_amount": "6240677.4306629368200704822080834181008381",
|
||||
"total_removed": "869802.17678213314859352",
|
||||
"locked_amount": "6211737.5003512443867025914959180178169776",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "16249.93",
|
||||
@@ -42713,6 +42713,11 @@
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
"tx": "0xc5cea08b124cdf6264d5b0c7494d63d9de264b429822d9a4e4cfadc43e611a1b"
|
||||
},
|
||||
{
|
||||
"amount": "2191.38799702042362",
|
||||
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
|
||||
"tx": "0xa809dbfa0c31522127924306a6117331a578dfc402cf5fa464832baec46e4ac2"
|
||||
},
|
||||
{
|
||||
"amount": "981.387731774910625",
|
||||
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
|
||||
@@ -46611,6 +46616,12 @@
|
||||
"tranche_id": 2,
|
||||
"tx": "0x9d12dadb940c59a796a405a4971e73f24d7ad284528ec7ce676ae361a4ac1f51"
|
||||
},
|
||||
{
|
||||
"amount": "2191.38799702042362",
|
||||
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
|
||||
"tranche_id": 2,
|
||||
"tx": "0xa809dbfa0c31522127924306a6117331a578dfc402cf5fa464832baec46e4ac2"
|
||||
},
|
||||
{
|
||||
"amount": "2873.74755113623213",
|
||||
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
|
||||
@@ -46883,8 +46894,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "150551.801",
|
||||
"withdrawn_tokens": "89385.81693579122338",
|
||||
"remaining_tokens": "61165.98406420877662"
|
||||
"withdrawn_tokens": "91577.204932811647",
|
||||
"remaining_tokens": "58974.596067188353"
|
||||
},
|
||||
{
|
||||
"address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148",
|
||||
@@ -48747,8 +48758,8 @@
|
||||
"tranche_start": "2021-11-05T00:00:00.000Z",
|
||||
"tranche_end": "2023-05-05T00:00:00.000Z",
|
||||
"total_added": "14597706.0446472999",
|
||||
"total_removed": "6168897.174282186485373182",
|
||||
"locked_amount": "39908.33059608817917720398993360406",
|
||||
"total_removed": "6169862.126766503085603932",
|
||||
"locked_amount": "13191.17171553312990484487088804078",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "129284.449",
|
||||
@@ -49007,6 +49018,11 @@
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0x53d67a8df4d069f956ae1eb76d5e099642dcc8d7e40fa9357be0b8ae879b90c4"
|
||||
},
|
||||
{
|
||||
"amount": "964.95248431660023075",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tx": "0x35f722557b9bc3a2dc314f9fac173013697c484059bd8d5c65497f8df72b9097"
|
||||
},
|
||||
{
|
||||
"amount": "1360.32447632896938825",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
@@ -52349,6 +52365,12 @@
|
||||
"tranche_id": 3,
|
||||
"tx": "0x53d67a8df4d069f956ae1eb76d5e099642dcc8d7e40fa9357be0b8ae879b90c4"
|
||||
},
|
||||
{
|
||||
"amount": "964.95248431660023075",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
"tranche_id": 3,
|
||||
"tx": "0x35f722557b9bc3a2dc314f9fac173013697c484059bd8d5c65497f8df72b9097"
|
||||
},
|
||||
{
|
||||
"amount": "1360.32447632896938825",
|
||||
"user": "0x4Aa3c35F6CC2d507E5C18205ee57099A4C80B19b",
|
||||
@@ -55171,8 +55193,8 @@
|
||||
}
|
||||
],
|
||||
"total_tokens": "359123.469575",
|
||||
"withdrawn_tokens": "357792.4615146437139805",
|
||||
"remaining_tokens": "1331.0080603562860195"
|
||||
"withdrawn_tokens": "358757.41399896031421125",
|
||||
"remaining_tokens": "366.05557603968578875"
|
||||
},
|
||||
{
|
||||
"address": "0xBdd412797c1B78535Afc5F71503b91fAbD0160fB",
|
||||
@@ -58847,7 +58869,7 @@
|
||||
"tranche_end": "2023-06-05T00:00:00.000Z",
|
||||
"total_added": "472355.6199999996",
|
||||
"total_removed": "44078.8527972103416",
|
||||
"locked_amount": "42049.610748470926039581595332312",
|
||||
"locked_amount": "40756.38446007669418433357128362",
|
||||
"deposits": [
|
||||
{
|
||||
"amount": "3000",
|
||||
@@ -144155,6 +144177,11 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "0",
|
||||
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
|
||||
"tx": "0xbc31dbb6851f0e1a856afaedfe96fba29ed357ba1da151eb27c495aa49ab1f39"
|
||||
},
|
||||
{
|
||||
"amount": "0",
|
||||
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
|
||||
@@ -144445,6 +144472,12 @@
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"amount": "0",
|
||||
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
|
||||
"tranche_id": 9,
|
||||
"tx": "0xbc31dbb6851f0e1a856afaedfe96fba29ed357ba1da151eb27c495aa49ab1f39"
|
||||
},
|
||||
{
|
||||
"amount": "0",
|
||||
"user": "0x759C9ABABA492500c4c730bEB568B5b851Dec2c7",
|
||||
|
||||
@@ -9,6 +9,21 @@ describe('market bottom panel', { tags: '@smoke' }, () => {
|
||||
|
||||
it('on xxl screen should be splitted out into two tables', () => {
|
||||
cy.getByTestId('tab-positions').should('have.attr', 'data-state', 'active');
|
||||
cy.getByTestId('tab-open-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-closed-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-rejected-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-accounts').should(
|
||||
@@ -19,7 +34,22 @@ describe('market bottom panel', { tags: '@smoke' }, () => {
|
||||
|
||||
cy.viewport(1801, 1000);
|
||||
cy.getByTestId('tab-positions').should('have.attr', 'data-state', 'active');
|
||||
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'active');
|
||||
cy.getByTestId('tab-open-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-closed-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-rejected-orders').should(
|
||||
'have.attr',
|
||||
'data-state',
|
||||
'inactive'
|
||||
);
|
||||
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
|
||||
cy.getByTestId('tab-accounts').should(
|
||||
'have.attr',
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Partially Filled').click();
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.get(`[row-id="${partiallyFilledId}"]`)
|
||||
.eq(1)
|
||||
@@ -116,7 +116,7 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.get('[col-id="status"] .ag-icon-menu').click();
|
||||
});
|
||||
cy.contains('Reset').click();
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
|
||||
cy.getByTestId('tab-orders')
|
||||
.get(`.ag-center-cols-container [col-id='${orderSymbol}']`)
|
||||
@@ -139,14 +139,15 @@ describe('orders list', { tags: '@smoke', testIsolation: true }, () => {
|
||||
});
|
||||
|
||||
describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
let orderId = '0';
|
||||
beforeEach(() => {
|
||||
const subscriptionMocks = getSubscriptionMocks();
|
||||
cy.spy(subscriptionMocks, 'OrdersUpdate');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('[col-id="status"][role="columnheader"]')
|
||||
.focus()
|
||||
@@ -154,8 +155,8 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
.click();
|
||||
cy.get('.ag-filter-apply-panel-button').click();
|
||||
});
|
||||
orderId = (parseInt(orderId, 10) + 1).toString();
|
||||
});
|
||||
const orderId = '1234567890';
|
||||
// 7002-SORD-053
|
||||
// 7002-SORD-040
|
||||
// 7003-MORD-001
|
||||
@@ -299,7 +300,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
.should('have.text', '200.00');
|
||||
.should('have.text', '-');
|
||||
});
|
||||
|
||||
it('must see the time in force applied to the order', () => {
|
||||
@@ -370,7 +371,7 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
|
||||
cy.mockSubscription(subscriptionMocks);
|
||||
cy.setVegaWallet();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Orders').click();
|
||||
cy.getByTestId('All').click();
|
||||
cy.getByTestId('tab-orders').within(() => {
|
||||
cy.get('[col-id="status"][role="columnheader"]')
|
||||
.focus()
|
||||
|
||||
@@ -6,7 +6,7 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
|
||||
NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"STAGNET3\":\"https://stagnet3.console.vega.xyz\",\"STAGNET1\":\"https://stagnet1.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
|
||||
NX_VEGA_TOKEN_URL=https://validator-testnet.governance.fairground.wtf
|
||||
NX_VEGA_TOKEN_URL=https://validator-testnet.governance.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||
import { MarketInfoAccordionContainer } from '@vegaprotocol/market-info';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { OrderListContainer, Filter } from '@vegaprotocol/orders';
|
||||
import type { OrderListContainerProps } from '@vegaprotocol/orders';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
@@ -58,17 +59,59 @@ const requiresMarket = (View: MarketDependantView) => {
|
||||
};
|
||||
|
||||
const TradingViews = {
|
||||
Candles: requiresMarket(CandlesChartContainer),
|
||||
Depth: requiresMarket(DepthChartContainer),
|
||||
Liquidity: requiresMarket(LiquidityContainer),
|
||||
Ticket: requiresMarket(DealTicketContainer),
|
||||
Info: requiresMarket(MarketInfoAccordionContainer),
|
||||
Orderbook: requiresMarket(OrderbookContainer),
|
||||
Trades: requiresMarket(TradesContainer),
|
||||
Positions: PositionsContainer,
|
||||
Orders: OrderListContainer,
|
||||
Collateral: AccountsContainer,
|
||||
Fills: FillsContainer,
|
||||
candles: {
|
||||
label: 'Candles',
|
||||
component: requiresMarket(CandlesChartContainer),
|
||||
},
|
||||
depth: {
|
||||
label: 'Depth',
|
||||
component: requiresMarket(DepthChartContainer),
|
||||
},
|
||||
liquidity: {
|
||||
label: 'Liquidity',
|
||||
component: requiresMarket(LiquidityContainer),
|
||||
},
|
||||
ticket: {
|
||||
label: 'Ticket',
|
||||
component: requiresMarket(DealTicketContainer),
|
||||
},
|
||||
info: {
|
||||
label: 'Info',
|
||||
component: requiresMarket(MarketInfoAccordionContainer),
|
||||
},
|
||||
orderbook: {
|
||||
label: 'Orderbook',
|
||||
component: requiresMarket(OrderbookContainer),
|
||||
},
|
||||
trades: {
|
||||
label: 'Trades',
|
||||
component: requiresMarket(TradesContainer),
|
||||
},
|
||||
positions: { label: 'Positions', component: PositionsContainer },
|
||||
activeOrders: {
|
||||
label: 'Active',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Open} />
|
||||
),
|
||||
},
|
||||
closedOrders: {
|
||||
label: 'Closed',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Closed} />
|
||||
),
|
||||
},
|
||||
rejectedOrders: {
|
||||
label: 'Rejected',
|
||||
component: (props: OrderListContainerProps) => (
|
||||
<OrderListContainer {...props} filter={Filter.Rejected} />
|
||||
),
|
||||
},
|
||||
orders: {
|
||||
label: 'All',
|
||||
component: OrderListContainer,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
};
|
||||
|
||||
type TradingView = keyof typeof TradingViews;
|
||||
@@ -104,21 +147,59 @@ const MarketBottomPanel = memo(
|
||||
>
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-bottom-left">
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Orders
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketOpenOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketClosedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketRejectOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketAllOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Fills
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
id="marketFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -134,18 +215,20 @@ const MarketBottomPanel = memo(
|
||||
<Tabs storageKey="console-trade-grid-bottom-right">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Positions
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
id="marketPositions"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Collateral
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
noBottomPlaceholder
|
||||
hideButtons
|
||||
id="marketCollateral"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -158,30 +241,75 @@ const MarketBottomPanel = memo(
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Positions onMarketClick={onMarketClick} />
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
id="marketPositions"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Orders
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketOpenOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketClosedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketRejectedOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
enforceBottomPlaceholder
|
||||
id="marketAllOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Fills
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
id="marketFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.Collateral pinnedAsset={pinnedAsset} hideButtons />
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
hideButtons
|
||||
id="marketCollateral"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -221,13 +349,13 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-left">
|
||||
<Tab id="chart" name={t('Chart')}>
|
||||
<TradingViews.Candles marketId={marketId} />
|
||||
<TradingViews.candles.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="depth" name={t('Depth')}>
|
||||
<TradingViews.Depth marketId={marketId} />
|
||||
<TradingViews.depth.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="liquidity" name={t('Liquidity')}>
|
||||
<TradingViews.Liquidity marketId={marketId} />
|
||||
<TradingViews.liquidity.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@@ -240,13 +368,13 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-center">
|
||||
<Tab id="ticket" name={t('Ticket')}>
|
||||
<TradingViews.Ticket
|
||||
<TradingViews.ticket.component
|
||||
marketId={marketId}
|
||||
onClickCollateral={() => navigate('/portfolio')}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="info" name={t('Info')}>
|
||||
<TradingViews.Info marketId={marketId} />
|
||||
<TradingViews.info.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@@ -259,10 +387,10 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-main-right">
|
||||
<Tab id="orderbook" name={t('Orderbook')}>
|
||||
<TradingViews.Orderbook marketId={marketId} />
|
||||
<TradingViews.orderbook.component marketId={marketId} />
|
||||
</Tab>
|
||||
<Tab id="trades" name={t('Trades')}>
|
||||
<TradingViews.Trades marketId={marketId} />
|
||||
<TradingViews.trades.component marketId={marketId} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
@@ -330,7 +458,7 @@ export const TradePanels = ({
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler(true);
|
||||
|
||||
const [view, setView] = useState<TradingView>('Candles');
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
const renderView = () => {
|
||||
const Component = memo<{
|
||||
marketId: string;
|
||||
@@ -339,7 +467,7 @@ export const TradePanels = ({
|
||||
onOrderTypeClick?: (marketId: string) => void;
|
||||
onClickCollateral: () => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}>(TradingViews[view]);
|
||||
}>(TradingViews[view].component);
|
||||
|
||||
if (!Component) {
|
||||
throw new Error(`No component for view: ${view}`);
|
||||
@@ -388,7 +516,7 @@ export const TradePanels = ({
|
||||
className={className}
|
||||
key={key}
|
||||
>
|
||||
{key}
|
||||
{TradingViews[key as keyof typeof TradingViews].label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -264,6 +264,7 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
|
||||
{
|
||||
headerName: t('Market ID'),
|
||||
field: 'id',
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
return cols;
|
||||
@@ -271,12 +272,12 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
id="closedMarkets"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
rowData={rowData}
|
||||
columnDefs={colDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
}}
|
||||
overlayNoRowsTemplate="No data"
|
||||
|
||||
@@ -53,6 +53,7 @@ export const Portfolio = () => {
|
||||
<PositionsContainer
|
||||
onMarketClick={onMarketClick}
|
||||
noBottomPlaceholder
|
||||
id="portfolioPositions"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
@@ -61,12 +62,16 @@ export const Portfolio = () => {
|
||||
<OrderListContainer
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
id="portfolioOrders"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
<FillsContainer
|
||||
onMarketClick={onMarketClick}
|
||||
id="portfolioFills"
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
|
||||
@@ -24,6 +24,10 @@ import {
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
import { SettingsButton } from '../../client-pages/settings';
|
||||
import {
|
||||
ProtocolUpgradeCountdown,
|
||||
ProtocolUpgradeCountdownMode,
|
||||
} from '@vegaprotocol/proposals';
|
||||
|
||||
export const Navbar = ({
|
||||
theme = 'system',
|
||||
@@ -45,11 +49,14 @@ export const Navbar = ({
|
||||
theme={theme}
|
||||
actions={
|
||||
<>
|
||||
<ProtocolUpgradeCountdown
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<SettingsButton />
|
||||
<VegaWalletConnectButton />
|
||||
</>
|
||||
}
|
||||
breakpoints={[521, 1067]}
|
||||
breakpoints={[521, 1122]}
|
||||
>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
|
||||
|
||||
@@ -5,7 +5,5 @@ export const ViewingBanner = () => {
|
||||
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
|
||||
return isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -35,8 +35,12 @@ import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { ENV } from '../lib/config';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { activeOrdersProvider, allOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
import {
|
||||
ProtocolUpgradeCountdownMode,
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -89,7 +93,12 @@ function AppBody({ Component }: AppProps) {
|
||||
<div className={gridClasses}>
|
||||
<AnnouncementBanner />
|
||||
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} />
|
||||
<ViewingBanner />
|
||||
<div data-testid="banners">
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ViewingBanner />
|
||||
</div>
|
||||
<main data-testid={location.pathname}>
|
||||
<Component />
|
||||
</main>
|
||||
@@ -141,6 +150,11 @@ const PartyData = () => {
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
useDataProvider({
|
||||
dataProvider: allOrdersProvider,
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ export const AccountManager = ({
|
||||
variables,
|
||||
});
|
||||
const setId = useCallback(
|
||||
(data: AccountFields) => ({
|
||||
(data: AccountFields, id: string) => ({
|
||||
...data,
|
||||
asset: { ...data.asset, id: `${data.asset.id}-1` },
|
||||
asset: { ...data.asset, id },
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
isNumeric,
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
VegaICellRendererParams,
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { useColumnSizes } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
Button,
|
||||
ButtonLink,
|
||||
@@ -100,10 +101,16 @@ export interface AccountTableProps extends AgGridReactProps {
|
||||
onClickDeposit?: (assetId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const { onGridReady, onColumnResized } = useColumnSizes({
|
||||
id: 'accounts',
|
||||
container: containerRef,
|
||||
});
|
||||
const [openBreakdown, setOpenBreakdown] = useState(false);
|
||||
const [row, setRow] = useState<AccountFields>();
|
||||
const pinnedAssetId = props.pinnedAsset?.id;
|
||||
@@ -126,18 +133,24 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
return currentPinnedAssetRow;
|
||||
}, [pinnedAssetId, props.pinnedAsset, props.rowData]);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
(params: RowHeightParams) =>
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === pinnedAssetId &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
? 32
|
||||
: 24,
|
||||
[pinnedAssetId]
|
||||
const { getRowHeight } = props;
|
||||
|
||||
const getPinnedAssetRowHeight = useCallback(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === pinnedAssetId &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
},
|
||||
[pinnedAssetId, getRowHeight]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full h-full" ref={containerRef}>
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
@@ -148,14 +161,15 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
rowData={props.rowData?.filter(
|
||||
(data) => data.asset.id !== pinnedAssetId
|
||||
)}
|
||||
onGridReady={onGridReady}
|
||||
onColumnResized={onColumnResized}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
comparator: accountValuesComparator,
|
||||
}}
|
||||
getRowHeight={getRowHeight}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedAssetRow ? [pinnedAssetRow] : undefined}
|
||||
>
|
||||
<AgGridColumn
|
||||
@@ -181,7 +195,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
</ButtonLink>
|
||||
);
|
||||
}}
|
||||
maxWidth={300}
|
||||
/>
|
||||
<AgGridColumn
|
||||
headerName={t('Used')}
|
||||
@@ -356,6 +369,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
);
|
||||
}
|
||||
}}
|
||||
flex={1}
|
||||
/>
|
||||
}
|
||||
</AgGrid>
|
||||
@@ -381,7 +395,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,13 +1,50 @@
|
||||
import { Option } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AssetFieldsFragment } from './__generated__/Asset';
|
||||
import classNames from 'classnames';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const AssetOption = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
type AssetOptionProps = {
|
||||
asset: AssetFieldsFragment;
|
||||
balance?: ReactNode;
|
||||
};
|
||||
|
||||
export const Balance = ({
|
||||
balance,
|
||||
symbol,
|
||||
}: {
|
||||
balance?: string;
|
||||
symbol: string;
|
||||
}) =>
|
||||
balance ? (
|
||||
<div className="mt-1 font-alpha">
|
||||
{balance} {symbol}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-vega-orange-500">{t('Fetching balance…')}</div>
|
||||
);
|
||||
|
||||
export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
|
||||
return (
|
||||
<Option key={asset.id} value={asset.id}>
|
||||
<div className="flex flex-col items-start">
|
||||
<span>{asset.name}</span>
|
||||
<div className="text-[10px] font-mono w-full text-left break-all">
|
||||
<span className="text-gray-500">{asset.id} -</span> {asset.symbol}
|
||||
<div className="flex flex-row align-baseline gap-2">
|
||||
<span>{asset.name}</span>{' '}
|
||||
<span
|
||||
className={classNames(
|
||||
'bg-vega-light-200 dark:bg-vega-dark-200',
|
||||
'text-black dark:text-white text-xs',
|
||||
'py-[2px] px-[4px] rounded'
|
||||
)}
|
||||
>
|
||||
{asset.symbol}
|
||||
</span>
|
||||
</div>
|
||||
{balance}
|
||||
<div className="text-[12px] font-mono w-full text-left break-all">
|
||||
<span className="text-vega-light-300 dark:text-vega-dark-300">
|
||||
{asset.id}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Option>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export * from './__generated__/Asset';
|
||||
export * from './__generated__/Assets';
|
||||
export * from './asset-data-provider';
|
||||
export * from './assets-data-provider';
|
||||
export * from './asset-details-dialog';
|
||||
export * from './asset-details-table';
|
||||
export * from './asset-option';
|
||||
export * from './assets-data-provider';
|
||||
export * from './constants';
|
||||
export * from './use-balances-store';
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import type BigNumber from 'bignumber.js';
|
||||
import type { AssetFieldsFragment } from './__generated__/Asset';
|
||||
import { create } from 'zustand';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
type AssetWithBalance = {
|
||||
asset: AssetFieldsFragment;
|
||||
balanceOnEth?: BigNumber;
|
||||
balanceOnVega?: BigNumber;
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
type SetBalanceArgs = Omit<AssetWithBalance, 'updatedAt'> & {
|
||||
ethBalanceFetcher?: () => Promise<BigNumber | undefined>;
|
||||
};
|
||||
|
||||
type BalancesStore = {
|
||||
balances: (AssetWithBalance & { fetchFromEth?: () => void })[];
|
||||
getBalance: (assetId: string) => AssetWithBalance | undefined;
|
||||
setBalance: (args: SetBalanceArgs) => void;
|
||||
refetch: (assetId: string) => void;
|
||||
};
|
||||
|
||||
export const useBalancesStore = create(
|
||||
immer<BalancesStore>((set, get) => ({
|
||||
balances: [],
|
||||
getBalance: (assetId) =>
|
||||
get().balances.find(({ asset: a }) => a.id === assetId),
|
||||
setBalance: ({ asset, balanceOnEth, balanceOnVega, ethBalanceFetcher }) => {
|
||||
set((state) => {
|
||||
const found = state.balances.find(({ asset: a }) => a.id === asset.id);
|
||||
const fetchFromEth = ethBalanceFetcher
|
||||
? () => {
|
||||
if (!ethBalanceFetcher) return;
|
||||
ethBalanceFetcher()
|
||||
.then((balance) => {
|
||||
if (balance) {
|
||||
get().setBalance({ asset, balanceOnEth: balance });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
Sentry.captureException(err);
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (found) {
|
||||
if (balanceOnEth) found.balanceOnEth = balanceOnEth;
|
||||
if (balanceOnVega) found.balanceOnVega = balanceOnVega;
|
||||
if (fetchFromEth) found.fetchFromEth = fetchFromEth;
|
||||
found.updatedAt = Date.now();
|
||||
} else {
|
||||
state.balances.push({
|
||||
asset,
|
||||
balanceOnEth,
|
||||
balanceOnVega,
|
||||
fetchFromEth,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
refetch: (assetId) => {
|
||||
const found = get().balances.find((a) => a.asset.id === assetId);
|
||||
found?.fetchFromEth?.();
|
||||
},
|
||||
}))
|
||||
);
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './lib/ag-grid/ag-grid-lazy';
|
||||
export * from './lib/ag-grid/use-column-sizes';
|
||||
|
||||
export * from './lib/cells/cumulative-vol-cell';
|
||||
export * from './lib/cells/flash-cell';
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import type { ColumnResizedEvent, GridReadyEvent } from 'ag-grid-community';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { useColumnSizes } from './use-column-sizes';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const AgGridThemed = ({
|
||||
style,
|
||||
gridRef,
|
||||
id,
|
||||
children,
|
||||
...props
|
||||
}: (AgGridReactProps | AgReactUiProps) & {
|
||||
style?: React.CSSProperties;
|
||||
gridRef?: React.ForwardedRef<AgGridReact>;
|
||||
id?: string;
|
||||
children?: ReactNode[];
|
||||
}) => {
|
||||
const { theme } = useThemeSwitcher();
|
||||
const defaultProps = {
|
||||
@@ -17,14 +25,16 @@ export const AgGridThemed = ({
|
||||
headerHeight: 22,
|
||||
enableCellTextSelection: true,
|
||||
};
|
||||
|
||||
const wrapperClasses = classNames('vega-ag-grid', {
|
||||
'ag-theme-balham': theme === 'light',
|
||||
'ag-theme-balham-dark': theme === 'dark',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses} style={style}>
|
||||
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
||||
<AgGridReact {...defaultProps} {...props} ref={gridRef}>
|
||||
{children}
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
|
||||
type Props = AgGridReactProps & {
|
||||
style?: React.CSSProperties;
|
||||
gridRef?: React.Ref<AgGridReact>;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export const AgGridLazyInternal = lazy(() =>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { MutableRefObject, ReactElement } from 'react';
|
||||
import type { Column } from 'ag-grid-community';
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useColumnSizes } from './use-column-sizes';
|
||||
|
||||
const mockValueSetter = jest.fn();
|
||||
let mockStore = { sizes: {}, valueSetter: mockValueSetter };
|
||||
jest.mock('zustand', () => ({
|
||||
...jest.requireActual('zustand'),
|
||||
create: () =>
|
||||
jest.fn(() =>
|
||||
jest.fn().mockImplementation((creator) => {
|
||||
return creator(mockStore);
|
||||
})
|
||||
),
|
||||
}));
|
||||
describe('UseColumnsSizes hook', () => {
|
||||
const id = 'testid';
|
||||
const container = {
|
||||
current: { getBoundingClientRect: () => ({ width: 1000 }) },
|
||||
} as MutableRefObject<HTMLDivElement>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
it('should return proper methods', () => {
|
||||
const { result } = renderHook(() => useColumnSizes({ id, container }));
|
||||
expect(result.current).toHaveLength(3);
|
||||
expect(result.current).toStrictEqual([
|
||||
expect.any(Function),
|
||||
expect.any(Function),
|
||||
expect.any(Function),
|
||||
]);
|
||||
});
|
||||
|
||||
it('handleOnChange should fill up store', () => {
|
||||
jest.useFakeTimers();
|
||||
const columns: Column[] = [
|
||||
{ getColId: () => 'col1', getActualWidth: () => 100 },
|
||||
{ getColId: () => 'col2', getActualWidth: () => 200 },
|
||||
] as Column[];
|
||||
const sizeObj = { col1: 100, col2: 200, width: 1000 };
|
||||
const { result } = renderHook(() => useColumnSizes({ id, container }));
|
||||
result.current[0](columns);
|
||||
jest.runAllTimers();
|
||||
expect(mockValueSetter).toHaveBeenCalledWith(id, sizeObj);
|
||||
});
|
||||
|
||||
it('children should be reshaped', () => {
|
||||
mockStore = {
|
||||
sizes: { [id]: { col1: 100, col2: 200, width: 1000 } },
|
||||
valueSetter: mockValueSetter,
|
||||
};
|
||||
const children = [
|
||||
{ props: { colId: 'col1' } },
|
||||
{ props: { colId: 'col2' } },
|
||||
] as ReactElement[];
|
||||
const { result } = renderHook(() => useColumnSizes({ id, container }));
|
||||
expect(result.current[1](children)).toStrictEqual([
|
||||
{ props: { colId: 'col1', width: 100 } },
|
||||
{ props: { colId: 'col2', width: 200 } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('props should be reshaped', () => {
|
||||
mockStore = {
|
||||
sizes: { [id]: { col1: 100, col2: 200, width: 1000 } },
|
||||
valueSetter: mockValueSetter,
|
||||
};
|
||||
const props = { columnDefs: [{ colId: 'col1' }, { colId: 'col2' }] } as
|
||||
| AgGridReactProps
|
||||
| AgReactUiProps;
|
||||
const { result } = renderHook(() => useColumnSizes({ id, container }));
|
||||
expect(result.current[2](props, undefined)).toStrictEqual({
|
||||
columnDefs: [
|
||||
{ colId: 'col1', width: 100 },
|
||||
{ colId: 'col2', width: 200 },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { MutableRefObject, ReactElement } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import type {
|
||||
Column,
|
||||
ColDef,
|
||||
ColumnResizedEvent,
|
||||
GridReadyEvent,
|
||||
} from 'ag-grid-community';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { useResizeObserver } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const STORAGE_KEY = 'vega_columns_sizes_store';
|
||||
const COLUMNS_SET_DEBOUNCE_TIME = 300;
|
||||
|
||||
export const useColumnSizesStore = create<{
|
||||
sizes: Record<string, Record<string, number>>;
|
||||
valueSetter: (id: string, value: Record<string, number>) => void;
|
||||
}>()(
|
||||
persist(
|
||||
immer((set) => ({
|
||||
sizes: {},
|
||||
valueSetter: (id, value) =>
|
||||
set((state) => {
|
||||
state.sizes[id] = {
|
||||
...(state.sizes[id] || {}),
|
||||
...value,
|
||||
};
|
||||
return state;
|
||||
}),
|
||||
})),
|
||||
{ name: STORAGE_KEY }
|
||||
)
|
||||
);
|
||||
|
||||
interface UseColumnSizesProps {
|
||||
id?: string;
|
||||
container?: MutableRefObject<HTMLDivElement | null>;
|
||||
}
|
||||
export const useColumnSizes = ({ id = '', container }: UseColumnSizesProps) => {
|
||||
const sizes = useColumnSizesStore((store) => store.sizes[id] || {});
|
||||
const valueSetter = useColumnSizesStore((store) => store.valueSetter);
|
||||
const getWidthOfAll = useCallback(
|
||||
() =>
|
||||
(container?.current as HTMLDivElement)?.getBoundingClientRect().width ??
|
||||
0,
|
||||
[container]
|
||||
);
|
||||
// const recalculateSizes = useCallback(
|
||||
// (sizes: Record<string, number>) => {
|
||||
// const width = getWidthOfAll();
|
||||
// if (width && sizes['width'] && width !== sizes['width']) {
|
||||
// const oldWidth = sizes['width'];
|
||||
// const ratio = width / oldWidth;
|
||||
// return {
|
||||
// ...Object.entries(sizes).reduce((agg, [key, value]) => {
|
||||
// agg[key] = value * ratio;
|
||||
// return agg;
|
||||
// }, {} as Record<string, number>),
|
||||
// width,
|
||||
// } as Record<string, number>;
|
||||
// }
|
||||
// return sizes;
|
||||
// },
|
||||
// [getWidthOfAll]
|
||||
// );
|
||||
// const [calculatedSizes, setCalculatedSizes] = useState(
|
||||
// recalculateSizes(sizes)
|
||||
// );
|
||||
// const onResize = useCallback(() => {
|
||||
// const width = getWidthOfAll();
|
||||
// if (width && sizes['width'] && width !== sizes['width']) {
|
||||
// setCalculatedSizes(recalculateSizes(sizes));
|
||||
// }
|
||||
// }, [getWidthOfAll, recalculateSizes, sizes]);
|
||||
// useResizeObserver(container?.current as Element, onResize);
|
||||
|
||||
const onColumnResized = useCallback(
|
||||
(event: ColumnResizedEvent) => {
|
||||
if (
|
||||
event.finished &&
|
||||
event.source === 'uiColumnDragged' &&
|
||||
event.columns
|
||||
) {
|
||||
const colState = event.columnApi.getColumnState();
|
||||
const store: { [colId: string]: number } = {};
|
||||
colState.forEach((c) => {
|
||||
if (c.width) {
|
||||
store[c.colId] = c.width;
|
||||
}
|
||||
});
|
||||
valueSetter(id, store);
|
||||
}
|
||||
},
|
||||
[valueSetter, id]
|
||||
);
|
||||
|
||||
const onGridReady = useCallback(
|
||||
(event: GridReadyEvent) => {
|
||||
console.log(event);
|
||||
if (!Object.keys(sizes).length) {
|
||||
event.columnApi.sizeColumnsToFit(getWidthOfAll());
|
||||
} else {
|
||||
const initialSizes = Object.entries(sizes).map(([key, newWidth]) => ({
|
||||
key,
|
||||
newWidth,
|
||||
}));
|
||||
event.columnApi.setColumnWidths(initialSizes);
|
||||
}
|
||||
},
|
||||
[sizes, getWidthOfAll]
|
||||
);
|
||||
|
||||
return {
|
||||
onColumnResized,
|
||||
onGridReady,
|
||||
};
|
||||
};
|
||||
@@ -9,85 +9,90 @@ import {
|
||||
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const SetFilter = forwardRef((props: IFilterParams, ref) => {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
const valueRef = useRef(value);
|
||||
export const SetFilter = forwardRef(
|
||||
(props: IFilterParams & { readonly?: boolean }, ref) => {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
const valueRef = useRef(value);
|
||||
const { readonly } = props;
|
||||
// expose AG Grid Filter Lifecycle callbacks
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
doesFilterPass(params: IDoesFilterPassParams) {
|
||||
const { api, colDef, column, columnApi, context } = props;
|
||||
const { node } = params;
|
||||
const getValue = props.valueGetter({
|
||||
api,
|
||||
colDef,
|
||||
column,
|
||||
columnApi,
|
||||
context,
|
||||
data: node.data,
|
||||
getValue: (field) => node.data[field],
|
||||
node,
|
||||
});
|
||||
return Array.isArray(value)
|
||||
? value.includes(getValue)
|
||||
: getValue === value;
|
||||
},
|
||||
|
||||
// expose AG Grid Filter Lifecycle callbacks
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
doesFilterPass(params: IDoesFilterPassParams) {
|
||||
const { api, colDef, column, columnApi, context } = props;
|
||||
const { node } = params;
|
||||
const getValue = props.valueGetter({
|
||||
api,
|
||||
colDef,
|
||||
column,
|
||||
columnApi,
|
||||
context,
|
||||
data: node.data,
|
||||
getValue: (field) => node.data[field],
|
||||
node,
|
||||
});
|
||||
return Array.isArray(value)
|
||||
? value.includes(getValue)
|
||||
: getValue === value;
|
||||
},
|
||||
isFilterActive() {
|
||||
return valueRef.current.length !== 0;
|
||||
},
|
||||
|
||||
isFilterActive() {
|
||||
return valueRef.current.length !== 0;
|
||||
},
|
||||
getModel() {
|
||||
if (!this.isFilterActive()) {
|
||||
return null;
|
||||
}
|
||||
return { value: valueRef.current };
|
||||
},
|
||||
|
||||
getModel() {
|
||||
if (!this.isFilterActive()) {
|
||||
return null;
|
||||
}
|
||||
return { value: valueRef.current };
|
||||
},
|
||||
setModel(model?: { value: string[] } | null) {
|
||||
valueRef.current = !model ? [] : model.value;
|
||||
setValue(valueRef.current);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
setModel(model?: { value: string[] } | null) {
|
||||
valueRef.current = !model ? [] : model.value;
|
||||
setValue(valueRef.current);
|
||||
},
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
valueRef.current = event.target.checked
|
||||
? [...value, event.target.value]
|
||||
: value.filter((v) => v !== event.target.value);
|
||||
setValue(valueRef.current);
|
||||
};
|
||||
});
|
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
valueRef.current = event.target.checked
|
||||
? [...value, event.target.value]
|
||||
: value.filter((v) => v !== event.target.value);
|
||||
setValue(valueRef.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
props.filterChangedCallback();
|
||||
}, [value]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div className="ag-filter-body-wrapper">
|
||||
<fieldset className="ag-simple-filter-body-wrapper">
|
||||
{Object.keys(props.colDef.filterParams.set).map((key) => (
|
||||
<label className="flex" key={key}>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={key}
|
||||
className="mr-1"
|
||||
checked={value.includes(key)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span>{props.colDef.filterParams.set[key]}</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
<div className="ag-filter-apply-panel">
|
||||
<button
|
||||
type="button"
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => setValue((valueRef.current = []))}
|
||||
>
|
||||
{t('Reset')}
|
||||
</button>
|
||||
useEffect(() => {
|
||||
props.filterChangedCallback();
|
||||
}, [value]); //eslint-disable-line react-hooks/exhaustive-deps
|
||||
return (
|
||||
<div className="ag-filter-body-wrapper">
|
||||
<fieldset className="ag-simple-filter-body-wrapper">
|
||||
{Object.keys(props.colDef.filterParams.set).map((key) => (
|
||||
<label className="flex" key={key}>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={key}
|
||||
disabled={readonly}
|
||||
className="mr-1"
|
||||
checked={value.includes(key)}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<span>{props.colDef.filterParams.set[key]}</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
{!readonly && (
|
||||
<div className="ag-filter-apply-panel">
|
||||
<button
|
||||
type="button"
|
||||
disabled={readonly}
|
||||
className="ag-standard-button ag-filter-apply-panel-button"
|
||||
onClick={() => setValue((valueRef.current = []))}
|
||||
>
|
||||
{t('Reset')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useGetBalanceOfERC20Token } from './use-get-balance-of-erc20-token';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { useBalancesStore } from '@vegaprotocol/assets';
|
||||
import { Balance } from '@vegaprotocol/assets';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
|
||||
import { useTokenContract } from '@vegaprotocol/web3';
|
||||
|
||||
const REFETCH_DELAY = 5000;
|
||||
|
||||
export const AssetBalance = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
const [setBalance, getBalance] = useBalancesStore((state) => [
|
||||
state.setBalance,
|
||||
state.getBalance,
|
||||
]);
|
||||
|
||||
const tokenContract = useTokenContract(
|
||||
isAssetTypeERC20(asset) ? asset.source.contractAddress : undefined
|
||||
);
|
||||
const ethBalanceFetcher = useGetBalanceOfERC20Token(tokenContract, asset);
|
||||
|
||||
const fetchFromEth = useCallback(async () => {
|
||||
const balance = await ethBalanceFetcher();
|
||||
if (balance) {
|
||||
setBalance({ asset, balanceOnEth: balance, ethBalanceFetcher });
|
||||
}
|
||||
}, [asset, ethBalanceFetcher, setBalance]);
|
||||
|
||||
useEffect(() => {
|
||||
const balance = getBalance(asset.id);
|
||||
if (!balance || Date.now() - balance.updatedAt > REFETCH_DELAY) {
|
||||
fetchFromEth();
|
||||
}
|
||||
}, [asset.id, fetchFromEth, getBalance]);
|
||||
|
||||
return (
|
||||
<Balance
|
||||
balance={getBalance(asset.id)?.balanceOnEth?.toString()}
|
||||
symbol={asset.symbol}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -41,6 +41,7 @@ import type { DepositBalances } from './use-deposit-balances';
|
||||
import { FaucetNotification } from './faucet-notification';
|
||||
import { ApproveNotification } from './approve-notification';
|
||||
import { usePersistentDeposit } from './use-persistent-deposit';
|
||||
import { AssetBalance } from './asset-balance';
|
||||
|
||||
interface FormFields {
|
||||
asset: string;
|
||||
@@ -253,9 +254,15 @@ export const DepositForm = ({
|
||||
value={selectedAsset?.id}
|
||||
hasError={Boolean(errors.asset?.message)}
|
||||
>
|
||||
{assets.filter(isAssetTypeERC20).map((a) => (
|
||||
<AssetOption asset={a} key={a.id} />
|
||||
))}
|
||||
{assets
|
||||
.filter((asset) => isAssetTypeERC20(asset))
|
||||
.map((asset) => (
|
||||
<AssetOption
|
||||
asset={asset}
|
||||
key={asset.id}
|
||||
balance={<AssetBalance asset={asset} />}
|
||||
/>
|
||||
))}
|
||||
</RichSelect>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -24,9 +24,10 @@ export const DepositsTable = forwardRef<
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
id="depositTable"
|
||||
ref={ref}
|
||||
overlayNoRowsTemplate={t('No deposits')}
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
suppressCellFocus={true}
|
||||
{...props}
|
||||
@@ -80,6 +81,7 @@ export const DepositsTable = forwardRef<
|
||||
</EtherscanLink>
|
||||
);
|
||||
}}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
export * from './__generated__/Deposit';
|
||||
export * from './asset-balance';
|
||||
export * from './deposit-container';
|
||||
export * from './deposit-dialog';
|
||||
export * from './deposit-form';
|
||||
export * from './deposit-limits';
|
||||
export * from './deposit-manager';
|
||||
export * from './deposits-provider';
|
||||
export * from './deposits-table';
|
||||
export * from './use-deposit-balances';
|
||||
export * from './deposits-provider';
|
||||
export * from './use-get-allowance';
|
||||
export * from './use-get-balance-of-erc20-token';
|
||||
export * from './use-get-deposit-maximum';
|
||||
export * from './use-get-deposited-amount';
|
||||
export * from './use-submit-approval';
|
||||
export * from './use-submit-faucet';
|
||||
export * from './deposit-dialog';
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@vegaprotocol/web3';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { useBalancesStore } from '@vegaprotocol/assets';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useSubmitFaucet = (
|
||||
@@ -25,8 +26,9 @@ export const useSubmitFaucet = (
|
||||
useEffect(() => {
|
||||
if (tx?.status === EthTxStatus.Confirmed) {
|
||||
getBalances();
|
||||
if (asset) useBalancesStore.getState().refetch(asset.id);
|
||||
}
|
||||
}, [tx?.status, getBalances]);
|
||||
}, [tx?.status, getBalances, asset]);
|
||||
|
||||
return {
|
||||
id,
|
||||
|
||||
@@ -2,6 +2,7 @@ import trim from 'lodash/trim';
|
||||
import { useCallback } from 'react';
|
||||
import { Networks } from '../types';
|
||||
import { useEnvironment } from './use-environment';
|
||||
import { stripFullStops } from '@vegaprotocol/utils';
|
||||
|
||||
type Net = Exclude<Networks, 'CUSTOM'>;
|
||||
export enum DApp {
|
||||
@@ -89,15 +90,31 @@ export const useEtherscanLink = () => {
|
||||
// Vega blog
|
||||
export const BLOG = 'https://blog.vega.xyz/';
|
||||
|
||||
// Token pages
|
||||
// Governance pages
|
||||
export const TOKEN_NEW_MARKET_PROPOSAL = '/proposals/propose/new-market';
|
||||
export const TOKEN_NEW_NETWORK_PARAM_PROPOSAL =
|
||||
'/proposals/propose/network-parameter';
|
||||
export const TOKEN_GOVERNANCE = '/proposals';
|
||||
export const TOKEN_PROPOSALS = '/proposals';
|
||||
export const TOKEN_PROPOSAL = '/proposals/:id';
|
||||
export const TOKEN_PROTOCOL_UPGRADE_PROPOSAL =
|
||||
'/proposals/protocol-upgrade/:tag';
|
||||
export const TOKEN_VALIDATOR = '/validators/:id';
|
||||
|
||||
/**
|
||||
* Generates link to the protocol upgrade proposal details on Governance
|
||||
*/
|
||||
export const useProtocolUpgradeProposalLink = () => {
|
||||
const governance = useLinks(DApp.Token);
|
||||
return (releaseTag: string) =>
|
||||
governance(
|
||||
TOKEN_PROTOCOL_UPGRADE_PROPOSAL.replace(
|
||||
':tag',
|
||||
stripFullStops(releaseTag)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Explorer pages
|
||||
export const EXPLORER_TX = '/txs/:hash';
|
||||
export const EXPLORER_ORACLE = '/oracles/:id';
|
||||
|
||||
@@ -6,9 +6,11 @@ import { FillsManager } from './fills-manager';
|
||||
export const FillsContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
id,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
id?: string;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
@@ -25,6 +27,7 @@ export const FillsContainer = ({
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,12 +12,14 @@ interface FillsManagerProps {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const FillsManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
onMarketClick,
|
||||
id,
|
||||
}: FillsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const scrolledToTop = useRef(true);
|
||||
@@ -62,7 +64,7 @@ export const FillsManager = ({
|
||||
scrolledToTop.current = event.top <= 0;
|
||||
}, []);
|
||||
|
||||
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules } =
|
||||
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules, getRowHeight } =
|
||||
useBottomPlaceholder<Trade>({
|
||||
gridRef,
|
||||
});
|
||||
@@ -70,6 +72,7 @@ export const FillsManager = ({
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<FillsTable
|
||||
id={id}
|
||||
ref={gridRef}
|
||||
partyId={partyId}
|
||||
rowModelType="infinite"
|
||||
@@ -82,6 +85,7 @@ export const FillsManager = ({
|
||||
isFullWidthRow={isFullWidthRow}
|
||||
fullWidthCellRenderer={fullWidthCellRenderer}
|
||||
rowClassRules={rowClassRules}
|
||||
getRowHeight={getRowHeight}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
|
||||
@@ -34,6 +34,7 @@ export type Role = typeof TAKER | typeof MAKER | '-';
|
||||
export type Props = (AgGridReactProps | AgReactUiProps) & {
|
||||
partyId: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
@@ -42,7 +43,7 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
overlayNoRowsTemplate={t('No fills')}
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }) => data?.id}
|
||||
tooltipShowDelay={0}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -49,11 +49,11 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
(props, ref) => {
|
||||
return (
|
||||
<AgGrid
|
||||
id="ledgerTable"
|
||||
style={{ width: '100%', height: 'calc(100% - 50px)' }}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
tooltipComponent: TransferTooltipCellComponent,
|
||||
@@ -203,6 +203,7 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
}
|
||||
filterParams={dateRangeFilterParams}
|
||||
filter={DateRangeFilter}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
|
||||
@@ -54,13 +54,13 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
id="liquidityProvisionTable"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||
getRowId={({ data }) => getId(data)}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
minWidth: 100,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -33,11 +33,11 @@ export const MarketListTable = forwardRef<
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return (
|
||||
<AgGrid
|
||||
id="allMarkets"
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={getRowId}
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
@@ -198,7 +198,7 @@ export const MarketListTable = forwardRef<
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<AgGridColumn headerName={t('Market ID')} field="id" />
|
||||
<AgGridColumn headerName={t('Market ID')} field="id" flex={1} />
|
||||
</AgGrid>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,15 +7,14 @@ describe('order data provider', () => {
|
||||
const data = [
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-01-31').toISOString(),
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
id: '2',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
id: '1',
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
},
|
||||
] as Edge<OrderFieldsFragment>[];
|
||||
@@ -24,47 +23,51 @@ describe('order data provider', () => {
|
||||
// this one should be dropped because id don't exits and it's older than newest
|
||||
{
|
||||
id: '0',
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
createdAt: new Date('2022-01-27').toISOString(),
|
||||
},
|
||||
// this one should be dropped because newer below
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-01').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-02').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
updatedAt: new Date('2022-02-04').toISOString(),
|
||||
createdAt: new Date('2022-01-28').toISOString(),
|
||||
},
|
||||
// this should be added
|
||||
{
|
||||
id: '4',
|
||||
createdAt: new Date('2022-02-04').toISOString(),
|
||||
},
|
||||
// this should be move to top
|
||||
{
|
||||
id: '2',
|
||||
updatedAt: new Date('2022-02-03').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
updatedAt: new Date('2022-02-04').toISOString(),
|
||||
createdAt: new Date('2022-01-30').toISOString(),
|
||||
},
|
||||
// this should be added
|
||||
{
|
||||
id: '5',
|
||||
createdAt: new Date('2022-02-05').toISOString(),
|
||||
},
|
||||
] as OrderUpdateFieldsFragment[];
|
||||
|
||||
const updatedData = update(data, delta, () => null, { partyId: '0x123' });
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
expect(updatedData && updatedData[3].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[3].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[4].id);
|
||||
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[5].id);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[4].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
delta[4].updatedAt
|
||||
);
|
||||
expect(update([], delta, () => null, { partyId: '0x123' })?.length).toEqual(
|
||||
4
|
||||
5
|
||||
);
|
||||
});
|
||||
it('add only data matching date range filter', () => {
|
||||
@@ -72,7 +75,6 @@ describe('order data provider', () => {
|
||||
{
|
||||
node: {
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-01-31').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
},
|
||||
@@ -90,12 +92,6 @@ describe('order data provider', () => {
|
||||
id: '0',
|
||||
createdAt: new Date('2022-02-02').toISOString(),
|
||||
},
|
||||
// this one should be removed because it does not match date range
|
||||
{
|
||||
id: '1',
|
||||
updatedAt: new Date('2022-02-02').toISOString(),
|
||||
createdAt: new Date('2022-01-29').toISOString(),
|
||||
},
|
||||
// this one should be updated
|
||||
{
|
||||
id: '2',
|
||||
@@ -118,16 +114,13 @@ describe('order data provider', () => {
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[0].id)
|
||||
).toEqual(-1);
|
||||
expect(
|
||||
updatedData?.findIndex((edge) => edge.node.id === delta[1].id)
|
||||
).toEqual(-1);
|
||||
expect(updatedData && updatedData[0].node.id).toEqual(delta[2].id);
|
||||
expect(updatedData && updatedData[0].node.updatedAt).toEqual(
|
||||
delta[2].updatedAt
|
||||
);
|
||||
expect(updatedData && updatedData[1].node.id).toEqual(delta[3].id);
|
||||
expect(updatedData && updatedData[1].node.updatedAt).toEqual(
|
||||
delta[3].updatedAt
|
||||
expect(updatedData && updatedData[2].node.id).toEqual(delta[1].id);
|
||||
expect(updatedData && updatedData[2].node.updatedAt).toEqual(
|
||||
delta[1].updatedAt
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
paginatedCombineDelta as combineDelta,
|
||||
paginatedCombineInsertionData as combineInsertionData,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import { marketsProvider } from '@vegaprotocol/market-list';
|
||||
@@ -28,6 +26,11 @@ export type Order = Omit<OrderFieldsFragment, 'market'> & {
|
||||
};
|
||||
export type OrderEdge = Edge<Order>;
|
||||
|
||||
const liveOnlyOrderStatuses = [
|
||||
OrderStatus.STATUS_ACTIVE,
|
||||
OrderStatus.STATUS_PARKED,
|
||||
];
|
||||
|
||||
const orderMatchFilters = (
|
||||
order: OrderUpdateFieldsFragment,
|
||||
variables: OrdersQueryVariables
|
||||
@@ -41,6 +44,12 @@ const orderMatchFilters = (
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.filter?.liveOnly &&
|
||||
!(order.status && liveOnlyOrderStatuses.includes(order.status))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.filter?.types &&
|
||||
!(order.type && variables.filter.types.includes(order.type))
|
||||
@@ -58,19 +67,13 @@ const orderMatchFilters = (
|
||||
}
|
||||
if (
|
||||
variables?.filter?.dateRange?.start &&
|
||||
!(
|
||||
(order.updatedAt || order.createdAt) &&
|
||||
variables.filter.dateRange.start < (order.updatedAt || order.createdAt)
|
||||
)
|
||||
!(order.createdAt && variables.filter.dateRange.start < order.createdAt)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
variables?.filter?.dateRange?.end &&
|
||||
!(
|
||||
(order.updatedAt || order.createdAt) &&
|
||||
variables.filter.dateRange.end > (order.updatedAt || order.createdAt)
|
||||
)
|
||||
!(order.createdAt && variables.filter.dateRange.end > order.createdAt)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -120,28 +123,25 @@ export const update = (
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
return produce(data, (draft) => {
|
||||
// A single update can contain the same order with multiple updates, so we need to find
|
||||
// the latest version of the order and only update using that
|
||||
const incoming = uniqBy(
|
||||
// A single update can contain the same order with multiple updates, so we need to find
|
||||
// the latest version of the order and only update using that
|
||||
const incoming = orderBy(
|
||||
uniqBy(
|
||||
orderBy(delta, (order) => order.updatedAt || order.createdAt, 'desc'),
|
||||
'id'
|
||||
);
|
||||
|
||||
),
|
||||
'createdAt'
|
||||
);
|
||||
return produce(data, (draft) => {
|
||||
// Add or update incoming orders
|
||||
incoming.reverse().forEach((node) => {
|
||||
incoming.forEach((node) => {
|
||||
const index = draft.findIndex((edge) => edge.node.id === node.id);
|
||||
const newer =
|
||||
draft.length === 0 ||
|
||||
(node.updatedAt || node.createdAt) >=
|
||||
(draft[0].node.updatedAt || draft[0].node.createdAt);
|
||||
draft.length === 0 || node.createdAt >= draft[0].node.createdAt;
|
||||
const doesFilterPass = !variables || orderMatchFilters(node, variables);
|
||||
if (index !== -1) {
|
||||
if (doesFilterPass) {
|
||||
Object.assign(draft[index].node, node);
|
||||
if (newer) {
|
||||
draft.unshift(...draft.splice(index, 1));
|
||||
}
|
||||
} else {
|
||||
draft.splice(index, 1);
|
||||
}
|
||||
@@ -194,6 +194,34 @@ const ordersProvider = makeDataProvider<
|
||||
additionalContext: { isEnlargedTimeout: true },
|
||||
});
|
||||
|
||||
const allOrderMaxCount = 50000;
|
||||
|
||||
export const allOrdersProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
{ partyId: string; marketId?: string }
|
||||
>(
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
ordersProvider(callback, client, { partyId: variables.partyId }),
|
||||
],
|
||||
(partsData, variables, prevData, parts, subscriptions) => {
|
||||
const orders = partsData[0] as ReturnType<typeof getData>;
|
||||
// load next pages until allOrderMaxCount reached
|
||||
if (
|
||||
!parts[0].isUpdate &&
|
||||
subscriptions &&
|
||||
subscriptions[0].load &&
|
||||
orders?.length < allOrderMaxCount
|
||||
) {
|
||||
subscriptions[0].load();
|
||||
}
|
||||
return variables.marketId
|
||||
? orders.filter((edge) => variables.marketId === edge.node.market.id)
|
||||
: orders;
|
||||
}
|
||||
);
|
||||
|
||||
export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
@@ -204,11 +232,12 @@ export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
ordersProvider(callback, client, {
|
||||
partyId: variables.partyId,
|
||||
filter: {
|
||||
status: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
liveOnly: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
(partsData, variables, prevData, parts, subscriptions) => {
|
||||
// load all pages
|
||||
if (!parts[0].isUpdate && subscriptions && subscriptions[0].load) {
|
||||
subscriptions[0].load();
|
||||
}
|
||||
@@ -220,7 +249,7 @@ export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
);
|
||||
|
||||
export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
(OrderEdge | null)[],
|
||||
(Order | null)[],
|
||||
Order[],
|
||||
OrdersQueryVariables
|
||||
>(
|
||||
@@ -228,18 +257,13 @@ export const ordersWithMarketProvider = makeDerivedDataProvider<
|
||||
ordersProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): OrderEdge[] =>
|
||||
(partsData): Order[] =>
|
||||
((partsData[0] as ReturnType<typeof getData>) || []).map((edge) => ({
|
||||
cursor: edge.cursor,
|
||||
node: {
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
},
|
||||
})),
|
||||
combineDelta<Order, ReturnType<typeof getDelta>['0']>,
|
||||
combineInsertionData<Order>
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
}))
|
||||
);
|
||||
|
||||
export const hasActiveOrderProvider = makeDerivedDataProvider<
|
||||
|
||||
@@ -2,18 +2,25 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { OrderListManager } from './order-list-manager';
|
||||
import type { Filter } from './order-list-manager';
|
||||
|
||||
export interface OrderListContainerProps {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
filter?: Filter;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const OrderListContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
enforceBottomPlaceholder,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
}) => {
|
||||
filter,
|
||||
id,
|
||||
}: OrderListContainerProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
@@ -24,10 +31,12 @@ export const OrderListContainer = ({
|
||||
<OrderListManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
filter={filter}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
isReadOnly={isReadOnly}
|
||||
enforceBottomPlaceholder={enforceBottomPlaceholder}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './order-list-manager';
|
||||
export * from './use-order-list-data';
|
||||
|
||||
@@ -1,25 +1,41 @@
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { FilterChangedEvent, SortChangedEvent } from 'ag-grid-community';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { GridReadyEvent } from 'ag-grid-community';
|
||||
import type { GridReadyEvent, FilterChangedEvent } from 'ag-grid-community';
|
||||
|
||||
import { OrderListTable } from '../order-list/order-list';
|
||||
import { useOrderListData } from './use-order-list-data';
|
||||
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
|
||||
import type { Filter, Sort } from './use-order-list-data';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import {
|
||||
normalizeOrderAmendment,
|
||||
useVegaTransactionStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order, OrderEdge } from '../order-data-provider';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
|
||||
export enum Filter {
|
||||
'Open',
|
||||
'Closed',
|
||||
'Rejected',
|
||||
}
|
||||
|
||||
const FilterStatusValue = {
|
||||
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
[Filter.Closed]: [
|
||||
OrderStatus.STATUS_CANCELLED,
|
||||
OrderStatus.STATUS_EXPIRED,
|
||||
OrderStatus.STATUS_FILLED,
|
||||
OrderStatus.STATUS_PARTIALLY_FILLED,
|
||||
OrderStatus.STATUS_STOPPED,
|
||||
],
|
||||
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
|
||||
};
|
||||
|
||||
export interface OrderListManagerProps {
|
||||
partyId: string;
|
||||
@@ -28,6 +44,8 @@ export interface OrderListManagerProps {
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
enforceBottomPlaceholder?: boolean;
|
||||
filter?: Filter;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
@@ -43,12 +61,6 @@ const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const initialFilter: Filter = {
|
||||
status: {
|
||||
value: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
|
||||
},
|
||||
};
|
||||
|
||||
export const OrderListManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
@@ -56,27 +68,23 @@ export const OrderListManager = ({
|
||||
onOrderTypeClick,
|
||||
isReadOnly,
|
||||
enforceBottomPlaceholder,
|
||||
filter,
|
||||
id,
|
||||
}: OrderListManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [dataCount, setDataCount] = useState(0);
|
||||
const scrolledToTop = useRef(false);
|
||||
const [sort, setSort] = useState<Sort[] | undefined>();
|
||||
const [filter, setFilter] = useState<Filter | undefined>(initialFilter);
|
||||
const filterRef = useRef(initialFilter);
|
||||
const [hasData, setHasData] = useState(false);
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
|
||||
const { data, error, loading, reload } = useOrderListData({
|
||||
partyId,
|
||||
sort,
|
||||
filter,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
const { data, error, loading, reload } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
variables:
|
||||
filter === Filter.Open
|
||||
? { partyId, filter: { liveOnly: true } }
|
||||
: { partyId },
|
||||
});
|
||||
|
||||
const {
|
||||
onSortChanged: bottomPlaceholderOnSortChanged,
|
||||
onFilterChanged: bottomPlaceholderOnFilterChanged,
|
||||
...bottomPlaceholderProps
|
||||
} = useBottomPlaceholder<Order>({
|
||||
@@ -84,42 +92,6 @@ export const OrderListManager = ({
|
||||
disabled: !enforceBottomPlaceholder && !isReadOnly && !hasAmendableOrder,
|
||||
});
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const updatedFilter = event.api.getFilterModel();
|
||||
if (isEqual(updatedFilter, filterRef.current)) {
|
||||
return;
|
||||
}
|
||||
filterRef.current = updatedFilter;
|
||||
if (Object.keys(updatedFilter).length) {
|
||||
setFilter(updatedFilter);
|
||||
} else {
|
||||
setFilter(undefined);
|
||||
}
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
bottomPlaceholderOnFilterChanged?.();
|
||||
},
|
||||
[setFilter, bottomPlaceholderOnFilterChanged]
|
||||
);
|
||||
|
||||
const onSortChange = useCallback(
|
||||
(event: SortChangedEvent) => {
|
||||
const sort = event.columnApi
|
||||
.getColumnState()
|
||||
.sort((a, b) => (a.sortIndex || 0) - (b.sortIndex || 0))
|
||||
.reduce((acc, col) => {
|
||||
if (col.sort) {
|
||||
const { colId, sort } = col;
|
||||
acc.push({ colId, sort });
|
||||
}
|
||||
return acc;
|
||||
}, [] as { colId: string; sort: string }[]);
|
||||
setSort(sort.length > 0 ? sort : undefined);
|
||||
bottomPlaceholderOnSortChanged?.();
|
||||
},
|
||||
[setSort, bottomPlaceholderOnSortChanged]
|
||||
);
|
||||
|
||||
const cancel = useCallback(
|
||||
(order: Order) => {
|
||||
if (!order.market) return;
|
||||
@@ -133,12 +105,30 @@ export const OrderListManager = ({
|
||||
[create]
|
||||
);
|
||||
|
||||
const onGridReady = useCallback(({ api }: GridReadyEvent) => {
|
||||
api.setFilterModel(initialFilter);
|
||||
}, []);
|
||||
const onGridReady = useCallback(
|
||||
({ api }: GridReadyEvent) => {
|
||||
if (filter !== undefined) {
|
||||
api.setFilterModel({
|
||||
status: {
|
||||
value: FilterStatusValue[filter],
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[filter]
|
||||
);
|
||||
|
||||
const onFilterChanged = useCallback(
|
||||
(event: FilterChangedEvent) => {
|
||||
const rowCount = gridRef.current?.api?.getModel().getRowCount();
|
||||
setHasData((rowCount ?? 0) > 0);
|
||||
bottomPlaceholderOnFilterChanged?.();
|
||||
},
|
||||
[bottomPlaceholderOnFilterChanged]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
|
||||
setHasData((gridRef.current?.api?.getModel().getRowCount() ?? 0) > 0);
|
||||
}, [data]);
|
||||
|
||||
const cancelAll = useCallback(() => {
|
||||
@@ -148,30 +138,26 @@ export const OrderListManager = ({
|
||||
},
|
||||
});
|
||||
}, [create, marketId]);
|
||||
const extractedData =
|
||||
data && !loading
|
||||
? data
|
||||
.filter((item) => item !== null)
|
||||
.map((item) => (item as OrderEdge).node)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<OrderListTable
|
||||
rowData={extractedData}
|
||||
id={id}
|
||||
rowData={data as Order[]}
|
||||
ref={gridRef}
|
||||
readonlyStatusFilter={filter !== undefined}
|
||||
onGridReady={onGridReady}
|
||||
onFilterChanged={onFilterChanged}
|
||||
onSortChanged={onSortChange}
|
||||
cancel={cancel}
|
||||
setEditOrder={setEditOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onFilterChanged={onFilterChanged}
|
||||
isReadOnly={isReadOnly}
|
||||
blockLoadDebounceMillis={100}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
suppressAutoSize
|
||||
{...bottomPlaceholderProps}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
@@ -180,7 +166,7 @@ export const OrderListManager = ({
|
||||
error={error}
|
||||
data={data}
|
||||
noDataMessage={t('No orders')}
|
||||
noDataCondition={(data) => !dataCount}
|
||||
noDataCondition={(data) => !hasData}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useOrderListData } from './use-order-list-data';
|
||||
import type { Edge } from '@vegaprotocol/utils';
|
||||
import type { OrderFieldsFragment } from '../order-data-provider/__generated__/Orders';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
|
||||
const loadMock = jest.fn();
|
||||
|
||||
let mockData: Edge<OrderFieldsFragment>[] | null = null;
|
||||
let mockDataProviderData = {
|
||||
data: mockData as (Edge<OrderFieldsFragment> | null)[] | null,
|
||||
error: undefined,
|
||||
loading: true,
|
||||
load: loadMock,
|
||||
totalCount: undefined,
|
||||
};
|
||||
|
||||
let updateMock: jest.Mock;
|
||||
const mockDataProvider = jest.fn((args) => {
|
||||
updateMock = args.update;
|
||||
return mockDataProviderData;
|
||||
});
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useDataProvider: jest.fn((args) => mockDataProvider(args)),
|
||||
}));
|
||||
|
||||
describe('useOrderListData Hook', () => {
|
||||
const mockRefreshAgGridApi = jest.fn();
|
||||
const partyId = 'partyId';
|
||||
const gridRef = {
|
||||
current: {
|
||||
api: {
|
||||
refreshInfiniteCache: mockRefreshAgGridApi,
|
||||
getModel: () => ({ getType: () => 'infinite' }),
|
||||
},
|
||||
} as unknown as AgGridReact,
|
||||
};
|
||||
const scrolledToTop = {
|
||||
current: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return proper dataProvider results', () => {
|
||||
const { result } = renderHook(
|
||||
() => useOrderListData({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current).toMatchObject({
|
||||
data: null,
|
||||
error: undefined,
|
||||
loading: true,
|
||||
addNewRows: expect.any(Function),
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('return proper mocked results', () => {
|
||||
mockData = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_1',
|
||||
createdAt: 1,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_2',
|
||||
createdAt: 2,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
];
|
||||
mockDataProviderData = {
|
||||
...mockDataProviderData,
|
||||
data: mockData,
|
||||
loading: false,
|
||||
};
|
||||
const { result } = renderHook(
|
||||
() => useOrderListData({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
expect(result.current).toMatchObject({
|
||||
data: mockData,
|
||||
error: undefined,
|
||||
loading: false,
|
||||
addNewRows: expect.any(Function),
|
||||
getRows: expect.any(Function),
|
||||
});
|
||||
updateMock({ data: mockData, delta: [] });
|
||||
expect(mockRefreshAgGridApi).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('methods for pagination should work', async () => {
|
||||
const successCallback = jest.fn();
|
||||
mockData = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_1',
|
||||
createdAt: 1,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_2',
|
||||
createdAt: 2,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
];
|
||||
Object.assign(mockDataProviderData, {
|
||||
data: mockData,
|
||||
loading: false,
|
||||
});
|
||||
const mockDelta = [
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_3',
|
||||
createdAt: 3,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
{
|
||||
node: {
|
||||
id: 'data_id_4',
|
||||
createdAt: 4,
|
||||
},
|
||||
} as unknown as Edge<OrderFieldsFragment>,
|
||||
];
|
||||
const mockNextData = [...mockData, ...mockDelta];
|
||||
const { result } = renderHook(
|
||||
() => useOrderListData({ partyId, gridRef, scrolledToTop }),
|
||||
{
|
||||
wrapper: MockedProvider,
|
||||
}
|
||||
);
|
||||
|
||||
const getRowsParams = {
|
||||
successCallback,
|
||||
failCallback: jest.fn(),
|
||||
startRow: 2,
|
||||
endRow: 4,
|
||||
} as unknown as IGetRowsParams;
|
||||
|
||||
await waitFor(async () => {
|
||||
updateMock({ data: mockData });
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
const promise = result.current.getRows(getRowsParams);
|
||||
updateMock({ data: mockNextData, delta: mockDelta });
|
||||
await promise;
|
||||
});
|
||||
expect(loadMock).toHaveBeenCalled();
|
||||
expect(successCallback).toHaveBeenLastCalledWith(
|
||||
mockDelta.map((item) => item.node),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,171 +0,0 @@
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { makeInfiniteScrollGetRows } from '@vegaprotocol/utils';
|
||||
import { useDataProvider, updateGridData } from '@vegaprotocol/react-helpers';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
import type {
|
||||
OrderEdge,
|
||||
Order,
|
||||
} from '../order-data-provider/order-data-provider';
|
||||
import type {
|
||||
OrdersQueryVariables,
|
||||
OrdersUpdateSubscriptionVariables,
|
||||
} from '../order-data-provider/__generated__/Orders';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
export interface Sort {
|
||||
colId: string;
|
||||
sort: string;
|
||||
}
|
||||
export interface Filter {
|
||||
updatedAt?: {
|
||||
value: Types.DateRange;
|
||||
};
|
||||
type?: {
|
||||
value: Types.OrderType[];
|
||||
};
|
||||
status?: {
|
||||
value: Types.OrderStatus[];
|
||||
};
|
||||
timeInForce?: {
|
||||
value: Types.OrderTimeInForce[];
|
||||
};
|
||||
}
|
||||
interface Props {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
filter?: Filter;
|
||||
sort?: Sort[];
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
scrolledToTop: RefObject<boolean>;
|
||||
}
|
||||
|
||||
export const useOrderListData = ({
|
||||
partyId,
|
||||
marketId,
|
||||
sort,
|
||||
filter,
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
}: Props) => {
|
||||
const dataRef = useRef<(OrderEdge | null)[] | null>(null);
|
||||
const totalCountRef = useRef<number | undefined>(undefined);
|
||||
const newRows = useRef(0);
|
||||
const placeholderAdded = useRef(-1);
|
||||
|
||||
const makeBottomPlaceholders = useCallback((order?: Order) => {
|
||||
if (!order) {
|
||||
if (placeholderAdded.current >= 0) {
|
||||
dataRef.current?.splice(placeholderAdded.current, 1);
|
||||
}
|
||||
placeholderAdded.current = -1;
|
||||
} else if (placeholderAdded.current === -1) {
|
||||
dataRef.current?.push({
|
||||
node: { ...order, id: `${order?.id}-1`, isLastPlaceholder: true },
|
||||
});
|
||||
placeholderAdded.current = (dataRef.current?.length || 0) - 1;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const variables = useMemo(() => {
|
||||
// define variable as const to get type safety, using generic with useMemo resulted in lost type safety
|
||||
const allVars: OrdersQueryVariables & OrdersUpdateSubscriptionVariables = {
|
||||
partyId,
|
||||
};
|
||||
if (
|
||||
filter?.updatedAt?.value ||
|
||||
filter?.status?.value.length ||
|
||||
filter?.timeInForce?.value.length ||
|
||||
filter?.type?.value.length
|
||||
) {
|
||||
allVars.filter = {};
|
||||
if (filter?.updatedAt?.value) {
|
||||
allVars.filter.dateRange = filter?.updatedAt?.value;
|
||||
}
|
||||
if (filter?.status?.value.length) {
|
||||
allVars.filter.status = filter?.status?.value;
|
||||
}
|
||||
if (filter?.timeInForce?.value.length) {
|
||||
allVars.filter.timeInForce = filter?.timeInForce?.value;
|
||||
}
|
||||
if (filter?.type?.value.length) {
|
||||
allVars.filter.types = filter?.type?.value;
|
||||
}
|
||||
}
|
||||
return allVars;
|
||||
}, [partyId, filter]);
|
||||
|
||||
const addNewRows = useCallback(() => {
|
||||
if (newRows.current === 0) {
|
||||
return;
|
||||
}
|
||||
if (totalCountRef.current !== undefined) {
|
||||
totalCountRef.current += newRows.current;
|
||||
}
|
||||
newRows.current = 0;
|
||||
gridRef.current?.api?.refreshInfiniteCache();
|
||||
}, [gridRef]);
|
||||
|
||||
const update = useCallback(
|
||||
({
|
||||
data,
|
||||
delta,
|
||||
}: {
|
||||
data: (OrderEdge | null)[] | null;
|
||||
delta?: Order[];
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
if (dataRef.current?.length && delta?.length && !scrolledToTop.current) {
|
||||
const createdAt = dataRef.current?.[0]?.node.createdAt;
|
||||
if (createdAt) {
|
||||
newRows.current += (delta || []).filter(
|
||||
(trade) => trade.createdAt > createdAt
|
||||
).length;
|
||||
}
|
||||
}
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[gridRef, scrolledToTop]
|
||||
);
|
||||
|
||||
const insert = useCallback(
|
||||
({
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
data: (OrderEdge | null)[] | null;
|
||||
totalCount?: number;
|
||||
}) => {
|
||||
totalCountRef.current = totalCount;
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const { data, error, loading, load, totalCount, reload } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
update,
|
||||
insert,
|
||||
variables,
|
||||
});
|
||||
totalCountRef.current = totalCount;
|
||||
|
||||
const getRows = useRef(
|
||||
makeInfiniteScrollGetRows<OrderEdge>(dataRef, totalCountRef, load, newRows)
|
||||
);
|
||||
return {
|
||||
loading,
|
||||
error,
|
||||
data,
|
||||
addNewRows,
|
||||
getRows: getRows.current,
|
||||
reload,
|
||||
makeBottomPlaceholders,
|
||||
};
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridColumn } from 'ag-grid-react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, forwardRef } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
@@ -25,6 +26,7 @@ import type {
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import * as React from 'react';
|
||||
|
||||
type OrderListProps = TypedDataAgGrid<Order> & { marketId?: string };
|
||||
|
||||
@@ -33,20 +35,30 @@ export type OrderListTableProps = OrderListProps & {
|
||||
setEditOrder: (order: Order) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
readonlyStatusFilter?: boolean;
|
||||
isReadOnly: boolean;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export const OrderListTable = memo(
|
||||
export const OrderListTable = memo<
|
||||
OrderListTableProps & { ref?: ForwardedRef<AgGridReact> }
|
||||
>(
|
||||
forwardRef<AgGridReact, OrderListTableProps>(
|
||||
(
|
||||
{ cancel, setEditOrder, onMarketClick, onOrderTypeClick, ...props },
|
||||
{
|
||||
cancel,
|
||||
setEditOrder,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
readonlyStatusFilter,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
@@ -119,6 +131,7 @@ export const OrderListTable = memo(
|
||||
filter={SetFilter}
|
||||
filterParams={{
|
||||
set: Schema.OrderStatusMapping,
|
||||
readonly: readonlyStatusFilter,
|
||||
}}
|
||||
valueFormatter={({
|
||||
value,
|
||||
@@ -154,7 +167,6 @@ export const OrderListTable = memo(
|
||||
valueFormatter={({
|
||||
data,
|
||||
value,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'remaining'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
@@ -180,7 +192,6 @@ export const OrderListTable = memo(
|
||||
valueFormatter={({
|
||||
value,
|
||||
data,
|
||||
node,
|
||||
}: VegaValueFormatterParams<Order, 'price'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
@@ -229,8 +240,8 @@ export const OrderListTable = memo(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="createdAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<Order, 'createdAt'>) => {
|
||||
return (
|
||||
@@ -243,7 +254,6 @@ export const OrderListTable = memo(
|
||||
/>
|
||||
<AgGridColumn
|
||||
field="updatedAt"
|
||||
filter={DateRangeFilter}
|
||||
cellRenderer={({
|
||||
data,
|
||||
value,
|
||||
@@ -282,6 +292,7 @@ export const OrderListTable = memo(
|
||||
) : null;
|
||||
}}
|
||||
sortable={false}
|
||||
flex={1}
|
||||
/>
|
||||
</AgGrid>
|
||||
);
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -6,9 +6,11 @@ import { PositionsManager } from './positions-manager';
|
||||
export const PositionsContainer = ({
|
||||
onMarketClick,
|
||||
noBottomPlaceholder,
|
||||
id,
|
||||
}: {
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
noBottomPlaceholder?: boolean;
|
||||
id?: string;
|
||||
}) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
|
||||
@@ -25,6 +27,7 @@ export const PositionsContainer = ({
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
noBottomPlaceholder={noBottomPlaceholder}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ interface PositionsManagerProps {
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
isReadOnly: boolean;
|
||||
noBottomPlaceholder?: boolean;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const PositionsManager = ({
|
||||
@@ -20,6 +21,7 @@ export const PositionsManager = ({
|
||||
onMarketClick,
|
||||
isReadOnly,
|
||||
noBottomPlaceholder,
|
||||
id,
|
||||
}: PositionsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data, error, loading, reload } = usePositionsData(partyId, gridRef);
|
||||
@@ -55,10 +57,10 @@ export const PositionsManager = ({
|
||||
},
|
||||
});
|
||||
|
||||
const setId = useCallback((data: Position) => {
|
||||
const setId = useCallback((data: Position, id: string) => {
|
||||
return {
|
||||
...data,
|
||||
marketId: `${data.marketId}-1`,
|
||||
marketId: id,
|
||||
};
|
||||
}, []);
|
||||
const bottomPlaceholderProps = useBottomPlaceholder<Position>({
|
||||
@@ -82,6 +84,7 @@ export const PositionsManager = ({
|
||||
onFilterChanged={updateRowCount}
|
||||
onRowDataUpdated={updateRowCount}
|
||||
{...bottomPlaceholderProps}
|
||||
id={id}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
|
||||
@@ -46,6 +46,7 @@ interface Props extends TypedDataAgGrid<Position> {
|
||||
onMarketClick?: (id: string, metaKey?: boolean) => void;
|
||||
style?: CSSProperties;
|
||||
isReadOnly: boolean;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface AmountCellProps {
|
||||
@@ -89,7 +90,6 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
@@ -383,6 +383,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
|
||||
) : null
|
||||
}
|
||||
minWidth={80}
|
||||
flex={1}
|
||||
/>
|
||||
) : null}
|
||||
</AgGrid>
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './asset-proposal-notification';
|
||||
export * from './market-proposal-notification';
|
||||
export * from './protocol-upgrade-countdown';
|
||||
export * from './protocol-upgrade-proposal-notification';
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useNextProtocolUpgradeProposal, useTimeToUpgrade } from '../lib';
|
||||
import { convertToCountdownString } from '@vegaprotocol/utils';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import classNames from 'classnames';
|
||||
import { Icon, NavigationContext } from '@vegaprotocol/ui-toolkit';
|
||||
import { useProtocolUpgradeProposalLink } from '@vegaprotocol/environment';
|
||||
import { useContext } from 'react';
|
||||
export enum ProtocolUpgradeCountdownMode {
|
||||
IN_BLOCKS,
|
||||
IN_ESTIMATED_TIME_REMAINING,
|
||||
}
|
||||
type ProtocolUpgradeCountdownProps = {
|
||||
mode?: ProtocolUpgradeCountdownMode;
|
||||
};
|
||||
export const ProtocolUpgradeCountdown = ({
|
||||
mode = ProtocolUpgradeCountdownMode.IN_BLOCKS,
|
||||
}: ProtocolUpgradeCountdownProps) => {
|
||||
const { theme } = useContext(NavigationContext);
|
||||
const { data, lastBlockHeight } = useNextProtocolUpgradeProposal();
|
||||
|
||||
const time = useTimeToUpgrade(
|
||||
data && data.upgradeBlockHeight
|
||||
? Number(data.upgradeBlockHeight)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const detailsLink = useProtocolUpgradeProposalLink();
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
const emphasis = classNames(
|
||||
'text-vega-orange-500 dark:text-vega-orange-500',
|
||||
{
|
||||
'!text-black': theme === 'yellow',
|
||||
}
|
||||
);
|
||||
|
||||
let countdown;
|
||||
switch (mode) {
|
||||
case ProtocolUpgradeCountdownMode.IN_BLOCKS:
|
||||
countdown = (
|
||||
<>
|
||||
<span className={emphasis}>
|
||||
{Number(data.upgradeBlockHeight) - Number(lastBlockHeight)}
|
||||
</span>{' '}
|
||||
{t('blocks')}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING:
|
||||
countdown =
|
||||
time !== undefined ? (
|
||||
<span className={emphasis}>
|
||||
{convertToCountdownString(time, '0:00:00:00')}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={classNames('italic lowercase text-vega-orange-600', {
|
||||
'!text-black': theme === 'yellow',
|
||||
})}
|
||||
>
|
||||
{t('estimating...')}
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={detailsLink(data.vegaReleaseTag)}
|
||||
target="_blank"
|
||||
rel="noreferrer nofollow noopener"
|
||||
>
|
||||
<div
|
||||
data-testid="protocol-upgrade-counter"
|
||||
className={classNames(
|
||||
'flex flex-nowrap items-center text-xs py-2 px-4',
|
||||
'border rounded',
|
||||
'border-vega-orange-500 dark:border-vega-orange-500',
|
||||
'bg-vega-orange-300 dark:bg-vega-orange-700',
|
||||
{
|
||||
'!bg-transparent !border-black': theme === 'yellow',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
name={IconNames.WARNING_SIGN}
|
||||
size={3}
|
||||
className={classNames('mr-2', emphasis)}
|
||||
/>{' '}
|
||||
<span className="flex gap-1 flex-nowrap whitespace-nowrap">
|
||||
<span>{t('Network upgrade in')} </span>
|
||||
{countdown}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
NotificationBanner,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useNextProtocolUpgradeProposal, useTimeToUpgrade } from '../lib';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useProtocolUpgradeProposalLink } from '@vegaprotocol/environment';
|
||||
import { ProtocolUpgradeCountdownMode } from './protocol-upgrade-countdown';
|
||||
import { convertToCountdownString } from '@vegaprotocol/utils';
|
||||
import { useState } from 'react';
|
||||
|
||||
type ProtocolUpgradeProposalNotificationProps = {
|
||||
mode?: ProtocolUpgradeCountdownMode;
|
||||
};
|
||||
export const ProtocolUpgradeProposalNotification = ({
|
||||
mode = ProtocolUpgradeCountdownMode.IN_BLOCKS,
|
||||
}: ProtocolUpgradeProposalNotificationProps) => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
const { data, lastBlockHeight } = useNextProtocolUpgradeProposal();
|
||||
const detailsLink = useProtocolUpgradeProposalLink();
|
||||
const time = useTimeToUpgrade(
|
||||
data && data.upgradeBlockHeight
|
||||
? Number(data.upgradeBlockHeight)
|
||||
: undefined
|
||||
);
|
||||
|
||||
if (!data || !lastBlockHeight || !visible) return null;
|
||||
|
||||
const { vegaReleaseTag, upgradeBlockHeight } = data;
|
||||
|
||||
let countdown;
|
||||
switch (mode) {
|
||||
case ProtocolUpgradeCountdownMode.IN_BLOCKS:
|
||||
countdown = (
|
||||
<>
|
||||
<span className="text-vega-orange-500">
|
||||
{Number(upgradeBlockHeight) - Number(lastBlockHeight)}
|
||||
</span>{' '}
|
||||
{t('blocks')}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING:
|
||||
countdown =
|
||||
time !== undefined ? (
|
||||
<span className="text-vega-orange-500">
|
||||
{convertToCountdownString(time, '0:00:00:00')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-vega-orange-600 lowercase italic">
|
||||
{t('estimating...')}
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationBanner
|
||||
intent={Intent.Warning}
|
||||
onClose={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
<div className="uppercase ">
|
||||
{t('The network will upgrade to %s in ', [data.vegaReleaseTag])}
|
||||
{countdown}
|
||||
</div>
|
||||
<div>
|
||||
{t(
|
||||
'Trading activity will be interrupted, manage your risk appropriately.'
|
||||
)}{' '}
|
||||
<ExternalLink href={detailsLink(vegaReleaseTag)}>
|
||||
{t('View details')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</NotificationBanner>
|
||||
);
|
||||
};
|
||||
@@ -3,3 +3,4 @@ export * from './voting-hooks';
|
||||
export * from './proposals-data-provider';
|
||||
export * from './proposals-list';
|
||||
export * from './voting-progress';
|
||||
export * from './protocol-upgrade-proposals';
|
||||
|
||||
@@ -21,9 +21,6 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
|
||||
export const ProposalsList = () => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [dataCount, setDataCount] = useState(0);
|
||||
const handleOnGridReady = useCallback(() => {
|
||||
gridRef.current?.api?.sizeColumnsToFit();
|
||||
}, [gridRef]);
|
||||
const { data, loading, error, reload } = useDataProvider({
|
||||
dataProvider: proposalsDataProvider,
|
||||
variables: {
|
||||
@@ -41,13 +38,13 @@ export const ProposalsList = () => {
|
||||
return (
|
||||
<div className="relative">
|
||||
<AgGrid
|
||||
id="proposedMarkets"
|
||||
ref={gridRef}
|
||||
className="w-full h-full"
|
||||
domLayout="autoHeight"
|
||||
columnDefs={columnDefs}
|
||||
rowData={filteredData}
|
||||
defaultColDef={defaultColDef}
|
||||
onGridReady={handleOnGridReady}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
onFilterChanged={onFilterChanged}
|
||||
|
||||
@@ -129,6 +129,7 @@ export const useColumnDefs = () => {
|
||||
'terms.enactmentDatetime'
|
||||
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-'),
|
||||
filter: DateRangeFilter,
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
}, [VEGA_TOKEN_URL, requiredMajorityPercentage]);
|
||||
@@ -136,7 +137,6 @@ export const useColumnDefs = () => {
|
||||
return {
|
||||
sortable: true,
|
||||
cellClass: cellCss,
|
||||
flex: 1,
|
||||
resizable: true,
|
||||
filter: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
query BlockStatistics {
|
||||
statistics {
|
||||
blockHeight
|
||||
blockDuration
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,9 +5,9 @@ fragment ProtocolUpgradeProposalFields on ProtocolUpgradeProposal {
|
||||
status
|
||||
}
|
||||
|
||||
query ProtocolUpgrades {
|
||||
query ProtocolUpgradeProposals($inState: ProtocolUpgradeProposalStatus) {
|
||||
lastBlockHeight
|
||||
protocolUpgradeProposals {
|
||||
protocolUpgradeProposals(inState: $inState) {
|
||||
edges {
|
||||
node {
|
||||
...ProtocolUpgradeProposalFields
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type BlockStatisticsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type BlockStatisticsQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', blockHeight: string, blockDuration: string } };
|
||||
|
||||
|
||||
export const BlockStatisticsDocument = gql`
|
||||
query BlockStatistics {
|
||||
statistics {
|
||||
blockHeight
|
||||
blockDuration
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useBlockStatisticsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useBlockStatisticsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useBlockStatisticsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useBlockStatisticsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useBlockStatisticsQuery(baseOptions?: Apollo.QueryHookOptions<BlockStatisticsQuery, BlockStatisticsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<BlockStatisticsQuery, BlockStatisticsQueryVariables>(BlockStatisticsDocument, options);
|
||||
}
|
||||
export function useBlockStatisticsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<BlockStatisticsQuery, BlockStatisticsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<BlockStatisticsQuery, BlockStatisticsQueryVariables>(BlockStatisticsDocument, options);
|
||||
}
|
||||
export type BlockStatisticsQueryHookResult = ReturnType<typeof useBlockStatisticsQuery>;
|
||||
export type BlockStatisticsLazyQueryHookResult = ReturnType<typeof useBlockStatisticsLazyQuery>;
|
||||
export type BlockStatisticsQueryResult = Apollo.QueryResult<BlockStatisticsQuery, BlockStatisticsQueryVariables>;
|
||||
Generated
+62
@@ -0,0 +1,62 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ProtocolUpgradeProposalFieldsFragment = { __typename?: 'ProtocolUpgradeProposal', upgradeBlockHeight: string, vegaReleaseTag: string, approvers: Array<string>, status: Types.ProtocolUpgradeProposalStatus };
|
||||
|
||||
export type ProtocolUpgradeProposalsQueryVariables = Types.Exact<{
|
||||
inState?: Types.InputMaybe<Types.ProtocolUpgradeProposalStatus>;
|
||||
}>;
|
||||
|
||||
|
||||
export type ProtocolUpgradeProposalsQuery = { __typename?: 'Query', lastBlockHeight: string, protocolUpgradeProposals?: { __typename?: 'ProtocolUpgradeProposalConnection', edges?: Array<{ __typename?: 'ProtocolUpgradeProposalEdge', node: { __typename?: 'ProtocolUpgradeProposal', upgradeBlockHeight: string, vegaReleaseTag: string, approvers: Array<string>, status: Types.ProtocolUpgradeProposalStatus } }> | null } | null };
|
||||
|
||||
export const ProtocolUpgradeProposalFieldsFragmentDoc = gql`
|
||||
fragment ProtocolUpgradeProposalFields on ProtocolUpgradeProposal {
|
||||
upgradeBlockHeight
|
||||
vegaReleaseTag
|
||||
approvers
|
||||
status
|
||||
}
|
||||
`;
|
||||
export const ProtocolUpgradeProposalsDocument = gql`
|
||||
query ProtocolUpgradeProposals($inState: ProtocolUpgradeProposalStatus) {
|
||||
lastBlockHeight
|
||||
protocolUpgradeProposals(inState: $inState) {
|
||||
edges {
|
||||
node {
|
||||
...ProtocolUpgradeProposalFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ProtocolUpgradeProposalFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useProtocolUpgradeProposalsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useProtocolUpgradeProposalsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useProtocolUpgradeProposalsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useProtocolUpgradeProposalsQuery({
|
||||
* variables: {
|
||||
* inState: // value for 'inState'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useProtocolUpgradeProposalsQuery(baseOptions?: Apollo.QueryHookOptions<ProtocolUpgradeProposalsQuery, ProtocolUpgradeProposalsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ProtocolUpgradeProposalsQuery, ProtocolUpgradeProposalsQueryVariables>(ProtocolUpgradeProposalsDocument, options);
|
||||
}
|
||||
export function useProtocolUpgradeProposalsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProtocolUpgradeProposalsQuery, ProtocolUpgradeProposalsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ProtocolUpgradeProposalsQuery, ProtocolUpgradeProposalsQueryVariables>(ProtocolUpgradeProposalsDocument, options);
|
||||
}
|
||||
export type ProtocolUpgradeProposalsQueryHookResult = ReturnType<typeof useProtocolUpgradeProposalsQuery>;
|
||||
export type ProtocolUpgradeProposalsLazyQueryHookResult = ReturnType<typeof useProtocolUpgradeProposalsLazyQuery>;
|
||||
export type ProtocolUpgradeProposalsQueryResult = Apollo.QueryResult<ProtocolUpgradeProposalsQuery, ProtocolUpgradeProposalsQueryVariables>;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './__generated__/ProtocolUpgradeProposals';
|
||||
export * from './use-next-protocol-upgrade-proposals';
|
||||
export * from './use-time-to-upgrade';
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { useMemo } from 'react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useProtocolUpgradeProposalsQuery } from './__generated__/ProtocolUpgradeProposals';
|
||||
|
||||
export const useNextProtocolUpgradeProposals = (since?: number) => {
|
||||
const { data, loading, error } = useProtocolUpgradeProposalsQuery({
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
variables: {
|
||||
inState:
|
||||
Schema.ProtocolUpgradeProposalStatus
|
||||
.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
|
||||
},
|
||||
});
|
||||
|
||||
const nextUpgrades = useMemo(() => {
|
||||
if (!data) return [];
|
||||
|
||||
const proposals = removePaginationWrapper(
|
||||
data?.protocolUpgradeProposals?.edges
|
||||
);
|
||||
|
||||
return proposals
|
||||
.filter(
|
||||
(p) =>
|
||||
Number(p.upgradeBlockHeight) > (since || Number(data.lastBlockHeight))
|
||||
)
|
||||
.sort(
|
||||
(a, b) => Number(a.upgradeBlockHeight) - Number(b.upgradeBlockHeight)
|
||||
);
|
||||
}, [data, since]);
|
||||
|
||||
return {
|
||||
data: nextUpgrades,
|
||||
lastBlockHeight: data?.lastBlockHeight,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export const useNextProtocolUpgradeProposal = (since?: number) => {
|
||||
const { data, lastBlockHeight, loading, error } =
|
||||
useNextProtocolUpgradeProposals(since);
|
||||
|
||||
return {
|
||||
data: !data ? undefined : data[0],
|
||||
lastBlockHeight,
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useTimeToUpgrade } from './use-time-to-upgrade';
|
||||
|
||||
jest.mock('./__generated__/BlockStatistics', () => ({
|
||||
...jest.requireActual('./__generated__/BlockStatistics'),
|
||||
useBlockStatisticsQuery: jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
statistics: {
|
||||
blockHeight: 1,
|
||||
blockDuration: 500,
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useTimeToUpgrade', () => {
|
||||
it.each([
|
||||
[-1, -1000],
|
||||
[0, -500],
|
||||
[1, 0],
|
||||
[2, 500],
|
||||
[3, 1000],
|
||||
[10, 4500],
|
||||
])('time in %d block(s) should be %d ms', async (block, avg) => {
|
||||
const { result } = renderHook(() => useTimeToUpgrade(block, 1));
|
||||
await waitFor(() => {
|
||||
expect(result.current).toEqual(avg);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBlockStatisticsQuery } from './__generated__/BlockStatistics';
|
||||
import sum from 'lodash/sum';
|
||||
|
||||
const DEFAULT_POLLS = 10;
|
||||
const INTERVAL = 1000;
|
||||
const durations = [] as number[];
|
||||
|
||||
const useAverageBlockDuration = (polls = DEFAULT_POLLS) => {
|
||||
const [avg, setAvg] = useState<number | undefined>(undefined);
|
||||
const { data } = useBlockStatisticsQuery({
|
||||
pollInterval: INTERVAL,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
skip: durations.length === polls,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (durations.length < polls && data) {
|
||||
durations.push(parseFloat(data.statistics.blockDuration));
|
||||
}
|
||||
if (durations.length === polls) {
|
||||
const averageBlockDuration = sum(durations) / durations.length; // ms
|
||||
console.log('setting avg', averageBlockDuration);
|
||||
setAvg(averageBlockDuration);
|
||||
}
|
||||
}, [data, polls]);
|
||||
|
||||
return avg;
|
||||
};
|
||||
|
||||
export const useTimeToUpgrade = (
|
||||
upgradeBlockHeight?: number,
|
||||
polls = DEFAULT_POLLS
|
||||
) => {
|
||||
const [time, setTime] = useState<number | undefined>(undefined);
|
||||
const avg = useAverageBlockDuration(polls);
|
||||
const { data } = useBlockStatisticsQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const t =
|
||||
(Number(upgradeBlockHeight) - Number(data?.statistics.blockHeight)) *
|
||||
Number(avg);
|
||||
if (!isNaN(t)) {
|
||||
setTime(t);
|
||||
}
|
||||
}, [avg, data?.statistics.blockHeight, upgradeBlockHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
const i = setInterval(() => {
|
||||
if (time !== undefined) {
|
||||
setTime(time - 1000);
|
||||
}
|
||||
}, 1000);
|
||||
return () => {
|
||||
clearInterval(i);
|
||||
};
|
||||
}, [time]);
|
||||
|
||||
return time;
|
||||
};
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IsFullWidthRowParams } from 'ag-grid-community';
|
||||
import type { IsFullWidthRowParams, RowHeightParams } from 'ag-grid-community';
|
||||
|
||||
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
|
||||
const ROW_ID = 'bottomPlaceholder';
|
||||
const fullWidthCellRenderer = () => null;
|
||||
const isFullWidthRow = (params: IsFullWidthRowParams) =>
|
||||
params.rowNode.data?.isLastPlaceholder;
|
||||
|
||||
interface Props<T> {
|
||||
gridRef: RefObject<AgGridReact>;
|
||||
setId?: (data: T) => T;
|
||||
setId?: (data: T, id: string) => T;
|
||||
disabled?: boolean;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
@@ -20,45 +21,42 @@ export const useBottomPlaceholder = <T extends {}>({
|
||||
disabled,
|
||||
}: Props<T>) => {
|
||||
const onBodyScrollEnd = useCallback(() => {
|
||||
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
|
||||
const lastRowIndex = gridRef.current?.api.getLastDisplayedRow() ?? 0;
|
||||
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
|
||||
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
|
||||
if (lastRow?.data && !lastRow?.data.isLastPlaceholder) {
|
||||
const newData = setId
|
||||
? setId({ ...lastRow.data, isLastPlaceholder: true })
|
||||
const rowCont = gridRef.current?.api.getDisplayedRowCount() ?? 0;
|
||||
if (rowCont) {
|
||||
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(rowCont - 1);
|
||||
if (lastRow && lastRow.data) {
|
||||
const placeholderRow = setId
|
||||
? setId({ ...lastRow.data, isLastPlaceholder: true }, ROW_ID)
|
||||
: {
|
||||
...lastRow.data,
|
||||
isLastPlaceholder: true,
|
||||
id: `${lastRow.data?.id || '-'}-1`,
|
||||
id: ROW_ID,
|
||||
};
|
||||
const add = [newData];
|
||||
const newIndex = lastRowIndex + 1;
|
||||
gridRef.current?.api.applyTransaction({
|
||||
add,
|
||||
addIndex: newIndex,
|
||||
});
|
||||
const newLastRow =
|
||||
gridRef.current?.api.getDisplayedRowAtIndex(newIndex);
|
||||
newLastRow?.setRowHeight(50);
|
||||
gridRef.current?.api.onRowHeightChanged();
|
||||
const transaction = gridRef.current?.api.getRowNode(ROW_ID)
|
||||
? { update: [placeholderRow] }
|
||||
: { add: [placeholderRow] };
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
}
|
||||
}, [gridRef, setId]);
|
||||
|
||||
const onRowsChanged = useCallback(() => {
|
||||
const remove: T[] = [];
|
||||
gridRef.current?.api.forEachNodeAfterFilterAndSort((rowNode) => {
|
||||
if (rowNode.data.isLastPlaceholder) {
|
||||
remove.push(rowNode.data);
|
||||
}
|
||||
});
|
||||
gridRef.current?.api.applyTransaction({
|
||||
remove,
|
||||
});
|
||||
const placeholderNode = gridRef.current?.api.getRowNode(ROW_ID);
|
||||
if (placeholderNode) {
|
||||
const transaction = {
|
||||
remove: [placeholderNode.data],
|
||||
};
|
||||
gridRef.current?.api.applyTransaction(transaction);
|
||||
}
|
||||
onBodyScrollEnd();
|
||||
}, [gridRef, onBodyScrollEnd]);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
(params: RowHeightParams) =>
|
||||
params.data?.isLastPlaceholder ? 50 : undefined,
|
||||
[]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
!disabled
|
||||
@@ -69,8 +67,9 @@ export const useBottomPlaceholder = <T extends {}>({
|
||||
fullWidthCellRenderer,
|
||||
onSortChanged: onRowsChanged,
|
||||
onFilterChanged: onRowsChanged,
|
||||
getRowHeight,
|
||||
}
|
||||
: {},
|
||||
[onBodyScrollEnd, onRowsChanged, disabled]
|
||||
[onBodyScrollEnd, onRowsChanged, disabled, getRowHeight]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Asset {
|
||||
};
|
||||
}
|
||||
|
||||
interface ERC20Asset {
|
||||
export interface ERC20Asset {
|
||||
__typename?: 'Asset';
|
||||
source: {
|
||||
__typename: 'ERC20';
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { getSecondsFromInterval } from './time';
|
||||
import {
|
||||
convertToCountdown,
|
||||
convertToCountdownString,
|
||||
getSecondsFromInterval,
|
||||
} from './time';
|
||||
|
||||
describe('getSecondsFromInterval', () => {
|
||||
it('returns 0 for bad data', () => {
|
||||
@@ -34,3 +38,44 @@ describe('getSecondsFromInterval', () => {
|
||||
expect(getSecondsFromInterval('1D1h30m1s')).toEqual(91801);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertToCountdown', () => {
|
||||
it.each([
|
||||
[1 * 1000, [0, 0, 0, 1]],
|
||||
[2 * 1000, [0, 0, 0, 2]],
|
||||
[3999, [0, 0, 0, 3]],
|
||||
[1 * 60 * 1000 + 3 * 1000, [0, 0, 1, 3]],
|
||||
[12 * 60 * 1000 + 3 * 1000, [0, 0, 12, 3]],
|
||||
[3 * 60 * 60 * 1000 + 12 * 60 * 1000 + 3 * 1000, [0, 3, 12, 3]],
|
||||
[
|
||||
30 * 24 * 60 * 60 * 1000 + 3 * 60 * 60 * 1000 + 12 * 60 * 1000 + 3 * 1000,
|
||||
[30, 3, 12, 3],
|
||||
],
|
||||
[
|
||||
-1 *
|
||||
(30 * 24 * 60 * 60 * 1000 +
|
||||
3 * 60 * 60 * 1000 +
|
||||
12 * 60 * 1000 +
|
||||
3 * 1000),
|
||||
[30, 3, 12, 3],
|
||||
],
|
||||
])('converts %d ms to %s', (time, countdown) => {
|
||||
expect(convertToCountdown(time)).toEqual(countdown);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertToCountdownString', () => {
|
||||
it.each([
|
||||
[1 * 1000, '00m01s'],
|
||||
[2 * 1000, '00m02s'],
|
||||
[1 * 60 * 1000 + 3 * 1000, '01m03s'],
|
||||
[12 * 60 * 1000 + 3 * 1000, '12m03s'],
|
||||
[3 * 60 * 60 * 1000 + 12 * 60 * 1000 + 3 * 1000, '03h12m03s'],
|
||||
[
|
||||
30 * 24 * 60 * 60 * 1000 + 3 * 60 * 60 * 1000 + 12 * 60 * 1000 + 3 * 1000,
|
||||
'30d03h12m03s',
|
||||
],
|
||||
])('converts %d ms to %s', (time, countdown) => {
|
||||
expect(convertToCountdownString(time)).toEqual(countdown);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,3 +46,47 @@ export function getSecondsFromInterval(str: string) {
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
export const convertToCountdown = (time: number) => {
|
||||
const s = 1000;
|
||||
const m = 1000 * 60;
|
||||
const h = 1000 * 60 * 60;
|
||||
const d = 1000 * 60 * 60 * 24;
|
||||
|
||||
const t = Math.abs(time);
|
||||
|
||||
const days = Math.floor(t / d);
|
||||
const hours = Math.floor((t - days * d) / h);
|
||||
const minutes = Math.floor((t - days * d - hours * h) / m);
|
||||
const seconds = Math.floor((t - days * d - hours * h - minutes * m) / s);
|
||||
|
||||
return [days, hours, minutes, seconds];
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts given time in ms to countdown string, e.g. 1d20h34m10s
|
||||
*/
|
||||
export const convertToCountdownString = (
|
||||
time: number,
|
||||
pattern = '0d00h00m00s'
|
||||
) => {
|
||||
const values = convertToCountdown(time);
|
||||
|
||||
let i = 0;
|
||||
const countdown = pattern
|
||||
.replace(/00*/g, (match) => {
|
||||
const value = String(values[i++]);
|
||||
if (value.length < match.length) {
|
||||
const filler = Array(match.length - value.length)
|
||||
.fill('0')
|
||||
.join('');
|
||||
return `${filler}${value}`;
|
||||
}
|
||||
|
||||
return value;
|
||||
})
|
||||
.replace(/^00*[^\d]*/g, '') // replace leading 00, e.g. 00d01h23m45s -> 01h23m45s
|
||||
.replace(/^00[^\d]*/g, ''); // replace leading 00, e.g. 00d00h23m45s -> 23m45s
|
||||
|
||||
return countdown;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useAccountBalance } from '@vegaprotocol/accounts';
|
||||
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { useBalancesStore } from '@vegaprotocol/assets';
|
||||
import { Balance } from '@vegaprotocol/assets';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const AssetBalance = ({ asset }: { asset: AssetFieldsFragment }) => {
|
||||
const [setBalance, getBalance] = useBalancesStore((state) => [
|
||||
state.setBalance,
|
||||
state.getBalance,
|
||||
]);
|
||||
const { accountBalance, accountDecimals } = useAccountBalance(asset?.id);
|
||||
|
||||
useEffect(() => {
|
||||
const balance =
|
||||
accountBalance && accountDecimals
|
||||
? new BigNumber(addDecimal(accountBalance, accountDecimals))
|
||||
: undefined;
|
||||
setBalance({ asset, balanceOnVega: balance });
|
||||
}, [accountBalance, accountDecimals, asset, setBalance]);
|
||||
|
||||
return (
|
||||
<Balance
|
||||
balance={getBalance(asset.id)?.balanceOnVega?.toString()}
|
||||
symbol={asset.symbol}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render, screen, act, waitFor } from '@testing-library/react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { Account } from '@vegaprotocol/accounts';
|
||||
import { useAccountBalance } from '@vegaprotocol/accounts';
|
||||
import { WithdrawFormContainer } from './withdraw-form-container';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
@@ -12,6 +13,7 @@ jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
}),
|
||||
}));
|
||||
jest.mock('@web3-react/core');
|
||||
jest.mock('@vegaprotocol/accounts');
|
||||
|
||||
describe('WithdrawFormContainer', () => {
|
||||
const props = {
|
||||
@@ -91,6 +93,10 @@ describe('WithdrawFormContainer', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
(useWeb3React as jest.Mock).mockReturnValue({ account: MOCK_ETH_ADDRESS });
|
||||
(useAccountBalance as jest.Mock).mockReturnValue({
|
||||
accountBalance: 0,
|
||||
accountDecimals: null,
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
@@ -8,6 +8,11 @@ import type { Asset } from '@vegaprotocol/assets';
|
||||
|
||||
jest.mock('@web3-react/core');
|
||||
|
||||
jest.mock('@vegaprotocol/accounts', () => ({
|
||||
...jest.requireActual('@vegaprotocol/accounts'),
|
||||
useAccountBalance: jest.fn(() => ({ accountBalance: 0, accountDecimals: 0 })),
|
||||
}));
|
||||
|
||||
const MOCK_ETH_ADDRESS = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
|
||||
let assets: Asset[];
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
useWeb3ConnectStore,
|
||||
useWeb3Disconnect,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { AssetBalance } from './asset-balance';
|
||||
|
||||
interface FormFields {
|
||||
asset: string;
|
||||
@@ -154,7 +155,11 @@ export const WithdrawForm = ({
|
||||
hasError={Boolean(errors.asset?.message)}
|
||||
>
|
||||
{assets.filter(isAssetTypeERC20).map((a) => (
|
||||
<AssetOption key={a.id} asset={a} />
|
||||
<AssetOption
|
||||
key={a.id}
|
||||
asset={a}
|
||||
balance={<AssetBalance asset={a} />}
|
||||
/>
|
||||
))}
|
||||
</RichSelect>
|
||||
);
|
||||
|
||||
@@ -25,6 +25,11 @@ jest.mock('./use-withdraw-asset', () => ({
|
||||
useWithdrawAsset: () => withdrawAsset,
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/accounts', () => ({
|
||||
...jest.requireActual('@vegaprotocol/accounts'),
|
||||
useAccountBalance: jest.fn(() => ({ accountBalance: 0, accountDecimals: 0 })),
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/web3', () => ({
|
||||
...jest.requireActual('@vegaprotocol/web3'),
|
||||
useGetWithdrawThreshold: () => {
|
||||
|
||||
@@ -34,8 +34,9 @@ export const WithdrawalsTable = (
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
|
||||
return (
|
||||
<AgGrid
|
||||
id="withdrawals"
|
||||
overlayNoRowsTemplate={t('No withdrawals')}
|
||||
defaultColDef={{ flex: 1, resizable: true }}
|
||||
defaultColDef={{ resizable: true }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
components={{
|
||||
RecipientCell,
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
Reference in New Issue
Block a user