feat(explorer): bring back oracles view (#2551)
* build(explorer): enable oracles view by default * feat(explorer): restore and update oracles view * feat(explorer): dumb and in need of refactor oracles tables * feat(oracles): disable key view and rejig table * feat(oracles): filter all JSON views to remove typename * chore(explorer): oracles component refactor * test(explorer): add tests for oracle markets component * test(explorer): add tests for oracle signers component
This commit is contained in:
parent
b10effa3c7
commit
866a11fd89
@ -18,4 +18,4 @@ NX_EXPLORER_PARTIES=1
|
||||
NX_EXPLORER_VALIDATORS=1
|
||||
NX_EXPLORER_MARKETS=1
|
||||
NX_EXPLORER_ORACLES=1
|
||||
NX_EXPLORER_TXS_LIST=0
|
||||
NX_EXPLORER_TXS_LIST=1
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react';
|
||||
import { Routes } from '../../../routes/route-names';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
90
apps/explorer/src/app/routes/oracles/Oracles.graphql
Normal file
90
apps/explorer/src/app/routes/oracles/Oracles.graphql
Normal file
@ -0,0 +1,90 @@
|
||||
fragment ExplorerOracleDataSource on OracleSpec {
|
||||
dataSourceSpec {
|
||||
spec {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
data {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
matchedSpecIds
|
||||
broadcastAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
fragment ExplorerOracleForMarketsMarket on Market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
... on Future {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
}
|
||||
dataSourceSpecForTradingTermination {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerOracleFormMarkets {
|
||||
marketsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
136
apps/explorer/src/app/routes/oracles/__generated__/Oracles.ts
generated
Normal file
136
apps/explorer/src/app/routes/oracles/__generated__/Oracles.ts
generated
Normal file
@ -0,0 +1,136 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerOracleDataSourceFragment = { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } } };
|
||||
|
||||
export type ExplorerOracleDataConnectionFragment = { __typename?: 'OracleSpec', dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } };
|
||||
|
||||
export type ExplorerOracleSpecsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerOracleSpecsQuery = { __typename?: 'Query', oracleSpecsConnection?: { __typename?: 'OracleSpecsConnection', edges?: Array<{ __typename?: 'OracleSpecEdge', node: { __typename?: 'OracleSpec', dataSourceSpec: { __typename?: 'ExternalDataSourceSpec', spec: { __typename?: 'DataSourceSpec', id: string, createdAt: any, updatedAt?: any | null, status: Types.DataSourceSpecStatus, data: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator }> | null }> | null } } | { __typename?: 'DataSourceDefinitionInternal', sourceType: { __typename?: 'DataSourceSpecConfigurationTime', conditions: Array<{ __typename?: 'Condition', value?: string | null, operator: Types.ConditionOperator } | null> } } } } }, dataConnection: { __typename?: 'OracleDataConnection', edges?: Array<{ __typename?: 'OracleDataEdge', node: { __typename?: 'OracleData', externalData: { __typename?: 'ExternalData', data: { __typename?: 'Data', matchedSpecIds?: Array<string> | null, broadcastAt: any, signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, data?: Array<{ __typename?: 'Property', name: string, value: string }> | null } } } } | null> | null } } } | null> | null } | null };
|
||||
|
||||
export const ExplorerOracleDataSourceFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataSource on OracleSpec {
|
||||
dataSourceSpec {
|
||||
spec {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
status
|
||||
data {
|
||||
sourceType {
|
||||
... on DataSourceDefinitionInternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfigurationTime {
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
... on DataSourceDefinitionExternal {
|
||||
sourceType {
|
||||
... on DataSourceSpecConfiguration {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
filters {
|
||||
key {
|
||||
name
|
||||
type
|
||||
}
|
||||
conditions {
|
||||
value
|
||||
operator
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ExplorerOracleDataConnectionFragmentDoc = gql`
|
||||
fragment ExplorerOracleDataConnection on OracleSpec {
|
||||
dataConnection {
|
||||
edges {
|
||||
node {
|
||||
externalData {
|
||||
data {
|
||||
signers {
|
||||
signer {
|
||||
... on ETHAddress {
|
||||
address
|
||||
}
|
||||
... on PubKey {
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
data {
|
||||
name
|
||||
value
|
||||
}
|
||||
matchedSpecIds
|
||||
broadcastAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ExplorerOracleSpecsDocument = gql`
|
||||
query ExplorerOracleSpecs {
|
||||
oracleSpecsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleDataSource
|
||||
...ExplorerOracleDataConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ExplorerOracleDataSourceFragmentDoc}
|
||||
${ExplorerOracleDataConnectionFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleSpecsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerOracleSpecsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerOracleSpecsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerOracleSpecsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerOracleSpecsQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>(ExplorerOracleSpecsDocument, options);
|
||||
}
|
||||
export function useExplorerOracleSpecsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>(ExplorerOracleSpecsDocument, options);
|
||||
}
|
||||
export type ExplorerOracleSpecsQueryHookResult = ReturnType<typeof useExplorerOracleSpecsQuery>;
|
||||
export type ExplorerOracleSpecsLazyQueryHookResult = ReturnType<typeof useExplorerOracleSpecsLazyQuery>;
|
||||
export type ExplorerOracleSpecsQueryResult = Apollo.QueryResult<ExplorerOracleSpecsQuery, ExplorerOracleSpecsQueryVariables>;
|
69
apps/explorer/src/app/routes/oracles/__generated__/OraclesForMarkets.ts
generated
Normal file
69
apps/explorer/src/app/routes/oracles/__generated__/OraclesForMarkets.ts
generated
Normal file
@ -0,0 +1,69 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerOracleForMarketsMarketFragment = { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } } };
|
||||
|
||||
export type ExplorerOracleFormMarketsQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ExplorerOracleFormMarketsQuery = { __typename?: 'Query', marketsConnection?: { __typename?: 'MarketConnection', edges: Array<{ __typename?: 'MarketEdge', node: { __typename?: 'Market', id: string, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', dataSourceSpecForSettlementData: { __typename?: 'DataSourceSpec', id: string }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceSpec', id: string } } } } } }> } | null };
|
||||
|
||||
export const ExplorerOracleForMarketsMarketFragmentDoc = gql`
|
||||
fragment ExplorerOracleForMarketsMarket on Market {
|
||||
id
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
product {
|
||||
... on Future {
|
||||
dataSourceSpecForSettlementData {
|
||||
id
|
||||
}
|
||||
dataSourceSpecForTradingTermination {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ExplorerOracleFormMarketsDocument = gql`
|
||||
query ExplorerOracleFormMarkets {
|
||||
marketsConnection {
|
||||
edges {
|
||||
node {
|
||||
...ExplorerOracleForMarketsMarket
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${ExplorerOracleForMarketsMarketFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useExplorerOracleFormMarketsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerOracleFormMarketsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerOracleFormMarketsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerOracleFormMarketsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerOracleFormMarketsQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>(ExplorerOracleFormMarketsDocument, options);
|
||||
}
|
||||
export function useExplorerOracleFormMarketsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>(ExplorerOracleFormMarketsDocument, options);
|
||||
}
|
||||
export type ExplorerOracleFormMarketsQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsQuery>;
|
||||
export type ExplorerOracleFormMarketsLazyQueryHookResult = ReturnType<typeof useExplorerOracleFormMarketsLazyQuery>;
|
||||
export type ExplorerOracleFormMarketsQueryResult = Apollo.QueryResult<ExplorerOracleFormMarketsQuery, ExplorerOracleFormMarketsQueryVariables>;
|
@ -0,0 +1,67 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { OracleData } from './oracle-data';
|
||||
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
|
||||
|
||||
function renderComponent(data: ExplorerOracleDataConnectionFragment) {
|
||||
return <OracleData data={data} />;
|
||||
}
|
||||
|
||||
describe('Oracle Data view', () => {
|
||||
it('Renders nothing when data is null', () => {
|
||||
const res = render(
|
||||
renderComponent(null as unknown as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing when dataConnection is empty', () => {
|
||||
const res = render(
|
||||
renderComponent({} as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
it('Renders nothing when dataConnection has no edges', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
dataConnection: {
|
||||
edges: null,
|
||||
},
|
||||
} as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing when dataConnection edges is empty', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
dataConnection: {
|
||||
edges: [],
|
||||
},
|
||||
} as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
// This stops short of asserting how the data is presented
|
||||
// because the current view is pretty rudimentary
|
||||
it('Renders details component when there is data', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
dataConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
externalData: {
|
||||
data: {
|
||||
broadcastAt: '2022-01-01',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
} as ExplorerOracleDataConnectionFragment)
|
||||
);
|
||||
expect(res.getByText('Broadcast data')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import filter from 'recursive-key-filter';
|
||||
import type { ExplorerOracleDataConnectionFragment } from '../__generated__/Oracles';
|
||||
|
||||
interface OracleDataTypeProps {
|
||||
data: ExplorerOracleDataConnectionFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is data that has matched this oracle, this view will
|
||||
* render the data inside a collapsed element so that it can be viewed.
|
||||
* Currently the data is just rendered as a JSON view, because
|
||||
* that Does The Job, rather than because it's good.
|
||||
*/
|
||||
export function OracleData({ data }: OracleDataTypeProps) {
|
||||
if (
|
||||
!data ||
|
||||
!data.dataConnection ||
|
||||
!data.dataConnection.edges?.length ||
|
||||
data.dataConnection.edges.length > 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<details data-testid="oracle-data">
|
||||
<summary>{t('Broadcast data')}</summary>
|
||||
<ul>
|
||||
{data.dataConnection.edges.map((d) => {
|
||||
if (!d) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={d.node.externalData.data.broadcastAt}>
|
||||
<SyntaxHighlighter data={filter(d, ['__typename'])} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</details>
|
||||
);
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { OracleDetailsType } from './oracle-details-type';
|
||||
import type { SourceTypeName } from './oracle-details-type';
|
||||
|
||||
function renderComponent(type: SourceTypeName) {
|
||||
return <OracleDetailsType type={type} />;
|
||||
}
|
||||
|
||||
function renderWrappedComponent(type: SourceTypeName) {
|
||||
return (
|
||||
<table>
|
||||
<tbody>{renderComponent(type)}</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Oracle type view', () => {
|
||||
it('Renders nothing when type is null', () => {
|
||||
const res = render(renderComponent(null as unknown as SourceTypeName));
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders Internal time for internal sources', () => {
|
||||
const res = render(renderWrappedComponent('DataSourceDefinitionInternal'));
|
||||
expect(res.getByText('Internal time')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders External data otherwise', () => {
|
||||
const res = render(renderWrappedComponent('DataSourceDefinitionExternal'));
|
||||
expect(res.getByText('External data')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
import { TableRow, TableCell, TableHeader } from '../../../components/table';
|
||||
import type { SourceType } from './oracle';
|
||||
|
||||
export type SourceTypeName = SourceType['__typename'] | undefined;
|
||||
|
||||
interface OracleDetailsTypeProps {
|
||||
type: SourceTypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a a single table row for the Oracle Details view that shows
|
||||
* if the oracle is using the internal time oracle or external data
|
||||
*/
|
||||
export function OracleDetailsType({ type }: OracleDetailsTypeProps) {
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">Type</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
{type === 'DataSourceDefinitionInternal'
|
||||
? 'Internal time'
|
||||
: 'External data'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { getConditionsOrFilters, OracleFilter } from './oracle-filter';
|
||||
import type { Filter } from './oracle-filter';
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
import {
|
||||
ConditionOperator,
|
||||
DataSourceSpecStatus,
|
||||
PropertyKeyType,
|
||||
} from '@vegaprotocol/types';
|
||||
|
||||
const mockExternalSpec = {
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
filters: [
|
||||
{
|
||||
__typename: 'Filter',
|
||||
key: {
|
||||
type: PropertyKeyType.TYPE_INTEGER,
|
||||
name: 'test',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
__typename: 'Condition',
|
||||
value: 'test',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimeSpec = {
|
||||
__typename: 'DataSourceSpecConfigurationTime',
|
||||
conditions: [
|
||||
{
|
||||
value: '123',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function renderComponent(data: ExplorerOracleDataSourceFragment) {
|
||||
return <OracleFilter data={data} />;
|
||||
}
|
||||
|
||||
describe('Oracle Filter view', () => {
|
||||
it('Renders nothing when data is null', () => {
|
||||
const res = render(
|
||||
renderComponent(null as unknown as ExplorerOracleDataSourceFragment)
|
||||
);
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Renders nothing when data is empty', () => {
|
||||
const res = render(renderComponent({} as ExplorerOracleDataSourceFragment));
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ExplorerOracleDataSourceFragment)
|
||||
);
|
||||
|
||||
expect(res.getByText('Filter')).toBeInTheDocument();
|
||||
// Avoids asserting on how the data is presented because it is very rudimentary
|
||||
});
|
||||
|
||||
it('Renders conditions if type is DataSourceSpecConfigurationTime', () => {
|
||||
const res = render(
|
||||
renderComponent({
|
||||
dataSourceSpec: {
|
||||
spec: {
|
||||
id: 'irrelevant-test-data',
|
||||
createdAt: 'irrelevant-test-data',
|
||||
status: DataSourceSpecStatus.STATUS_ACTIVE,
|
||||
data: {
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionInternal',
|
||||
sourceType: mockTimeSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ExplorerOracleDataSourceFragment)
|
||||
);
|
||||
|
||||
expect(res.getByText('Filter')).toBeInTheDocument();
|
||||
// Avoids asserting on how the data is presented because it is very rudimentary
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConditionsOrFilter', () => {
|
||||
it('Returns null if the type is undetermined (not DataSourceSpecConfiguration or DataSourceSpecConfigurationTime', () => {
|
||||
expect(getConditionsOrFilters({})).toBeNull();
|
||||
});
|
||||
|
||||
it('Returns the conditions object for time specs', () => {
|
||||
const mock: Filter = {
|
||||
__typename: 'DataSourceSpecConfigurationTime',
|
||||
conditions: [
|
||||
{
|
||||
__typename: 'Condition',
|
||||
value: '100',
|
||||
operator: ConditionOperator.OPERATOR_GREATER_THAN,
|
||||
},
|
||||
],
|
||||
};
|
||||
const res = getConditionsOrFilters(mock);
|
||||
// This ugly construction is due to lazy typing on getConditionsOrFilter
|
||||
if (!res || res.length !== 1 || !res[0] || 'key' in res[0]) {
|
||||
throw new Error(
|
||||
'getConditionsOrFilter did not return conditions on a time spec'
|
||||
);
|
||||
}
|
||||
|
||||
expect(res[0].__typename).toEqual('Condition');
|
||||
});
|
||||
|
||||
it('Returns the filters object for external specs', () => {
|
||||
const mock: Filter = {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
filters: [
|
||||
{
|
||||
__typename: 'Filter',
|
||||
key: {
|
||||
type: PropertyKeyType.TYPE_INTEGER,
|
||||
name: 'test',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
__typename: 'Condition',
|
||||
value: 'test',
|
||||
operator: ConditionOperator.OPERATOR_EQUALS,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = getConditionsOrFilters(mock);
|
||||
// This ugly construction is due to lazy typing on getConditionsOrFilter
|
||||
if (!res || res.length !== 1 || !res[0] || 'value' in res[0]) {
|
||||
throw new Error(
|
||||
'getConditionsOrFilter did not return filters on a external spec'
|
||||
);
|
||||
}
|
||||
|
||||
expect(res[0].__typename).toEqual('Filter');
|
||||
});
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import filter from 'recursive-key-filter';
|
||||
import type { ExplorerOracleDataSourceFragment } from '../__generated__/Oracles';
|
||||
|
||||
interface OracleFilterProps {
|
||||
data: ExplorerOracleDataSourceFragment;
|
||||
}
|
||||
|
||||
export type Filter =
|
||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType']['sourceType'];
|
||||
|
||||
/**
|
||||
* Given the main Filter view just uses a JSON dump view, this function
|
||||
* selects the correct filter to dump in to that view. Internal oracles
|
||||
* (i.e. the Time oracle) have conditions while external data sources
|
||||
* have filters
|
||||
*
|
||||
* @param s A data source
|
||||
* @returns Object an object containing conditions or filters
|
||||
*/
|
||||
export function getConditionsOrFilters(s: Filter) {
|
||||
if (s.__typename === 'DataSourceSpecConfiguration') {
|
||||
return s.filters;
|
||||
} else if (s.__typename === 'DataSourceSpecConfigurationTime') {
|
||||
return s.conditions;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the conditions that this oracle is using to filter
|
||||
* data sources.
|
||||
*
|
||||
* Renders nothing if there is no data (which will frequently)
|
||||
* be the case) and if there is data, currently renders a simple
|
||||
* JSON view.
|
||||
*/
|
||||
export function OracleFilter({ data }: OracleFilterProps) {
|
||||
if (!data?.dataSourceSpec?.spec?.data?.sourceType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const s = data.dataSourceSpec.spec.data.sourceType.sourceType;
|
||||
const f = getConditionsOrFilters(s);
|
||||
|
||||
if (!f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<details>
|
||||
<summary>{t('Filter')}</summary>
|
||||
<SyntaxHighlighter data={filter(f, ['__typename'])} />
|
||||
</details>
|
||||
);
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { OracleMarkets } from './oracle-markets';
|
||||
import { render } from '@testing-library/react';
|
||||
import { Table } from '../../../components/table';
|
||||
import { ExplorerOracleFormMarketsDocument } from '../__generated__/OraclesForMarkets';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
function renderComponent(id: string, mocks: MockedResponse[]) {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Table>
|
||||
<tbody>
|
||||
<OracleMarkets id={id} />
|
||||
</tbody>
|
||||
</Table>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Oracle Markets component', () => {
|
||||
it('Renders a row with the market ID initially', () => {
|
||||
const res = render(renderComponent('123', []));
|
||||
expect(res.getByText('Market')).toBeInTheDocument();
|
||||
expect(res.getByText('123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders that this is a termination source for the right market', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerOracleFormMarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: '123',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: '456',
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
id: '789',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: 'abc',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'def',
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
id: 'ghi',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = render(renderComponent('789', [mock]));
|
||||
expect(await res.findByText('Termination for')).toBeInTheDocument();
|
||||
expect(await res.findByTestId('m-123')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders that this is a settlement source for the right market', async () => {
|
||||
const mock = {
|
||||
request: {
|
||||
query: ExplorerOracleFormMarketsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: '123',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: '789',
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: 'abc',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'def',
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
id: 'ghi',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = render(renderComponent('789', [mock]));
|
||||
expect(await res.findByText('Settlement for')).toBeInTheDocument();
|
||||
expect(await res.findByTestId('m-123')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
import { getNodes, t } from '@vegaprotocol/react-helpers';
|
||||
import { MarketLink } from '../../../components/links';
|
||||
import { TableRow, TableCell, TableHeader } from '../../../components/table';
|
||||
import type { ExplorerOracleForMarketsMarketFragment } from '../__generated__/OraclesForMarkets';
|
||||
import { useExplorerOracleFormMarketsQuery } from '../__generated__/OraclesForMarkets';
|
||||
|
||||
interface OracleMarketsProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly misleadlingly names, OracleMarkets lists the market (almost always singular)
|
||||
* to which an oracle is attached. It also checks what it triggers, by checking on the
|
||||
* market whether it is attached to the dataSourceSpecForSettlementData or ..TradingTermination
|
||||
*/
|
||||
export function OracleMarkets({ id }: OracleMarketsProps) {
|
||||
const { data } = useExplorerOracleFormMarketsQuery({
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
const markets = getNodes<ExplorerOracleForMarketsMarketFragment>(
|
||||
data?.marketsConnection
|
||||
);
|
||||
|
||||
if (markets) {
|
||||
const m = markets.find((m) => {
|
||||
const p = m.tradableInstrument.instrument.product;
|
||||
if (
|
||||
p.dataSourceSpecForSettlementData.id === id ||
|
||||
p.dataSourceSpecForTradingTermination.id === id
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (m && m.id) {
|
||||
const type =
|
||||
id ===
|
||||
m.tradableInstrument.instrument.product.dataSourceSpecForSettlementData
|
||||
.id
|
||||
? 'Settlement for'
|
||||
: 'Termination for';
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{type}</TableHeader>
|
||||
<TableCell modifier="bordered" data-testid={`m-${m.id}`}>
|
||||
<MarketLink id={m.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Market')}</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<span>{id}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { SourceType } from './oracle';
|
||||
import { OracleSigners } from './oracle-signers';
|
||||
|
||||
function renderComponent(sourceType: SourceType) {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<OracleSigners sourceType={sourceType} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
function renderComponentWrapped(sourceType: SourceType) {
|
||||
return (
|
||||
<table>
|
||||
<tbody>{renderComponent(sourceType)}</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Oracle Signers component', () => {
|
||||
it('returns empty if there are no signers (null)', () => {
|
||||
const res = render(renderComponent({} as SourceType));
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('returns empty if there are no signers (empty)', () => {
|
||||
const mock: SourceType = {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
signers: [],
|
||||
},
|
||||
};
|
||||
const res = render(renderComponent(mock));
|
||||
expect(res.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('Correctly identifies an Ethereum based signer', () => {
|
||||
const mock: SourceType = {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'ETHAddress',
|
||||
address: '0x123',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const res = render(renderComponentWrapped(mock));
|
||||
expect(res.getByTestId('keytype')).toHaveTextContent('ETH');
|
||||
});
|
||||
|
||||
it('Correctly identifies an Vega based signer', () => {
|
||||
const mock: SourceType = {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: '123',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const res = render(renderComponentWrapped(mock));
|
||||
expect(res.getByTestId('keytype')).toHaveTextContent('Vega');
|
||||
});
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
import { PartyLink } from '../../../components/links';
|
||||
import {
|
||||
EthExplorerLink,
|
||||
EthExplorerLinkTypes,
|
||||
} from '../../../components/links/eth-explorer-link/eth-explorer-link';
|
||||
import { TableRow, TableCell, TableHeader } from '../../../components/table';
|
||||
|
||||
import type { SourceType } from './oracle';
|
||||
|
||||
export type Signer = {
|
||||
__typename?: 'ETHAddress' | 'PubKey' | undefined;
|
||||
address?: string | null;
|
||||
key?: string | null;
|
||||
};
|
||||
|
||||
export function getAddressTypeLabel(signer: Signer) {
|
||||
return signer.__typename === 'ETHAddress' ? 'ETH' : 'Vega';
|
||||
}
|
||||
|
||||
export function getAddress(signer: Signer) {
|
||||
return signer.address ? signer.address : signer.key ? signer.key : null;
|
||||
}
|
||||
|
||||
export function getAddressLink(signer: Signer) {
|
||||
const address = getAddress(signer);
|
||||
if (!address) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (signer.__typename === 'ETHAddress') {
|
||||
return <EthExplorerLink id={address} type={EthExplorerLinkTypes.address} />;
|
||||
} else if (signer.__typename === 'PubKey') {
|
||||
return <PartyLink id={address} />;
|
||||
}
|
||||
|
||||
return <span>{address}</span>;
|
||||
}
|
||||
|
||||
interface OracleDetailsSignersProps {
|
||||
sourceType: SourceType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an Oracle, this component will render either a link to Ethereum
|
||||
* or the Vega party depending on which type is specified
|
||||
*/
|
||||
export function OracleSigners({ sourceType }: OracleDetailsSignersProps) {
|
||||
if (sourceType.__typename !== 'DataSourceDefinitionExternal') {
|
||||
return null;
|
||||
}
|
||||
const signers = sourceType.sourceType.signers;
|
||||
|
||||
if (!signers || signers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{signers.map((s) => {
|
||||
return (
|
||||
<TableRow modifier="bordered" key={getAddress(s.signer)}>
|
||||
<TableHeader scope="row">Signer</TableHeader>
|
||||
<TableCell modifier="bordered">
|
||||
<div>
|
||||
<span data-testid="keytype">
|
||||
{getAddressTypeLabel(s.signer)}
|
||||
</span>
|
||||
: {getAddressLink(s.signer)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
56
apps/explorer/src/app/routes/oracles/components/oracle.tsx
Normal file
56
apps/explorer/src/app/routes/oracles/components/oracle.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableWithTbody,
|
||||
TableHeader,
|
||||
} from '../../../components/table';
|
||||
import type {
|
||||
ExplorerOracleDataConnectionFragment,
|
||||
ExplorerOracleDataSourceFragment,
|
||||
} from '../__generated__/Oracles';
|
||||
import { OracleData } from './oracle-data';
|
||||
import { OracleFilter } from './oracle-filter';
|
||||
import { OracleDetailsType } from './oracle-details-type';
|
||||
import { OracleMarkets } from './oracle-markets';
|
||||
|
||||
export type SourceType =
|
||||
ExplorerOracleDataSourceFragment['dataSourceSpec']['spec']['data']['sourceType'];
|
||||
|
||||
interface OracleDetailsProps {
|
||||
id: string;
|
||||
dataSource: ExplorerOracleDataSourceFragment;
|
||||
dataConnection: ExplorerOracleDataConnectionFragment;
|
||||
}
|
||||
|
||||
export const OracleDetails = ({
|
||||
id,
|
||||
dataSource,
|
||||
dataConnection,
|
||||
}: OracleDetailsProps) => {
|
||||
const sourceType = dataSource.dataSourceSpec.spec.data.sourceType;
|
||||
const reportsCount: number = dataConnection.dataConnection.edges?.length || 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableWithTbody>
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('ID')}</TableHeader>
|
||||
<TableCell modifier="bordered">{id}</TableCell>
|
||||
</TableRow>
|
||||
<OracleDetailsType type={sourceType.__typename} />
|
||||
{
|
||||
// Disabled until https://github.com/vegaprotocol/vega/issues/7286 is released
|
||||
/*<OracleSigners sourceType={sourceType} />*/
|
||||
}
|
||||
<OracleMarkets id={id} />
|
||||
<TableRow modifier="bordered">
|
||||
<TableHeader scope="row">{t('Broadcasts')}</TableHeader>
|
||||
<TableCell modifier="bordered">{reportsCount}</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<OracleFilter data={dataSource} />
|
||||
<OracleData data={dataConnection} />
|
||||
</div>
|
||||
);
|
||||
};
|
45
apps/explorer/src/app/routes/oracles/index.tsx
Normal file
45
apps/explorer/src/app/routes/oracles/index.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useExplorerOracleSpecsQuery } from './__generated__/Oracles';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { OracleDetails } from './components/oracle';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import filter from 'recursive-key-filter';
|
||||
|
||||
const Oracles = () => {
|
||||
const { data, loading } = useExplorerOracleSpecsQuery();
|
||||
|
||||
useDocumentTitle(['Oracles']);
|
||||
useScrollToLocation();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
|
||||
{loading ? <Loader /> : null}
|
||||
{data?.oracleSpecsConnection?.edges
|
||||
? data.oracleSpecsConnection.edges.map((o) => {
|
||||
const id = o?.node.dataSourceSpec.spec.id;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div id={id} key={id} className="mb-10 cursor-pointer">
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
dataConnection={o?.node}
|
||||
/>
|
||||
<details>
|
||||
<summary className="pointer">JSON</summary>
|
||||
<SyntaxHighlighter data={filter(o, ['__typename'])} />
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Oracles;
|
@ -3,6 +3,7 @@ import BlockPage from './blocks';
|
||||
import Governance from './governance';
|
||||
import Home from './home';
|
||||
import Markets from './markets';
|
||||
import Oracles from './oracles';
|
||||
import Party from './parties';
|
||||
import { Parties } from './parties/home';
|
||||
import { Party as PartySingle } from './parties/id';
|
||||
@ -84,6 +85,17 @@ const marketsRoutes = flags.markets
|
||||
]
|
||||
: [];
|
||||
|
||||
const oraclesRoutes = flags.oracles
|
||||
? [
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: 'Oracles',
|
||||
text: t('Oracles'),
|
||||
element: <Oracles />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const networkParametersRoutes = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
@ -154,6 +166,7 @@ const routerConfig = [
|
||||
...genesisRoutes,
|
||||
...governanceRoutes,
|
||||
...marketsRoutes,
|
||||
...oraclesRoutes,
|
||||
...networkParametersRoutes,
|
||||
...validators,
|
||||
];
|
||||
|
@ -78,6 +78,7 @@
|
||||
"react-window": "^1.8.7",
|
||||
"react-window-infinite-loader": "^1.0.7",
|
||||
"recharts": "^2.1.2",
|
||||
"recursive-key-filter": "^1.0.2",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
"tslib": "^2.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
|
@ -19690,6 +19690,11 @@ rechoir@^0.6.2:
|
||||
dependencies:
|
||||
resolve "^1.1.6"
|
||||
|
||||
recursive-key-filter@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/recursive-key-filter/-/recursive-key-filter-1.0.2.tgz#78420279ec536e383c4437df367bc3da0cee8f12"
|
||||
integrity sha512-glJv733zlpupnUlswNb7u0OEJaR0gojHWD00fDyISRJdXO9lVsllTJj6R4oJAYyRbIdadLffzsz28BU7nooXqA==
|
||||
|
||||
redent@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
|
||||
|
Loading…
Reference in New Issue
Block a user