-
Notifications
You must be signed in to change notification settings - Fork 8
Add support for OCR2 Key Management - No creation for now #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
619b986
Ignore operating system specific files
hans-kessock b704a07
Added support for OCR2 keys in the operator-ui's key management page.
hans-kessock 6d834b5
Added tests for OCR2 Key Management UI support
hans-kessock e70b350
Added changeset for the addition of OCR2 Key Viewing/Deletion
hans-kessock b23e749
Missed removal of creation support from testing, not fixed
hans-kessock fd974ee
Merge branch 'main' of github.com:smartcontractkit/operator-ui into b…
hans-kessock b7cf713
Updated tests to use the correct enum (that enum in graphQL now inclu…
hans-kessock abdd8f9
Merge remote-tracking branch 'origin/main' into bug/BCF-1991
george-dorin 0e400df
Add useQueryErrorHandler
george-dorin 2079e2a
Implement feedback
george-dorin e8c4aa9
Merge remote-tracking branch 'origin/main' into bug/BCF-1991
george-dorin 511e7fb
Implement feedback
george-dorin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@smartcontractkit/operator-ui': minor | ||
| --- | ||
|
|
||
| Added support for the display and deletion of OCR version 2 (OCR2) keys |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,3 +8,6 @@ tsconfig.tsbuildinfo | |
| .npmrc | ||
| assets | ||
| yarn-error.log | ||
|
|
||
| # OS specific | ||
| .DS_Store | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import React from 'react' | ||
|
|
||
| import { ApolloError } from '@apollo/client' | ||
| import { GraphQLError } from 'graphql' | ||
| import { Route } from 'react-router-dom' | ||
| import { renderWithRouter, screen } from 'support/test-utils' | ||
| import { getAuthentication } from 'utils/storage' | ||
|
|
||
| import Notifications from 'pages/Notifications' | ||
| import { useQueryErrorHandler } from 'hooks/useQueryErrorHandler' | ||
|
|
||
| const { getByText } = screen | ||
|
|
||
| const StubComponent = ({ mockError }: { mockError?: unknown }) => { | ||
| const { handleQueryError } = useQueryErrorHandler() | ||
|
|
||
| React.useEffect(() => { | ||
| handleQueryError(mockError) | ||
| }, [mockError, handleQueryError]) | ||
|
|
||
| return null | ||
| } | ||
|
|
||
| function renderComponent(mockError?: unknown) { | ||
| renderWithRouter( | ||
| <> | ||
| <Notifications /> | ||
| <Route exact path="/"> | ||
| <StubComponent mockError={mockError} /> | ||
| </Route> | ||
|
|
||
| <Route exact path="/signin"> | ||
| Redirect Success | ||
| </Route> | ||
| </>, | ||
| ) | ||
| } | ||
|
|
||
| describe('useQueryErrorHandler', () => { | ||
| it('renders an empty component if error undefined', () => { | ||
| renderComponent() | ||
|
|
||
| expect(document.documentElement).toHaveTextContent('') | ||
| }) | ||
|
|
||
| it('renders the apollo error message', () => { | ||
| const graphQLErrors = [new GraphQLError('GraphQL error')] | ||
| const errorMessage = 'Something went wrong' | ||
| const apolloError = new ApolloError({ | ||
| graphQLErrors, | ||
| errorMessage, | ||
| }) | ||
|
|
||
| renderComponent(apolloError) | ||
|
|
||
| expect(getByText('Something went wrong')).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('redirects an authenticated error', () => { | ||
| const graphQLErrors = [ | ||
| new GraphQLError( | ||
| 'Unauthorized', | ||
| undefined, | ||
| undefined, | ||
| undefined, | ||
| undefined, | ||
| undefined, | ||
| { code: 'UNAUTHORIZED' }, | ||
| ), | ||
| ] | ||
| const errorMessage = 'Something went wrong' | ||
| const apolloError = new ApolloError({ | ||
| graphQLErrors, | ||
| errorMessage, | ||
| }) | ||
|
|
||
| renderComponent(apolloError) | ||
|
|
||
| expect(getByText('Redirect Success')).toBeInTheDocument() | ||
| expect(getAuthentication()).toEqual({ allowed: false }) | ||
| }) | ||
|
|
||
| it('renders the message in an alert when it is a simple error', () => { | ||
| renderComponent(new Error('Something went wrong')) | ||
|
|
||
| expect(getByText('Something went wrong')).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('renders a generic message in an alert as a default', () => { | ||
| renderComponent('generic message') // A string type is not handled and falls to the default | ||
|
|
||
| expect(getByText('An error occurred')).toBeInTheDocument() | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import React from 'react' | ||
| import { useHistory } from 'react-router-dom' | ||
| import { useDispatch } from 'react-redux' | ||
|
|
||
| import { notifyErrorMsg } from 'actionCreators' | ||
| import { ApolloError } from '@apollo/client' | ||
| import { receiveSignoutSuccess } from 'actionCreators' | ||
| /** | ||
| * Handles an unknown error which is caught from a | ||
| * query operation. If the error returned is an authentication error, it | ||
| * signs the user out and redirects them to the sign-in page, otherwise it | ||
| * displays an alert with the error message. | ||
| */ | ||
| export const useQueryErrorHandler = () => { | ||
| const [error, handleQueryError] = React.useState<unknown>() | ||
| const history = useHistory() | ||
| const dispatch = useDispatch() | ||
|
|
||
| React.useEffect(() => { | ||
| if (!error) { | ||
| return | ||
| } | ||
|
|
||
| if (error instanceof ApolloError) { | ||
| // Check for an authentication error and logout | ||
| for (const gqlError of error.graphQLErrors) { | ||
| if (gqlError.extensions?.code == 'UNAUTHORIZED') { | ||
| dispatch( | ||
| notifyErrorMsg( | ||
| 'Unauthorized, please log in with proper credentials', | ||
| ), | ||
| ) | ||
| dispatch(receiveSignoutSuccess()) | ||
| history.push('/signin') | ||
|
|
||
| return | ||
| } | ||
| } | ||
| } | ||
| dispatch(notifyErrorMsg((error as Error).message || 'An error occurred')) | ||
| }, [dispatch, error, history]) | ||
|
|
||
| return { handleQueryError } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import * as React from 'react' | ||
|
|
||
| import { render, screen } from 'support/test-utils' | ||
|
|
||
| import { buildOCR2KeyBundle } from 'support/factories/gql/fetchOCR2KeyBundles' | ||
| import { OCR2KeyBundleRow } from './OCR2KeyBundleRow' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| const { getByRole, queryByText } = screen | ||
|
|
||
| describe('OCR2KeyBundleRow', () => { | ||
| let handleDelete: jest.Mock | ||
|
|
||
| beforeEach(() => { | ||
| handleDelete = jest.fn() | ||
| }) | ||
|
|
||
| function renderComponent(bundle: Ocr2KeyBundlesPayload_ResultsFields) { | ||
| render( | ||
| <table> | ||
| <tbody> | ||
| <OCR2KeyBundleRow bundle={bundle} onDelete={handleDelete} /> | ||
| </tbody> | ||
| </table>, | ||
| ) | ||
| } | ||
|
|
||
| it('renders a row', () => { | ||
| const bundle = buildOCR2KeyBundle() | ||
|
|
||
| renderComponent(bundle) | ||
|
|
||
| expect(queryByText(`Key ID: ${bundle.id}`)).toBeInTheDocument() | ||
| expect( | ||
| queryByText(`Chain Type: ${bundle.chainType}`), | ||
| ).toBeInTheDocument() | ||
| expect( | ||
| queryByText(`Config Public Key: ${bundle.configPublicKey}`), | ||
| ).toBeInTheDocument() | ||
| expect( | ||
| queryByText(`On-Chain Public Key: ${bundle.onChainPublicKey}`), | ||
| ).toBeInTheDocument() | ||
| expect( | ||
| queryByText(`Off-Chain Public Key: ${bundle.offChainPublicKey}`), | ||
| ).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('calls delete', () => { | ||
| const bundle = buildOCR2KeyBundle() | ||
|
|
||
| renderComponent(bundle) | ||
|
|
||
| userEvent.click(getByRole('button', { name: /delete/i })) | ||
|
|
||
| expect(handleDelete).toHaveBeenCalled() | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import React from 'react' | ||
|
|
||
| import Button from 'src/components/Button' | ||
| import TableCell from '@material-ui/core/TableCell' | ||
| import TableRow from '@material-ui/core/TableRow' | ||
|
|
||
| import { KeyBundle } from './KeyBundle' | ||
| import { CopyIconButton } from 'src/components/Copy/CopyIconButton' | ||
|
|
||
| interface Props { | ||
| bundle: Ocr2KeyBundlesPayload_ResultsFields | ||
| onDelete: () => void | ||
| } | ||
|
|
||
| /** | ||
| * This row follows the form and structure of OCRKeyBundleRow but | ||
| * uses the new data for keys from OCR2 | ||
| */ | ||
| export const OCR2KeyBundleRow: React.FC<Props> = ({ bundle, onDelete }) => { | ||
| return ( | ||
| <TableRow hover> | ||
| <TableCell> | ||
| <KeyBundle | ||
| primary={ | ||
| <b> | ||
| Key ID: {bundle.id} <CopyIconButton data={bundle.id} /> | ||
| </b> | ||
| } | ||
| secondary={[ | ||
| <>Chain Type: {bundle.chainType}</>, | ||
| <>Config Public Key: {bundle.configPublicKey}</>, | ||
| <>On-Chain Public Key: {bundle.onChainPublicKey}</>, | ||
| <>Off-Chain Public Key: {bundle.offChainPublicKey}</>, | ||
| ]} | ||
| /> | ||
| </TableCell> | ||
| <TableCell align="right"> | ||
| <Button onClick={onDelete} variant="danger" size="medium"> | ||
| Delete | ||
| </Button> | ||
| </TableCell> | ||
| </TableRow> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import * as React from 'react' | ||
|
|
||
| import { GraphQLError } from 'graphql' | ||
| import { | ||
| renderWithRouter, | ||
| screen, | ||
| waitForElementToBeRemoved, | ||
| } from 'support/test-utils' | ||
| import { MockedProvider, MockedResponse } from '@apollo/client/testing' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| import { | ||
| OCR2Keys, | ||
| DELETE_OCR2_KEY_BUNDLE_MUTATION, | ||
| } from './OCR2Keys' | ||
| import { | ||
| buildOCR2KeyBundle, | ||
| buildOCR2KeyBundles, | ||
| } from 'support/factories/gql/fetchOCR2KeyBundles' | ||
| import Notifications from 'pages/Notifications' | ||
| import { OCR2_KEY_BUNDLES_QUERY } from 'src/hooks/queries/useOCR2KeysQuery' | ||
| import { waitForLoading } from 'support/test-helpers/wait' | ||
|
|
||
| const { findByText, getByRole, queryByText } = screen | ||
|
|
||
| function renderComponent(mocks: MockedResponse[]) { | ||
| renderWithRouter( | ||
| <> | ||
| <Notifications /> | ||
| <MockedProvider mocks={mocks} addTypename={false}> | ||
| <OCR2Keys /> | ||
| </MockedProvider> | ||
| </>, | ||
| ) | ||
| } | ||
|
|
||
| function fetchOCR2KeyBundlesQuery( | ||
| bundles: ReadonlyArray<Ocr2KeyBundlesPayload_ResultsFields>, | ||
| ) { | ||
| return { | ||
| request: { | ||
| query: OCR2_KEY_BUNDLES_QUERY, | ||
| }, | ||
| result: { | ||
| data: { | ||
| ocr2KeyBundles: { | ||
| results: bundles, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| describe('OCR2Keys', () => { | ||
| it('renders the page', async () => { | ||
| const payload = buildOCR2KeyBundles() | ||
| const mocks: MockedResponse[] = [fetchOCR2KeyBundlesQuery(payload)] | ||
|
|
||
| renderComponent(mocks) | ||
|
|
||
| await waitForLoading() | ||
|
|
||
| expect(await findByText(`Key ID: ${payload[0].id}`)).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('renders GQL query errors', async () => { | ||
| const mocks: MockedResponse[] = [ | ||
| { | ||
| request: { | ||
| query: OCR2_KEY_BUNDLES_QUERY, | ||
| }, | ||
| result: { | ||
| errors: [new GraphQLError('Error!')], | ||
| }, | ||
| }, | ||
| ] | ||
|
|
||
| renderComponent(mocks) | ||
|
|
||
| expect(await findByText('Error!')).toBeInTheDocument() | ||
| }) | ||
|
|
||
| it('deletes an OCR2 Key Bundle', async () => { | ||
| const payload = buildOCR2KeyBundle() | ||
|
|
||
| const mocks: MockedResponse[] = [ | ||
| fetchOCR2KeyBundlesQuery([payload]), | ||
| { | ||
| request: { | ||
| query: DELETE_OCR2_KEY_BUNDLE_MUTATION, | ||
| variables: { id: payload.id }, | ||
| }, | ||
| result: { | ||
| data: { | ||
| deleteOCR2KeyBundle: { | ||
| __typename: 'DeleteOCR2KeyBundleSuccess', | ||
| bundle: payload, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| fetchOCR2KeyBundlesQuery([]), | ||
| ] | ||
|
|
||
| renderComponent(mocks) | ||
|
|
||
| expect(await findByText(`Key ID: ${payload.id}`)).toBeInTheDocument() | ||
|
|
||
| userEvent.click(getByRole('button', { name: /delete/i })) | ||
| userEvent.click(getByRole('button', { name: /confirm/i })) | ||
|
|
||
| await waitForElementToBeRemoved(getByRole('dialog')) | ||
|
|
||
| expect( | ||
| await findByText( | ||
| 'Successfully deleted Off-ChainReporting Key Bundle Key', | ||
| ), | ||
| ).toBeInTheDocument() | ||
|
|
||
| expect(queryByText(`Key ID: ${payload.id}`)).toBeNull() | ||
| }) | ||
| }) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.