From 22813fc3c6ca11ca05c52e361303074a867c6116 Mon Sep 17 00:00:00 2001 From: Graham Goh Date: Tue, 12 Nov 2024 16:30:43 +0900 Subject: [PATCH] feat(chain-config): support create solana config Support creating solana chain config in the job distributor page. JIRA: https://smartcontract-it.atlassian.net/browse/DPA-1265 --- .changeset/flat-impalas-peel.md | 5 + .../Form/ChainConfigurationForm.test.tsx | 157 +++++++++++++----- .../Form/ChainConfigurationForm.tsx | 36 ++-- src/hooks/queries/useAptosAccountsQuery.ts | 24 --- ...st.tsx => useNonEvmAccountsQuery.test.tsx} | 30 +++- src/hooks/queries/useNonEvmAccountsQuery.ts | 36 ++++ .../FeedsManager/EditSupportedChainDialog.tsx | 9 +- .../FeedsManager/NewSupportedChainDialog.tsx | 9 +- 8 files changed, 209 insertions(+), 97 deletions(-) create mode 100644 .changeset/flat-impalas-peel.md delete mode 100644 src/hooks/queries/useAptosAccountsQuery.ts rename src/hooks/queries/{useAptosAccountsQuery.test.tsx => useNonEvmAccountsQuery.test.tsx} (62%) create mode 100644 src/hooks/queries/useNonEvmAccountsQuery.ts diff --git a/.changeset/flat-impalas-peel.md b/.changeset/flat-impalas-peel.md new file mode 100644 index 00000000..2f98fd7d --- /dev/null +++ b/.changeset/flat-impalas-peel.md @@ -0,0 +1,5 @@ +--- +'@smartcontractkit/operator-ui': minor +--- + +Support creating solana chain config diff --git a/src/components/Form/ChainConfigurationForm.test.tsx b/src/components/Form/ChainConfigurationForm.test.tsx index 6c4c5826..1befe72d 100644 --- a/src/components/Form/ChainConfigurationForm.test.tsx +++ b/src/components/Form/ChainConfigurationForm.test.tsx @@ -18,7 +18,6 @@ describe('ChainConfigurationForm', () => { initialValues={initialValues} onSubmit={handleSubmit} accountsEVM={[]} - accountsAptos={[]} chains={[]} p2pKeys={[]} ocrKeys={[]} @@ -46,7 +45,6 @@ describe('ChainConfigurationForm', () => { initialValues={initialValues} onSubmit={handleSubmit} accountsEVM={[]} - accountsAptos={[]} chains={[]} p2pKeys={[]} ocrKeys={[]} @@ -85,7 +83,6 @@ describe('ChainConfigurationForm', () => { initialValues={initialValues} onSubmit={handleSubmit} accountsEVM={[]} - accountsAptos={[]} chains={[]} p2pKeys={[]} ocrKeys={[]} @@ -125,43 +122,9 @@ describe('ChainConfigurationForm', () => { initialValues.chainType = ChainTypes.EVM initialValues.adminAddr = '0x1234567' - const { container } = render( - handleSubmit(x)} - accountsEVM={[ - { - address: '0x1111', - chain: { - id: '1111', - }, - createdAt: '2021-10-06T00:00:00Z', - isDisabled: false, - }, - ]} - accountsAptos={[ - { - account: '0x123', - id: '2222', - }, - ]} - chains={[ - { - id: '1111', - enabled: true, - network: 'evm', - }, - { - id: '2222', - enabled: true, - network: 'aptos', - }, - ]} - p2pKeys={[]} - ocrKeys={[]} - ocr2Keys={[]} - showSubmit - />, + const { container } = renderChainConfigurationForm( + initialValues, + handleSubmit, ) const chainType = getByRole('button', { name: 'EVM' }) @@ -217,6 +180,69 @@ describe('ChainConfigurationForm', () => { }) }) +test('should able to create Solana chain config', async () => { + const handleSubmit = jest.fn() + const initialValues = emptyFormValues() + initialValues.chainType = ChainTypes.EVM + initialValues.adminAddr = '0x1234567' + + const { container } = renderChainConfigurationForm( + initialValues, + handleSubmit, + ) + + const chainType = getByRole('button', { name: 'EVM' }) + userEvent.click(chainType) + userEvent.click(getByRole('option', { name: 'SOLANA' })) + await screen.findByRole('button', { name: 'SOLANA' }) + + // no easy way to use react testing framework to do what i want, + // had to resort to using #id and querySelector + // formik does not seem to work well with react testing framework + const chainId = container.querySelector('#select-chainID') + expect(chainId).toBeInTheDocument() + // workaround ts lint warning - unable to use chainId! + chainId && userEvent.click(chainId) + userEvent.click(getByRole('option', { name: '3333' })) + await screen.findByRole('button', { name: '3333' }) + + const address = container.querySelector('#select-accountAddr') + expect(address).toBeInTheDocument() + address && userEvent.click(address) + userEvent.click(getByRole('option', { name: 'solana_xxxx' })) + await screen.findByRole('button', { name: 'solana_xxxx' }) + + await userEvent.click(getByRole('button', { name: /submit/i })) + + await waitFor(() => { + expect(handleSubmit).toHaveBeenCalledWith({ + accountAddr: 'solana_xxxx', + accountAddrPubKey: '', + adminAddr: '0x1234567', + chainID: '3333', + chainType: 'SOLANA', + fluxMonitorEnabled: false, + ocr1Enabled: false, + ocr1IsBootstrap: false, + ocr1KeyBundleID: '', + ocr1Multiaddr: '', + ocr1P2PPeerID: '', + ocr2CommitPluginEnabled: false, + ocr2Enabled: false, + ocr2ExecutePluginEnabled: false, + ocr2ForwarderAddress: '', + ocr2IsBootstrap: false, + ocr2KeyBundleID: '', + ocr2MedianPluginEnabled: false, + ocr2MercuryPluginEnabled: false, + ocr2Multiaddr: '', + ocr2P2PPeerID: '', + ocr2RebalancerPluginEnabled: false, + }) + expect(handleSubmit).toHaveBeenCalledTimes(1) + }) +}) + function emptyFormValues(): FormValues { return { chainID: '', @@ -243,3 +269,54 @@ function emptyFormValues(): FormValues { ocr2ForwarderAddress: '', } } + +function renderChainConfigurationForm( + initialValues: FormValues, + onSubmit: (x: FormValues) => void, +) { + return render( + onSubmit(x)} + accountsEVM={[ + { + address: '0x1111', + chain: { + id: '1111', + }, + createdAt: '2021-10-06T00:00:00Z', + isDisabled: false, + }, + ]} + accountsNonEvm={{ + aptosKeys: { + results: [{ account: '0x123', id: '2222' }], + }, + solanaKeys: { + results: [{ id: 'solana_xxxx' }], + }, + }} + chains={[ + { + id: '1111', + enabled: true, + network: 'evm', + }, + { + id: '2222', + enabled: true, + network: 'aptos', + }, + { + id: '3333', + enabled: true, + network: 'solana', + }, + ]} + p2pKeys={[]} + ocrKeys={[]} + ocr2Keys={[]} + showSubmit + />, + ) +} diff --git a/src/components/Form/ChainConfigurationForm.tsx b/src/components/Form/ChainConfigurationForm.tsx index 03030d8a..5732d36a 100644 --- a/src/components/Form/ChainConfigurationForm.tsx +++ b/src/components/Form/ChainConfigurationForm.tsx @@ -204,7 +204,7 @@ export interface Props extends WithStyles { ) => void | Promise chains: ReadonlyArray accountsEVM: ReadonlyArray - accountsAptos: ReadonlyArray + accountsNonEvm?: FetchNonEvmKeys p2pKeys: ReadonlyArray ocrKeys: ReadonlyArray ocr2Keys: ReadonlyArray @@ -222,7 +222,7 @@ export const ChainConfigurationForm = withStyles(styles)( onSubmit, chains = [], accountsEVM = [], - accountsAptos = [], + accountsNonEvm, p2pKeys = [], ocrKeys = [], ocr2Keys = [], @@ -237,17 +237,26 @@ export const ChainConfigurationForm = withStyles(styles)( > {({ values }) => { let chainAccountAddresses: string[] = [] - if (values.chainType === ChainTypes.EVM) { - chainAccountAddresses = accountsEVM - .filter( - (acc) => acc.chain.id == values.chainID && !acc.isDisabled, - ) - .map((acc) => acc.address) + switch (values.chainType) { + case ChainTypes.EVM: + chainAccountAddresses = accountsEVM + .filter( + (acc) => acc.chain.id == values.chainID && !acc.isDisabled, + ) + .map((acc) => acc.address) + break + case ChainTypes.APTOS: + chainAccountAddresses = + accountsNonEvm?.aptosKeys.results.map((acc) => acc.account) ?? + [] + break + case ChainTypes.SOLANA: + chainAccountAddresses = + accountsNonEvm?.solanaKeys.results.map((acc) => acc.id) ?? [] + break + default: + chainAccountAddresses = [] } - if (values.chainType === ChainTypes.APTOS) { - chainAccountAddresses = accountsAptos.map((acc) => acc.account) - } - return (
APTOS + + SOLANA + diff --git a/src/hooks/queries/useAptosAccountsQuery.ts b/src/hooks/queries/useAptosAccountsQuery.ts deleted file mode 100644 index 780412d3..00000000 --- a/src/hooks/queries/useAptosAccountsQuery.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { gql, QueryHookOptions, useQuery } from '@apollo/client' - -export const APTOS_KEYS_PAYLOAD__RESULTS_FIELDS = gql` - fragment AptosKeysPayload_ResultsFields on AptosKey { - account - id - } -` - -export const APTOS_KEYS_QUERY = gql` - ${APTOS_KEYS_PAYLOAD__RESULTS_FIELDS} - query FetchAptosKeys { - aptosKeys { - results { - ...AptosKeysPayload_ResultsFields - } - } - } -` - -// useAptosAccountsQuery fetches the Aptos accounts. -export const useAptosAccountsQuery = (opts: QueryHookOptions = {}) => { - return useQuery(APTOS_KEYS_QUERY, opts) -} diff --git a/src/hooks/queries/useAptosAccountsQuery.test.tsx b/src/hooks/queries/useNonEvmAccountsQuery.test.tsx similarity index 62% rename from src/hooks/queries/useAptosAccountsQuery.test.tsx rename to src/hooks/queries/useNonEvmAccountsQuery.test.tsx index 4ee6c28d..1fb5a8f7 100644 --- a/src/hooks/queries/useAptosAccountsQuery.test.tsx +++ b/src/hooks/queries/useNonEvmAccountsQuery.test.tsx @@ -2,9 +2,9 @@ import React from 'react' import { render, screen, waitFor } from '@testing-library/react' import { MockedProvider } from '@apollo/client/testing' import { - useAptosAccountsQuery, - APTOS_KEYS_QUERY, -} from './useAptosAccountsQuery' + useNonEvmAccountsQuery, + NON_EVM_KEYS_QUERY, +} from './useNonEvmAccountsQuery' const mockData = { data: { @@ -15,20 +15,24 @@ const mockData = { { __typename: 'AptosKey', account: 'account2', id: '2' }, ], }, + solanaKeys: { + __typename: 'SolanaKeys', + results: [{ __typename: 'SolanaKey', id: '3' }], + }, }, } const mocks = [ { request: { - query: APTOS_KEYS_QUERY, + query: NON_EVM_KEYS_QUERY, }, result: mockData, }, ] const TestComponent: React.FC = () => { - const { data, loading, error } = useAptosAccountsQuery() + const { data, loading, error } = useNonEvmAccountsQuery() if (loading) return

Loading...

if (error) return

Error: {error.message}

@@ -38,14 +42,20 @@ const TestComponent: React.FC = () => { {data?.aptosKeys.results.map((key, i) => (

Account: {key.account}

-

ID: {key.id}

+

Aptos ID: {key.id}

+
+ ))} + + {data?.solanaKeys.results.map((key, i) => ( +
+

Solana ID: {key.id}

))} ) } -describe('useAptosAccountsQuery', () => { +describe('useNonEvmAccountsQuery', () => { test('renders data with correct graphql query', async () => { render( @@ -55,9 +65,11 @@ describe('useAptosAccountsQuery', () => { await waitFor(() => { expect(screen.getByText('Account: account1')).toBeInTheDocument() - expect(screen.getByText('ID: 1')).toBeInTheDocument() + expect(screen.getByText('Aptos ID: 1')).toBeInTheDocument() expect(screen.getByText('Account: account2')).toBeInTheDocument() - expect(screen.getByText('ID: 2')).toBeInTheDocument() + expect(screen.getByText('Aptos ID: 2')).toBeInTheDocument() + + expect(screen.getByText('Solana ID: 3')).toBeInTheDocument() }) }) }) diff --git a/src/hooks/queries/useNonEvmAccountsQuery.ts b/src/hooks/queries/useNonEvmAccountsQuery.ts new file mode 100644 index 00000000..d9c16731 --- /dev/null +++ b/src/hooks/queries/useNonEvmAccountsQuery.ts @@ -0,0 +1,36 @@ +import { gql, QueryHookOptions, useQuery } from '@apollo/client' + +export const APTOS_KEYS_PAYLOAD__RESULTS_FIELDS = gql` + fragment AptosKeysPayload_ResultsFields on AptosKey { + account + id + } +` + +export const SOLANA_KEYS_PAYLOAD__RESULTS_FIELDS = gql` + fragment SolanaKeysPayload_ResultsFields on SolanaKey { + id + } +` + +export const NON_EVM_KEYS_QUERY = gql` + ${APTOS_KEYS_PAYLOAD__RESULTS_FIELDS} + ${SOLANA_KEYS_PAYLOAD__RESULTS_FIELDS} + query FetchNonEvmKeys { + aptosKeys { + results { + ...AptosKeysPayload_ResultsFields + } + } + solanaKeys { + results { + ...SolanaKeysPayload_ResultsFields + } + } + } +` + +// useNonEvmAccountsQuery fetches the non evm accounts. +export const useNonEvmAccountsQuery = (opts: QueryHookOptions = {}) => { + return useQuery(NON_EVM_KEYS_QUERY, opts) +} diff --git a/src/screens/FeedsManager/EditSupportedChainDialog.tsx b/src/screens/FeedsManager/EditSupportedChainDialog.tsx index e9624c44..0a100710 100644 --- a/src/screens/FeedsManager/EditSupportedChainDialog.tsx +++ b/src/screens/FeedsManager/EditSupportedChainDialog.tsx @@ -13,7 +13,7 @@ import { } from 'src/components/Form/ChainConfigurationForm' import { useChainsQuery } from 'src/hooks/queries/useChainsQuery' import { useEVMAccountsQuery } from 'src/hooks/queries/useEVMAccountsQuery' -import { useAptosAccountsQuery } from 'src/hooks/queries/useAptosAccountsQuery' +import { useNonEvmAccountsQuery } from 'src/hooks/queries/useNonEvmAccountsQuery' import { useP2PKeysQuery } from 'src/hooks/queries/useP2PKeysQuery' import { useOCRKeysQuery } from 'src/hooks/queries/useOCRKeysQuery' import { useOCR2KeysQuery } from 'src/hooks/queries/useOCR2KeysQuery' @@ -41,7 +41,7 @@ export const EditSupportedChainDialog = ({ fetchPolicy: 'cache-and-network', }) - const { data: accountDataAptos } = useAptosAccountsQuery({ + const { data: accountDataNonEvm } = useNonEvmAccountsQuery({ fetchPolicy: 'cache-and-network', }) @@ -89,9 +89,6 @@ export const EditSupportedChainDialog = ({ const chains = chainData ? chainData.chains.results : [] const accountsEVM = accountDataEVM ? accountDataEVM.ethKeys.results : [] - const accountsAptos = accountDataAptos - ? accountDataAptos.aptosKeys.results - : [] const p2pKeys = p2pKeysData ? p2pKeysData.p2pKeys.results : [] const ocrKeys = ocrKeysData ? ocrKeysData.ocrKeyBundles.results : [] const ocr2Keys = ocr2KeysData ? ocr2KeysData.ocr2KeyBundles.results : [] @@ -109,7 +106,7 @@ export const EditSupportedChainDialog = ({ onSubmit={onSubmit} chains={chains} accountsEVM={accountsEVM} - accountsAptos={accountsAptos} + accountsNonEvm={accountDataNonEvm} p2pKeys={p2pKeys} ocrKeys={ocrKeys} ocr2Keys={ocr2Keys} diff --git a/src/screens/FeedsManager/NewSupportedChainDialog.tsx b/src/screens/FeedsManager/NewSupportedChainDialog.tsx index ef590f96..21d2b543 100644 --- a/src/screens/FeedsManager/NewSupportedChainDialog.tsx +++ b/src/screens/FeedsManager/NewSupportedChainDialog.tsx @@ -13,7 +13,7 @@ import { } from 'src/components/Form/ChainConfigurationForm' import { useChainsQuery } from 'src/hooks/queries/useChainsQuery' import { useEVMAccountsQuery } from 'src/hooks/queries/useEVMAccountsQuery' -import { useAptosAccountsQuery } from 'src/hooks/queries/useAptosAccountsQuery' +import { useNonEvmAccountsQuery } from 'src/hooks/queries/useNonEvmAccountsQuery' import { useP2PKeysQuery } from 'src/hooks/queries/useP2PKeysQuery' import { useOCRKeysQuery } from 'src/hooks/queries/useOCRKeysQuery' import { useOCR2KeysQuery } from 'src/hooks/queries/useOCR2KeysQuery' @@ -35,7 +35,7 @@ export const NewSupportedChainDialog = ({ onClose, open, onSubmit }: Props) => { fetchPolicy: 'cache-and-network', }) - const { data: accountDataAptos } = useAptosAccountsQuery({ + const { data: accountDataNonEvm } = useNonEvmAccountsQuery({ fetchPolicy: 'cache-and-network', }) @@ -79,9 +79,6 @@ export const NewSupportedChainDialog = ({ onClose, open, onSubmit }: Props) => { const chains = chainData ? chainData.chains.results : [] const accountsEVM = accountDataEVM ? accountDataEVM.ethKeys.results : [] - const accountsAptos = accountDataAptos - ? accountDataAptos.aptosKeys.results - : [] const p2pKeys = p2pKeysData ? p2pKeysData.p2pKeys.results : [] const ocrKeys = ocrKeysData ? ocrKeysData.ocrKeyBundles.results : [] const ocr2Keys = ocr2KeysData ? ocr2KeysData.ocr2KeyBundles.results : [] @@ -99,7 +96,7 @@ export const NewSupportedChainDialog = ({ onClose, open, onSubmit }: Props) => { onSubmit={onSubmit} chains={chains} accountsEVM={accountsEVM} - accountsAptos={accountsAptos} + accountsNonEvm={accountDataNonEvm} p2pKeys={p2pKeys} ocrKeys={ocrKeys} ocr2Keys={ocr2Keys}