Skip to content
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

[OPCORE-858]: feat: new distributor home page (NOT INTEGRATED) #84

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions src/hooks/queries/useFeedsManagersQuery.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { gql, useQuery } from '@apollo/client'
import { gql, QueryHookOptions, useQuery } from '@apollo/client'

export const FEEDS_MANAGERS_QUERY = gql`
fragment FetchFeedsManagersPayload_ResultsFields on FeedsManager {
__typename
id
name
uri
publicKey
isConnectionActive
createdAt
}
query FetchFeedsManagers {
feedsManagers {
results {
__typename
id
name
uri
publicKey
isConnectionActive
createdAt
...FetchFeedsManagersPayload_ResultsFields
}
}
}
`

export const useFeedsManagersQuery = () => {
return useQuery<FetchFeedsManagers, FetchFeedsManagersVariables>(
FEEDS_MANAGERS_QUERY,
)
export const useFeedsManagersQuery = (
options?: QueryHookOptions<FetchFeedsManagers>,
) => {
return useQuery<FetchFeedsManagers>(FEEDS_MANAGERS_QUERY, options)
}
47 changes: 47 additions & 0 deletions src/screens/FeedsManager/ConnectionStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react'
import green from '@material-ui/core/colors/green'
import red from '@material-ui/core/colors/red'
import { createStyles, WithStyles, withStyles } from '@material-ui/core/styles'
import Typography from '@material-ui/core/Typography'
import CancelIcon from '@material-ui/icons/Cancel'
import CheckCircleIcon from '@material-ui/icons/CheckCircle'

const connectionStatusStyles = () => {
return createStyles({
root: {
display: 'flex',
},
connectedIcon: {
color: green[500],
},
disconnectedIcon: {
color: red[500],
},
text: {
marginLeft: 4,
},
})
}

interface ConnectionStatusProps
extends WithStyles<typeof connectionStatusStyles> {
isConnected: boolean
}

export const ConnectionStatus = withStyles(connectionStatusStyles)(
({ isConnected, classes }: ConnectionStatusProps) => {
return (
<div className={classes.root}>
{isConnected ? (
<CheckCircleIcon fontSize="small" className={classes.connectedIcon} />
) : (
<CancelIcon fontSize="small" className={classes.disconnectedIcon} />
)}

<Typography variant="body1" inline className={classes.text}>
{isConnected ? 'Connected' : 'Disconnected'}
</Typography>
</div>
)
},
)
55 changes: 5 additions & 50 deletions src/screens/FeedsManager/FeedsManagerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,22 @@
import React from 'react'

import CancelIcon from '@material-ui/icons/Cancel'
import CheckCircleIcon from '@material-ui/icons/CheckCircle'
import EditIcon from '@material-ui/icons/Edit'
import IconButton from '@material-ui/core/IconButton'
import Grid from '@material-ui/core/Grid'
import IconButton from '@material-ui/core/IconButton'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Menu from '@material-ui/core/Menu'
import EditIcon from '@material-ui/icons/Edit'
import MoreVertIcon from '@material-ui/icons/MoreVert'
import { createStyles, WithStyles, withStyles } from '@material-ui/core/styles'
import Typography from '@material-ui/core/Typography'
import green from '@material-ui/core/colors/green'
import red from '@material-ui/core/colors/red'

import { CopyIconButton } from 'src/components/Copy/CopyIconButton'
import {
DetailsCard,
DetailsCardItemTitle,
DetailsCardItemValue,
} from 'src/components/Cards/DetailsCard'
import { shortenHex } from 'src/utils/shortenHex'
import { CopyIconButton } from 'src/components/Copy/CopyIconButton'
import { MenuItemLink } from 'src/components/MenuItemLink'

const connectionStatusStyles = () => {
return createStyles({
root: {
display: 'flex',
},
connectedIcon: {
color: green[500],
},
disconnectedIcon: {
color: red[500],
},
text: {
marginLeft: 4,
},
})
}

interface ConnectionStatusProps
extends WithStyles<typeof connectionStatusStyles> {
isConnected: boolean
}

const ConnectionStatus = withStyles(connectionStatusStyles)(
({ isConnected, classes }: ConnectionStatusProps) => {
return (
<div className={classes.root}>
{isConnected ? (
<CheckCircleIcon fontSize="small" className={classes.connectedIcon} />
) : (
<CancelIcon fontSize="small" className={classes.disconnectedIcon} />
)}

<Typography variant="body1" inline className={classes.text}>
{isConnected ? 'Connected' : 'Disconnected'}
</Typography>
</div>
)
},
)
import { shortenHex } from 'src/utils/shortenHex'
import { ConnectionStatus } from './ConnectionStatus'

interface Props {
manager: FeedsManagerFields
Expand Down
40 changes: 40 additions & 0 deletions src/screens/JobDistributors/JobDistributorsRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'

import { withStyles, WithStyles } from '@material-ui/core/styles'
import TableCell from '@material-ui/core/TableCell'
import TableRow from '@material-ui/core/TableRow'

import Link from 'components/Link'
import { tableStyles } from 'components/Table'
import { CopyIconButton } from 'src/components/Copy/CopyIconButton'
import { shortenHex } from 'src/utils/shortenHex'
import { ConnectionStatus } from '../FeedsManager/ConnectionStatus'

interface Props extends WithStyles<typeof tableStyles> {
jobDistributor: FetchFeedsManagersPayload_ResultsFields
}

export const JobDistributorsRow = withStyles(tableStyles)(
({ jobDistributor, classes }: Props) => {
return (
<TableRow className={classes.row} hover>
<TableCell className={classes.cell} component="th" scope="row">
<Link
className={classes.link}
href={`/job_distributors/${jobDistributor.id}`}
>
{jobDistributor.name}
</Link>
</TableCell>
<TableCell>
<ConnectionStatus isConnected={jobDistributor.isConnectionActive} />
</TableCell>
<TableCell>
{shortenHex(jobDistributor.publicKey, { start: 6, end: 6 })}
<CopyIconButton data={jobDistributor.publicKey} />
</TableCell>
<TableCell>{jobDistributor.uri}</TableCell>
</TableRow>
)
},
)
74 changes: 74 additions & 0 deletions src/screens/JobDistributors/JobDistributorsScreen.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react'

import { MockedProvider, MockedResponse } from '@apollo/client/testing'
import { GraphQLError } from 'graphql'
import { Route } from 'react-router-dom'
import { renderWithRouter, screen, within } from 'test-utils'

import { FEEDS_MANAGERS_QUERY } from 'src/hooks/queries/useFeedsManagersQuery'
import { buildFeedsManager } from 'support/factories/gql/fetchFeedsManagers'
import { waitForLoading } from 'support/test-helpers/wait'
import { JobDistributorsScreen } from './JobDistributorsScreen'

const { findAllByRole, findByText } = screen

function renderComponent(mocks: MockedResponse[]) {
renderWithRouter(
<>
<Route exact path="/job_distributors">
<MockedProvider mocks={mocks} addTypename={false}>
<JobDistributorsScreen />
</MockedProvider>
</Route>
</>,
{ initialEntries: ['/job_distributors'] },
)
}

describe('JobDistributorsScreen', () => {
it('should render the list of job distributors', async () => {
const mocks: MockedResponse[] = [
{
request: {
query: FEEDS_MANAGERS_QUERY,
},
result: {
data: {
feedsManagers: {
results: [buildFeedsManager()],
},
},
},
},
]

renderComponent(mocks)

await waitForLoading()

const rows = await findAllByRole('row')

// header counts as a row
expect(rows).toHaveLength(2)
expect(
within(rows[1]).getByText('Chainlink Feeds Manager'),
).toBeInTheDocument()
})

it('should renders GQL errors', async () => {
const mocks: MockedResponse[] = [
{
request: {
query: FEEDS_MANAGERS_QUERY,
},
result: {
errors: [new GraphQLError('Error!')],
},
},
]

renderComponent(mocks)

expect(await findByText('Error: Error!')).toBeInTheDocument()
})
})
24 changes: 24 additions & 0 deletions src/screens/JobDistributors/JobDistributorsScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'

import { GraphqlErrorHandler } from 'src/components/ErrorHandler/GraphqlErrorHandler'
import { Loading } from 'src/components/Feedback/Loading'
import { useFeedsManagersQuery } from 'src/hooks/queries/useFeedsManagersQuery'
import { JobDistributorsView } from './JobDistributorsView'

export const JobDistributorsScreen = () => {
const { data, loading, error } = useFeedsManagersQuery({
fetchPolicy: 'cache-and-network',
})

if (loading) {
return <Loading />
}

if (error) {
return <GraphqlErrorHandler error={error} />
}

return (
<JobDistributorsView jobDistributors={data?.feedsManagers.results ?? []} />
)
}
87 changes: 87 additions & 0 deletions src/screens/JobDistributors/JobDistributorsView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react'

import userEvent from '@testing-library/user-event'
import { Route, Switch } from 'react-router-dom'

import { buildFeedsManager } from 'support/factories/gql/fetchFeedsManagers'
import { renderWithRouter, screen, within } from 'support/test-utils'
import { JobDistributorsView } from './JobDistributorsView'

const { getAllByRole, getByRole, getByText, findByText } = screen

function renderComponent(
mockData: ReadonlyArray<FetchFeedsManagersPayload_ResultsFields>,
) {
renderWithRouter(
<Switch>
<Route exact path="/job_distributors">
<JobDistributorsView jobDistributors={mockData} />,
</Route>
<Route exact path="/job_distributors/new">
New Job Distributor Page
</Route>
<Route exact path="/job_distributors/1">
Edit Job Distributor Page
</Route>
</Switch>,
{ initialEntries: ['/job_distributors'] },
)
}

describe('JobDistributorsView', () => {
test('should render the list of job distributors', () => {
renderComponent([
buildFeedsManager(),
buildFeedsManager({
name: 'Job Distributor 2',
id: '2',
isConnectionActive: true,
}),
])

expect(getByRole('heading')).toHaveTextContent('Job Distributors')

// header row counts as 1 row too
const rows = getAllByRole('row')
expect(rows).toHaveLength(3)

expect(getByText('Name')).toBeInTheDocument()
expect(getByText('Status')).toBeInTheDocument()
expect(getByText('CSA Public Key')).toBeInTheDocument()
expect(getByText('RPC URL')).toBeInTheDocument()

expect(
within(rows[1]).getByText('Chainlink Feeds Manager'),
).toBeInTheDocument()
expect(within(rows[1]).getByText('Disconnected')).toBeInTheDocument()
expect(within(rows[1]).getByText('localhost:8080')).toBeInTheDocument()

expect(within(rows[2]).getByText('Job Distributor 2')).toBeInTheDocument()
expect(within(rows[2]).getByText('Connected')).toBeInTheDocument()
expect(within(rows[2]).getByText('localhost:8080')).toBeInTheDocument()
})

test('should navigate to create new job distributor page when new button is clicked', async () => {
renderComponent([buildFeedsManager()])

userEvent.click(getByText(/New Job Distributor/i))

expect(await findByText('New Job Distributor Page')).toBeInTheDocument()
})

test('should show placeholder message when there are no job distributors', async () => {
renderComponent([])

expect(
await findByText('Job Distributors have not been registered'),
).toBeInTheDocument()
})

test('should navigate to detail job distributor page when row is clicked', async () => {
renderComponent([buildFeedsManager()])

userEvent.click(getByText(/chainlink feeds manager/i))

expect(await findByText('Edit Job Distributor Page')).toBeInTheDocument()
})
})
Loading
Loading