Skip to content
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
1 change: 1 addition & 0 deletions .changelog/990.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show block-level evens in block details
71 changes: 71 additions & 0 deletions src/app/components/RuntimeEvents/EventListFilterSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { FC } from 'react'
import Chip from '@mui/material/Chip'
import CheckIcon from '@mui/icons-material/Check'
import { useTranslation } from 'react-i18next'
import { COLORS } from '../../../styles/theme/colors'
import { TFunction } from 'i18next'
import Box from '@mui/material/Box'

export const EventFilterMode = {
All: 'all',
NonTX: 'nonTX',
} as const

const eventFilterModes: EventFilterMode[] = [EventFilterMode.All, EventFilterMode.NonTX]

const getEventFilterModeName = (t: TFunction, mode: EventFilterMode): string => {
switch (mode) {
case 'all':
return t('runtimeEvent.filter.all')
case 'nonTX':
return t('runtimeEvent.filter.nonTx')
}
}

const Pill: FC<{ label: string; selected: boolean; onSelect: () => void }> = ({
label,
selected,
onSelect,
}) => {
return (
<Chip
icon={selected ? <CheckIcon htmlColor={'#FFFFFF'} /> : undefined}
label={label}
onClick={onSelect}
sx={{
display: 'flex',
height: 30,
padding: '3px 10px',
alignItems: 'center',
borderRadius: 9,
border: `1px solid ${COLORS.brandMedium}`,
background: selected ? COLORS.brandMedium : COLORS.brandLightBlue,
color: selected ? 'white' : 'black',
}}
/>
)
}

// eslint-disable-next-line @typescript-eslint/no-redeclare
export type EventFilterMode = (typeof EventFilterMode)[keyof typeof EventFilterMode]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this + export const EventFilterMode = { work

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On is a type, the other is a const. TS compiler is smart enough to figure out which one we want in each situation. (The type is actually eliminated during the transpilation phase, so in the JS we only have the const.)

In any case, this is not something I came up with here, we already have a similar solution for Layer and other things, too.


type EventFilterSwitchProps = {
selected?: EventFilterMode
onSelectionChange: (selection: EventFilterMode) => void
}

export const EventFilterSwitch: FC<EventFilterSwitchProps> = ({ selected, onSelectionChange }) => {
const { t } = useTranslation()
return (
<Box sx={{ display: 'flex', gap: 3 }}>
{eventFilterModes.map(mode => (
<Pill
key={mode}
label={getEventFilterModeName(t, mode)}
selected={selected === mode}
onSelect={() => onSelectionChange(mode)}
/>
))}
</Box>
)
}
17 changes: 15 additions & 2 deletions src/app/pages/AccountDetailsPage/AccountEventsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,32 @@ import { useTranslation } from 'react-i18next'
import { RuntimeEventsDetailedList } from '../../components/RuntimeEvents/RuntimeEventsDetailedList'
import { SearchScope } from '../../../types/searchScope'
import { AddressSwitchOption } from '../../components/AddressSwitch'
import { EventFilterMode, EventFilterSwitch } from '../../components/RuntimeEvents/EventListFilterSwitch'

type AccountEventProps = {
scope: SearchScope
isLoading: boolean
filterMode: EventFilterMode
setFilterMode: (mode: EventFilterMode) => void
isError: boolean
events: RuntimeEvent[] | undefined
}

export const AccountEventsCard: FC<AccountEventProps> = ({ scope, isLoading, isError, events }) => {
export const AccountEventsCard: FC<AccountEventProps> = ({
scope,
isLoading,
filterMode,
setFilterMode,
isError,
events,
}) => {
const { t } = useTranslation()

return (
<SubPageCard title={t('common.events')}>
<SubPageCard
title={t('common.events')}
action={<EventFilterSwitch selected={filterMode} onSelectionChange={setFilterMode} />}
>
<RuntimeEventsDetailedList
scope={scope}
events={events}
Expand Down
8 changes: 6 additions & 2 deletions src/app/pages/AccountDetailsPage/hook.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Layer,
RuntimeEventType,
useGetRuntimeAccountsAddress,
useGetRuntimeEvents,
useGetRuntimeTransactions,
Expand All @@ -8,6 +9,7 @@ import { AppErrors } from '../../../types/errors'
import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination'
import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config'
import { SearchScope } from '../../../types/searchScope'
import { EventFilterMode } from '../../components/RuntimeEvents/EventListFilterSwitch'

export const useAccount = (scope: SearchScope, oasisAddress: string) => {
const { network, layer } = scope
Expand Down Expand Up @@ -60,7 +62,7 @@ export const useAccountTransactions = (scope: SearchScope, address: string) => {
}
}

export const useAccountEvents = (scope: SearchScope, address: string) => {
export const useAccountEvents = (scope: SearchScope, address: string, filterMode: EventFilterMode) => {
const { network, layer } = scope
if (layer === Layer.consensus) {
throw AppErrors.UnsupportedLayer
Expand All @@ -72,6 +74,8 @@ export const useAccountEvents = (scope: SearchScope, address: string) => {
// TODO: implement filtering for non-transactional events
})
const { isFetched, isLoading, isError, data } = query
const events = data?.data.events
const events = data?.data.events.filter(
event => filterMode === EventFilterMode.All || event.type !== RuntimeEventType.accountstransfer,
)
return { isFetched, isLoading, isError, events }
}
19 changes: 16 additions & 3 deletions src/app/pages/AccountDetailsPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHref, useLoaderData, useOutletContext } from 'react-router-dom'
import { PageLayout } from '../../components/PageLayout'
Expand All @@ -17,6 +17,7 @@ import { getTokenTypePluralName } from '../../../types/tokens'
import { SearchScope } from '../../../types/searchScope'
import { AccountDetailsCard } from './AccountDetailsCard'
import { AccountEventsCard } from './AccountEventsCard'
import { EventFilterMode } from '../../components/RuntimeEvents/EventListFilterSwitch'

export type AccountDetailsContext = {
scope: SearchScope
Expand All @@ -35,8 +36,13 @@ export const AccountDetailsPage: FC = () => {
const { token, isLoading: isTokenLoading } = useTokenInfo(scope, address, isContract)

const tokenPriceInfo = useTokenPrice(account?.ticker || Ticker.ROSE)
const [eventFilterMode, setEventFilterMode] = useState<EventFilterMode>(EventFilterMode.All)

const { isLoading: areEventsLoading, isError: isEventsError, events } = useAccountEvents(scope, address)
const {
isLoading: areEventsLoading,
isError: isEventsError,
events,
} = useAccountEvents(scope, address, eventFilterMode)

const tokenTransfersLink = useHref(`token-transfers#${accountTokenTransfersContainerId}`)
const erc20Link = useHref(`tokens/erc-20#${accountTokenContainerId}`)
Expand Down Expand Up @@ -76,7 +82,14 @@ export const AccountDetailsPage: FC = () => {
]}
context={context}
/>
<AccountEventsCard scope={scope} isLoading={areEventsLoading} isError={isEventsError} events={events} />
<AccountEventsCard
scope={scope}
filterMode={eventFilterMode}
setFilterMode={setEventFilterMode}
isLoading={areEventsLoading}
isError={isEventsError}
events={events}
/>
</PageLayout>
)
}
82 changes: 82 additions & 0 deletions src/app/pages/BlockDetailPage/EventsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollingCard } from '../../components/PageLayout/ScrollingCard'
import CardHeader from '@mui/material/CardHeader'
import CardContent from '@mui/material/CardContent'

import { Layer, RuntimeEventType, useGetRuntimeEvents } from '../../../oasis-nexus/api'
import { ErrorBoundary } from '../../components/ErrorBoundary'
import { AppErrors } from '../../../types/errors'
import { SearchScope } from '../../../types/searchScope'
import { RuntimeEventsDetailedList } from '../../components/RuntimeEvents/RuntimeEventsDetailedList'
import { AddressSwitchOption } from '../../components/AddressSwitch'
import { EventFilterMode, EventFilterSwitch } from '../../components/RuntimeEvents/EventListFilterSwitch'
import { EmptyState } from '../../components/EmptyState'

export const eventsContainerId = 'events'

const EventsList: FC<{ scope: SearchScope; blockHeight: number; filterMode: EventFilterMode }> = ({
scope,
blockHeight,
filterMode,
}) => {
const { t } = useTranslation()
if (scope.layer === Layer.consensus) {
// Loading events for consensus blocks is not yet supported.
// Should use useGetConsensusEvents()
throw AppErrors.UnsupportedLayer
}
const eventsQuery = useGetRuntimeEvents(scope.network, scope.layer, {
block: blockHeight,
Comment thread
csillag marked this conversation as resolved.
// TODO: search for tx_hash = null
limit: 100, // We want to avoid pagination here, if possible
})

const { isLoading, isError, data } = eventsQuery

const events = data?.data.events.filter(
event =>
!event.tx_hash && // TODO: remove filtering here if it's implemented using the query parameters
(filterMode === EventFilterMode.All || event.type !== RuntimeEventType.accountstransfer),
)

if (!events?.length && !isLoading) {
return (
<EmptyState
description={t('runtimeEvent.cantFindMatchingEvents')}
title={t('runtimeEvent.noEvents')}
light={true}
/>
)
}

return (
<RuntimeEventsDetailedList
scope={scope}
events={events}
isLoading={isLoading}
isError={isError}
addressSwitchOption={AddressSwitchOption.ETH}
/>
)
}

export const EventsCard: FC<{ scope: SearchScope; blockHeight: number }> = ({ scope, blockHeight }) => {
const [filterMode, setFilterMode] = useState<EventFilterMode>(EventFilterMode.All)
const { t } = useTranslation()
return (
<ScrollingCard id={eventsContainerId}>
<CardHeader
disableTypography
component="h3"
title={t('common.events')}
action={<EventFilterSwitch selected={filterMode} onSelectionChange={setFilterMode} />}
/>
<CardContent>
<ErrorBoundary light={true}>
<EventsList scope={scope} blockHeight={blockHeight} filterMode={filterMode} />
</ErrorBoundary>
</CardContent>
</ScrollingCard>
)
}
2 changes: 2 additions & 0 deletions src/app/pages/BlockDetailPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { BlockLink, BlockHashLink } from '../../components/Blocks/BlockLink'
import { RouteUtils } from '../../utils/route-utils'
import { useRequiredScopeParam } from '../../hooks/useScopeParam'
import { DashboardLink } from '../ParatimeDashboardPage/DashboardLink'
import { EventsCard } from './EventsCard'

export const BlockDetailPage: FC = () => {
const { t } = useTranslation()
Expand All @@ -43,6 +44,7 @@ export const BlockDetailPage: FC = () => {
<BlockDetailView isLoading={isLoading} block={block} />
</SubPageCard>
{!!block?.num_transactions && <TransactionsCard scope={scope} blockHeight={blockHeight} />}
<EventsCard scope={scope} blockHeight={blockHeight} />
</PageLayout>
)
}
Expand Down
6 changes: 6 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,18 @@
},
"runtimeEvent": {
"cantLoadEvents": "Unfortunately we couldn't load the list of events. Please try again later.",
"noEvents": "No events",
"cantFindMatchingEvents": "We can't find any matching events.",
"accountsburn": "Tokens burnt",
"accountsmint": "Tokens minted",
"accountstransfer": "Transfer",
"consensusDeposit": "Deposit from consensus",
"consensusWithdrawal": "Withdrawal to consensus",
"evmLog": "EVM log message",
"filter": {
"all": "All events",
"nonTx": "Non-transactional"
},
"gasUsed": "Gas used",
"fields": {
"amount": "Amount",
Expand Down
1 change: 1 addition & 0 deletions src/styles/theme/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const COLORS = {
brandExtraDark: '#000062',
brandExtraLight: '#e5e5ef',
brandLight: '#6665d8',
brandLightBlue: '#E8F5FF',
brandMedium: '#0092f6',
brandMedium15: '#d9effe',
brightGray2: '#ececec',
Expand Down