feat(explorer): add full details to oracle page (#6005)
This commit is contained in:
parent
7b162104b6
commit
f4212724d0
@ -81,6 +81,7 @@ describe('TxDetailsTransfer', () => {
|
|||||||
hash: 'test',
|
hash: 'test',
|
||||||
submitter:
|
submitter:
|
||||||
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
|
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
|
||||||
|
block: '100',
|
||||||
command: {
|
command: {
|
||||||
nonce: '5188810881378065222',
|
nonce: '5188810881378065222',
|
||||||
blockHeight: '14951513',
|
blockHeight: '14951513',
|
||||||
|
@ -89,7 +89,7 @@ fragment ExplorerOracleDataSourceSpec on ExternalDataSourceSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query ExplorerOracleFormMarkets {
|
query ExplorerOracleFormMarkets {
|
||||||
marketsConnection {
|
marketsConnection(includeSettled: false, pagination: { first: 20 }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...ExplorerOracleForMarketsMarket
|
...ExplorerOracleForMarketsMarket
|
||||||
@ -102,7 +102,7 @@ query ExplorerOracleFormMarkets {
|
|||||||
dataSourceSpec {
|
dataSourceSpec {
|
||||||
...ExplorerOracleDataSourceSpec
|
...ExplorerOracleDataSourceSpec
|
||||||
}
|
}
|
||||||
dataConnection(pagination: { last: 1 }) {
|
dataConnection(pagination: { first: 1 }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
externalData {
|
externalData {
|
||||||
|
@ -120,7 +120,7 @@ export const ExplorerOracleDataSourceSpecFragmentDoc = gql`
|
|||||||
`;
|
`;
|
||||||
export const ExplorerOracleFormMarketsDocument = gql`
|
export const ExplorerOracleFormMarketsDocument = gql`
|
||||||
query ExplorerOracleFormMarkets {
|
query ExplorerOracleFormMarkets {
|
||||||
marketsConnection {
|
marketsConnection(includeSettled: false, pagination: {first: 20}) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...ExplorerOracleForMarketsMarket
|
...ExplorerOracleForMarketsMarket
|
||||||
@ -133,7 +133,7 @@ export const ExplorerOracleFormMarketsDocument = gql`
|
|||||||
dataSourceSpec {
|
dataSourceSpec {
|
||||||
...ExplorerOracleDataSourceSpec
|
...ExplorerOracleDataSourceSpec
|
||||||
}
|
}
|
||||||
dataConnection(pagination: {last: 1}) {
|
dataConnection(pagination: {first: 1}) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
externalData {
|
externalData {
|
||||||
|
@ -6,6 +6,11 @@ import {
|
|||||||
} from '../../../components/links/external-explorer-link/external-explorer-link';
|
} from '../../../components/links/external-explorer-link/external-explorer-link';
|
||||||
import { getExternalChainLabel } from '@vegaprotocol/environment';
|
import { getExternalChainLabel } from '@vegaprotocol/environment';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import isArray from 'lodash/isArray';
|
||||||
|
import type { components } from '../../../../types/explorer';
|
||||||
|
|
||||||
|
type Normalisers = components['schemas']['vegaNormaliser'][];
|
||||||
|
|
||||||
interface OracleDetailsEthSourceProps {
|
interface OracleDetailsEthSourceProps {
|
||||||
sourceType: SourceType;
|
sourceType: SourceType;
|
||||||
@ -34,21 +39,117 @@ export function OracleEthSource({
|
|||||||
|
|
||||||
const chainLabel = getExternalChainLabel(chain);
|
const chainLabel = getExternalChainLabel(chain);
|
||||||
|
|
||||||
|
const abi = prepareOracleSpecField(sourceType?.sourceType?.abi);
|
||||||
|
const args = prepareOracleSpecField(sourceType?.sourceType?.args);
|
||||||
|
const normalisers = serialiseNormalisers(sourceType.sourceType.normalisers);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableHeader scope="row">
|
<TableHeader scope="row" className="pt-1 align-text-top">
|
||||||
{chainLabel} {t('Contract')}
|
{chainLabel} {t('Contract')}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableCell modifier="bordered">
|
<TableCell modifier="bordered">
|
||||||
<ExternalExplorerLink
|
<details>
|
||||||
chain={chain}
|
<summary className="cursor-pointer">
|
||||||
id={address}
|
<ExternalExplorerLink
|
||||||
type={EthExplorerLinkTypes.address}
|
chain={chain}
|
||||||
code={true}
|
id={address}
|
||||||
/>
|
type={EthExplorerLinkTypes.address}
|
||||||
<span className="mx-3">⇒</span>
|
code={true}
|
||||||
<code>{sourceType.sourceType.method}</code>
|
/>
|
||||||
|
<span className="mx-3">⇒</span>
|
||||||
|
<code>{sourceType.sourceType.method}</code>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
{args && (
|
||||||
|
<>
|
||||||
|
<h2 className={'mt-5 mb-1 text-xl'}>{t('Arguments')}</h2>
|
||||||
|
<div className="max-w-3">
|
||||||
|
<SyntaxHighlighter
|
||||||
|
data={JSON.parse(
|
||||||
|
sourceType.sourceType.args as unknown as string
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{abi && (
|
||||||
|
<>
|
||||||
|
<h2 className={'mt-5 mb-1 text-xl'}>{t('ABI')}</h2>
|
||||||
|
<div className="max-w-3">
|
||||||
|
<SyntaxHighlighter data={abi} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{normalisers && (
|
||||||
|
<>
|
||||||
|
<h2 className={'mt-5 mb-1 text-xl'}>{t('Normalisers')}</h2>
|
||||||
|
<div className="max-w-3 mb-3">
|
||||||
|
<SyntaxHighlighter data={normalisers} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</details>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constant to define the absence of a valid string from the Oracle Spec fields
|
||||||
|
const NO_DATA = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ABI and args are stored as either a (JSON escaped, probably) string
|
||||||
|
* or array of strings. Given that OracleEthSource is simply throwing the
|
||||||
|
* data in to a SyntaxHighlighter, we don't really care about the format,
|
||||||
|
* so this function will just try to parse the data and return it as a string.
|
||||||
|
*
|
||||||
|
* @param abi
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function prepareOracleSpecField(
|
||||||
|
specField?: string[] | null
|
||||||
|
): string | false {
|
||||||
|
if (!specField) {
|
||||||
|
return NO_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isArray(specField)) {
|
||||||
|
return JSON.parse(specField.join(''));
|
||||||
|
} else {
|
||||||
|
return JSON.parse(specField);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return NO_DATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to prepareOracleSpecField above, but processes an array of normaliser objects
|
||||||
|
* removing the __typename and returning a serialised array of normalisers for
|
||||||
|
* SyntaxHighlighter
|
||||||
|
*
|
||||||
|
* @param normalisers
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function serialiseNormalisers(
|
||||||
|
normalisers?: Normalisers | null
|
||||||
|
): Normalisers | false {
|
||||||
|
if (!normalisers) {
|
||||||
|
return NO_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return normalisers.map((normaliser) => {
|
||||||
|
return {
|
||||||
|
name: normaliser.name,
|
||||||
|
expression: normaliser.expression,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return NO_DATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,38 +1,9 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { OracleFilter } from './oracle-filter';
|
import { OracleFilter } from './oracle-filter';
|
||||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||||
import {
|
import { ConditionOperator, DataSourceSpecStatus } from '@vegaprotocol/types';
|
||||||
ConditionOperator,
|
|
||||||
DataSourceSpecStatus,
|
|
||||||
PropertyKeyType,
|
|
||||||
} from '@vegaprotocol/types';
|
|
||||||
import type { Condition } from '@vegaprotocol/types';
|
import type { Condition } from '@vegaprotocol/types';
|
||||||
|
|
||||||
type Spec =
|
|
||||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
|
|
||||||
|
|
||||||
const mockExternalSpec: Spec = {
|
|
||||||
sourceType: {
|
|
||||||
__typename: 'DataSourceSpecConfiguration',
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
__typename: 'Filter',
|
|
||||||
key: {
|
|
||||||
type: PropertyKeyType.TYPE_INTEGER,
|
|
||||||
name: 'testKey',
|
|
||||||
},
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
__typename: 'Condition',
|
|
||||||
value: 'testValue',
|
|
||||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function renderComponent(data: ExplorerOracleDataSourceFragment) {
|
function renderComponent(data: ExplorerOracleDataSourceFragment) {
|
||||||
return <OracleFilter data={data} />;
|
return <OracleFilter data={data} />;
|
||||||
}
|
}
|
||||||
@ -50,31 +21,6 @@ describe('Oracle Filter view', () => {
|
|||||||
expect(res.container).toBeEmptyDOMElement();
|
expect(res.container).toBeEmptyDOMElement();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders filters if type is DataSourceSpecConfiguration', () => {
|
|
||||||
const res = render(
|
|
||||||
renderComponent({
|
|
||||||
dataSourceSpec: {
|
|
||||||
spec: {
|
|
||||||
id: 'irrelevant-test-data',
|
|
||||||
createdAt: 'irrelevant-test-data',
|
|
||||||
status: DataSourceSpecStatus.STATUS_ACTIVE,
|
|
||||||
data: {
|
|
||||||
sourceType: mockExternalSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dataConnection: {
|
|
||||||
edges: [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Renders a comprehensible summary of key = value
|
|
||||||
expect(res.getByText('testKey')).toBeInTheDocument();
|
|
||||||
expect(res.getByText('=')).toBeInTheDocument();
|
|
||||||
expect(res.getByText('testValue')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
|
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
|
||||||
const res = render(
|
const res = render(
|
||||||
renderComponent({
|
renderComponent({
|
||||||
@ -136,7 +82,7 @@ describe('Oracle Filter view', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// This should never happen, but for coverage sake we test that it does this
|
// This should never happen, but for coverage we test that it does this
|
||||||
const ul = res.getByRole('list');
|
const ul = res.getByRole('list');
|
||||||
expect(ul).toBeInTheDocument();
|
expect(ul).toBeInTheDocument();
|
||||||
expect(ul).toBeEmptyDOMElement();
|
expect(ul).toBeEmptyDOMElement();
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||||
import { OracleSpecInternalTimeTrigger } from './oracle-spec/internal-time-trigger';
|
import {
|
||||||
|
OracleSpecInternalTimeTrigger,
|
||||||
|
TimeTrigger,
|
||||||
|
} from './oracle-spec/internal-time-trigger';
|
||||||
import { OracleSpecCondition } from './oracle-spec/condition';
|
import { OracleSpecCondition } from './oracle-spec/condition';
|
||||||
import { getCharacterForOperator } from './oracle-spec/operator';
|
import { getCharacterForOperator } from './oracle-spec/operator';
|
||||||
|
|
||||||
@ -11,7 +14,7 @@ interface OracleFilterProps {
|
|||||||
* Shows the conditions that this oracle is using to filter
|
* Shows the conditions that this oracle is using to filter
|
||||||
* data sources, as a list.
|
* data sources, as a list.
|
||||||
*
|
*
|
||||||
* Renders nothing if there is no data (which will frequently)
|
* Renders nothing if there is no data (which will frequently
|
||||||
* be the case) and if there is data, currently renders a simple
|
* be the case) and if there is data, currently renders a simple
|
||||||
* JSON view.
|
* JSON view.
|
||||||
*/
|
*/
|
||||||
@ -21,6 +24,7 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
|
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
|
||||||
|
|
||||||
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
|
if (s.__typename === 'DataSourceSpecConfigurationTime' && s.conditions) {
|
||||||
return (
|
return (
|
||||||
<ul>
|
<ul>
|
||||||
@ -41,30 +45,30 @@ export function OracleFilter({ data }: OracleFilterProps) {
|
|||||||
s.triggers
|
s.triggers
|
||||||
) {
|
) {
|
||||||
return <OracleSpecInternalTimeTrigger data={s} />;
|
return <OracleSpecInternalTimeTrigger data={s} />;
|
||||||
} else if (
|
} else if (s.__typename === 'EthCallSpec') {
|
||||||
s.__typename === 'EthCallSpec' ||
|
|
||||||
s.__typename === 'DataSourceSpecConfiguration'
|
|
||||||
) {
|
|
||||||
if (s.filters !== null && s.filters && 'filters' in s) {
|
if (s.filters !== null && s.filters && 'filters' in s) {
|
||||||
return (
|
return (
|
||||||
<ul>
|
<div>
|
||||||
{s.filters.map((f) => {
|
<ul>
|
||||||
const prop = <code title={f.key.type}>{f.key.name}</code>;
|
{s.filters.map((f) => {
|
||||||
|
const prop = <code title={f.key.type}>{f.key.name}</code>;
|
||||||
|
|
||||||
if (!f.conditions || f.conditions.length === 0) {
|
if (!f.conditions || f.conditions.length === 0) {
|
||||||
return prop;
|
return prop;
|
||||||
} else {
|
} else {
|
||||||
return f.conditions.map((c) => {
|
return f.conditions.map((c) => {
|
||||||
return (
|
return (
|
||||||
<li key={`${prop}${c.value}`}>
|
<li key={`${prop}${c.value}`}>
|
||||||
{prop} {getCharacterForOperator(c.operator)}{' '}
|
{prop} {getCharacterForOperator(c.operator)}{' '}
|
||||||
<code>{c.value ? c.value : '-'}</code>
|
<code>{c.value ? c.value : '-'}</code>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
{s.trigger && <TimeTrigger data={s.trigger.trigger} />}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type { DataSourceSpecConfigurationTimeTrigger } from '@vegaprotocol/types';
|
import type {
|
||||||
|
DataSourceSpecConfigurationTimeTrigger,
|
||||||
|
EthTimeTrigger,
|
||||||
|
InternalTimeTrigger,
|
||||||
|
Maybe,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
import secondsToMinutes from 'date-fns/secondsToMinutes';
|
import secondsToMinutes from 'date-fns/secondsToMinutes';
|
||||||
import fromUnixTime from 'date-fns/fromUnixTime';
|
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||||
|
|
||||||
@ -13,32 +18,60 @@ export function OracleSpecInternalTimeTrigger({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span>{t('Time')}</span>,
|
<span>{t('Time')}</span>,
|
||||||
{data.triggers.map((tr) => {
|
{data.triggers.map((tr) => (
|
||||||
return (
|
<TimeTrigger data={tr} />
|
||||||
<span>
|
))}
|
||||||
{tr?.initial ? (
|
|
||||||
<span title={`${tr.initial}`}>
|
|
||||||
<strong>{t('starting at')}</strong>{' '}
|
|
||||||
<em className="not-italic underline decoration-dotted">
|
|
||||||
{fromUnixTime(tr.initial).toLocaleString()}
|
|
||||||
</em>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{tr?.every ? (
|
|
||||||
<span title={`${tr.every} ${t('seconds')}`}>
|
|
||||||
, <strong>{t('every')}</strong>{' '}
|
|
||||||
<em className="not-italic underline decoration-dotted">
|
|
||||||
{secondsToMinutes(tr.every)} {t('minutes')}
|
|
||||||
</em>{' '}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimeTriggerProps {
|
||||||
|
data: Maybe<InternalTimeTrigger> | Maybe<EthTimeTrigger>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TimeTrigger({ data }: TimeTriggerProps) {
|
||||||
|
const d = parseDate(data?.initial);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={JSON.stringify(data)}>
|
||||||
|
{data?.initial ? (
|
||||||
|
<span title={`${data.initial}`}>
|
||||||
|
<strong>{t('starting at')}</strong>{' '}
|
||||||
|
<em className="not-italic underline decoration-dotted">{d}</em>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{data?.every ? (
|
||||||
|
<span title={`${data.every} ${t('seconds')}`}>
|
||||||
|
, <strong>{t('every')}</strong>{' '}
|
||||||
|
<em className="not-italic underline decor</em>ation-dotted">
|
||||||
|
{secondsToMinutes(data.every)} {t('minutes')}
|
||||||
|
</em>{' '}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dates in oracle triggers can be (or maybe were previously) Unix Time or timestamps
|
||||||
|
* depending on type. This function handles both cases and returns a nicely formatted date.
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* @returns string Localestring for date
|
||||||
|
*/
|
||||||
|
export function parseDate(date?: string | number): string {
|
||||||
|
if (!date) {
|
||||||
|
return 'Invalid date';
|
||||||
|
}
|
||||||
|
const d = fromUnixTime(+date).toLocaleString();
|
||||||
|
|
||||||
|
if (d === 'Invalid Date') {
|
||||||
|
return new Date(date).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
@ -48,6 +48,11 @@ export const OracleDetails = ({
|
|||||||
? dataSource.dataSourceSpec.spec.data.sourceType.sourceType.sourceChainId.toString()
|
? dataSource.dataSourceSpec.spec.data.sourceType.sourceType.sourceChainId.toString()
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const requiredConfirmations =
|
||||||
|
(sourceType.sourceType.__typename === 'EthCallSpec' &&
|
||||||
|
sourceType.sourceType.requiredConfirmations) ||
|
||||||
|
'';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TableWithTbody className="mb-2">
|
<TableWithTbody className="mb-2">
|
||||||
@ -64,15 +69,23 @@ export const OracleDetails = ({
|
|||||||
{getStatusString(dataSource.dataSourceSpec.spec.status)}
|
{getStatusString(dataSource.dataSourceSpec.spec.status)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
<OracleMarkets id={id} />
|
||||||
<OracleSigners sourceType={sourceType} />
|
<OracleSigners sourceType={sourceType} />
|
||||||
<OracleEthSource sourceType={sourceType} chain={chain} />
|
<OracleEthSource sourceType={sourceType} chain={chain} />
|
||||||
<OracleMarkets id={id} />
|
|
||||||
<TableRow modifier="bordered">
|
<TableRow modifier="bordered">
|
||||||
<TableHeader scope="row">{t('Filter')}</TableHeader>
|
<TableHeader scope="row" className="pt-1 align-text-top">
|
||||||
|
{t('Filter')}
|
||||||
|
</TableHeader>
|
||||||
<TableCell modifier="bordered">
|
<TableCell modifier="bordered">
|
||||||
<OracleFilter data={dataSource} />
|
<OracleFilter data={dataSource} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
{requiredConfirmations && requiredConfirmations > 0 && (
|
||||||
|
<TableRow modifier="bordered">
|
||||||
|
<TableHeader scope="row">{t('Required Confirmations')}</TableHeader>
|
||||||
|
<TableCell modifier="bordered">{requiredConfirmations}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
</TableWithTbody>
|
</TableWithTbody>
|
||||||
{dataConnection ? <OracleData data={dataConnection} /> : null}
|
{dataConnection ? <OracleData data={dataConnection} /> : null}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user