Compare commits

...
Author SHA1 Message Date
Matthew Russell 64bf2b3b52 feat: add check for operation name in apollo client error link 2023-05-18 15:05:32 -07:00
Matthew Russell 28f9e169e7 chore: rename nodes so its more clear what the query is used for 2023-05-18 14:54:13 -07:00
dexturr 162b167860 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 18:07:36 +00:00
Art a4149cc55e feat(web3): withdrawal details (#3701) 2023-05-18 16:50:15 +02:00
Mikołaj Młodzikowski 4161a74eaf feat(ci): fix release body (#3822) 2023-05-18 14:59:46 +01:00
Mikołaj Młodzikowski 5008e57a56 feat(ci): adjust variables during release pipeline to cover actual tag_name 2023-05-18 15:12:12 +02:00
Mikołaj Młodzikowski 89031bf826 feat(ci): append ipfs related information to release body (#3818) 2023-05-18 14:51:53 +02:00
dexturr 61501d4d14 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-18 12:10:20 +00:00
24 changed files with 2327 additions and 1505 deletions
@@ -0,0 +1,74 @@
name: After Release
on:
release:
types: [published]
jobs:
after-release:
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: resolve ipfs hashes for release
run: |
echo "Tag name: ${{ github.event.release.tag_name }}"
echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}"
until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
echo "Image not pushed yet, waiting 60 seconds"
sleep 60
done
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} cat /ipfs-hash > ipfs-hash
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
echo IPFS_V0=$(cat ipfs-hash) >> $GITHUB_ENV
echo IPFS_V1=$(ipfs cid format -v 1 -b base32 $(cat ipfs-hash)) >> $GITHUB_ENV
- name: Edit Release
uses: irongut/EditRelease@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: ${{ github.event.release.id }}
body: |
---
# Deployments
* https://explorer.vega.xyz
* https://governance.vega.xyz
# IPFS releases
Tye IPFS hash of this release of the Trading app is:
CIDv0: ${{ env.IPFS_V0 }}
CIDv1: ${{ env.IPFS_V1 }}
You can always access the latest IPFS release by visiting [vega.trading](https://vega.trading).
You can also access Trading directly from an IPFS gateway.
BEWARE: The Trading interface uses [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. You should always use an IPFS gateway that enforces [origin separation](https://ipfs.github.io/public-gateway-checker/).
Your settings are not remembered across different URLs.
IPFS gateways:
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
ipfs://${{ env.IPFS_V0 }}/
-6
View File
@@ -170,12 +170,6 @@ jobs:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
- name: Add ipfs hash to release
uses: softprops/action-gh-release@v1
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
with:
files: ${{ matrix.app }}-ipfs-hash
- name: Trigger fleek deployment
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
run: |
+4 -2
View File
@@ -16,6 +16,7 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
import { ContractsProvider } from './contexts/contracts/contracts-provider';
import { AppRouter } from './routes';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
import {
createConnectors,
useEthTransactionManager,
@@ -43,7 +44,7 @@ import {
} from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client';
import { WithdrawalDialog } from '@vegaprotocol/withdraws';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager';
import {
@@ -158,7 +159,8 @@ const Web3Container = ({
<InitializeHandlers />
<VegaWalletDialogs />
<TransactionModal />
<WithdrawalDialog />
<CreateWithdrawalDialog />
<WithdrawalApprovalDialogContainer />
<TelemetryDialog />
</>
</BalanceManager>
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -6,7 +6,10 @@ import { VegaConnectDialog } from '@vegaprotocol/wallet';
import { Connectors } from '../lib/vega-connectors';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { DepositDialog } from '@vegaprotocol/deposits';
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
import {
Web3ConnectUncontrolledDialog,
WithdrawalApprovalDialogContainer,
} from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog';
import { TransferDialog } from '@vegaprotocol/accounts';
@@ -27,6 +30,7 @@ const DialogsContainer = () => {
<Web3ConnectUncontrolledDialog />
<CreateWithdrawalDialog />
<TransferDialog />
<WithdrawalApprovalDialogContainer />
</>
);
};
+13 -1
View File
@@ -115,7 +115,19 @@ export function createClient({
)
: httpLink;
const errorLink = onError(({ graphQLErrors, networkError }) => {
const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
// if any of these queries error don't capture any errors, its
// likely the user is connecting to a dodgy node, NodeGuard gets
// called on startup, NodeCheck and NodeCheckTimeUpdate are called
// by the NodeSwitcher component and the useNodeHealth hook
if (
['NodeGuard', 'NodeCheck', 'NodeCheckTimeUpdate'].includes(
operation.operationName
)
) {
return;
}
if (graphQLErrors) {
graphQLErrors.forEach((e) => {
if (e.extensions && e.extensions['type'] !== NOT_FOUND) {
@@ -3,11 +3,13 @@ import { MockedProvider } from '@apollo/react-testing';
import { act, render, screen, waitFor } from '@testing-library/react';
import { RadioGroup } from '@vegaprotocol/ui-toolkit';
import type {
BlockTimeSubscription,
StatisticsQuery,
} from '../../utils/__generated__/Node';
import { BlockTimeDocument } from '../../utils/__generated__/Node';
import { StatisticsDocument } from '../../utils/__generated__/Node';
NodeCheckTimeUpdateSubscription,
NodeCheckQuery,
} from '../../utils/__generated__/NodeCheck';
import {
NodeCheckDocument,
NodeCheckTimeUpdateDocument,
} from '../../utils/__generated__/NodeCheck';
import type { RowDataProps } from './row-data';
import { POLL_INTERVAL } from './row-data';
import { BLOCK_THRESHOLD, RowData } from './row-data';
@@ -19,9 +21,9 @@ jest.mock('@vegaprotocol/apollo-client', () => ({
useHeaderStore: jest.fn().mockReturnValue({}),
}));
const statsQueryMock: MockedResponse<StatisticsQuery> = {
const statsQueryMock: MockedResponse<NodeCheckQuery> = {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: {
@@ -34,9 +36,9 @@ const statsQueryMock: MockedResponse<StatisticsQuery> = {
},
};
const subMock: MockedResponse<BlockTimeSubscription> = {
const subMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
request: {
query: BlockTimeDocument,
query: NodeCheckTimeUpdateDocument,
},
result: {
data: {
@@ -71,8 +73,8 @@ const mockHeaders = (
const renderComponent = (
props: RowDataProps,
queryMock: MockedResponse<StatisticsQuery>,
subMock: MockedResponse<BlockTimeSubscription>
queryMock: MockedResponse<NodeCheckQuery>,
subMock: MockedResponse<NodeCheckTimeUpdateSubscription>
) => {
return (
<MockedProvider mocks={[queryMock, subMock, subMock, subMock]}>
@@ -127,16 +129,16 @@ describe('RowData', () => {
it('radio button still enabled if query fails', async () => {
mockHeaders(props.url, {});
const failedQueryMock: MockedResponse<StatisticsQuery> = {
const failedQueryMock: MockedResponse<NodeCheckQuery> = {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
error: new Error('failed'),
};
const failedSubMock: MockedResponse<BlockTimeSubscription> = {
const failedSubMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
request: {
query: BlockTimeDocument,
query: NodeCheckTimeUpdateDocument,
},
error: new Error('failed'),
};
@@ -244,10 +246,10 @@ describe('RowData', () => {
jest.useFakeTimers();
const createStatsQueryMock = (
blockHeight: string
): MockedResponse<StatisticsQuery> => {
): MockedResponse<NodeCheckQuery> => {
return {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: {
@@ -261,10 +263,10 @@ describe('RowData', () => {
};
};
const createFailedStatsQueryMock = (): MockedResponse<StatisticsQuery> => {
const createFailedStatsQueryMock = (): MockedResponse<NodeCheckQuery> => {
return {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: undefined,
@@ -6,9 +6,9 @@ import { Radio } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react';
import { CUSTOM_NODE_KEY } from '../../types';
import {
useBlockTimeSubscription,
useStatisticsQuery,
} from '../../utils/__generated__/Node';
useNodeCheckQuery,
useNodeCheckTimeUpdateSubscription,
} from '../../utils/__generated__/NodeCheck';
import { LayoutCell } from './layout-cell';
export const POLL_INTERVAL = 1000;
@@ -30,13 +30,14 @@ export const RowData = ({
const [subFailed, setSubFailed] = useState(false);
const [time, setTime] = useState<number>();
// no use of data here as we need the data nodes reference to block height
const { data, error, loading, startPolling, stopPolling } =
useStatisticsQuery({
const { data, error, loading, startPolling, stopPolling } = useNodeCheckQuery(
{
pollInterval: POLL_INTERVAL,
// fix for pollInterval
// https://github.com/apollographql/apollo-client/issues/9819
ssr: false,
});
}
);
const headerStore = useHeaderStore();
const headers = headerStore[url];
@@ -44,7 +45,7 @@ export const RowData = ({
data: subData,
error: subError,
loading: subLoading,
} = useBlockTimeSubscription();
} = useNodeCheckTimeUpdateSubscription();
useEffect(() => {
const timeout = setTimeout(() => {
@@ -1,11 +1,11 @@
import {
StatisticsDocument,
BlockTimeDocument,
} from '../../utils/__generated__/Node';
NodeCheckDocument,
NodeCheckTimeUpdateDocument,
} from '../../utils/__generated__/NodeCheck';
import type {
StatisticsQuery,
BlockTimeSubscription,
} from '../../utils/__generated__/Node';
NodeCheckQuery,
NodeCheckTimeUpdateSubscription,
} from '../../utils/__generated__/NodeCheck';
import { Networks } from '../../types';
import type { RequestHandlerResponse } from 'mock-apollo-client';
import { createMockClient } from 'mock-apollo-client';
@@ -21,7 +21,7 @@ type MockClientProps = {
busEvents?: MockRequestConfig;
};
export const getMockBusEventsResult = (): BlockTimeSubscription => ({
export const getMockBusEventsResult = (): NodeCheckTimeUpdateSubscription => ({
busEvents: [
{
__typename: 'BusEvent',
@@ -32,19 +32,21 @@ export const getMockBusEventsResult = (): BlockTimeSubscription => ({
export const getMockStatisticsResult = (
env: Networks = Networks.TESTNET
): StatisticsQuery => ({
): NodeCheckQuery => ({
statistics: {
__typename: 'Statistics',
chainId: `${env.toLowerCase()}-0123`,
blockHeight: '11',
vegaTime: new Date().toISOString(),
},
});
export const getMockQueryResult = (env: Networks): StatisticsQuery => ({
export const getMockQueryResult = (env: Networks): NodeCheckQuery => ({
statistics: {
__typename: 'Statistics',
chainId: `${env.toLowerCase()}-0123`,
blockHeight: '11',
vegaTime: new Date().toISOString(),
},
});
@@ -72,11 +74,11 @@ export default function ({
const mockClient = createMockClient();
mockClient.setRequestHandler(
StatisticsDocument,
NodeCheckDocument,
getHandler(statistics, getMockStatisticsResult(network))
);
mockClient.setRequestHandler(
BlockTimeDocument,
NodeCheckTimeUpdateDocument,
getHandler(busEvents, getMockBusEventsResult())
);
+11 -9
View File
@@ -5,11 +5,13 @@ import { useEffect } from 'react';
import { create } from 'zustand';
import { createClient } from '@vegaprotocol/apollo-client';
import type {
BlockTimeSubscription,
StatisticsQuery,
} from '../utils/__generated__/Node';
import { BlockTimeDocument } from '../utils/__generated__/Node';
import { StatisticsDocument } from '../utils/__generated__/Node';
NodeCheckTimeUpdateSubscription,
NodeCheckQuery,
} from '../utils/__generated__/NodeCheck';
import {
NodeCheckDocument,
NodeCheckTimeUpdateDocument,
} from '../utils/__generated__/NodeCheck';
import type { Environment } from '../types';
import { Networks } from '../types';
import { compileErrors } from '../utils/compile-errors';
@@ -220,8 +222,8 @@ const testNode = async (
*/
const testQuery = async (client: Client) => {
try {
const result = await client.query<StatisticsQuery>({
query: StatisticsDocument,
const result = await client.query<NodeCheckQuery>({
query: NodeCheckDocument,
});
if (!result || result.error) {
return false;
@@ -240,8 +242,8 @@ const testQuery = async (client: Client) => {
const testSubscription = (client: Client) => {
return new Promise((resolve) => {
const sub = client
.subscribe<BlockTimeSubscription>({
query: BlockTimeDocument,
.subscribe<NodeCheckTimeUpdateSubscription>({
query: NodeCheckTimeUpdateDocument,
errorPolicy: 'all',
})
.subscribe({
@@ -2,8 +2,8 @@ import { renderHook, waitFor } from '@testing-library/react';
import { useNodeHealth } from './use-node-health';
import type { MockedResponse } from '@apollo/react-testing';
import { MockedProvider } from '@apollo/react-testing';
import type { StatisticsQuery } from '../utils/__generated__/Node';
import { StatisticsDocument } from '../utils/__generated__/Node';
import type { NodeCheckQuery } from '../utils/__generated__/NodeCheck';
import { NodeCheckDocument } from '../utils/__generated__/NodeCheck';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { Intent } from '@vegaprotocol/ui-toolkit';
@@ -16,10 +16,10 @@ jest.mock('@vegaprotocol/apollo-client');
const createStatsMock = (
blockHeight: number
): MockedResponse<StatisticsQuery> => {
): MockedResponse<NodeCheckQuery> => {
return {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
data: {
@@ -34,7 +34,7 @@ const createStatsMock = (
};
function setup(
mock: MockedResponse<StatisticsQuery>,
mock: MockedResponse<NodeCheckQuery>,
headers:
| {
blockHeight: number;
@@ -93,9 +93,9 @@ describe('useNodeHealth', () => {
);
it('block diff is null if query fails indicating non operational', async () => {
const failedQuery: MockedResponse<StatisticsQuery> = {
const failedQuery: MockedResponse<NodeCheckQuery> = {
request: {
query: StatisticsDocument,
query: NodeCheckDocument,
},
result: {
// @ts-ignore failed query with no result
@@ -1,5 +1,5 @@
import { useEffect, useMemo } from 'react';
import { useStatisticsQuery } from '../utils/__generated__/Node';
import { useNodeCheckQuery } from '../utils/__generated__/NodeCheck';
import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { useEnvironment } from './use-environment';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
@@ -16,7 +16,7 @@ export const useNodeHealth = () => {
const url = useEnvironment((store) => store.VEGA_URL);
const headerStore = useHeaderStore();
const headers = url ? headerStore[url] : undefined;
const { data, error, startPolling, stopPolling } = useStatisticsQuery({
const { data, error, startPolling, stopPolling } = useNodeCheckQuery({
fetchPolicy: 'no-cache',
});
+1 -1
View File
@@ -8,4 +8,4 @@ export * from './hooks';
export * from './types';
// Utils
export * from './utils/__generated__/Node';
export * from './utils/__generated__/NodeCheck';
@@ -1,4 +1,4 @@
query Statistics {
query NodeCheck {
statistics {
chainId
blockHeight
@@ -6,7 +6,7 @@ query Statistics {
}
}
subscription BlockTime {
subscription NodeCheckTimeUpdate {
busEvents(types: TimeUpdate, batchSize: 1) {
id
}
-81
View File
@@ -1,81 +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 StatisticsQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type StatisticsQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any } };
export type BlockTimeSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
export type BlockTimeSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', id: string }> | null };
export const StatisticsDocument = gql`
query Statistics {
statistics {
chainId
blockHeight
vegaTime
}
}
`;
/**
* __useStatisticsQuery__
*
* To run a query within a React component, call `useStatisticsQuery` and pass it any options that fit your needs.
* When your component renders, `useStatisticsQuery` 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 } = useStatisticsQuery({
* variables: {
* },
* });
*/
export function useStatisticsQuery(baseOptions?: Apollo.QueryHookOptions<StatisticsQuery, StatisticsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<StatisticsQuery, StatisticsQueryVariables>(StatisticsDocument, options);
}
export function useStatisticsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<StatisticsQuery, StatisticsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<StatisticsQuery, StatisticsQueryVariables>(StatisticsDocument, options);
}
export type StatisticsQueryHookResult = ReturnType<typeof useStatisticsQuery>;
export type StatisticsLazyQueryHookResult = ReturnType<typeof useStatisticsLazyQuery>;
export type StatisticsQueryResult = Apollo.QueryResult<StatisticsQuery, StatisticsQueryVariables>;
export const BlockTimeDocument = gql`
subscription BlockTime {
busEvents(types: TimeUpdate, batchSize: 1) {
id
}
}
`;
/**
* __useBlockTimeSubscription__
*
* To run a query within a React component, call `useBlockTimeSubscription` and pass it any options that fit your needs.
* When your component renders, `useBlockTimeSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useBlockTimeSubscription({
* variables: {
* },
* });
*/
export function useBlockTimeSubscription(baseOptions?: Apollo.SubscriptionHookOptions<BlockTimeSubscription, BlockTimeSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<BlockTimeSubscription, BlockTimeSubscriptionVariables>(BlockTimeDocument, options);
}
export type BlockTimeSubscriptionHookResult = ReturnType<typeof useBlockTimeSubscription>;
export type BlockTimeSubscriptionResult = Apollo.SubscriptionResult<BlockTimeSubscription>;
+81
View File
@@ -0,0 +1,81 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type NodeCheckQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type NodeCheckQuery = { __typename?: 'Query', statistics: { __typename?: 'Statistics', chainId: string, blockHeight: string, vegaTime: any } };
export type NodeCheckTimeUpdateSubscriptionVariables = Types.Exact<{ [key: string]: never; }>;
export type NodeCheckTimeUpdateSubscription = { __typename?: 'Subscription', busEvents?: Array<{ __typename?: 'BusEvent', id: string }> | null };
export const NodeCheckDocument = gql`
query NodeCheck {
statistics {
chainId
blockHeight
vegaTime
}
}
`;
/**
* __useNodeCheckQuery__
*
* To run a query within a React component, call `useNodeCheckQuery` and pass it any options that fit your needs.
* When your component renders, `useNodeCheckQuery` 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 } = useNodeCheckQuery({
* variables: {
* },
* });
*/
export function useNodeCheckQuery(baseOptions?: Apollo.QueryHookOptions<NodeCheckQuery, NodeCheckQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<NodeCheckQuery, NodeCheckQueryVariables>(NodeCheckDocument, options);
}
export function useNodeCheckLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<NodeCheckQuery, NodeCheckQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<NodeCheckQuery, NodeCheckQueryVariables>(NodeCheckDocument, options);
}
export type NodeCheckQueryHookResult = ReturnType<typeof useNodeCheckQuery>;
export type NodeCheckLazyQueryHookResult = ReturnType<typeof useNodeCheckLazyQuery>;
export type NodeCheckQueryResult = Apollo.QueryResult<NodeCheckQuery, NodeCheckQueryVariables>;
export const NodeCheckTimeUpdateDocument = gql`
subscription NodeCheckTimeUpdate {
busEvents(types: TimeUpdate, batchSize: 1) {
id
}
}
`;
/**
* __useNodeCheckTimeUpdateSubscription__
*
* To run a query within a React component, call `useNodeCheckTimeUpdateSubscription` and pass it any options that fit your needs.
* When your component renders, `useNodeCheckTimeUpdateSubscription` 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 subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useNodeCheckTimeUpdateSubscription({
* variables: {
* },
* });
*/
export function useNodeCheckTimeUpdateSubscription(baseOptions?: Apollo.SubscriptionHookOptions<NodeCheckTimeUpdateSubscription, NodeCheckTimeUpdateSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<NodeCheckTimeUpdateSubscription, NodeCheckTimeUpdateSubscriptionVariables>(NodeCheckTimeUpdateDocument, options);
}
export type NodeCheckTimeUpdateSubscriptionHookResult = ReturnType<typeof useNodeCheckTimeUpdateSubscription>;
export type NodeCheckTimeUpdateSubscriptionResult = Apollo.SubscriptionResult<NodeCheckTimeUpdateSubscription>;
+4 -4
View File
@@ -1,11 +1,11 @@
import type { StatisticsQuery } from './__generated__/Node';
import type { NodeCheckQuery } from './__generated__/NodeCheck';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
export const statisticsQuery = (
override?: PartialDeep<StatisticsQuery>
): StatisticsQuery => {
const defaultResult: StatisticsQuery = {
override?: PartialDeep<NodeCheckQuery>
): NodeCheckQuery => {
const defaultResult: NodeCheckQuery = {
statistics: {
__typename: 'Statistics',
chainId: 'chain-id',
+108 -4
View File
@@ -3,7 +3,8 @@ import styles from './toast.module.css';
import type { IconName } from '@blueprintjs/icons';
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import type { HTMLAttributes, HtmlHTMLAttributes } from 'react';
import type { HTMLAttributes, HtmlHTMLAttributes, ReactNode } from 'react';
import { useState } from 'react';
import { forwardRef, useEffect } from 'react';
import { useCallback } from 'react';
import { useLayoutEffect } from 'react';
@@ -11,6 +12,7 @@ import { useRef } from 'react';
import { Intent } from '../../utils/intent';
import { Icon } from '../icon';
import { Loader } from '../loader';
import { t } from '@vegaprotocol/i18n';
export type ToastContent = JSX.Element | undefined;
@@ -63,11 +65,88 @@ export const Panel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
}
);
type CollapsiblePanelProps = {
actions?: ReactNode;
};
export const CollapsiblePanel = forwardRef<
HTMLDivElement,
CollapsiblePanelProps & HTMLAttributes<HTMLDivElement>
>(({ children, className, actions, ...props }, ref) => {
const [collapsed, setCollapsed] = useState(true);
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
data-panel
ref={ref}
data-test
className={classNames(
'relative',
'p-2 rounded mt-[10px]',
'font-mono text-[12px] leading-[16px] font-normal',
'[&>h4]:font-bold',
'overflow-auto',
{
'h-[64px] overflow-hidden': collapsed,
'pb-4': !collapsed,
},
className
)}
aria-expanded={!collapsed}
onDoubleClick={(e) => {
e.preventDefault();
setCollapsed(!collapsed);
}}
{...props}
>
{children}
{collapsed && (
<div
data-panel-curtain
className={classNames(
'bg-gradient-to-b from-transparent to-inherit',
'absolute bottom-0 left-0 h-8 w-full pointer-events-none'
)}
></div>
)}
<div
data-panel-actions
className={classNames(
'absolute bottom-0 right-0',
'p-2',
'rounded-tl',
'flex align-middle gap-1'
)}
>
{actions}
<button
className="cursor-pointer"
onClick={(e) => {
e.preventDefault();
setCollapsed(!collapsed);
}}
title={collapsed ? t('Expand') : t('Collapse')}
aria-label={collapsed ? t('Expand') : t('Collapse')}
>
{collapsed ? (
<Icon name="expand-all" size={3} />
) : (
<Icon name="collapse-all" size={3} />
)}
</button>
</div>
</div>
);
});
export const ToastHeading = forwardRef<
HTMLHeadingElement,
HtmlHTMLAttributes<HTMLHeadingElement>
>(({ children, ...props }, ref) => (
<h3 ref={ref} className="text-sm uppercase mb-1" {...props}>
>(({ children, className, ...props }, ref) => (
<h3
ref={ref}
className={classNames('text-sm uppercase mb-1', className)}
{...props}
>
{children}
</h3>
));
@@ -167,7 +246,7 @@ export const Toast = ({
},
// panel's colours
{
'[&_[data-panel]]:bg-vega-light-150 [&_[data-panel]]:dark:bg-vega-dark-150 ':
'[&_[data-panel]]:bg-vega-light-150 [&_[data-panel]]:dark:bg-vega-dark-150':
intent === Intent.None,
'[&_[data-panel]]:bg-vega-blue-350 [&_[data-panel]]:dark:bg-vega-blue-650':
intent === Intent.Primary,
@@ -178,6 +257,31 @@ export const Toast = ({
'[&_[data-panel]]:bg-vega-pink-350 [&_[data-panel]]:dark:bg-vega-pink-650':
intent === Intent.Danger,
},
{
'[&_[data-panel]]:to-vega-light-150 [&_[data-panel]]:dark:to-vega-dark-150':
intent === Intent.None,
'[&_[data-panel]]:to-vega-blue-350 [&_[data-panel]]:dark:to-vega-blue-650':
intent === Intent.Primary,
'[&_[data-panel]]:to-vega-green-350 [&_[data-panel]]:dark:to-vega-green-650':
intent === Intent.Success,
'[&_[data-panel]]:to-vega-orange-350 [&_[data-panel]]:dark:to-vega-orange-650':
intent === Intent.Warning,
'[&_[data-panel]]:to-vega-pink-350 [&_[data-panel]]:dark:to-vega-pink-650':
intent === Intent.Danger,
},
// panel's actions
{
'[&_[data-panel-actions]]:bg-vega-light-200 [&_[data-panel-actions]]:dark:bg-vega-dark-100 ':
intent === Intent.None,
'[&_[data-panel-actions]]:bg-vega-blue-400 [&_[data-panel-actions]]:dark:bg-vega-blue-600':
intent === Intent.Primary,
'[&_[data-panel-actions]]:bg-vega-green-400 [&_[data-panel-actions]]:dark:bg-vega-green-600':
intent === Intent.Success,
'[&_[data-panel-actions]]:bg-vega-orange-400 [&_[data-panel-actions]]:dark:bg-vega-orange-600':
intent === Intent.Warning,
'[&_[data-panel-actions]]:bg-vega-pink-400 [&_[data-panel-actions]]:dark:bg-vega-pink-600':
intent === Intent.Danger,
},
// panels's progress bar colours
'[&_[data-progress-bar]]:mt-[10px] [&_[data-progress-bar]]:mb-[4px]',
{
+4 -3
View File
@@ -9,20 +9,21 @@ export * from './lib/use-ethereum-config';
export * from './lib/use-ethereum-read-contract';
export * from './lib/use-ethereum-transaction-manager';
export * from './lib/use-ethereum-transaction-store';
export * from './lib/use-ethereum-transaction-toasts';
export * from './lib/use-ethereum-transaction-updater';
export * from './lib/use-ethereum-transaction';
export * from './lib/use-ethereum-withdraw-approval-toasts';
export * from './lib/use-ethereum-withdraw-approvals-manager';
export * from './lib/use-ethereum-withdraw-approvals-store';
export * from './lib/use-get-withdraw-delay';
export * from './lib/use-get-withdraw-threshold';
export * from './lib/use-token-contract';
export * from './lib/use-token-decimals';
export * from './lib/use-vega-transaction-toasts';
export * from './lib/use-web3-disconnect';
export * from './lib/web3-connect-dialog';
export * from './lib/web3-connect-store';
export * from './lib/web3-connectors';
export * from './lib/web3-provider';
export * from './lib/use-vega-transaction-toasts';
export * from './lib/use-ethereum-transaction-toasts';
export * from './lib/use-ethereum-withdraw-approval-toasts';
export * from './lib/withdrawal-approval-dialog';
export * from './lib/withdrawal-approval-status';
@@ -50,11 +50,10 @@ const EthWithdrawalApprovalToastContent = ({
</strong>
</Panel>
);
return (
<>
{title.length > 0 && (
<ToastHeading className="font-bold">{title}</ToastHeading>
)}
{title.length > 0 && <ToastHeading>{title}</ToastHeading>}
<VerificationStatus state={tx} />
{details}
</>
@@ -49,6 +49,7 @@ import { useMarketList } from '@vegaprotocol/markets';
import type { Side } from '@vegaprotocol/types';
import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/datagrid';
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
const intentMap: { [s in VegaTxStatus]: Intent } = {
Default: Intent.Primary,
@@ -457,20 +458,41 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
</Button>
</p>
);
const dialogTrigger = (
// It has to stay as <a> due to the word breaking issue
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a
href="#"
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
onClick={(e) => {
e.preventDefault();
if (tx.withdrawal?.id) {
useWithdrawalApprovalDialog.getState().open(tx.withdrawal?.id);
}
}}
>
{t('save your withdrawal details')}
</a>
);
return (
<>
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
<p>{t('Your funds have been unlocked for withdrawal')}</p>
<p>{t('Your funds have been unlocked for withdrawal.')}</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
rel="noreferrer"
>
{t('View in block explorer')}
</ExternalLink>
</p>
<ExternalLink
className="block mb-[5px] break-all"
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
rel="noreferrer"
>
{t('View in block explorer')}
</ExternalLink>
)}
{/* TODO: Delay message - This withdrawal is subject to a delay. Come back in 5 days to complete the withdrawal. */}
<p className="break-words">
{t('You can')} {dialogTrigger} {t('for extra security.')}
</p>
<VegaTransactionDetails tx={tx} />
{completeWithdrawalButton}
</>
@@ -638,8 +660,9 @@ export const useVegaTransactionToasts = () => {
);
const fromVegaTransaction = (tx: VegaStoredTxState): Toast => {
const closeAfter = isFinal(tx) ? CLOSE_AFTER : undefined;
const { intent, content } = getVegaTransactionContentIntent(tx);
const closeAfter =
isFinal(tx) && !isWithdrawTransaction(tx.body) ? CLOSE_AFTER : undefined;
return {
id: `vega-${tx.id}`,
@@ -680,10 +703,22 @@ export const getVegaTransactionContentIntent = (tx: VegaStoredTxState) => {
}
// Transaction can be successful but the order can be rejected by the network
const intentForRejectedOrder =
tx.order &&
!isOrderAmendmentTransaction(tx.body) &&
getOrderToastIntent(tx.order.status);
// Although the transaction is completed on the vega network the whole
// withdrawal process is not - funds are only released at this point
const intentForCompletedWithdrawal =
tx.status === VegaTxStatus.Complete &&
isWithdrawTransaction(tx.body) &&
Intent.Warning;
const intent =
(tx.order &&
!isOrderAmendmentTransaction(tx.body) &&
getOrderToastIntent(tx.order.status)) ||
intentForRejectedOrder ||
intentForCompletedWithdrawal ||
intentMap[tx.status];
return { intent, content };
};
@@ -0,0 +1,187 @@
import { t } from '@vegaprotocol/i18n';
import {
Button,
CopyWithTooltip,
Dialog,
Icon,
KeyValueTable,
KeyValueTableRow,
Splash,
SyntaxHighlighter,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useWithdrawalApprovalQuery } from '@vegaprotocol/wallet';
import omit from 'lodash/omit';
import { create } from 'zustand';
type WithdrawalApprovalDialogProps = {
withdrawalId: string | undefined;
trigger?: HTMLElement | null;
open: boolean;
onChange: (open: boolean) => void;
asJson?: boolean;
};
export const WithdrawalApprovalDialog = ({
withdrawalId,
trigger,
open,
onChange,
asJson,
}: WithdrawalApprovalDialogProps) => {
return (
<Dialog
title={t('Save withdrawal details')}
icon={<Icon name="info-sign"></Icon>}
open={open}
onChange={(isOpen) => onChange(isOpen)}
onCloseAutoFocus={(e) => {
/**
* This mimics radix's default behaviour that focuses the dialog's
* trigger after closing itself
*/
if (trigger) {
e.preventDefault();
trigger.focus();
}
}}
>
<div className="pr-8">
<p>
{t(
`If the network is reset or has an outage, records of your withdrawal
may be lost. We recommend you save these details in a safe place so
you can still complete your withdrawal.`
)}
</p>
{withdrawalId ? (
<WithdrawalApprovalDialogContent
withdrawalId={withdrawalId}
asJson={Boolean(asJson)}
/>
) : (
<NoDataContent />
)}
</div>
<div className="w-1/4">
<Button
data-testid="close-withdrawal-approval-dialog"
fill={true}
size="sm"
onClick={() => onChange(false)}
>
{t('Close')}
</Button>
</div>
</Dialog>
);
};
type WithdrawalApprovalDialogContentProps = {
withdrawalId: string;
asJson: boolean;
};
const NoDataContent = ({ msg = t('No data') }) => (
<div className="py-12" data-testid="splash">
<Splash>{msg}</Splash>
</div>
);
const WithdrawalApprovalDialogContent = ({
withdrawalId,
asJson,
}: WithdrawalApprovalDialogContentProps) => {
const { data, loading } = useWithdrawalApprovalQuery({
variables: {
withdrawalId,
},
});
if (loading) {
return <NoDataContent msg={t('Loading')} />;
}
if (data?.erc20WithdrawalApproval) {
const details = omit(data.erc20WithdrawalApproval, '__typename');
if (asJson) {
return (
<div className="py-4">
<SyntaxHighlighter size="smaller" data={details} />
</div>
);
} else {
return (
<div className="py-4 flex flex-col">
<div className="self-end mb-1">
<CopyWithTooltip text={JSON.stringify(details, undefined, 2)}>
<Button
className="flex gap-1 items-center no-underline"
size="xs"
variant="primary"
>
<VegaIcon name={VegaIconNames.COPY} size={14} />
<span className="text-sm no-underline">{t('Copy')}</span>
</Button>
</CopyWithTooltip>
</div>
<KeyValueTable>
{Object.entries(details).map(([key, value]) => (
<KeyValueTableRow key={key}>
<div data-testid={`${key}_label`}>{key}</div>
<div data-testid={`${key}_value`} className="break-all">
{value}
</div>
</KeyValueTableRow>
))}
</KeyValueTable>
</div>
);
}
}
return <NoDataContent />;
};
export type WithdrawalApprovalDialogStore = {
isOpen: boolean;
id: string;
trigger: HTMLElement | null | undefined;
asJson: boolean;
setOpen: (isOpen: boolean) => void;
open: (id: string, trigger?: HTMLElement | null, asJson?: boolean) => void;
};
export const useWithdrawalApprovalDialog =
create<WithdrawalApprovalDialogStore>()((set) => ({
isOpen: false,
id: '',
trigger: null,
asJson: false,
setOpen: (isOpen) => {
set({ isOpen: isOpen });
},
open: (id, trigger?, asJson = false) => {
set({
isOpen: true,
id,
trigger,
asJson,
});
},
}));
export const WithdrawalApprovalDialogContainer = () => {
const { isOpen, id, trigger, setOpen, asJson } =
useWithdrawalApprovalDialog();
return (
<WithdrawalApprovalDialog
withdrawalId={id}
trigger={trigger || null}
open={isOpen}
onChange={setOpen}
asJson={asJson}
/>
);
};
+8 -8
View File
@@ -1,13 +1,13 @@
export * from './lib/__generated__/Erc20Approval';
export * from './lib/__generated__/Withdrawal';
export * from './lib/create-withdrawal-dialog';
export * from './lib/withdrawal-dialog';
export * from './lib/withdraw-form';
export * from './lib/withdraw-form-container';
export * from './lib/withdraw-manager';
export * from './lib/withdrawals-table';
export * from './lib/withdrawal-feedback';
export * from './lib/use-complete-withdraw';
export * from './lib/use-create-withdraw';
export * from './lib/use-verify-withdrawal';
export * from './lib/withdraw-form-container';
export * from './lib/withdraw-form';
export * from './lib/withdraw-manager';
export * from './lib/withdrawal-dialog';
export * from './lib/withdrawal-feedback';
export * from './lib/withdrawals-provider';
export * from './lib/__generated__/Withdrawal';
export * from './lib/__generated__/Erc20Approval';
export * from './lib/withdrawals-table';
+54 -8
View File
@@ -9,7 +9,16 @@ import {
} from '@vegaprotocol/utils';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import {
ButtonLink,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Icon,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import type {
TypedDataAgGrid,
VegaICellRendererParams,
@@ -18,7 +27,10 @@ import type {
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import { EtherscanLink } from '@vegaprotocol/environment';
import type { WithdrawalFieldsFragment } from './__generated__/Withdrawal';
import { useEthWithdrawApprovalsStore } from '@vegaprotocol/web3';
import {
useEthWithdrawApprovalsStore,
useWithdrawalApprovalDialog,
} from '@vegaprotocol/web3';
import * as Schema from '@vegaprotocol/types';
export const WithdrawalsTable = (
@@ -119,6 +131,7 @@ export const WithdrawalsTable = (
headerName={t('Transaction')}
field="txHash"
flex={2}
type="rightAligned"
cellRendererParams={{
complete: (withdrawal: WithdrawalFieldsFragment) => {
createWithdrawApproval(withdrawal);
@@ -139,18 +152,51 @@ export type CompleteCellProps = {
complete: (withdrawal: WithdrawalFieldsFragment) => void;
};
export const CompleteCell = ({ data, complete }: CompleteCellProps) => {
const open = useWithdrawalApprovalDialog((state) => state.open);
const ref = useRef<HTMLDivElement>(null);
if (!data) {
return null;
}
return data.pendingOnForeignChain ? (
'-'
) : (
<ButtonLink
data-testid="complete-withdrawal"
onClick={() => complete(data)}
>
{t('Complete withdrawal')}
</ButtonLink>
<div className="flex justify-end gap-1">
<ButtonLink
data-testid="complete-withdrawal"
onClick={() => complete(data)}
>
{t('Complete withdrawal')}
</ButtonLink>
<DropdownMenu
trigger={
<DropdownMenuTrigger
className="hover:bg-vega-light-200 dark:hover:bg-vega-dark-200 p-0.5 focus:rounded-full hover:rounded-full"
data-testid="dropdown-menu"
>
<VegaIcon name={VegaIconNames.KEBAB} />
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
<DropdownMenuItem
key={'withdrawal-approval'}
data-testid="withdrawal-approval"
ref={ref}
onClick={() => {
if (data.id) {
open(data.id, ref.current, false);
}
}}
>
<span>
<Icon name="info-sign" size={4} /> {t('View withdrawal details')}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};